summaryrefslogtreecommitdiffstats
path: root/scripts/automation/trex_control_plane/client_utils
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/automation/trex_control_plane/client_utils')
-rwxr-xr-x[-rw-r--r--]scripts/automation/trex_control_plane/client_utils/external_packages.py (renamed from scripts/automation/trex_control_plane/client_utils/outer_packages.py)4
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/general_utils.py2
-rwxr-xr-x[-rw-r--r--]scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py344
-rwxr-xr-x[-rw-r--r--]scripts/automation/trex_control_plane/client_utils/packet_builder.py524
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/trex_yaml_gen.py8
5 files changed, 770 insertions, 112 deletions
diff --git a/scripts/automation/trex_control_plane/client_utils/outer_packages.py b/scripts/automation/trex_control_plane/client_utils/external_packages.py
index a6c9a2eb..4b10609b 100644..100755
--- a/scripts/automation/trex_control_plane/client_utils/outer_packages.py
+++ b/scripts/automation/trex_control_plane/client_utils/external_packages.py
@@ -1,7 +1,6 @@
#!/router/bin/python
import sys
-import site
import os
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
@@ -12,7 +11,6 @@ CLIENT_UTILS_MODULES = ['zmq',
'dpkt-1.8.6'
]
-
def import_client_utils_modules():
# must be in a higher priority
sys.path.insert(0, PATH_TO_PYTHON_LIB)
@@ -25,7 +23,7 @@ def import_module_list(modules_list):
for p in modules_list:
full_path = os.path.join(PATH_TO_PYTHON_LIB, p)
fix_path = os.path.normcase(full_path)
- site.addsitedir(full_path)
+ sys.path.insert(1, full_path)
import_client_utils_modules()
diff --git a/scripts/automation/trex_control_plane/client_utils/general_utils.py b/scripts/automation/trex_control_plane/client_utils/general_utils.py
index b5912628..5488b9dd 100755
--- a/scripts/automation/trex_control_plane/client_utils/general_utils.py
+++ b/scripts/automation/trex_control_plane/client_utils/general_utils.py
@@ -37,7 +37,7 @@ def find_path_to_pardir (pardir, base_path = os.getcwd() ):
"""
Finds the absolute path for some parent dir `pardir`, starting from base_path
- The request is only valid if the stop intitiator is the same client as the T-Rex run intitiator.
+ The request is only valid if the stop initiator is the same client as the TRex run initiator.
:parameters:
pardir : str
diff --git a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
index c6b22218..8c8987b6 100644..100755
--- a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
+++ b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
@@ -1,6 +1,6 @@
#!/router/bin/python
-import outer_packages
+import external_packages
import zmq
import json
import general_utils
@@ -17,13 +17,37 @@ class bcolors:
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
+# sub class to describe a batch
+class BatchMessage(object):
+ def __init__ (self, rpc_client):
+ self.rpc_client = rpc_client
+ self.batch_list = []
+ def add (self, method_name, params = {}):
+
+ id, msg = self.rpc_client.create_jsonrpc_v2(method_name, params, encode = False)
+ self.batch_list.append(msg)
+
+ def invoke (self, block = False):
+ if not self.rpc_client.connected:
+ return False, "Not connected to server"
+
+ msg = json.dumps(self.batch_list)
+
+ rc, resp_list = self.rpc_client.send_raw_msg(msg, block = False)
+ if len(self.batch_list) == 1:
+ return True, [(rc, resp_list)]
+ else:
+ return rc, resp_list
+
+
+# JSON RPC v2.0 client
class JsonRpcClient(object):
def __init__ (self, default_server, default_port):
self.verbose = False
self.connected = False
-
+
# default values
self.port = default_port
self.server = default_server
@@ -36,6 +60,7 @@ class JsonRpcClient(object):
return rc
+ # pretty print for JSON
def pretty_json (self, json_str, use_colors = True):
pretty_str = json.dumps(json.loads(json_str), indent = 4, separators=(',', ': '), sort_keys = True)
@@ -63,7 +88,11 @@ class JsonRpcClient(object):
print "[verbose] " + msg
- def create_jsonrpc_v2 (self, method_name, params = {}):
+ # batch messages
+ def create_batch (self):
+ return BatchMessage(self)
+
+ def create_jsonrpc_v2 (self, method_name, params = {}, encode = True):
msg = {}
msg["jsonrpc"] = "2.0"
msg["method"] = method_name
@@ -72,21 +101,23 @@ class JsonRpcClient(object):
msg["id"] = self.id_gen.next()
- return id, json.dumps(msg)
-
- def invoke_rpc_method (self, method_name, params = {}, block = False):
- rc, msg = self._invoke_rpc_method(method_name, params, block)
- if not rc:
- self.disconnect()
+ if encode:
+ return id, json.dumps(msg)
+ else:
+ return id, msg
- return rc, msg
- def _invoke_rpc_method (self, method_name, params = {}, block = False):
+ def invoke_rpc_method (self, method_name, params = {}, block = False):
if not self.connected:
return False, "Not connected to server"
id, msg = self.create_jsonrpc_v2(method_name, params)
+ return self.send_raw_msg(msg, block)
+
+
+ # low level send of string message
+ def send_raw_msg (self, msg, block = False):
self.verbose_msg("Sending Request To Server:\n\n" + self.pretty_json(msg) + "\n")
if block:
@@ -95,6 +126,7 @@ class JsonRpcClient(object):
try:
self.socket.send(msg, flags = zmq.NOBLOCK)
except zmq.error.ZMQError as e:
+ self.disconnect()
return False, "Failed To Get Send Message"
got_response = False
@@ -112,22 +144,41 @@ class JsonRpcClient(object):
sleep(0.2)
if not got_response:
+ self.disconnect()
return False, "Failed To Get Server Response"
self.verbose_msg("Server Response:\n\n" + self.pretty_json(response) + "\n")
# decode
+
+ # batch ?
response_json = json.loads(response)
+ if isinstance(response_json, list):
+ rc_list = []
+
+ for single_response in response_json:
+ rc, msg = self.process_single_response(single_response)
+ rc_list.append( (rc, msg) )
+
+ return True, rc_list
+
+ else:
+ rc, msg = self.process_single_response(response_json)
+ return rc, msg
+
+
+ def process_single_response (self, response_json):
+
if (response_json.get("jsonrpc") != "2.0"):
return False, "Malfromed Response ({0})".format(str(response))
- if (response_json.get("id") != id):
- return False, "Server Replied With Bad ID ({0})".format(str(response))
-
# error reported by server
if ("error" in response_json):
- return True, response_json["error"]["message"]
+ if "specific_err" in response_json["error"]:
+ return False, response_json["error"]["specific_err"]
+ else:
+ return False, response_json["error"]["message"]
# if no error there should be a result
if ("result" not in response_json):
@@ -136,17 +187,7 @@ class JsonRpcClient(object):
return True, response_json["result"]
- def ping_rpc_server(self):
-
- return self.invoke_rpc_method("ping", block = False)
-
- def get_rpc_server_status (self):
- return self.invoke_rpc_method("get_status")
-
- def query_rpc_server(self):
- return self.invoke_rpc_method("get_supported_cmds")
-
-
+
def set_verbose(self, mode):
self.verbose = mode
@@ -182,12 +223,6 @@ class JsonRpcClient(object):
self.connected = True
- # ping the server
- rc, err = self.ping_rpc_server()
- if not rc:
- self.disconnect()
- return rc, err
-
return True, ""
@@ -205,11 +240,250 @@ class JsonRpcClient(object):
def is_connected(self):
return self.connected
-
def __del__(self):
print "Shutting down RPC client\n"
if hasattr(self, "context"):
self.context.destroy(linger=0)
-if __name__ == "__main__":
- pass
+# MOVE THIS TO DAN'S FILE
+class TrexStatelessClient(JsonRpcClient):
+
+ def __init__ (self, server, port, user):
+
+ super(TrexStatelessClient, self).__init__(server, port)
+
+ self.user = user
+ self.port_handlers = {}
+
+ self.supported_cmds = []
+ self.system_info = None
+ self.server_version = None
+
+
+ def whoami (self):
+ return self.user
+
+ def ping_rpc_server(self):
+
+ return self.invoke_rpc_method("ping", block = False)
+
+ def get_rpc_server_version (self):
+ return self.server_version
+
+ def get_system_info (self):
+ if not self.system_info:
+ return {}
+
+ return self.system_info
+
+ def get_supported_cmds(self):
+ if not self.supported_cmds:
+ return {}
+
+ return self.supported_cmds
+
+ def get_port_count (self):
+ if not self.system_info:
+ return 0
+
+ return self.system_info["port_count"]
+
+ # sync the client with all the server required data
+ def sync (self):
+
+ # get server version
+ rc, msg = self.invoke_rpc_method("get_version")
+ if not rc:
+ self.disconnect()
+ return rc, msg
+
+ self.server_version = msg
+
+ # get supported commands
+ rc, msg = self.invoke_rpc_method("get_supported_cmds")
+ if not rc:
+ self.disconnect()
+ return rc, msg
+
+ self.supported_cmds = [str(x) for x in msg if x]
+
+ # get system info
+ rc, msg = self.invoke_rpc_method("get_system_info")
+ if not rc:
+ self.disconnect()
+ return rc, msg
+
+ self.system_info = msg
+
+ return True, ""
+
+ def connect (self):
+ rc, err = super(TrexStatelessClient, self).connect()
+ if not rc:
+ return rc, err
+
+ return self.sync()
+
+
+ # take ownership over ports
+ def take_ownership (self, port_id_array, force = False):
+ if not self.connected:
+ return False, "Not connected to server"
+
+ batch = self.create_batch()
+
+ for port_id in port_id_array:
+ batch.add("acquire", params = {"port_id":port_id, "user":self.user, "force":force})
+
+ rc, resp_list = batch.invoke()
+ if not rc:
+ return rc, resp_list
+
+ for i, rc in enumerate(resp_list):
+ if rc[0]:
+ self.port_handlers[port_id_array[i]] = rc[1]
+
+ return True, resp_list
+
+
+ def release_ports (self, port_id_array):
+ batch = self.create_batch()
+
+ for port_id in port_id_array:
+
+ # let the server handle un-acquired errors
+ if self.port_handlers.get(port_id):
+ handler = self.port_handlers[port_id]
+ else:
+ handler = ""
+
+ batch.add("release", params = {"port_id":port_id, "handler":handler})
+
+
+ rc, resp_list = batch.invoke()
+ if not rc:
+ return rc, resp_list
+
+ for i, rc in enumerate(resp_list):
+ if rc[0]:
+ self.port_handlers.pop(port_id_array[i])
+
+ return True, resp_list
+
+ def get_owned_ports (self):
+ return self.port_handlers.keys()
+
+ # fetch port stats
+ def get_port_stats (self, port_id_array):
+ if not self.connected:
+ return False, "Not connected to server"
+
+ batch = self.create_batch()
+
+ # empty list means all
+ if port_id_array == []:
+ port_id_array = list([x for x in xrange(0, self.system_info["port_count"])])
+
+ for port_id in port_id_array:
+
+ # let the server handle un-acquired errors
+ if self.port_handlers.get(port_id):
+ handler = self.port_handlers[port_id]
+ else:
+ handler = ""
+
+ batch.add("get_port_stats", params = {"port_id":port_id, "handler":handler})
+
+
+ rc, resp_list = batch.invoke()
+
+ return rc, resp_list
+
+ # snapshot will take a snapshot of all your owned ports for streams and etc.
+ def snapshot(self):
+
+
+ if len(self.get_owned_ports()) == 0:
+ return {}
+
+ snap = {}
+
+ batch = self.create_batch()
+
+ for port_id in self.get_owned_ports():
+
+ batch.add("get_port_stats", params = {"port_id": port_id, "handler": self.port_handlers[port_id]})
+ batch.add("get_stream_list", params = {"port_id": port_id, "handler": self.port_handlers[port_id]})
+
+ rc, resp_list = batch.invoke()
+ if not rc:
+ return rc, resp_list
+
+ # split the list to 2s
+ index = 0
+ for port_id in self.get_owned_ports():
+ if not resp_list[index] or not resp_list[index + 1]:
+ snap[port_id] = None
+ continue
+
+ # fetch the first two
+ stats = resp_list[index][1]
+ stream_list = resp_list[index + 1][1]
+
+ port = {}
+ port['status'] = stats['status']
+ port['stream_list'] = []
+
+ # get all the streams
+ if len(stream_list) > 0:
+ batch = self.create_batch()
+ for stream_id in stream_list:
+ batch.add("get_stream", params = {"port_id": port_id, "stream_id": stream_id, "handler": self.port_handlers[port_id]})
+
+ rc, stream_resp_list = batch.invoke()
+ if not rc:
+ port = {}
+
+ port['streams'] = {}
+ for i, resp in enumerate(stream_resp_list):
+ if resp[0]:
+ port['streams'][stream_list[i]] = resp[1]
+
+ snap[port_id] = port
+
+ # move to next one
+ index += 2
+
+
+ return snap
+
+ # add stream
+ def add_stream (self, port_id, stream_id, isg, next_stream_id, packet):
+ if not port_id in self.get_owned_ports():
+ return False, "Port {0} is not owned... please take ownership before adding streams".format(port_id)
+
+ handler = self.port_handlers[port_id]
+
+ stream = {}
+ stream['enabled'] = True
+ stream['self_start'] = True
+ stream['isg'] = isg
+ stream['next_stream_id'] = next_stream_id
+ stream['packet'] = {}
+ stream['packet']['binary'] = packet
+ stream['packet']['meta'] = ""
+ stream['vm'] = []
+ stream['rx_stats'] = {}
+ stream['rx_stats']['enabled'] = False
+
+ stream['mode'] = {}
+ stream['mode']['type'] = 'continuous'
+ stream['mode']['pps'] = 10.0
+
+ params = {}
+ params['handler'] = handler
+ params['stream'] = stream
+ params['port_id'] = port_id
+ params['stream_id'] = stream_id
+
+ return self.invoke_rpc_method('add_stream', params = params)
diff --git a/scripts/automation/trex_control_plane/client_utils/packet_builder.py b/scripts/automation/trex_control_plane/client_utils/packet_builder.py
index fc34d931..c687126b 100644..100755
--- a/scripts/automation/trex_control_plane/client_utils/packet_builder.py
+++ b/scripts/automation/trex_control_plane/client_utils/packet_builder.py
@@ -1,7 +1,6 @@
#!/router/bin/python
-
-import outer_packages
+import external_packages
import dpkt
import socket
import binascii
@@ -10,6 +9,8 @@ import random
import string
import struct
import re
+from abc import ABCMeta, abstractmethod
+from collections import namedtuple
class CTRexPktBuilder(object):
@@ -30,7 +31,7 @@ class CTRexPktBuilder(object):
self._pkt_by_hdr = {}
self._pkt_top_layer = None
self._max_pkt_size = max_pkt_size
- self.payload_generator = CTRexPktBuilder.CTRexPayloadGen(self._packet, self._max_pkt_size)
+ self.payload_gen = CTRexPktBuilder.CTRexPayloadGen(self._packet, self._max_pkt_size)
self.vm = CTRexPktBuilder.CTRexVM()
def add_pkt_layer(self, layer_name, pkt_layer):
@@ -63,6 +64,38 @@ class CTRexPktBuilder(object):
return
def set_ip_layer_addr(self, layer_name, attr, ip_addr, ip_type="ipv4"):
+ """
+ This method sets the IP address fields of an IP header (source or destination, for both IPv4 and IPv6)
+ using a human readable addressing representation.
+
+ :parameters:
+ layer_name: str
+ a string representing the name of the layer.
+ Example: "l3_ip", etc.
+
+ attr: str
+ a string representation of the sub-field to be set:
+
+ + "src" for source
+ + "dst" for destination
+
+ ip_addr: str
+ a string representation of the IP address to be set.
+ Example: "10.0.0.1" for IPv4, or "5001::DB8:1:3333:1:1" for IPv6
+
+ ip_type : str
+ a string representation of the IP version to be set:
+
+ + "ipv4" for IPv4
+ + "ipv6" for IPv6
+
+ Default: **ipv4**
+
+ :raises:
+ + :exc:`ValueError`, in case the desired layer_name is not an IP layer
+ + :exc:`KeyError`, in case the desired layer_name does not exists.
+
+ """
try:
layer = self._pkt_by_hdr[layer_name.lower()]
if not (isinstance(layer, dpkt.ip.IP) or isinstance(layer, dpkt.ip6.IP6)):
@@ -74,9 +107,55 @@ class CTRexPktBuilder(object):
raise KeyError("Specified layer '{0}' doesn't exist on packet.".format(layer_name))
def set_ipv6_layer_addr(self, layer_name, attr, ip_addr):
+ """
+ This method sets the IPv6 address fields of an IP header (source or destination)
+
+ :parameters:
+ layer_name: str
+ a string representing the name of the layer.
+ Example: "l3_ip", etc.
+
+ attr: str
+ a string representation of the sub-field to be set:
+
+ + "src" for source
+ + "dst" for destination
+
+ ip_addr: str
+ a string representation of the IP address to be set.
+ Example: "5001::DB8:1:3333:1:1"
+
+ :raises:
+ + :exc:`ValueError`, in case the desired layer_name is not an IPv6 layer
+ + :exc:`KeyError`, in case the desired layer_name does not exists.
+
+ """
self.set_ip_layer_addr(layer_name, attr, ip_addr, ip_type="ipv6")
def set_eth_layer_addr(self, layer_name, attr, mac_addr):
+ """
+ This method sets the ethernet address fields of an Ethernet header (source or destination)
+ using a human readable addressing representation.
+
+ :parameters:
+ layer_name: str
+ a string representing the name of the layer.
+ Example: "l2", etc.
+
+ attr: str
+ a string representation of the sub-field to be set:
+ + "src" for source
+ + "dst" for destination
+
+ mac_addr: str
+ a string representation of the MAC address to be set.
+ Example: "00:de:34:ef:2e:f4".
+
+ :raises:
+ + :exc:`ValueError`, in case the desired layer_name is not an Ethernet layer
+ + :exc:`KeyError`, in case the desired layer_name does not exists.
+
+ """
try:
layer = self._pkt_by_hdr[layer_name.lower()]
if not isinstance(layer, dpkt.ethernet.Ethernet):
@@ -135,6 +214,30 @@ class CTRexPktBuilder(object):
raise KeyError("Specified layer '{0}' doesn't exist on packet.".format(layer_name))
def set_layer_bit_attr(self, layer_name, attr, val):
+ """
+ This method enables the user to set the value of a field smaller that 1 Byte in size.
+ This method isn't used to set full-sized fields value (>= 1 byte).
+ Use :func:`packet_builder.CTRexPktBuilder.set_layer_attr` instead.
+
+ :parameters:
+ layer_name: str
+ a string representing the name of the layer.
+ Example: "l2", "l4_tcp", etc.
+
+ attr : str
+ a string representing the attribute to be set on desired layer
+
+ val : int
+ value of attribute.
+ This value will be set "ontop" of the existing value using bitwise "OR" operation.
+
+ .. tip:: It is very useful to use dpkt constants to define the values of these fields.
+
+ :raises:
+ + :exc:`KeyError`, in case of missing layer (the desired layer isn't part of packet)
+ + :exc:`ValueError`, in case invalid attribute to the specified layer.
+
+ """
return self.set_layer_attr(layer_name, attr, val, True)
def set_pkt_payload(self, payload):
@@ -238,17 +341,88 @@ class CTRexPktBuilder(object):
return copy.copy(layer) if layer else None
# VM access methods
- def set_vm_ip_range(self, ip_start, ip_end, ip_type="ipv4"):
- pass
-
- def set_vm_range_type(self, ip_type):
- pass
+ def set_vm_ip_range(self, ip_layer_name, ip_field,
+ ip_init, ip_start, ip_end, add_value,
+ operation, is_big_endian=False, val_size=4,
+ ip_type="ipv4", add_checksum_inst=True):
+ if ip_field not in ["src", "dst"]:
+ raise ValueError("set_vm_ip_range only available for source ('src') or destination ('dst') ip addresses")
+ # set differences between IPv4 and IPv6
+ if ip_type == "ipv4":
+ ip_class = dpkt.ip.IP
+ ip_addr_size = val_size if val_size <= 4 else 4
+ elif ip_type == "ipv6":
+ ip_class = dpkt.ip6.IP6
+ ip_addr_size = val_size if val_size <= 8 else 4
+ else:
+ raise CTRexPktBuilder.IPAddressError()
- def set_vm_core_mask(self, ip_type):
- pass
+ self._verify_layer_prop(ip_layer_name, ip_class)
+ trim_size = ip_addr_size*2
+ init_val = int(binascii.hexlify(CTRexPktBuilder._decode_ip_addr(ip_init, ip_type))[-trim_size:], 16)
+ start_val = int(binascii.hexlify(CTRexPktBuilder._decode_ip_addr(ip_start, ip_type))[-trim_size:], 16)
+ end_val = int(binascii.hexlify(CTRexPktBuilder._decode_ip_addr(ip_end, ip_type))[-trim_size:], 16)
+ # All validations are done, start adding VM instructions
+ flow_var_name = "{layer}__{field}".format(layer=ip_layer_name, field=ip_field)
+ hdr_offset, field_abs_offset = self._calc_offset(ip_layer_name, ip_field, ip_addr_size)
+ self.vm.add_flow_man_inst(flow_var_name, size=ip_addr_size, operation=operation,
+ init_value=init_val,
+ min_value=start_val,
+ max_value=end_val)
+ self.vm.add_write_flow_inst(flow_var_name, field_abs_offset)
+ self.vm.set_vm_off_inst_field(flow_var_name, "add_value", add_value)
+ self.vm.set_vm_off_inst_field(flow_var_name, "is_big_endian", is_big_endian)
+ if ip_type == "ipv4" and add_checksum_inst:
+ self.vm.add_fix_checksum_inst(self._pkt_by_hdr.get(ip_layer_name), hdr_offset)
+
+ def set_vm_eth_range(self, eth_layer_name, eth_field,
+ mac_init, mac_start, mac_end, add_value,
+ operation, val_size=4, is_big_endian=False):
+ if eth_field not in ["src", "dst"]:
+ raise ValueError("set_vm_eth_range only available for source ('src') or destination ('dst') eth addresses")
+ self._verify_layer_prop(eth_layer_name, dpkt.ethernet.Ethernet)
+ eth_addr_size = val_size if val_size <= 4 else 4
+ trim_size = eth_addr_size*2
+ init_val = int(binascii.hexlify(CTRexPktBuilder._decode_mac_addr(mac_init))[-trim_size:], 16)
+ start_val = int(binascii.hexlify(CTRexPktBuilder._decode_mac_addr(mac_start))[-trim_size:], 16)
+ end_val = int(binascii.hexlify(CTRexPktBuilder._decode_mac_addr(mac_end))[-trim_size:], 16)
+ # All validations are done, start adding VM instructions
+ flow_var_name = "{layer}__{field}".format(layer=eth_layer_name, field=eth_field)
+ hdr_offset, field_abs_offset = self._calc_offset(eth_layer_name, eth_field, eth_addr_size)
+ self.vm.add_flow_man_inst(flow_var_name, size=8, operation=operation,
+ init_value=init_val,
+ min_value=start_val,
+ max_value=end_val)
+ self.vm.add_write_flow_inst(flow_var_name, field_abs_offset)
+ self.vm.set_vm_off_inst_field(flow_var_name, "add_value", add_value)
+ self.vm.set_vm_off_inst_field(flow_var_name, "is_big_endian", is_big_endian)
+
+ def set_vm_custom_range(self, layer_name, hdr_field,
+ init_val, start_val, end_val, add_val, val_size,
+ operation, is_big_endian=False, range_name="",
+ add_checksum_inst=True):
+ # verify input validity for init/start/end values
+ for val in [init_val, start_val, end_val]:
+ if not isinstance(val, int):
+ raise ValueError("init/start/end values are expected integers, but received type '{0}'".
+ format(type(val)))
+ self._verify_layer_prop(layer_name=layer_name, field_name=hdr_field)
+ if not range_name:
+ range_name = "{layer}__{field}".format(layer=layer_name, field=hdr_field)
+ trim_size = val_size*2
+ hdr_offset, field_abs_offset = self._calc_offset(layer_name, hdr_field, val_size)
+ self.vm.add_flow_man_inst(range_name, size=val_size, operation=operation,
+ init_value=init_val,
+ min_value=start_val,
+ max_value=end_val)
+ self.vm.add_write_flow_inst(range_name, field_abs_offset)
+ self.vm.set_vm_off_inst_field(range_name, "add_value", add_val)
+ self.vm.set_vm_off_inst_field(range_name, "is_big_endian", is_big_endian)
+ if isinstance(self._pkt_by_hdr.get(layer_name), dpkt.ip.IP) and add_checksum_inst:
+ self.vm.add_fix_checksum_inst(self._pkt_by_hdr.get(layer_name), hdr_offset)
def get_vm_data(self):
- pass
+ return self.vm.dump()
def dump_pkt(self):
"""
@@ -302,9 +476,7 @@ class CTRexPktBuilder(object):
except IOError:
raise IOError(2, "The provided path could not be accessed")
-
- # ----- useful shortcut methods ----- #
- def gen_dns_packet(self):
+ def export_pkt(self, file_path, link_pcap=False, pcap_name=None, pcap_ts=None):
pass
# ----- internal methods ----- #
@@ -342,6 +514,41 @@ class CTRexPktBuilder(object):
if self._pkt_by_hdr[layer] is layer_obj:
return layer
+ def _calc_offset(self, layer_name, hdr_field, hdr_field_size):
+ pkt_header = self._pkt_by_hdr.get(layer_name)
+ hdr_offset = len(self._packet) - len(pkt_header)
+ inner_hdr_offsets = []
+ for field in pkt_header.__hdr__:
+ if field[0] == hdr_field:
+ field_size = struct.calcsize(field[1])
+ if field_size == hdr_field_size:
+ break
+ elif field_size < hdr_field_size:
+ raise CTRexPktBuilder.PacketLayerError(layer_name,
+ "The specified field '{0}' size is smaller than given range"
+ " size ('{1}')".format(hdr_field, hdr_field_size))
+ else:
+ inner_hdr_offsets.append(field_size - hdr_field_size)
+ break
+ else:
+ inner_hdr_offsets.append(struct.calcsize(field[1]))
+ return hdr_offset, hdr_offset + sum(inner_hdr_offsets)
+
+ def _verify_layer_prop(self, layer_name, layer_type=None, field_name=None):
+ if layer_name not in self._pkt_by_hdr:
+ raise CTRexPktBuilder.PacketLayerError(layer_name)
+ pkt_layer = self._pkt_by_hdr.get(layer_name)
+ if layer_type:
+ # check for layer type
+ if not isinstance(pkt_layer, layer_type):
+ raise CTRexPktBuilder.PacketLayerTypeError(layer_name, type(pkt_layer), layer_type)
+ if field_name and not hasattr(pkt_layer, field_name):
+ # check if field exists on certain header
+ raise CTRexPktBuilder.PacketLayerError(layer_name, "The specified field '{0}' does not exists on "
+ "given packet layer ('{1}')".format(field_name,
+ layer_name))
+ return
+
@staticmethod
def _decode_mac_addr(mac_addr):
"""
@@ -449,6 +656,8 @@ class CTRexPktBuilder(object):
This class defines the TRex VM which represents how TRex will regenerate packets.
The packets will be regenerated based on the built packet containing this class.
"""
+ InstStore = namedtuple('InstStore', ['type', 'inst'])
+
def __init__(self):
"""
Instantiate a CTRexVM object
@@ -458,8 +667,10 @@ class CTRexPktBuilder(object):
"""
super(CTRexPktBuilder.CTRexVM, self).__init__()
self.vm_variables = {}
+ self._inst_by_offset = {} # this data structure holds only offset-related instructions, ordered in tuples
+ self._off_inst_by_name = {}
- def set_vm_var_field(self, var_name, field_name, val):
+ def set_vm_var_field(self, var_name, field_name, val, offset_inst=False):
"""
Set VM variable field. Only existing variables are allowed to be changed.
@@ -477,9 +688,15 @@ class CTRexPktBuilder(object):
+ :exc:`CTRexPktBuilder.VMVarValueError`, in case val isn't one of allowed options of field_name.
"""
- return self.vm_variables[var_name].set_field(field_name, val)
+ if offset_inst:
+ return self._off_inst_by_name[var_name].inst.set_field(field_name, val)
+ else:
+ return self.vm_variables[var_name].set_field(field_name, val)
+
+ def set_vm_off_inst_field(self, var_name, field_name, val):
+ return self.set_vm_var_field(var_name, field_name, val, True)
- def add_flow_man_simple(self, name, **kwargs):
+ def add_flow_man_inst(self, name, **kwargs):
"""
Adds a new flow manipulation object to the VM instance.
@@ -488,7 +705,7 @@ class CTRexPktBuilder(object):
name of the manipulation, must be distinct.
Example: 'source_ip_change'
- **kwargs : dict
+ **kwargs** : dict
optional, set flow_man fields on initialization (key = field_name, val = field_val).
Must be used with legit fields, see :func:`CTRexPktBuilder.CTRexVM.CTRexVMVariable.set_field`.
@@ -500,14 +717,40 @@ class CTRexPktBuilder(object):
+ Exceptions from :func:`CTRexPktBuilder.CTRexVM.CTRexVMVariable.set_field` method.
Will rise when VM variables were misconfiguration.
"""
- if name not in self.vm_variables.keys():
- self.vm_variables[name] = self.CTRexVMVariable(name)
- # try configuring VM var attributes
+ if name not in self.vm_variables:
+ self.vm_variables[name] = self.CTRexVMFlowVariable(name)
+ # try configuring VM instruction attributes
for (field, value) in kwargs.items():
self.vm_variables[name].set_field(field, value)
else:
raise CTRexPktBuilder.VMVarNameExistsError(name)
+ def add_fix_checksum_inst(self, linked_ipv4_obj, offset_to_obj=14, name=None):
+ # check if specified linked_ipv4_obj is indeed an ipv4 object
+ if not (isinstance(linked_ipv4_obj, dpkt.ip.IP)):
+ raise ValueError("The provided layer object is not of IPv4.")
+ if not name:
+ name = "checksum_{off}".format(off=offset_to_obj) # name will override previous checksum inst, OK
+ new_checksum_inst = self.CTRexVMChecksumInst(name, offset_to_obj)
+ # store the checksum inst in the end of the IP header (20 Bytes long)
+ inst = self.InstStore('checksum', new_checksum_inst)
+ self._inst_by_offset[offset_to_obj + 20] = inst
+ self._off_inst_by_name[name] = inst
+
+ def add_write_flow_inst(self, name, pkt_offset, **kwargs):
+ if name not in self.vm_variables:
+ raise KeyError("Trying to add write_flow_var instruction to a not-exists VM flow variable ('{0}')".
+ format(name))
+ else:
+ new_write_inst = self.CTRexVMWrtFlowVarInst(name, pkt_offset)
+ # try configuring VM instruction attributes
+ for (field, value) in kwargs.items():
+ new_write_inst.set_field(field, value)
+ # add the instruction to the date-structure
+ inst = self.InstStore('write', new_write_inst)
+ self._inst_by_offset[pkt_offset] = inst
+ self._off_inst_by_name[name] = inst
+
def load_flow_man(self, flow_obj):
"""
Loads an outer VM variable (instruction) into current VM.
@@ -521,7 +764,7 @@ class CTRexPktBuilder(object):
list holds variables data of VM
"""
- assert isinstance(flow_obj, CTRexPktBuilder.CTRexVM.CTRexVMVariable)
+ assert isinstance(flow_obj, CTRexPktBuilder.CTRexVM.CTRexVMFlowVariable)
if flow_obj.name not in self.vm_variables.keys():
self.vm_variables[flow_obj.name] = flow_obj
else:
@@ -529,7 +772,7 @@ class CTRexPktBuilder(object):
def dump(self):
"""
- dumps a VM variables (instructions) into an list data structure.
+ dumps a VM variables (instructions) into a list data structure.
:parameters:
None
@@ -538,14 +781,42 @@ class CTRexPktBuilder(object):
list holds variables data of VM
"""
- return [var.dump()
- for key, var in self.vm_variables.items()]
+ # at first, dump all CTRexVMFlowVariable instructions
+ ret_val = [var.dump()
+ for key, var in self.vm_variables.items()]
+ # then, dump all the CTRexVMWrtFlowVarInst and CTRexVMChecksumInst instructions
+ ret_val += [self._inst_by_offset.get(key).inst.dump()
+ for key in sorted(self._inst_by_offset)]
+ return ret_val
+
+ class CVMAbstractInstruction(object):
+ __metaclass__ = ABCMeta
+
+ def __init__(self, name):
+ """
+ Instantiate a CTRexVMVariable object
+
+ :parameters:
+ name : str
+ a string representing the name of the VM variable.
+ """
+ super(CTRexPktBuilder.CTRexVM.CVMAbstractInstruction, self).__init__()
+ self.name = name
+
+ def set_field(self, field_name, val):
+ if not hasattr(self, field_name):
+ raise CTRexPktBuilder.VMFieldNameError(field_name)
+ setattr(self, field_name, val)
+
+ @abstractmethod
+ def dump(self):
+ pass
- class CTRexVMVariable(object):
+ class CTRexVMFlowVariable(CVMAbstractInstruction):
"""
This class defines a single VM variable to be used as part of CTRexVar object.
"""
- VALID_SIZE = [1, 2, 4, 8]
+ VALID_SIZE = [1, 2, 4, 8] # size in Bytes
VALID_OPERATION = ["inc", "dec", "random"]
def __init__(self, name):
@@ -556,12 +827,12 @@ class CTRexPktBuilder(object):
name : str
a string representing the name of the VM variable.
"""
- super(CTRexPktBuilder.CTRexVM.CTRexVMVariable, self).__init__()
- self.name = name
+ super(CTRexPktBuilder.CTRexVM.CTRexVMFlowVariable, self).__init__(name)
+ # self.name = name
self.size = 4
self.big_endian = True
self.operation = "inc"
- self.split_by_core = False
+ # self.split_by_core = False
self.init_value = 1
self.min_value = self.init_value
self.max_value = self.init_value
@@ -586,32 +857,25 @@ class CTRexPktBuilder(object):
"""
if not hasattr(self, field_name):
- raise CTRexPktBuilder.VMVarNameError(field_name)
+ raise CTRexPktBuilder.VMFieldNameError(field_name)
elif field_name == "size":
if type(val) != int:
- raise CTRexPktBuilder.VMVarFieldTypeError("size", int)
+ raise CTRexPktBuilder.VMFieldTypeError("size", int)
elif val not in self.VALID_SIZE:
- raise CTRexPktBuilder.VMVarValueError("size", self.VALID_SIZE)
- elif field_name == "init_value":
+ raise CTRexPktBuilder.VMFieldValueError("size", self.VALID_SIZE)
+ elif field_name in ["init_value", "min_value", "max_value"]:
if type(val) != int:
- raise CTRexPktBuilder.VMVarFieldTypeError("init_value", int)
+ raise CTRexPktBuilder.VMFieldTypeError(field_name, int)
elif field_name == "operation":
if type(val) != str:
- raise CTRexPktBuilder.VMVarFieldTypeError("operation", str)
+ raise CTRexPktBuilder.VMFieldTypeError("operation", str)
elif val not in self.VALID_OPERATION:
- raise CTRexPktBuilder.VMVarValueError("operation", self.VALID_OPERATION)
- elif field_name == "split_by_core":
- val = bool(val)
+ raise CTRexPktBuilder.VMFieldValueError("operation", self.VALID_OPERATION)
+ # elif field_name == "split_by_core":
+ # val = bool(val)
# update field value on success
setattr(self, field_name, val)
- def is_valid(self):
- if self.size not in self.VALID_SIZE:
- return False
- if self.type not in self.VALID_OPERATION:
- return False
- return True
-
def dump(self):
"""
dumps a variable fields in a dictionary data structure.
@@ -623,15 +887,116 @@ class CTRexPktBuilder(object):
dictionary holds variable data of VM variable
"""
- return {"ins_name": "flow_man_simple", # VM variable dump always refers to manipulate instruction.
- "flow_variable_name": self.name,
- "object_size": self.size,
- # "big_endian": self.big_endian,
- "Operation": self.operation,
- "split_by_core": self.split_by_core,
- "init_value": self.init_value,
- "min_value": self.min_value,
- "max_value": self.max_value}
+ return {"ins_name": "flow_var", # VM variable dump always refers to manipulate instruction.
+ "name": self.name,
+ "size": self.size,
+ "op": self.operation,
+ # "split_by_core": self.split_by_core,
+ "init_value": str(self.init_value),
+ "min_value": str(self.min_value),
+ "max_value": str(self.max_value)}
+
+ class CTRexVMChecksumInst(CVMAbstractInstruction):
+
+ def __init__(self, name, offset):
+ """
+ Instantiate a CTRexVMChecksumInst object
+
+ :parameters:
+ name : str
+ a string representing the name of the VM variable.
+ """
+ super(CTRexPktBuilder.CTRexVM.CTRexVMChecksumInst, self).__init__(name)
+ self.pkt_offset = offset
+
+ def dump(self):
+ return {"type": "fix_checksum_ipv4",
+ "pkt_offset": int(self.pkt_offset)}
+
+ class CTRexVMWrtFlowVarInst(CVMAbstractInstruction):
+
+ def __init__(self, name, pkt_offset):
+ """
+ Instantiate a CTRexVMWrtFlowVarInst object
+
+ :parameters:
+ name : str
+ a string representing the name of the VM variable.
+ """
+ super(CTRexPktBuilder.CTRexVM.CTRexVMWrtFlowVarInst, self).__init__(name)
+ self.pkt_offset = int(pkt_offset)
+ self.add_value = 0
+ self.is_big_endian = False
+
+ def set_field(self, field_name, val):
+ if not hasattr(self, field_name):
+ raise CTRexPktBuilder.VMFieldNameError(field_name)
+ elif field_name == 'pkt_offset':
+ raise ValueError("pkt_offset value cannot be changed")
+ cur_attr_type = type(getattr(self, field_name))
+ if cur_attr_type == type(val):
+ setattr(self, field_name, val)
+ else:
+ CTRexPktBuilder.VMFieldTypeError(field_name, cur_attr_type)
+
+ def dump(self):
+ return {"type": "write_flow_var",
+ "name": self.name,
+ "pkt_offset": self.pkt_offset,
+ "add_value": int(self.add_value),
+ "is_big_endian": bool(self.is_big_endian)
+ }
+
+ class CTRexVMChecksumInst(CVMAbstractInstruction):
+
+ def __init__(self, name, offset):
+ """
+ Instantiate a CTRexVMChecksumInst object
+
+ :parameters:
+ name : str
+ a string representing the name of the VM variable.
+ """
+ super(CTRexPktBuilder.CTRexVM.CTRexVMChecksumInst, self).__init__(name)
+ self.pkt_offset = offset
+
+ def dump(self):
+ return {"type": "fix_checksum_ipv4",
+ "pkt_offset": int(self.pkt_offset)}
+
+ class CTRexVMWrtFlowVarInst(CVMAbstractInstruction):
+
+ def __init__(self, name, pkt_offset):
+ """
+ Instantiate a CTRexVMWrtFlowVarInst object
+
+ :parameters:
+ name : str
+ a string representing the name of the VM variable.
+ """
+ super(CTRexPktBuilder.CTRexVM.CTRexVMWrtFlowVarInst, self).__init__(name)
+ self.pkt_offset = int(pkt_offset)
+ self.add_value = 0
+ self.is_big_endian = False
+
+ def set_field(self, field_name, val):
+ if not hasattr(self, field_name):
+ raise CTRexPktBuilder.VMFieldNameError(field_name)
+ elif field_name == 'pkt_offset':
+ raise ValueError("pkt_offset value cannot be changed")
+ cur_attr_type = type(getattr(self, field_name))
+ if cur_attr_type == type(val):
+ setattr(self, field_name, val)
+ else:
+ CTRexPktBuilder.VMFieldTypeError(field_name, cur_attr_type)
+
+ def dump(self):
+ return {"type": "write_flow_var",
+ "name": self.name,
+ "pkt_offset": self.pkt_offset,
+ "add_value": int(self.add_value),
+ "is_big_endian": bool(self.is_big_endian)
+ }
class CPacketBuildException(Exception):
"""
@@ -672,7 +1037,28 @@ class CTRexPktBuilder(object):
def __init__(self, message=''):
self._default_message = 'Illegal MAC address has been provided.'
self.message = message or self._default_message
- super(CTRexPktBuilder.MACAddressError, self).__init__(-11, self.message)
+ super(CTRexPktBuilder.MACAddressError, self).__init__(-12, self.message)
+
+ class PacketLayerError(CPacketBuildException):
+ """
+ This exception is used to indicate an error caused by operation performed on an non-exists layer of the packet.
+ """
+ def __init__(self, name, message=''):
+ self._default_message = "The given packet layer name ({0}) does not exists.".format(name)
+ self.message = message or self._default_message
+ super(CTRexPktBuilder.PacketLayerError, self).__init__(-13, self.message)
+
+ class PacketLayerTypeError(CPacketBuildException):
+ """
+ This exception is used to indicate an error caused by operation performed on an non-exists layer of the packet.
+ """
+ def __init__(self, name, layer_type, ok_type, message=''):
+ self._default_message = "The type of packet layer {layer_name} is of type {layer_type}, " \
+ "and not of the expected {allowed_type}.".format(layer_name=name,
+ layer_type=layer_type,
+ allowed_type=ok_type.__name__)
+ self.message = message or self._default_message
+ super(CTRexPktBuilder.PacketLayerTypeError, self).__init__(-13, self.message)
class VMVarNameExistsError(CPacketBuildException):
"""
@@ -683,37 +1069,37 @@ class CTRexPktBuilder(object):
self.message = message or self._default_message
super(CTRexPktBuilder.VMVarNameExistsError, self).__init__(-21, self.message)
- class VMVarNameError(CPacketBuildException):
+ class VMFieldNameError(CPacketBuildException):
"""
This exception is used to indicate that an undefined VM var field name has been accessed.
"""
def __init__(self, name, message=''):
self._default_message = "The given VM field name ({0}) is not defined and isn't legal.".format(name)
self.message = message or self._default_message
- super(CTRexPktBuilder.VMVarNameError, self).__init__(-22, self.message)
+ super(CTRexPktBuilder.VMFieldNameError, self).__init__(-22, self.message)
- class VMVarFieldTypeError(CPacketBuildException):
+ class VMFieldTypeError(CPacketBuildException):
"""
This exception is used to indicate an illegal value has type has been given to VM variable field.
"""
def __init__(self, name, ok_type, message=''):
- self._default_message = 'The desired value of field {field_name} is of type {field_type}, \
- and not of the allowed {allowed_type}.'.format(field_name=name,
- field_type=type(name).__name__,
- allowed_type=ok_type.__name__)
+ self._default_message = "The desired value of field {field_name} is of type {field_type}, " \
+ "and not of the allowed {allowed_type}.".format(field_name=name,
+ field_type=type(name).__name__,
+ allowed_type=ok_type.__name__)
self.message = message or self._default_message
- super(CTRexPktBuilder.VMVarFieldTypeError, self).__init__(-31, self.message)
+ super(CTRexPktBuilder.VMFieldTypeError, self).__init__(-31, self.message)
- class VMVarValueError(CPacketBuildException):
+ class VMFieldValueError(CPacketBuildException):
"""
This exception is used to indicate an error an illegal value has been assigned to VM variable field.
"""
def __init__(self, name, ok_opts, message=''):
- self._default_message = 'The desired value of field {field_name} is illegal.\n \
- The only allowed options are: {allowed_opts}.'.format(field_name=name,
- allowed_opts=ok_opts)
+ self._default_message = "The desired value of field {field_name} is illegal.\n" \
+ "The only allowed options are: {allowed_opts}.".format(field_name=name,
+ allowed_opts=ok_opts)
self.message = message or self._default_message
- super(CTRexPktBuilder.VMVarValueError, self).__init__(-32, self.message)
+ super(CTRexPktBuilder.VMFieldValueError, self).__init__(-32, self.message)
if __name__ == "__main__":
diff --git a/scripts/automation/trex_control_plane/client_utils/trex_yaml_gen.py b/scripts/automation/trex_control_plane/client_utils/trex_yaml_gen.py
index 755674ea..c26fef29 100755
--- a/scripts/automation/trex_control_plane/client_utils/trex_yaml_gen.py
+++ b/scripts/automation/trex_control_plane/client_utils/trex_yaml_gen.py
@@ -7,7 +7,7 @@ import os
class CTRexYaml(object):
"""
- This class functions as a YAML generator according to T-Rex YAML format.
+ This class functions as a YAML generator according to TRex YAML format.
CTRexYaml is compatible with both Python 2 and Python 3.
"""
@@ -38,7 +38,7 @@ class CTRexYaml(object):
:parameters:
trex_files_path : str
- a path (on T-Rex server side) for the pcap files using which T-Rex can access it.
+ a path (on TRex server side) for the pcap files using which TRex can access it.
"""
self.yaml_obj = list(CTRexYaml.YAML_TEMPLATE)
@@ -114,7 +114,7 @@ class CTRexYaml(object):
:parameters:
None
- :reaturn:
+ :return:
None
"""
@@ -193,7 +193,7 @@ class CTRexYaml(object):
"""
Returns a list of all files related to the YAML object, including the YAML filename itself.
- .. tip:: This method is especially useful for listing all the files that should be pushed to T-Rex server as part of the same yaml selection.
+ .. tip:: This method is especially useful for listing all the files that should be pushed to TRex server as part of the same yaml selection.
:parameters:
None