diff options
Diffstat (limited to 'scripts/automation/trex_control_plane/stl/services/scapy_server')
4 files changed, 519 insertions, 7 deletions
diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/field_engine.json b/scripts/automation/trex_control_plane/stl/services/scapy_server/field_engine.json new file mode 100644 index 00000000..85d10e65 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/field_engine.json @@ -0,0 +1,269 @@ +{ + "instructions": [ + { + "id": "STLVmFlowVar", + "parameters": ["name", "init_value", "max_value","min_value","step", "size","op"] + }, + { + "id": "STLVmWrFlowVar", + "parameters": ["fv_name", "pkt_offset","offset_fixup","add_val","is_big"] + }, + { + "id": "STLVmWrMaskFlowVar", + "parameters": ["fv_name", "pkt_offset", "pkt_cast_size","mask", "shift","add_value","is_big"] + }, + { + "id": "STLVmFixIpv4", + "parameters": ["offset"] + }, + { + "id": "STLVmTrimPktSize", + "parameters": ["fv_name"] + }, + { + "id": "STLVmTupleGen", + "parameters": ["name", "ip_min", "ip_max", "port_min", "port_max", "limit_flows", "flags"] + }, + { + "id": "STLVmFlowVarRepetableRandom", + "parameters": ["name", "size", "limit", "seed", "min_value", "max_value"] + }, + { + "id": "STLVmFixChecksumHw", + "parameters": ["l3_offset","l4_offset","l4_type"] + } + ], + + "instruction_params_meta": [ + { + "id": "name", + "name": "Name", + "type": "ENUM", + "editable": true, + "required": true, + "defaultValue": "Not defined" + }, + { + "id": "init_value", + "name": "Initial value", + "type": "STRING", + "defaultValue": "0" + }, + { + "id": "max_value", + "name": "Maximum value", + "type": "STRING", + "required": true, + "defaultValue": "0" + }, + { + "id": "min_value", + "name": "Minimum value", + "type": "STRING", + "required": true, + "defaultValue": "0" + }, + { + "id": "step", + "name": "Step", + "type": "NUMBER", + "required": true, + "defaultValue": "1" + }, + { + "id": "op", + "name": "Operation", + "type": "ENUM", + "defaultValue": "inc", + "dict": { + "dec": "Decrement", + "inc": "Increment", + "random": "Random" + }, + "required": true + }, + { + "id": "size", + "name": "Size", + "type": "ENUM", + "defaultValue": "4", + "dict": { + "1": "1", + "2": "2", + "4": "4", + "8": "8" + } + }, + { + "id": "fv_name", + "name": "Variable name", + "type": "ENUM", + "required": true, + "editable": true + }, + { + "id": "pkt_offset", + "name": "Offset", + "type": "ENUM", + "required": true, + "editable": true, + "defaultValue": 0 + }, + { + "id": "pkt_cast_size", + "name": "Packet cast size", + "type": "ENUM", + "defaultValue": 1, + "dict":{ + "1":1, + "2":2, + "4":4 + } + }, + { + "id": "shift", + "name": "Shift", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "mask", + "name": "Mask", + "type": "STRING", + "defaultValue": "0xff" + }, + { + "id": "offset_fixup", + "name": "offset_fixup", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "add_val", + "name": "add_val", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "add_value", + "name": "add_value", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "is_big", + "name": "is_big", + "type": "ENUM", + "defaultValue": "true", + "dict": { + "true": "true", + "false": "false" + } + }, + { + "id": "offset", + "name": "Offset", + "type": "ENUM", + "required": true, + "editable": true, + "defaultValue": 0 + }, + { + "id": "l3_offset", + "name": "L3 offset", + "type": "STRING", + "required": true, + "autocomplete": true, + "defaultValue": "IP" + }, + { + "id": "l4_offset", + "name": "L4 offset", + "type": "STRING", + "required": true, + "defaultValue": "TCP" + }, + { + "id": "ip_min", + "name": "Min IP", + "type": "STRING", + "defaultValue": "0.0.0.1" + }, + { + "id": "ip_max", + "name": "Max IP", + "type": "STRING", + "defaultValue": "0.0.0.10" + }, + { + "id": "port_max", + "name": "Max Port number", + "type": "NUMBER", + "defaultValue": 65535 + }, + { + "id": "port_min", + "name": "Min Port number", + "type": "NUMBER", + "defaultValue": 1025 + }, + { + "id": "limit_flows", + "name": "FLows limit", + "type": "NUMBER", + "defaultValue": 100000 + }, + { + "id": "limit", + "name": "Limit", + "type": "NUMBER", + "defaultValue": 100 + }, + { + "id": "seed", + "name": "Seed", + "type": "String", + "defaultValue": "None" + }, + { + "id": "flags", + "name": "Flags", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "l4_type", + "name": "L4 type", + "type": "ENUM", + "required": true, + "editable": false, + "defaultValue": "13", + "dict": { + "11": "L4_TYPE_UDP", + "13": "L4_TYPE_TCP" + } + } + ], + "supported_protocols": ["IP","TCP","UDP"], + "templates":[ + { + "id": "simple_flow_var", + "name": "Simple variable", + "instructionIds": ["STLVmFlowVar", "STLVmWrFlowVar"] + }, + { + "id": "rep_rand_var", + "name": "Repeatable random", + "instructionIds": ["STLVmFlowVarRepetableRandom", "STLVmWrFlowVar"] + } + ], + "global_params_meta":[ + { + "id": "cache_size", + "name": "Cache size", + "type": "NUMBER", + "defaultValue": "1000" + } + ] +} + diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py b/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py index 8d99fe92..e5f1b20c 100755 --- a/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py @@ -1,17 +1,20 @@ import os import sys + stl_pathname = os.path.abspath(os.path.join(os.pardir, os.pardir)) sys.path.append(stl_pathname) from trex_stl_lib.api import * +import trex_stl_lib.trex_stl_packet_builder_scapy import tempfile import hashlib import base64 import numbers import random -import inspect +from inspect import getdoc import json +import re from pprint import pprint # add some layers as an example @@ -121,6 +124,45 @@ class Scapy_service_api(): """ pass + def build_pkt_ex(self, client_v_handler, pkt_model_descriptor, extra_options): + """ build_pkt_ex(self,client_v_handler,pkt_model_descriptor, extra_options) -> Dictionary (of Offsets,Show2 and Buffer) + Performs calculations on the given packet and returns results for that packet. + + Parameters + ---------- + pkt_descriptor - An array of dictionaries describing a network packet + extra_options - A dictionary of extra options required for building packet + + Returns + ------- + - The packets offsets: each field in every layer is mapped inside the Offsets Dictionary + - The Show2: A description of each field and its value in every layer of the packet + - The Buffer: The Hexdump of packet encoded in base64 + + Raises + ------ + will raise an exception when the Scapy string format is illegal, contains syntax error, contains non-supported + protocl, etc. + """ + pass + + def load_instruction_parameter_values(self, client_v_handler, pkt_model_descriptor, vm_instructions_model, parameter_id): + """ load_instruction_parameter_values(self,client_v_handler,pkt_model_descriptor, vm_instructions_model, parameter_id) -> Dictionary (of possible parameter values) + Returns possible valies for given pararameter id depends on current pkt structure and vm_instructions + model. + + Parameters + ---------- + pkt_descriptor - An array of dictionaries describing a network packet + vm_instructions_model - A dictionary of extra options required for building packet + parameter_id - A string of parameter id + + Returns + ------- + Possible parameter values map. + + """ + pass def get_tree(self,client_v_handler): """ get_tree(self) -> Dictionary describing an example of hierarchy in layers @@ -372,7 +414,15 @@ class Scapy_service(Scapy_service_api): self.version_minor = '01' self.server_v_hashed = self._generate_version_hash(self.version_major,self.version_minor) self.protocol_definitions = {} # protocolId -> prococol definition overrides data + self.field_engine_supported_protocols = {} + self.instruction_parameter_meta_definitions = [] + self.field_engine_parameter_meta_definitions = [] + self.field_engine_templates_definitions = [] + self.field_engine_instructions_meta = [] + self.field_engine_instruction_expressions = [] self._load_definitions_from_json() + self._load_field_engine_meta_from_json() + self._vm_instructions = dict([m for m in inspect.getmembers(trex_stl_lib.trex_stl_packet_builder_scapy, inspect.isclass) if m[1].__module__ == 'trex_stl_lib.trex_stl_packet_builder_scapy']) def _load_definitions_from_json(self): # load protocol definitions from a json file @@ -382,6 +432,27 @@ class Scapy_service(Scapy_service_api): for protocol in protocols: self.protocol_definitions[ protocol['id'] ] = protocol + def _load_field_engine_meta_from_json(self): + # load protocol definitions from a json file + self.instruction_parameter_meta_definitions = [] + self.field_engine_supported_protocols = {} + self.field_engine_parameter_meta_definitions = [] + self.field_engine_templates_definitions = [] + with open('field_engine.json', 'r') as f: + metas = json.load(f) + self.instruction_parameter_meta_definitions = metas["instruction_params_meta"] + self.field_engine_instructions_meta = metas["instructions"] + self._append_intructions_help() + self.field_engine_supported_protocols = metas["supported_protocols"] + self.field_engine_parameter_meta_definitions = metas["global_params_meta"] + self.field_engine_templates_definitions = metas["templates"] + + + def _append_intructions_help(self): + for instruction_meta in self.field_engine_instructions_meta: + clazz = eval(instruction_meta['id']) + instruction_meta['help'] = base64.b64encode(getdoc(clazz.__init__)).decode('ascii') + def _all_protocol_structs(self): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() @@ -708,6 +779,134 @@ class Scapy_service(Scapy_service_api): pkt = self._packet_model_to_scapy_packet(pkt_model_descriptor) return self._pkt_data(pkt) + + def build_pkt_ex(self, client_v_handler, pkt_model_descriptor, extra_options): + res = self.build_pkt(client_v_handler, pkt_model_descriptor) + pkt = self._packet_model_to_scapy_packet(pkt_model_descriptor) + + field_engine = {} + field_engine['instructions'] = [] + field_engine['error'] = None + try: + field_engine['instructions'] = self._generate_vm_instructions(pkt, extra_options['field_engine']) + except AssertionError as e: + field_engine['error'] = e.message + except CTRexPacketBuildException as e: + field_engine['error'] = e.message + + field_engine['vm_instructions_expressions'] = self.field_engine_instruction_expressions + res['field_engine'] = field_engine + return res + + def load_instruction_parameter_values(self, client_v_handler, pkt_model_descriptor, vm_instructions_model, parameter_id): + + given_protocol_ids = [str(proto['id']) for proto in pkt_model_descriptor] + + values = {} + if parameter_id == "name": + values = self._curent_pkt_protocol_fields(given_protocol_ids, "_") + + if parameter_id == "fv_name": + values = self._existed_flow_var_names(vm_instructions_model['field_engine']['instructions']) + + if parameter_id == "pkt_offset": + values = self._curent_pkt_protocol_fields(given_protocol_ids, ".") + + if parameter_id == "offset": + for ip_idx in range(given_protocol_ids.count("IP")): + value = "IP:{0}".format(ip_idx) + values[value] = value + + return {"map": values} + + def _existed_flow_var_names(self, instructions): + return dict((instruction['parameters']['name'], instruction['parameters']['name']) for instruction in instructions if self._nameParamterExist(instruction)) + + def _nameParamterExist(self, instruction): + try: + instruction['parameters']['name'] + return True + except KeyError: + return False + + def _curent_pkt_protocol_fields(self, given_protocol_ids, delimiter): + given_protocol_classes = [c for c in Packet.__subclasses__() if c.__name__ in given_protocol_ids] + protocol_fields = {} + for protocol_class in given_protocol_classes: + protocol_name = protocol_class.__name__ + protocol_count = given_protocol_ids.count(protocol_name) + for field_desc in protocol_class.fields_desc: + if delimiter == '.' and protocol_count > 1: + for idx in range(protocol_count): + formatted_name = "{0}:{1}{2}{3}".format(protocol_name, idx, delimiter, field_desc.name) + protocol_fields[formatted_name] = formatted_name + else: + formatted_name = "{0}{1}{2}".format(protocol_name, delimiter, field_desc.name) + protocol_fields[formatted_name] = formatted_name + + return protocol_fields + + def _generate_vm_instructions(self, pkt, field_engine_model_descriptor): + self.field_engine_instruction_expressions = [] + instructions = [] + instructions_def = field_engine_model_descriptor['instructions'] + for instruction_def in instructions_def: + instruction_id = instruction_def['id'] + instruction_class = self._vm_instructions[instruction_id] + parameters = {k: self._sanitize_value(k, v) for (k, v) in instruction_def['parameters'].iteritems()} + instructions.append(instruction_class(**parameters)) + + fe_parameters = field_engine_model_descriptor['global_parameters'] + + cache_size = None + if "cache_size" in fe_parameters: + assert self._is_int(fe_parameters['cache_size']), 'Cache size must be a number' + cache_size = int(fe_parameters['cache_size']) + + + pkt_builder = STLPktBuilder(pkt=pkt, vm=STLScVmRaw(instructions, cache_size=cache_size)) + pkt_builder.compile() + return pkt_builder.get_vm_data() + + def _sanitize_value(self, param_id, val): + if param_id == "pkt_offset": + if self._is_int(val): + return int(val) + elif val == "Ether.src": + return 0 + elif val == "Ether.dst": + return 6 + elif val == "Ether.type": + return 12 + else: + if val == "None" or val == "none": + return None + if val == "true": + return True + elif val == "false": + return False + elif re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", str(val.lower())): + return int(str(val).replace(":", ""), 16) + + if self._is_int(val): + return int(val) + + str_val = str(val) + return int(str_val, 16) if str_val.startswith("0x") else str_val + + def _get_instruction_parameter_meta(self, param_id): + for meta in self.instruction_parameter_meta_definitions: + if meta['id'] == param_id: + return meta + raise Scapy_Exception("Unable to get meta for {0}" % param_id) + + def _is_int(self, val): + try: + int(val) + return True + except ValueError: + return False + # @deprecated. to be removed def get_all(self,client_v_handler): if not (self._verify_version_handler(client_v_handler)): @@ -796,7 +995,11 @@ class Scapy_service(Scapy_service_api): "name": protoDef.get('name') or pkt_class.name, "fields": self._get_fields_definition(pkt_class, protoDef.get('fields') or []) }) - res = {"protocols": protocols} + res = {"protocols": protocols, + "feInstructionParameters": self.instruction_parameter_meta_definitions, + "feInstructions": self.field_engine_instructions_meta, + "feParameters": self.field_engine_parameter_meta_definitions, + "feTemplates": self.field_engine_templates_definitions} return res def get_payload_classes(self,client_v_handler, pkt_model_descriptor): @@ -889,8 +1092,6 @@ class Scapy_service(Scapy_service_api): pcap_bin = tmpPcap.read() return bytes_to_b64(pcap_bin) - - #--------------------------------------------------------------------------- diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py index 1db2c62b..e48880e8 100644 --- a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py @@ -53,6 +53,9 @@ def get_version_handler(): def build_pkt(model_def): return pass_result(service.build_pkt(v_handler, model_def)) +def build_pkt_ex(model_def, instructions_def): + return pass_result(service.build_pkt_ex(v_handler, model_def, instructions_def)) + def build_pkt_get_scapy(model_def): return build_pkt_to_scapy(build_pkt(model_def)) diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py index 91a457dc..e1094a79 100644 --- a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py @@ -113,15 +113,23 @@ def test_get_all(): def test_get_definitions_all(): get_definitions(None) - def_classnames = [pdef['id'] for pdef in get_definitions(None)['protocols']] + defs = get_definitions(None) + def_classnames = [pdef['id'] for pdef in defs['protocols']] assert("IP" in def_classnames) assert("Dot1Q" in def_classnames) assert("TCP" in def_classnames) + # All instructions should have a help description. + fe_instructions = defs['feInstructions'] + for instruction in fe_instructions: + print(instruction['help']) + assert("help" in instruction) + def test_get_definitions_ether(): res = get_definitions(["Ether"]) - assert(len(res) == 1) - assert(res['protocols'][0]['id'] == "Ether") + protocols = res['protocols'] + assert(len(protocols) == 1) + assert(protocols[0]['id'] == "Ether") def test_get_payload_classes(): eth_payloads = get_payload_classes([{"id":"Ether"}]) @@ -250,3 +258,34 @@ def test_ip_definitions(): assert(fields[9]['id'] == 'chksum') assert(fields[9]['auto'] == True) +def test_generate_vm_instructions(): + ip_pkt_model = [ + layer_def("Ether"), + layer_def("IP", src="16.0.0.1", dst="48.0.0.1") + ] + ip_instructions_model = {"field_engine": {"instructions": [{"id": "STLVmFlowVar", + "parameters": {"op": "inc", "min_value": "192.168.0.10", + "size": "1", "name": "ip_src", + "step": "1", + "max_value": "192.168.0.100"}}, + {"id": "STLVmWrFlowVar", + "parameters": {"pkt_offset": "IP.src", "is_big": "true", + "add_val": "0", "offset_fixup": "0", + "fv_name": "ip_src"}}, + {"id": "STLVmFlowVar", + "parameters": {"op": "dec", "min_value": "32", + "size": "1", "name": "ip_ttl", + "step": "4", "max_value": "64"}}, + {"id": "STLVmWrFlowVar", + "parameters": {"pkt_offset": "IP.ttl", "is_big": "true", + "add_val": "0", "offset_fixup": "0", + "fv_name": "ip_ttl"}}], + "global_parameters": {}}} + res = build_pkt_ex(ip_pkt_model, ip_instructions_model) + src_instruction = res['field_engine']['instructions']['instructions'][0] + assert(src_instruction['min_value'] == 3232235530) + assert(src_instruction['max_value'] == 3232235620) + + ttl_instruction = res['field_engine']['instructions']['instructions'][2] + assert(ttl_instruction['min_value'] == 32) + assert(ttl_instruction['max_value'] == 64) |