Source code for trex.stl.trex_stl_packet_builder_scapy

import random
import string
import struct
import socket
import json
import yaml
import binascii
import base64
import inspect
import copy
import collections

from scapy.all import *

from ..utils.common import *
from ..common.trex_types import *
from ..common.trex_exceptions import TRexError

from .trex_stl_packet_builder_interface import CTrexPktBuilderInterface



class CTRexPacketBuildException(Exception):
    """
    This is the general Packet Building error exception class.
    """
    def __init__(self, code, message):
        self.code = code
        self.message = message

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return u"[errcode:%r] %r" % (self.code, self.message)

################################################################################################


class CTRexScriptsBase(object):
    """
    VM Script base class
    """
    def clone (self):
        return copy.deepcopy(self)


class CTRexScFieldRangeBase(CTRexScriptsBase):

    FILED_TYPES = ['inc', 'dec', 'rand']

    def __init__(self, field_name,
                       field_type
                            ):
        super(CTRexScFieldRangeBase, self).__init__()
        self.field_name =field_name
        self.field_type =field_type
        if self.field_type not in CTRexScFieldRangeBase.FILED_TYPES :
            raise CTRexPacketBuildException(-12, 'Field type should be in %s' % FILED_TYPES);


class CTRexScFieldRangeValue(CTRexScFieldRangeBase):
    """
    Range of field values
    """
    def __init__(self, field_name,
                       field_type,
                       min_value,
                       max_value
                           ):
        super(CTRexScFieldRangeValue, self).__init__(field_name,field_type)
        self.min_value =min_value;
        self.max_value =max_value;
        if min_value > max_value:
            raise CTRexPacketBuildException(-12, 'Invalid range: min is greater than max.');
        if min_value == max_value:
            raise CTRexPacketBuildException(-13, "Invalid range: min value is equal to max value.");


class CTRexScIpv4SimpleRange(CTRexScFieldRangeBase):
    """
    Range of ipv4 ip
    """
    def __init__(self, field_name, field_type, min_ip, max_ip):
        super(CTRexScIpv4SimpleRange, self).__init__(field_name,field_type)
        self.min_ip = min_ip
        self.max_ip = max_ip
        mmin=ipv4_str_to_num (is_valid_ipv4_ret(min_ip))
        mmax=ipv4_str_to_num (is_valid_ipv4_ret(max_ip))
        if  mmin > mmax :
            raise CTRexPacketBuildException(-11, 'CTRexScIpv4SimpleRange m_min ip is bigger than max');


class CTRexScIpv4TupleGen(CTRexScriptsBase):
    """
    Range tuple
    """
    FLAGS_ULIMIT_FLOWS =1

    def __init__(self, min_ipv4, max_ipv4, num_flows=100000, min_port=1025, max_port=65535, flags=0):
        super(CTRexScIpv4TupleGen, self).__init__()
        self.min_ip = min_ipv4
        self.max_ip = max_ipv4
        mmin=ipv4_str_to_num (is_valid_ipv4_ret(min_ipv4))
        mmax=ipv4_str_to_num (is_valid_ipv4_ret(max_ipv4))
        if  mmin > mmax :
            raise CTRexPacketBuildException(-11, 'CTRexScIpv4SimpleRange m_min ip is bigger than max');

        self.num_flows=num_flows;

        self.min_port =min_port
        self.max_port =max_port
        self.flags =   flags


class CTRexScTrimPacketSize(CTRexScriptsBase):
    """
    Trim packet size. Field type is CTRexScFieldRangeBase.FILED_TYPES = ["inc","dec","rand"]
    """
    def __init__(self,field_type="rand",min_pkt_size=None, max_pkt_size=None):
        super(CTRexScTrimPacketSize, self).__init__()
        self.field_type = field_type
        self.min_pkt_size = min_pkt_size
        self.max_pkt_size = max_pkt_size
        if max_pkt_size != None and min_pkt_size !=None :
            if min_pkt_size == max_pkt_size:
                raise CTRexPacketBuildException(-11, 'CTRexScTrimPacketSize min_pkt_size is the same as max_pkt_size ');

            if min_pkt_size > max_pkt_size:
                raise CTRexPacketBuildException(-11, 'CTRexScTrimPacketSize min_pkt_size is bigger than max_pkt_size ');


[docs]class STLScVmRaw(CTRexScriptsBase): """ Raw instructions """ def __init__(self,list_of_commands=None, cache_size=None): """ Include a list of a basic instructions objects. :parameters: list_of_commands : list list of instructions cache_size : uint16_t In case it is bigger than zero, FE results will be cached - this will speedup of the program at the cost of limiting the number of possible packets to the number of cache. The cache size is limited to the pool size The following example splits the generated traffic by "ip_src" variable. .. code-block:: python # Split by # TCP SYN base_pkt = Ether()/IP(dst="48.0.0.1")/TCP(dport=80,flags="S") # vm vm = STLScVmRaw( [ STLVmFlowVar(name="ip_src", min_value="16.0.0.0", max_value="16.0.0.254", size=4, op="inc"), STLVmWrFlowVar(fv_name="ip_src", pkt_offset= "IP.src" ), STLVmFixIpv4(offset = "IP"), # fix checksum ] cache_size = 1000 ) """ super(STLScVmRaw, self).__init__() self.cache_size = cache_size if list_of_commands==None: self.commands =[] else: self.commands = list_of_commands def add_cmd (self,cmd): self.commands.append(cmd) ################################################################################################ # VM raw instructions ################################################################################################
class CTRexVmInsBase(object): """ Instruction base """ def __init__(self, ins_type): self.type = ins_type validate_type('ins_type', ins_type, basestring) class CTRexVmInsFixIpv4(CTRexVmInsBase): def __init__(self, offset): super(CTRexVmInsFixIpv4, self).__init__("fix_checksum_ipv4") self.pkt_offset = offset validate_type('offset', offset, int) class CTRexVmInsFixHwCs(CTRexVmInsBase): L4_TYPE_UDP = 11 L4_TYPE_TCP = 13 L4_TYPE_IP = 17 def __init__(self, l2_len,l3_len,l4_type): super(CTRexVmInsFixHwCs, self).__init__("fix_checksum_hw") self.l2_len = l2_len validate_type('l2_len', l2_len, int) self.l3_len = l3_len validate_type('l3_len', l3_len, int) self.l4_type = l4_type validate_type('l4_type', l4_type, int) class CTRexVmInsFlowVar(CTRexVmInsBase): #TBD add more validation tests OPERATIONS =['inc', 'dec', 'random'] VALID_SIZES =[1, 2, 4, 8] def __init__(self, fv_name, size, op, init_value, min_value, max_value,step, value_list, split_to_cores, next_var): super(CTRexVmInsFlowVar, self).__init__("flow_var") self.name = fv_name validate_type('fv_name', fv_name, basestring) self.size = size self.op = op if next_var is not None: validate_type('next_var', next_var, basestring) if op == 'random': raise CTRexPacketBuildException(-11,"If next_var is defined then op can't be random. Check %s " % fv_name) if next_var == self.name: raise CTRexPacketBuildException(-11,"Self loops are forbidden.") self.next_var = next_var validate_type('split_to_cores', split_to_cores, bool) self.split_to_cores = split_to_cores if value_list == None: self.init_value = init_value validate_type('init_value', init_value, int) assert init_value >= 0, 'init_value (%s) is negative' % init_value assert init_value <= max_value and init_value >= min_value, 'init_value (%s) must be between min_value (%s) and max_value (%s)' % (init_value, min_value, max_value) self.min_value = min_value validate_type('min_value', min_value, int) assert min_value >= 0, 'min_value (%s) is negative' % min_value self.max_value = max_value validate_type('max_value', max_value, int) assert max_value >= 0, 'max_value (%s) is negative' % max_value else: self.value_list = value_list validate_type('value_list', value_list, list) for value in value_list: assert value >= 0, 'value_list (%s) is negative' % value self.step=step validate_type('step', step, int) assert step > 0, 'step (%s) is equals 0 or negative' % step class CTRexVmInsFlowVarRandLimit(CTRexVmInsBase): #TBD add more validation tests VALID_SIZES =[1, 2, 4, 8] def __init__(self, fv_name, size, limit, seed, min_value, max_value, split_to_cores, next_var): super(CTRexVmInsFlowVarRandLimit, self).__init__("flow_var_rand_limit") self.name = fv_name; validate_type('fv_name', fv_name, basestring) self.size = size self.limit=limit validate_type('limit', limit, int) assert limit > 0, 'limit (%s) is equals 0 or negative' % limit self.seed=seed validate_type('seed', seed, int) self.min_value=min_value validate_type('min_value', min_value, int) assert min_value >= 0, 'min_value (%s) is negative' % min_value self.max_value=max_value validate_type('max_value', max_value, int) assert max_value >= 0, 'max_value (%s) is negative' % max_value validate_type('split_to_cores', split_to_cores, bool) self.split_to_cores = split_to_cores if next_var is not None: validate_type('next_var', next_var, [basestring]) if next_var == self.name: raise CTRexPacketBuildException(-11,"Self loops are forbidden.") self.next_var = next_var class CTRexVmInsWrFlowVar(CTRexVmInsBase): def __init__(self, fv_name, pkt_offset, add_value=0, is_big_endian=True): super(CTRexVmInsWrFlowVar, self).__init__("write_flow_var") self.name = fv_name validate_type('fv_name', fv_name, basestring) self.pkt_offset = pkt_offset validate_type('pkt_offset', pkt_offset, int) self.add_value = add_value validate_type('add_value', add_value, int) self.is_big_endian = is_big_endian validate_type('is_big_endian', is_big_endian, bool) class CTRexVmInsWrMaskFlowVar(CTRexVmInsBase): def __init__(self, fv_name, pkt_offset,pkt_cast_size,mask,shift,add_value, is_big_endian=True): super(CTRexVmInsWrMaskFlowVar, self).__init__("write_mask_flow_var") self.name = fv_name validate_type('fv_name', fv_name, basestring) self.pkt_offset = pkt_offset validate_type('pkt_offset', pkt_offset, int) self.pkt_cast_size = pkt_cast_size validate_type('pkt_cast_size', pkt_cast_size, int) self.mask = mask validate_type('mask', mask, int) self.shift = shift validate_type('shift', shift, int) self.add_value =add_value validate_type('add_value', add_value, int) self.is_big_endian = is_big_endian validate_type('is_big_endian', is_big_endian, bool) class CTRexVmInsTrimPktSize(CTRexVmInsBase): def __init__(self,fv_name): super(CTRexVmInsTrimPktSize, self).__init__("trim_pkt_size") self.name = fv_name validate_type('fv_name', fv_name, basestring) class CTRexVmInsTupleGen(CTRexVmInsBase): def __init__(self, fv_name, ip_min, ip_max, port_min, port_max, limit_flows, flags=0): super(CTRexVmInsTupleGen, self).__init__("tuple_flow_var") self.name =fv_name validate_type('fv_name', fv_name, basestring) self.ip_min = ip_min; self.ip_max = ip_max; self.port_min = port_min; self.port_max = port_max; self.limit_flows = limit_flows; self.flags =flags; ################################################################################################ # class CTRexVmEngine(object): def __init__(self): """ Inlcude list of instructions. """ super(CTRexVmEngine, self).__init__() self.ins=[] self.cache_size = 0 # return as json def get_json (self): inst_array = []; # dump it as dict for obj in self.ins: inst_array.append(obj.__dict__); d={'instructions': inst_array}; if self.cache_size >0 : d['cache']=self.cache_size return d def add_ins (self,ins): #assert issubclass(ins, CTRexVmInsBase) self.ins.append(ins); def dump (self): cnt=0; for obj in self.ins: print("ins",cnt) cnt = cnt +1 print(obj.__dict__) def dump_bjson (self): print(json.dumps(self.get_json(), sort_keys=True, indent=4)) def dump_as_yaml (self): print(yaml.dump(self.get_json(), default_flow_style=False)) ################################################################################################ class CTRexScapyPktUtl(object): def __init__(self, scapy_pkt): assert isinstance(scapy_pkt, Packet) self.pkt = scapy_pkt def pkt_iter (self): p=self.pkt; while True: yield p p=p.payload if p ==None or isinstance(p,NoPayload): break; def get_list_iter(self): l=list(self.pkt_iter()) return l def get_pkt_layers(self): """ Return string 'IP:UDP:TCP' """ l=self.get_list_iter (); l1=map(lambda p: p.name,l ); return ":".join(l1); def _layer_offset(self, name, cnt = 0): """ Return offset of layer. Example: 'IP',1 returns offfset of layer ip:1 """ save_cnt=cnt for pkt in self.pkt_iter (): if name in (pkt.name, pkt.__class__.__name__): if cnt==0: return (pkt, pkt._offset) else: cnt=cnt -1 raise CTRexPacketBuildException(-11,("no layer %s-%d" % (name, save_cnt))); def layer_offset(self, name, cnt = 0): """ Return offset of layer. Example: 'IP',1 returns offfset of layer ip:1 """ save_cnt=cnt for pkt in self.pkt_iter (): if pkt.name == name: if cnt==0: return pkt._offset else: cnt=cnt -1 raise CTRexPacketBuildException(-11,("no layer %s-%d" % (name, save_cnt))); def get_field_offet(self, layer, layer_cnt, field_name): """ Return offset of layer. Example: 'IP',1 returns offfset of layer ip:1 """ t=self._layer_offset(layer,layer_cnt); l_offset=t[1]; layer_pkt=t[0] #layer_pkt.dump_fields_offsets () for f in layer_pkt.fields_desc: if f.name == field_name: return (l_offset+f._offset,f.get_size_bytes ()); raise CTRexPacketBuildException(-11, "No layer %s-%d." % (field_name, layer_cnt)) def get_field_by_offset(self, offset): ''' Try to convert numeric offset to layer name, layer index, field name. The opposite of get_field_offet() Return None if not successful. ''' offset_int = int(offset) assert offset_int == offset assert offset_int >= 0 # look for layer layer_cnt = {} for pkt in self.pkt_iter(): layer_name = pkt.__class__.__name__ if pkt._offset > offset_int: break if layer_name in layer_cnt: layer_cnt[layer_name] += 1 else: layer_cnt[layer_name] = 0 last_name = layer_name last_count = layer_cnt[layer_name] last_layer = pkt # look for field inside layer for f in last_layer.fields_desc: if last_layer._offset + f._offset == offset_int: return (last_name, last_count, f.name) def get_layer_offet_by_str(self, layer_des): """ Return layer offset by string. :parameters: IP:0 IP:1 return offset """ l1=layer_des.split(":") layer="" layer_cnt=0; if len(l1)==1: layer=l1[0]; else: layer=l1[0]; layer_cnt=int(l1[1]); return self.layer_offset(layer, layer_cnt) def get_field_offet_by_str(self, field_des): """ Return field_des (offset,size) layer:cnt.field Example: 802|1Q.vlan get 802.1Q->valn replace | with . IP.src IP:0.src (first IP.src like IP.src) Example: IP:1.src for internal IP Return (offset, size) as tuple. """ s=field_des.split("."); if len(s)!=2: raise CTRexPacketBuildException(-11, ("Field desription should be layer:cnt.field Example: IP.src or IP:1.src")); layer_ex = s[0].replace("|",".") field = s[1] l1=layer_ex.split(":") layer="" layer_cnt=0; if len(l1)==1: layer=l1[0]; else: layer=l1[0]; layer_cnt=int(l1[1]); return self.get_field_offet(layer,layer_cnt,field) def has_IPv4 (self): return self.pkt.has_layer("IP"); def has_IPv6 (self): return self.pkt.has_layer("IPv6"); def has_UDP (self): return self.pkt.has_layer("UDP"); ################################################################################################ class CTRexVmDescBase(object): """ Instruction base """ def __init__(self): pass; def get_obj(self): return self; def get_json(self): return self.get_obj().__dict__ def dump_bjson(self): print(json.dumps(self.get_json(), sort_keys=True, indent=4)) def dump_as_yaml(self): print(yaml.dump(self.get_json(), default_flow_style=False)) def get_var_ref (self): ''' Virtual function returns a ref var name. ''' return None def get_var_name(self): ''' Virtual function returns the varible name if it exists. ''' return None def get_next_var_name(self): ''' Virtual function that returns the name of the next variable name if it exists. ''' return None def compile(self,parent): ''' Virtual function to take parent that has function name_to_offset. ''' pass def valid_fv_size (size): if size not in CTRexVmInsFlowVar.VALID_SIZES: raise CTRexPacketBuildException(-11,("Flow var has invalid size %d ") % size ); def valid_fv_ops (op): if op not in CTRexVmInsFlowVar.OPERATIONS: raise CTRexPacketBuildException(-11,("Flow var has invalid op %s ") % op ); def get_max_by_size (size): d={ 1:((1<<8) -1), 2:((1<<16)-1), 4:((1<<32)-1), 8:0xffffffffffffffff }; return d[size] def convert_val (val): if is_integer(val): return val if type(val) == str: return ipv4_str_to_num (is_valid_ipv4_ret(val)) raise CTRexPacketBuildException(-11,("init val invalid %s ") % val ); def convert_val_list(val): if not isinstance(val, (list,)): raise CTRexPacketBuildException(-11, ("init val invalid(not list) %s ") % val); for index in range(0,len(val)): if is_integer(val[index]): continue elif type(val[index]) == str: val[index] = ipv4_str_to_num (is_valid_ipv4_ret(val[index])) else: raise CTRexPacketBuildException(-11, ("init val[%s] invalid %s ") % (index, val[index])); return val def check_for_int (val): validate_type('val', val, int) def valid_fv_var_sizes(size, vars): for val in vars: if val > get_max_by_size(size): raise CTRexPacketBuildException(-11, ("requested value %d with size %d is bigger than %s") % (val, size, hex(get_max_by_size(size)))); def valid_fv_init(init_value, min_value, max_value): if init_value > max_value or init_value < min_value: raise CTRexPacketBuildException(-11, ("init_value must be between min_value %d and max_value %d" % (min_value, max_value))); def valid_fv_min_max(min_value, max_value): if min_value > max_value : raise CTRexPacketBuildException(-11, ("max %d is lower than min %d ") % (max_value, min_value)); def valid_fv_cycle(min_value, max_value, step, op): pad = (max_value - min_value + 1) % step if pad != 0: step_pad = step - pad if op == 'inc': if (get_max_by_size(8) - max_value) < step_pad: raise CTRexPacketBuildException(-11,("could not pad range to be a true cycle - '(max_value - min_value + 1) % step' should be zero")); elif op == 'dec': if min_value < step_pad: raise CTRexPacketBuildException(-11,("could not pad range to be a true cycle - '(max_value - min_value + 1) % step' should be zero"));
[docs]class STLVmFlowVar(CTRexVmDescBase): def __init__(self, name, init_value=None, min_value=0, max_value=255, size=4, step=1, op="inc", value_list=None, split_to_cores=True, next_var=None): """ Flow variable instruction. Allocates a variable on a stream context. The size argument determines the variable size. The operation can be inc, dec, and random. For increment and decrement operations, can set the "step" size. For all operations, can set initialization value, minimum and maximum value. :parameters: name : string Name of the stream variable init_value : int Init value of the variable. If not specified, it will be min_value min_value : int Min value max_value : int Max value size : int Number of bytes of the variable. Possible values: 1,2,4,8 for uint8_t, uint16_t, uint32_t, uint64_t step : int Step in case of "inc" or "dec" operations op : string Possible values: "inc", "dec", "random" value_list : int List of values instead of init_value, min_value and max_value split_to_cores : bool Default value is True. If the value is True then each core updates the variable by step * num of cores. If split_to_cores = False then each core updates the variable by step. next_var : string or None In case it isn't None it is the name of the next variable. The next variable will perform its operation only when this variable completes a full wrap around. .. code-block:: python # Example1 # input STLVmFlowVar(min_value=0, max_value=3, size=1,op="inc") # output 0,1,2,3,0,1,2,3 .. # input STLVmFlowVar(min_value=0, max_value=3, size=1,op="dec") # output 0,3,2,1,0,3,2,1 .. # input STLVmFlowVar(min_value=0, max_value=3, size=1,op="random") # output 1,1,2,3,1,2,1,0 .. # input STLVmFlowVar(min_value=0, max_value=10, size=1,op="inc",step=3) # output 0,3,6,9,0,3,6,9,0.. # Example2 # input value_list STLVmFlowVar(value_list=["16.0.0.1","16.0.0.2","16.0.0.3","16.0.0.4"], op="inc")) # output 268435457,268435458,268435459,268435460,268435457,268435458,268435459,268435460,268435457.. # input value_list STLVmFlowVar(value_list=[9,0,4,7,5], op="dec")) # output 5,7,4,0,9,5,7,4,0,9,5,7.. """ super(STLVmFlowVar, self).__init__() self.name = name validate_type('name', name, basestring) self.size =size valid_fv_size(size) self.op =op valid_fv_ops (op) if next_var is not None: validate_type('next_var', next_var, [basestring]) if op == 'random': raise CTRexPacketBuildException(-11,"If next_var is defined then op can't be random. Check %s " % self.name) if next_var == self.name: raise CTRexPacketBuildException(-11,"Self loops are forbidden.") self.next_var = next_var self.previous = None validate_type('split_to_cores', split_to_cores, bool) self.split_to_cores = split_to_cores self.step = convert_val (step) if self.step <= 0: raise CTRexPacketBuildException(-11,("step %d must be more than 0") % (self.step)) if value_list == None: # choose default value for init val if init_value == None : init_value = max_value if op == "dec" else min_value self.init_value = convert_val(init_value) self.min_value = convert_val(min_value) self.max_value = convert_val(max_value) self.value_list = None valid_fv_var_sizes(self.size, [self.min_value, self.max_value, self.init_value, self.step]) valid_fv_min_max(self.min_value, self.max_value) valid_fv_init(self.init_value, self.min_value, self.max_value) valid_fv_cycle(self.min_value, self.max_value, self.step, self.op) else: if len(value_list) > get_max_by_size(2): raise CTRexPacketBuildException(-11,("value_list size %d bigger than %d") % (len(value_list), get_max_by_size(2))); self.init_value = None self.min_value = None self.max_value = None self.value_list = convert_val_list(value_list) valid_fv_var_sizes(self.size, self.value_list) def get_obj (self): return CTRexVmInsFlowVar(self.name,self.size,self.op,self.init_value,self.min_value,self.max_value,self.step,self.value_list, self.split_to_cores, self.next_var) def get_var_name(self): return [self.name] def get_next_var_name(self): return self.next_var def get_previous_var_name(self): return self.previous
[docs]class STLVmFlowVarRepeatableRandom(CTRexVmDescBase): def __init__(self, name, size=4, limit=100, seed=None, min_value=0, max_value=None, split_to_cores=True, next_var=None): """ Flow variable instruction for repeatable random with limit number of generating numbers. Allocates memory on a stream context. The size argument determines the variable size. Could be 1,2,4 or 8 1. The maximum number of distinct values will 'limit'. There could be a case of repetition 2. The values will be repeated after 'limit' number of values. :parameters: name : string Name of the stream variable size : int Number of bytes of the variable. Possible values: 1,2,4,8 for uint8_t, uint16_t, uint32_t, uint64_t limit : int The number of distinct repetable random number seed : int For deterministic result, you can set this to a uint16_t number min_value : int Min value max_value : int Max value split_to_cores : bool Default value is True. If the value is True then each core updates the variable by step * num of cores. If split_to_cores = False then each core updates the variable by step. next_var : string or None In case it isn't None it is the name of the next variable. The next variable will perform its operation only when this variable completes a full wrap around. .. code-block:: python # Example1 # input , 1 byte or random with limit of 5 STLVmFlowVarRepeatableRandom("var1",size=1,limit=5) # output 255,1,7,129,8, ==> repeat 255,1,7,129,8 STLVmFlowVarRepeatableRandom("var1",size=4,limit=100,min_value=0x12345678, max_value=0x32345678) """ super(STLVmFlowVarRepeatableRandom, self).__init__() self.name = name; validate_type('name', name, basestring) self.size =size valid_fv_size(size) self.limit =limit validate_type('split_to_cores', split_to_cores, bool) self.split_to_cores = split_to_cores if next_var is not None: validate_type('next_var', next_var, [basestring]) if next_var == self.name: raise CTRexPacketBuildException(-11,"Self loops are forbidden.") self.next_var = next_var self.previous = None if seed == None: self.seed = random.randint(1, 32000) else: self.seed = seed if min_value == None: self.min_value = convert_val (0); else: self.min_value = convert_val (min_value); if max_value == None : self.max_value = get_max_by_size (self.size) else: self.max_value = convert_val (max_value) valid_fv_var_sizes(self.size, [self.min_value, self.max_value]) valid_fv_min_max(self.min_value, self.max_value) if self.limit <= 0: raise CTRexPacketBuildException(-11,("limit %d must be more than 0") % (self.limit)); def get_obj (self): return CTRexVmInsFlowVarRandLimit(self.name, self.size, self.limit, self.seed, self.min_value, self.max_value, self.split_to_cores, self.next_var); def get_var_name(self): return [self.name] def get_next_var_name(self): return self.next_var def get_previous_var_name(self): return self.previous
[docs]class STLVmFixChecksumHw(CTRexVmDescBase): def __init__(self, l3_offset,l4_offset,l4_type): """ Fix IPv4 header checksum and/or TCP/UDP checksum using hardware assist. Use this if the packet header has changed or data payload has changed as it is necessary to fix the checksums. This instruction works on NICS that support this hardware offload. For fixing only IPv4 header checksum use STLVmFixIpv4. This instruction should be used if both L4 and L3 need to be fixed. example for supported packets Ether()/IPv4 SomeTunnel()/IPv4 Ether()/(IPv4|IPv6)/(UDP|TCP) Ether()/(IPv4|IPv6)/(UDP|TCP) SomeTunnel()/(IPv4|IPv6)/(UDP|TCP) SomeTunnel()/(IPv4|IPv6)/(UDP|TCP) :parameters: l3_offset : offset in bytes **IPv4/IPv6 header** offset from packet start. It is **not** the offset of the checksum field itself. in could be string in case of scapy packet. format IP[:[id]] l4_offset : offset in bytes to UDP/TCP header or IPv4 payload. in case of IPv4 checksum CTRexVmInsFixHwCs.L4_TYPE_IP could be set to zero to be auto calculated by the server l4_type : [CTRexVmInsFixHwCs.L4_TYPE_UDP or CTRexVmInsFixHwCs.L4_TYPE_TCP or CTRexVmInsFixHwCs.L4_TYPE_IP] see full example stl/syn_attack_fix_cs_hw.py .. code-block:: python # Example2 pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) # by offset STLVmFixChecksumHw(l3_offset=14,l4_offset=14+20,l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP) # in case of scapy packet can be defined by header name STLVmFixChecksumHw(l3_offset="IP",l4_offset="UDP",l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP) # string for second "IP" header in the packet is IP:1 STLVmFixChecksumHw(offset="IP:1") """ super(STLVmFixChecksumHw, self).__init__() self.l3_offset = l3_offset; # could be a name of offset self.l4_offset = l4_offset; # could be a name of offset self.l4_type = l4_type self.l2_len = 0 def get_obj (self): return CTRexVmInsFixHwCs(self.l2_len,self.l3_len,self.l4_type); def compile(self,parent): if type(self.l3_offset)==str: self.l2_len = parent._pkt_layer_offset(self.l3_offset); else: self.l2_len = self.l3_offset if type(self.l4_offset)==str: self.l4_offset = parent._pkt_layer_offset(self.l4_offset); if (self.l4_type != CTRexVmInsFixHwCs.L4_TYPE_IP): assert self.l4_offset >= self.l2_len+8, 'l4_offset should be higher than l3_offset offset' self.l3_len = self.l4_offset - self.l2_len; else: if self.l4_offset!=0: assert self.l4_offset >= self.l2_len+8, 'l4_offset should be higher than l3_offset offset' self.l3_len = self.l4_offset - self.l2_len; else: self.l3_len=0
[docs]class STLVmFixIpv4(CTRexVmDescBase): def __init__(self, offset): """ Fix IPv4 header checksum. Use this if the packet header has changed and it is necessary to change the checksum. :parameters: offset : uint16_t or string **IPv4 header** offset from packet start. It is **not** the offset of the checksum field itself. in could be string in case of scapy packet. format IP[:[id]] .. code-block:: python # Example2 pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) # by offset STLVmFixIpv4(offset=14) # in case of scapy packet can be defined by header name STLVmFixIpv4(offset="IP") # string for second "IP" header in the packet is IP:1 STLVmFixIpv4(offset="IP:1") """ super(STLVmFixIpv4, self).__init__() self.offset = offset; # could be a name of offset def get_obj (self): return CTRexVmInsFixIpv4(self.offset); def compile(self,parent): if type(self.offset)==str: self.offset = parent._pkt_layer_offset(self.offset);
[docs]class STLVmWrFlowVar(CTRexVmDescBase): def __init__(self, fv_name, pkt_offset, offset_fixup=0, add_val=0, is_big=True): """ Write a stream variable into a packet field. The write position is determined by the packet offset + offset fixup. The size of the write is determined by the stream variable. Example: Offset 10, fixup 0, variable size 4. This function writes at 10, 11, 12, and 13. For inromation about chaning the write size, offset, or fixup, see the `STLVmWrMaskFlowVar` command. The Field name/offset can be given by name in the following format: ``header[:id].field``. :parameters: fv_name : string Stream variable to write to a packet offset. pkt_offset : string or int Name of the field or offset in bytes from packet start. offset_fixup : int Number of bytes to move forward. If negative, move backward. add_value : int Value to add to the stream variable before writing it to the packet field. Can be used as a constant offset. is_big : bool How to write the variable to the the packet. True=big-endian, False=little-endian .. code-block:: python # Example3 pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) # write to ip.src offset STLVmWrFlowVar (fv_name="tuple", pkt_offset= "IP.src" ) # packet offset is varible STLVmWrFlowVar (fv_name="tuple", pkt_offset= 26 ) # add l3_len_fix before writing fv_rand into IP.len field STLVmWrFlowVar(fv_name="fv_rand", pkt_offset= "IP.len", add_val=l3_len_fix) """ super(STLVmWrFlowVar, self).__init__() self.fv_name =fv_name validate_type('fv_name', fv_name, basestring) self.offset_fixup =offset_fixup validate_type('offset_fixup', offset_fixup, int) self.pkt_offset =pkt_offset self.add_val =add_val validate_type('add_val', add_val, int) self.is_big =is_big; validate_type('is_big', is_big, bool) def get_var_ref (self): return self.fv_name def get_obj (self): return CTRexVmInsWrFlowVar(self.fv_name,self.pkt_offset+self.offset_fixup,self.add_val,self.is_big) def compile(self,parent): if type(self.pkt_offset)==str: t=parent._name_to_offset(self.pkt_offset) self.pkt_offset = t[0]
[docs]class STLVmWrMaskFlowVar(CTRexVmDescBase): def __init__(self, fv_name, pkt_offset, pkt_cast_size=1, mask=0xff, shift=0, add_value=0, offset_fixup=0, is_big=True): """ Write a stream variable into a packet field with some operations. Using this instruction, the variable size and the field can have different sizes. Pseudocode of this code:: uint32_t val=(cast_to_size)rd_from_variable("name") # read flow-var val+=m_add_value # add value if (m_shift>0) { # shift val=val<<m_shift }else{ if (m_shift<0) { val=val>>(-m_shift) } } pkt_val=rd_from_pkt(pkt_offset) # RMW to the packet pkt_val = (pkt_val & ~m_mask) | (val & m_mask) wr_to_pkt(pkt_offset,pkt_val) :parameters: fv_name : string The stream variable name to write to a packet field pkt_cast_size : uint8_t The size in bytes of the packet field mask : uint32_t The mask of the field. 1 means to write. 0 don't care shift : uint8_t How many bits to shift pkt_offset : string or int the name of the field or offset in byte from packet start. offset_fixup : int how many bytes to go forward. In case of a negative value go backward add_val : int value to add to stream variable before writing it to packet field. can be used as a constant offset is_big : bool how to write the variable to the the packet. is it big-edian or little edian Example 1 - Cast from uint16_t (var) to uint8_t (pkt):: base_pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) vm = STLScVmRaw( [ STLVmFlowVar(name="mac_src", min_value=1, max_value=30, size=2, op="dec",step=1), STLVmWrMaskFlowVar(fv_name="mac_src", pkt_offset= 11, pkt_cast_size=1, mask=0xff) # mask command ->write it as one byte ] ) pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) Example 2 - Change MSB of uint16_t variable:: vm = STLScVmRaw( [ STLVmFlowVar(name="mac_src", min_value=1, max_value=30, size=2, op="dec",step=1), STLVmWrMaskFlowVar(fv_name="mac_src", pkt_offset= 10, pkt_cast_size=2, mask=0xff00, shift=8) # take the var shift it 8 (x256) write only to LSB ] ) Example 3 - Every 2 packets, change the MAC (shift right):: vm = STLScVmRaw( [ STLVmFlowVar(name="mac_src", min_value=1, max_value=30, size=2, op="dec",step=1), STLVmWrMaskFlowVar(fv_name="mac_src", pkt_offset= 10, pkt_cast_size=1, mask=0x1, shift=-1) # take var mac_src>>1 and write the LSB every two packet there should be a change ] ) """ super(STLVmWrMaskFlowVar, self).__init__() self.fv_name =fv_name validate_type('fv_name', fv_name, basestring) self.offset_fixup =offset_fixup validate_type('offset_fixup', offset_fixup, int) self.pkt_offset =pkt_offset self.pkt_cast_size =pkt_cast_size validate_type('pkt_cast_size', pkt_cast_size, int) if pkt_cast_size not in [1,2,4]: raise CTRexPacketBuildException(-10,"not valid cast size"); self.mask = mask validate_type('mask', mask, int) self.shift = shift validate_type('shift', shift, int) self.add_value = add_value validate_type('add_value', add_value, int) self.is_big =is_big; validate_type('is_big', is_big, bool) def get_var_ref (self): return self.fv_name def get_obj (self): return CTRexVmInsWrMaskFlowVar(self.fv_name,self.pkt_offset+self.offset_fixup,self.pkt_cast_size,self.mask,self.shift,self.add_value,self.is_big) def compile(self,parent): if type(self.pkt_offset)==str: t=parent._name_to_offset(self.pkt_offset) self.pkt_offset = t[0]
[docs]class STLVmTrimPktSize(CTRexVmDescBase): def __init__(self,fv_name): """ Trim the packet size by the stream variable size. This instruction only changes the total packet size, and does not repair the fields to match the new size. :parameters: fv_name : string Stream variable name. The value of this variable is the new total packet size. For Example:: def create_stream (self): # pkt p_l2 = Ether(); p_l3 = IP(src="16.0.0.1",dst="48.0.0.1") p_l4 = UDP(dport=12,sport=1025) pyld_size = max(0, self.max_pkt_size_l3 - len(p_l3/p_l4)); base_pkt = p_l2/p_l3/p_l4/('\x55'*(pyld_size)) l3_len_fix =-(len(p_l2)); l4_len_fix =-(len(p_l2/p_l3)); # vm vm = STLScVmRaw( [ STLVmFlowVar(name="fv_rand", min_value=64, max_value=len(base_pkt), size=2, op="inc"), STLVmTrimPktSize("fv_rand"), # change total packet size <<< STLVmWrFlowVar(fv_name="fv_rand", pkt_offset= "IP.len", add_val=l3_len_fix), # fix ip len STLVmFixIpv4(offset = "IP"), # fix checksum STLVmWrFlowVar(fv_name="fv_rand", pkt_offset= "UDP.len", add_val=l4_len_fix) # fix udp len ] ) pkt = STLPktBuilder(pkt = base_pkt, vm = vm) return STLStream(packet = pkt, mode = STLTXCont()) """ super(STLVmTrimPktSize, self).__init__() self.fv_name = fv_name validate_type('fv_name', fv_name, basestring) def get_var_ref (self): return self.fv_name def get_obj (self): return CTRexVmInsTrimPktSize(self.fv_name)
[docs]class STLVmTupleGen(CTRexVmDescBase): def __init__(self,name, ip_min="0.0.0.1", ip_max="0.0.0.10", port_min=1025, port_max=65535, limit_flows=100000, flags=0): """ Generate a struct with two variables: ``var_name.ip`` as uint32_t and ``var_name.port`` as uint16_t The variables are dependent. When the ip variable value reaches its maximum, the port is incremented. For: * ip_min = 10.0.0.1 * ip_max = 10.0.0.5 * port_min = 1025 * port_max = 1028 * limit_flows = 10 The result: +------------+------------+-----------+ | ip | port | flow_id | +============+============+===========+ | 10.0.0.1 | 1025 | 1 | +------------+------------+-----------+ | 10.0.0.2 | 1025 | 2 | +------------+------------+-----------+ | 10.0.0.3 | 1025 | 3 | +------------+------------+-----------+ | 10.0.0.4 | 1025 | 4 | +------------+------------+-----------+ | 10.0.0.5 | 1025 | 5 | +------------+------------+-----------+ | 10.0.0.1 | 1026 | 6 | +------------+------------+-----------+ | 10.0.0.2 | 1026 | 7 | +------------+------------+-----------+ | 10.0.0.3 | 1026 | 8 | +------------+------------+-----------+ | 10.0.0.4 | 1026 | 9 | +------------+------------+-----------+ | 10.0.0.5 | 1026 | 10 | +------------+------------+-----------+ | 10.0.0.1 | 1025 | 1 | +------------+------------+-----------+ :parameters: name : string Name of the stream struct. ip_min : string or int Min value of the ip value. Number or IPv4 format. ip_max : string or int Max value of the ip value. Number or IPv4 format. port_min : int Min value of port variable. port_max : int Max value of port variable. limit_flows : int Limit of number of flows. flags : int 0 - noop 1 - ignore port min and max. .. code-block:: python # Example5 def create_stream (self): # pkt p_l2 = Ether(); p_l3 = IP(src="16.0.0.1",dst="48.0.0.1") p_l4 = UDP(dport=12,sport=1025) pyld_size = max(0, self.max_pkt_size_l3 - len(p_l3/p_l4)); base_pkt = p_l2/p_l3/p_l4/('\x55'*(pyld_size)) l3_len_fix =-(len(p_l2)); l4_len_fix =-(len(p_l2/p_l3)); # vm vm = STLScVmRaw( [ STLVmFlowVar(name="fv_rand", min_value=64, max_value=len(base_pkt), size=2, op="inc"), STLVmTrimPktSize("fv_rand"), # change total packet size <<< STLVmWrFlowVar(fv_name="fv_rand", pkt_offset= "IP.len", add_val=l3_len_fix), # fix ip len STLVmFixIpv4(offset = "IP"), # fix checksum STLVmWrFlowVar(fv_name="fv_rand", pkt_offset= "UDP.len", add_val=l4_len_fix) # fix udp len ] ) pkt = STLPktBuilder(pkt = base_pkt, vm = vm) return STLStream(packet = pkt, mode = STLTXCont()) """ super(STLVmTupleGen, self).__init__() self.name = name validate_type('name', name, basestring) self.ip_min = convert_val(ip_min); self.ip_max = convert_val(ip_max); self.port_min = port_min; check_for_int (port_min) self.port_max = port_max; check_for_int(port_max) self.limit_flows = limit_flows; check_for_int(limit_flows) self.flags =flags; check_for_int(flags) def get_var_name(self): return [self.name+".ip",self.name+".port"] def get_obj (self): return CTRexVmInsTupleGen(self.name, self.ip_min, self.ip_max, self.port_min, self.port_max, self.limit_flows, self.flags); ################################################################################################
[docs]class STLPktBuilder(CTrexPktBuilderInterface): def __init__(self, pkt = None, pkt_buffer = None, vm = None, path_relative_to_profile = False, build_raw = False, remove_fcs = True): """ This class defines a method for building a template packet and Field Engine using the Scapy package. Using this class the user can also define how TRex will handle the packet by specifying the Field engine settings. The pkt can be a Scapy pkt or pcap file name. If using a pcap file, and path_relative_to_profile is True, then the function loads the pcap file from a path relative to the profile. .. code-block:: python # Example6 # packet is scapy STLPktBuilder( pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/(10*'x') ) # packet is taken from pcap file relative to python STLPktBuilder( pkt ="stl/yaml/udp_64B_no_crc.pcap") # packet is taken from pcap file relative to profile file STLPktBuilder( pkt ="stl/yaml/udp_64B_no_crc.pcap", path_relative_to_profile = True ) vm = STLScVmRaw( [ STLVmTupleGen ( ip_min="16.0.0.1", ip_max="16.0.0.2", port_min=1025, port_max=65535, name="tuple"), # define tuple gen STLVmWrFlowVar (fv_name="tuple.ip", pkt_offset= "IP.src" ), # write ip to packet IP.src STLVmFixIpv4(offset = "IP"), # fix checksum STLVmWrFlowVar (fv_name="tuple.port", pkt_offset= "UDP.sport" ) #write udp.port ] ) base_pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025) pad = max(0, size - len(base_pkt)) * 'x' STLPktBuilder(pkt = base_pkt/pad, vm= vm) :parameters: pkt : string, Scapy object or pcap filename. pkt_buffer : bytes Packet as buffer. vm : list or base on :class:`trex.stl.trex_stl_packet_builder_scapy.STLScVmRaw` List of instructions to manipulate packet fields. path_relative_to_profile : bool If pkt is a pcap file, determines whether to load it relative to profile file. build_raw : bool If a buffer is specified (by pkt_buffer), determines whether to build Scapy. Useful in cases where it is necessary to take the offset from Scapy. remove_fcs : bool If a buffer is specified (by pkt_buffer), determines whether to remove FCS. """ super(STLPktBuilder, self).__init__() validate_type('pkt', pkt, (type(None), str, Packet)) validate_type('pkt_buffer', pkt_buffer, (type(None), bytes)) self.pkt = None # as input self.pkt_raw = None # from raw pcap file self.vm_scripts = [] # list of high level instructions self.vm_low_level = None self.is_pkt_built = False self.metadata="" self.path_relative_to_profile = path_relative_to_profile self.remove_fcs = remove_fcs self.is_binary_source = pkt_buffer != None if pkt != None and pkt_buffer != None: raise CTRexPacketBuildException(-15, "Packet builder cannot be provided with both pkt and pkt_buffer.") # process packet if pkt != None: self.set_packet(pkt) elif pkt_buffer != None: self.set_pkt_as_str(pkt_buffer) # process VM if vm != None: if not isinstance(vm, (STLScVmRaw, list)): raise CTRexPacketBuildException(-14, "Bad value for variable vm.") self.add_command(vm if isinstance(vm, STLScVmRaw) else STLScVmRaw(vm)) # raw source build to see MAC presence/ fields offset by name in VM if build_raw and self.pkt_raw and not self.pkt: self.__lazy_build_packet() # if we have packet and VM - compile now if (self.pkt or self.pkt_raw) and (self.vm_scripts): self.compile() def dump_vm_data_as_yaml(self): print(yaml.dump(self.get_vm_data(), default_flow_style=False))
[docs] def get_vm_data(self): """ Dumps the instructions :parameters: None :return: + json object of instructions :raises: + :exc:`AssertionError`, in case VM is not compiled (is None). """ assert self.vm_low_level is not None, 'vm_low_level is None, please use compile()' return self.vm_low_level.get_json()
[docs] def dump_pkt(self, encode = True): """ Dumps the packet as a decimal array of bytes (each item x gets value in range 0-255) :parameters: encode : bool Encode using base64. (disable for debug) Default: **True** :return: + packet representation as array of bytes :raises: + :exc:`AssertionError`, in case packet is empty. """ pkt_buf = self._get_pkt_as_str() return {'binary': base64.b64encode(pkt_buf).decode() if encode else pkt_buf, 'meta': self.metadata}
def dump_pkt_to_pcap(self, file_path): wrpcap(file_path, self._get_pkt_as_str()) def add_command (self, script): self.vm_scripts.append(script.clone()); def dump_scripts (self): self.vm_low_level.dump_as_yaml() def dump_as_hex (self): pkt_buf = self._get_pkt_as_str() print(hexdump(pkt_buf))
[docs] def pkt_layers_desc (self): """ Return layer description in this format: IP:TCP:Pyload """ pkt_buf = self._get_pkt_as_str() return self.pkt_layers_desc_from_buffer(pkt_buf)
@staticmethod def pkt_layers_desc_from_buffer (pkt_buf): scapy_pkt = Ether(pkt_buf); pkt_utl = CTRexScapyPktUtl(scapy_pkt); return pkt_utl.get_pkt_layers() def set_pkt_as_str (self, pkt_buffer): validate_type('pkt_buffer', pkt_buffer, bytes) self.pkt_raw = pkt_buffer
[docs] def set_pcap_file (self, pcap_file): """ Load raw pcap file into a buffer. Loads only the first packet. :parameters: pcap_file : file_name :raises: + :exc:`AssertionError`, if packet is empty. """ f_path = self._get_pcap_file_path (pcap_file) p=RawPcapReader(f_path) was_set = False for pkt in p: was_set=True; self.pkt_raw = pkt[0] break if not was_set : raise CTRexPacketBuildException(-14, "Empty pcap file {0}".format(f_path))
def to_pkt_dump(self): p = self.pkt if p and isinstance(p, Packet): p.show2(); hexdump(p); return; p = self.pkt_raw; if p: scapy_pkt = Ether(p); scapy_pkt.show2(); hexdump(p);
[docs] def set_packet (self, pkt): """ Scapy packet Example:: pkt =Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/IP()/('x'*10) """ if isinstance(pkt, Packet): self.pkt = pkt; else: if isinstance(pkt, str): self.set_pcap_file(pkt) else: raise CTRexPacketBuildException(-14, "bad packet" )
def is_default_src_mac (self): if self.is_binary_source: return True p = self.pkt if isinstance(p, Packet): if isinstance(p,Ether): if 'src' in p.fields : return False return True def is_default_dst_mac (self): if self.is_binary_source: return True p = self.pkt if isinstance(p, Packet): if isinstance(p,Ether): if 'dst' in p.fields : return False return True def compile (self): if self.pkt == None and self.pkt_raw == None: raise CTRexPacketBuildException(-14, "Packet is empty") self.vm_low_level = CTRexVmEngine() # compile the VM for sc in self.vm_scripts: if isinstance(sc, STLScVmRaw): self._compile_raw(sc) def get_pkt_len (self): if self.pkt: return len(self.pkt) elif self.pkt_raw: return len(self.pkt_raw) else: raise CTRexPacketBuildException(-14, "Packet is empty")
[docs] def to_json (self): ''' write packet builder to JSON format ''' return {'packet': self.dump_pkt(), 'vm': self.get_vm_data()}
@staticmethod
[docs] def from_json (json_data): ''' given a JSON, construct a valid packet builder object ''' try: # packet pkt_buffer = base64.b64decode(json_data['packet']['binary']) # VM vm = json_data['vm'] # create VM object vm_obj = STLVM() # set cache size if 'cache' in vm: vm_obj.set_cached(vm['cache']) # fetch instructions vm_instr = json_data['vm']['instructions'] if not type(vm_instr) == list: raise TRexError("from_json: bad type {0} for 'VM' field".format(type(vm_instr))) # iterate over instructions for instr in vm_instr: # flow var if instr['type'] == 'flow_var': vm_obj.var(name = instr['name'], op = instr['op'], min_value = instr.get('min_value',0), max_value = instr.get('max_value',0), value_list = instr.get('value_list',None), size = instr['size'], step = instr['step'], split_to_cores = instr.get('split_to_cores', True), next_var = instr.get('next_var', None)) # write flow var elif instr['type'] == 'write_flow_var': vm_obj.write(fv_name = instr['name'], pkt_offset = instr['pkt_offset'], add_val = instr['add_value'], byte_order = 'big' if instr['is_big_endian'] else 'little') # IPv4 checksum fix elif instr['type'] == 'fix_checksum_ipv4': vm_obj.fix_chksum(offset = instr['pkt_offset']) # HW checksum fix elif instr['type'] == 'fix_checksum_hw': vm_obj.fix_chksum_hw(l3_offset = instr['l2_len'], l4_offset = instr['l2_len'] + instr['l3_len'], l4_type = instr['l4_type']) # tuple flow var elif instr['type'] == 'tuple_flow_var': vm_obj.tuple_var(name = instr['name'], ip_min = instr['ip_min'], ip_max = instr['ip_max'], port_min = instr['port_min'], port_max = instr['port_max'], limit_flows = instr['limit_flows']) # trim elif instr['type'] == 'trim_pkt_size': vm_obj.trim(fv_name = instr['name']) # write masked flow var elif instr['type'] == 'write_mask_flow_var': vm_obj.write_mask(fv_name = instr['name'], pkt_offset = instr['pkt_offset'], pkt_cast_size = instr['pkt_cast_size'], mask = instr['mask'], shift = instr['shift'], add_val = instr['add_value'], byte_order = 'big' if instr['is_big_endian'] else 'little') elif instr['type'] == 'flow_var_rand_limit': vm_obj.repeatable_random_var(fv_name = instr['name'], size = instr['size'], limit = instr['limit'], seed = instr['seed'], min_value = instr['min_value'], max_value = instr['max_value'], split_to_cores = instr.get('split_to_cores', True), next_var = instr.get('next_var', None)) else: print(instr) raise TRexError("from_json: unknown VM instruction type '{0}'".format(instr['type'])) except binascii.Error: raise TRexError("from_json: bad packet format") except KeyError as e: raise TRexError("from_json: missing field {0} from JSON".format(e)) return STLPktBuilder(pkt_buffer = pkt_buffer, vm = vm_obj) #################################################### # private
def compute_previous(self, flow_variables): for var_name, var in flow_variables.items(): next_var_name = var.get_next_var_name() if next_var_name: next_var = flow_variables[next_var_name] if next_var.get_previous_var_name() and next_var.get_previous_var_name() != var_name: raise CTRexPacketBuildException(-11,"Variable %s is pointed by two vars." % next_var_name ) next_var.previous = var_name next_var.split_to_cores = False def order_flow_vars(self, flow_variables): ordered_flow_vars = [] for var in flow_variables.values(): if var.get_previous_var_name() is None: #it means that this var is the head of a chain head = var #start inserting the whole chain in the list while (head.get_next_var_name()): ordered_flow_vars.append(head) head = flow_variables[head.get_next_var_name()] # update the head to the next var # last var in a chain ordered_flow_vars.append(head) if len(flow_variables) != len(ordered_flow_vars): raise CTRexPacketBuildException(-11,"Loops are forbidden for dependent variables") return ordered_flow_vars def _get_pcap_file_path (self,pcap_file_name): f_path = pcap_file_name if os.path.isabs(pcap_file_name): f_path = pcap_file_name else: if self.path_relative_to_profile: p = self._get_path_relative_to_profile () # loader if p : f_path=os.path.abspath(os.path.join(os.path.dirname(p),pcap_file_name)) return f_path def _get_path_relative_to_profile (self): p = inspect.stack() for obj in p: if obj[3]=='get_streams': return obj[1] return None def _compile_raw (self,obj): # make sure we have varibles once vars={} flow_vars = collections.OrderedDict() #dictionary of flow vars # add vars to vars dict for desc in obj.commands: var_names = desc.get_var_name() if var_names : for var_name in var_names: if var_name in vars: raise CTRexPacketBuildException(-11,("Variable %s defined twice ") % (var_name) ) else: vars[var_name]=1 # check that all write command variables exist for desc in obj.commands: var_name = desc.get_var_ref() if var_name : if var_name not in vars: raise CTRexPacketBuildException(-11,("Variable %s does not exist ") % (var_name) ) # create dictionary of flow variables for desc in obj.commands: if type(desc) in (STLVmFlowVar,STLVmFlowVarRepeatableRandom): flow_vars[desc.get_var_name()[0]] = desc # check that all next_var exist for var_name, var in flow_vars.items(): next_var_name = var.get_next_var_name() if next_var_name: if next_var_name not in flow_vars: raise CTRexPacketBuildException(-11,"Variable %s does not exist " % next_var_name ) self.compute_previous(flow_vars) ordered_commands = self.order_flow_vars(flow_vars) ordered_commands += [cmd for cmd in obj.commands if type(cmd) not in (STLVmFlowVar,STLVmFlowVarRepeatableRandom)] for desc in ordered_commands: desc.compile(self) self.vm_low_level.add_ins(desc.get_obj()) #set cache size if obj.cache_size : validate_type('obj.cache_size', obj.cache_size, int) self.vm_low_level.cache_size = obj.cache_size # lazy packet build only on demand def __lazy_build_packet (self): # alrady built ? bail out if self.is_pkt_built: return # for buffer, promote to a scapy packet if self.pkt_raw: self.pkt = Ether(self.pkt_raw) self.pkt_raw = None # regular scapy packet elif not self.pkt: # should not reach here raise CTRexPacketBuildException(-11, 'Empty packet') if self.remove_fcs and self.pkt.lastlayer().name == 'Padding': self.pkt.lastlayer().underlayer.remove_payload() self.pkt.build() self.is_pkt_built = True def _pkt_layer_offset (self,layer_name): self.__lazy_build_packet() p_utl=CTRexScapyPktUtl(self.pkt); return p_utl.get_layer_offet_by_str(layer_name) def _name_to_offset(self,field_name): self.__lazy_build_packet() p_utl=CTRexScapyPktUtl(self.pkt); return p_utl.get_field_offet_by_str(field_name) def _get_pkt_as_str(self): if self.pkt: return bytes(self.pkt) if self.pkt_raw: return self.pkt_raw raise CTRexPacketBuildException(-11, 'Empty packet'); def _add_tuple_gen(self,tuple_gen): pass;
def STLIPRange (src = None, dst = None, fix_chksum = True): vm = [] if src: vm += [ STLVmFlowVar(name="src", min_value = src['start'], max_value = src['end'], size = 4, op = "inc", step = src['step']), STLVmWrFlowVar(fv_name="src",pkt_offset= "IP.src") ] if dst: vm += [ STLVmFlowVar(name="dst", min_value = dst['start'], max_value = dst['end'], size = 4, op = "inc", step = dst['step']), STLVmWrFlowVar(fv_name="dst",pkt_offset= "IP.dst") ] if fix_chksum: vm.append( STLVmFixIpv4(offset = "IP")) return vm class STLVM(STLScVmRaw): ''' Defines a VM/Field Engine object Describes the interaction on each packet ''' def __init__ (self): STLScVmRaw.__init__(self) def set_cached (self, cache_size): """ set VM as cached with a cache size """ self.cache_size = cache_size def var (self, name, min_value, max_value, size, op, step = 1, split_to_cores = True, next_var = None, **k): """ Defines a flow variable. Allocates a variable on a stream context. The size argument determines the variable size. The operation can be inc, dec, and random. For increment and decrement operations, can set the "step" size. For all operations, can set initialization value, minimum and maximum value. :parameters: name : string Name of the stream variable min_value : int Min value max_value : int Max value size : int Number of bytes of the variable. Possible values: 1,2,4,8 for uint8_t, uint16_t, uint32_t, uint64_t step : int Step in case of "inc" or "dec" operations op : string Possible values: "inc", "dec", "random" split_to_cores : bool Default value is True. If the value is True then each core updates the variable by step * num of cores. If split_to_cores = False then each core updates the variable by step. """ self.add_cmd(STLVmFlowVar(name = name, min_value = min_value, max_value = max_value, size = size, op = op, step = step, split_to_cores = split_to_cores, next_var = next_var, **k)) def write (self, fv_name, pkt_offset, offset_fixup = 0, add_val = 0, byte_order = 'big'): """ Write a previously defined varaible to the packet. The write position is determined by the packet offset + offset fixup. The size of the write is determined by the stream variable. Example: Offset 10, fixup 0, variable size 4. This function writes at 10, 11, 12, and 13. For inromation about chaning the write size, offset, or fixup, see the `STLVmWrMaskFlowVar` command. The Field name/offset can be given by name in the following format: ``header[:id].field``. :parameters: fv_name : string Stream variable to write to a packet offset. pkt_offset : string or int Name of the field or offset in bytes from packet start. offset_fixup : int Number of bytes to move forward. If negative, move backward. add_val : int Value to add to the stream variable before writing it to the packet field. Can be used as a constant offset. is_big : bool How to write the variable to the the packet. True=big-endian, False=little-endian """ self.add_cmd(STLVmWrFlowVar(fv_name = fv_name, pkt_offset = pkt_offset, offset_fixup = offset_fixup, add_val = add_val, is_big = (byte_order == 'big'))) def tuple_var (self, name, ip_min, ip_max, port_min, port_max, limit_flows = 0, **k): """ Generate a struct with two variables: ``var_name.ip`` as uint32_t and ``var_name.port`` as uint16_t The variables are dependent. When the ip variable value reaches its maximum, the port is incremented. For: * ip_min = 10.0.0.1 * ip_max = 10.0.0.5 * port_min = 1025 * port_max = 1028 * limit_flows = 10 The result: +------------+------------+-----------+ | ip | port | flow_id | +============+============+===========+ | 10.0.0.1 | 1025 | 1 | +------------+------------+-----------+ | 10.0.0.2 | 1025 | 2 | +------------+------------+-----------+ | 10.0.0.3 | 1025 | 3 | +------------+------------+-----------+ | 10.0.0.4 | 1025 | 4 | +------------+------------+-----------+ | 10.0.0.5 | 1025 | 5 | +------------+------------+-----------+ | 10.0.0.1 | 1026 | 6 | +------------+------------+-----------+ | 10.0.0.2 | 1026 | 7 | +------------+------------+-----------+ | 10.0.0.3 | 1026 | 8 | +------------+------------+-----------+ | 10.0.0.4 | 1026 | 9 | +------------+------------+-----------+ | 10.0.0.5 | 1026 | 10 | +------------+------------+-----------+ | 10.0.0.1 | 1025 | 1 | +------------+------------+-----------+ :parameters: name : string Name of the stream struct. ip_min : string or int Min value of the ip value. Number or IPv4 format. ip_max : string or int Max value of the ip value. Number or IPv4 format. port_min : int Min value of port variable. port_max : int Max value of port variable. limit_flows : int Limit of number of flows. """ self.add_cmd(STLVmTupleGen(name = name, ip_min = ip_min, ip_max = ip_max, port_min = port_min, port_max = port_max, limit_flows = limit_flows, **k)) def fix_chksum (self, offset = "IP"): """ Fix IPv4 header checksum. Use this if the packet header has changed and it is necessary to change the checksum. :parameters: offset : uint16_t or string **IPv4 header** offset from packet start. It is **not** the offset of the checksum field itself. in could be string in case of scapy packet. format IP[:[id]] """ self.add_cmd(STLVmFixIpv4(offset)) def fix_chksum_hw (self, l3_offset, l4_offset, l4_type): """ Fix IPv4 header checksum and/or TCP/UDP checksum using hardware assist. Use this if the packet header has changed or data payload has changed as it is necessary to fix the checksums. This instruction works on NICS that support this hardware offload. For fixing only IPv4 header checksum use STLVmFixIpv4. This instruction should be used if both L4 and L3 need to be fixed. :parameters: l3_offset : offset in bytes **IPv4/IPv6 header** offset from packet start. It is **not** the offset of the checksum field itself. in could be string in case of scapy packet. format IP[:[id]] l4_offset : offset in bytes to UDP/TCP header or IPv4 payload. in case of IPv4 checksum CTRexVmInsFixHwCs.L4_TYPE_IP could be set to zero to be auto calculated by the server l4_type : [CTRexVmInsFixHwCs.L4_TYPE_UDP or CTRexVmInsFixHwCs.L4_TYPE_TCP or CTRexVmInsFixHwCs.L4_TYPE_IP] see full example stl/syn_attack_fix_cs_hw.py """ self.add_cmd(STLVmFixChecksumHw(l3_offset = l3_offset, l4_offset = l4_offset, l4_type = l4_type)) def trim (self, fv_name): """ Trim the packet size by the stream variable size. This instruction only changes the total packet size, and does not repair the fields to match the new size. :parameters: fv_name : string Stream variable name. The value of this variable is the new total packet size. """ self.add_cmd(STLVmTrimPktSize(fv_name = fv_name)) def write_mask (self, fv_name, pkt_offset, pkt_cast_size, mask, shift = 0, offset_fixup = 0, add_val = 0, byte_order = 'big'): """ Write a stream variable into a packet field with some operations. Using this instruction, the variable size and the field can have different sizes. :parameters: fv_name : string The stream variable name to write to a packet field pkt_offset : string or int the name of the field or offset in byte from packet start. pkt_cast_size : uint8_t The size in bytes of the packet field mask : uint32_t The mask of the field. 1 means to write. 0 don't care shift : uint8_t How many bits to shift offset_fixup : int how many bytes to go forward. In case of a negative value go backward add_val : int value to add to stream variable before writing it to packet field. can be used as a constant offset is_big : bool how to write the variable to the the packet. is it big-edian or little edian """ self.add_cmd(STLVmWrMaskFlowVar(fv_name = fv_name, pkt_offset = pkt_offset, pkt_cast_size = pkt_cast_size, mask = mask, shift = shift, add_value = add_val, offset_fixup = offset_fixup, is_big = (byte_order == 'big'))) def repeatable_random_var (self, fv_name, size, limit, seed = None, min_value = None, max_value = None, split_to_cores = True, next_var = None): """ Flow variable instruction for repeatable random with limit number of generating numbers. Allocates memory on a stream context. The size argument determines the variable size. Could be 1,2,4 or 8 1. The maximum number of distinct values will 'limit'. There could be a case of repetition 2. The values will be repeated after 'limit' number of values. :parameters: name : string Name of the stream variable size : int Number of bytes of the variable. Possible values: 1,2,4,8 for uint8_t, uint16_t, uint32_t, uint64_t limit : int The number of distinct repetable random number seed : int For deterministic result, you can set this to a uint16_t number min_value : int Min value max_value : int Max value split_to_cores : bool Default value is True. If the value is True then each core updates the variable by step * num of cores. If split_to_cores = False then each core updates the variable by step. next_var : string or None In case it isn't None it is the name of the next variable. The next variable will perform its operation only when this variable completes a full wrap around. """ self.add_cmd(STLVmFlowVarRepeatableRandom(name = fv_name, size = size, limit = limit, seed = seed, min_value = min_value, max_value = max_value, split_to_cores= split_to_cores, next_var = next_var))