trex_stl_hltapi.py revision 6182075a
1#!/router/bin/python
2
3'''
4Supported functions/arguments/defaults:
5'''
6# connect()
7connect_kwargs = {
8    'device': 'localhost',                  # ip or hostname of TRex
9    'port_list': None,                      # list of ports
10    'username': 'TRexUser',
11    'reset': True,
12    'break_locks': False,
13}
14
15# cleanup_session()
16cleanup_session_kwargs = {
17    'maintain_lock': False,                 # release ports at the end or not
18    'port_list': None,
19    'port_handle': None,
20}
21
22# traffic_config()
23traffic_config_kwargs = {
24    'mode': None,                           # ( create | modify | remove | reset )
25    'split_by_cores': 'split',              # ( split | duplicate | single ) TRex extention: split = split traffic by cores, duplicate = duplicate traffic for all cores, single = run only with sinle core (not implemented yet)
26    'port_handle': None,
27    'port_handle2': None,
28    # stream builder parameters
29    'transmit_mode': 'continuous',          # ( continuous | multi_burst | single_burst )
30    'rate_pps': 1,                          # TODO: support bps and percent once stateless API will, use rate_percent by default
31    'rate_bps': None,
32    'rate_percent': 100,
33    'stream_id': None,
34    'name': None,
35    'bidirectional': 0,
36    'direction': 0,                         # ( 0 | 1 ) TRex extention: 1 = exchange sources and destinations
37    'pkts_per_burst': 1,
38    'burst_loop_count': 1,
39    'inter_burst_gap': 12,
40    'length_mode': 'fixed',                 #  ( auto | fixed | increment | decrement | random | imix )
41    'l3_imix1_size': 60,
42    'l3_imix1_ratio': 28,
43    'l3_imix2_size': 590,
44    'l3_imix2_ratio': 20,
45    'l3_imix3_size': 1514,
46    'l3_imix3_ratio': 4,
47    'l3_imix4_size': 9226,
48    'l3_imix4_ratio': 0,
49    #L2
50    'frame_size': 64,
51    'frame_size_min': 64,
52    'frame_size_max': 64,
53    'frame_size_step': 1,
54    'l2_encap': 'ethernet_ii',              # ( ethernet_ii | ethernet_ii_vlan )
55    'mac_src': '00:00:01:00:00:01',
56    'mac_dst': '00:00:00:00:00:00',
57    'mac_src2': '00:00:01:00:00:01',
58    'mac_dst2': '00:00:00:00:00:00',
59    'mac_src_mode': 'fixed',                # ( fixed | increment | decrement | random )
60    'mac_src_step': 1,
61    'mac_src_count': 1,
62    'mac_dst_mode': 'fixed',                # ( fixed | increment | decrement | random )
63    'mac_dst_step': 1,
64    'mac_dst_count': 1,
65    'mac_src2_mode': 'fixed',                # ( fixed | increment | decrement | random )
66    'mac_src2_step': 1,
67    'mac_src2_count': 1,
68    'mac_dst2_mode': 'fixed',                # ( fixed | increment | decrement | random )
69    'mac_dst2_step': 1,
70    'mac_dst2_count': 1,
71    # vlan options below can have multiple values for nested Dot1Q headers
72    'vlan_user_priority': 1,
73    'vlan_priority_mode': 'fixed',          # ( fixed | increment | decrement | random )
74    'vlan_priority_count': 1,
75    'vlan_priority_step': 1,
76    'vlan_id': 0,
77    'vlan_id_mode': 'fixed',                # ( fixed | increment | decrement | random )
78    'vlan_id_count': 1,
79    'vlan_id_step': 1,
80    'vlan_cfi': 1,
81    'vlan_protocol_tag_id': None,
82    #L3, general
83    'l3_protocol': 'ipv4',                  # ( ipv4 | ipv6 )
84    'l3_length_min': 110,
85    'l3_length_max': 238,
86    'l3_length_step': 1,
87    #L3, IPv4
88    'ip_precedence': 0,
89    'ip_tos_field': 0,
90    'ip_mbz': 0,
91    'ip_delay': 0,
92    'ip_throughput': 0,
93    'ip_reliability': 0,
94    'ip_cost': 0,
95    'ip_reserved': 0,
96    'ip_dscp': 0,
97    'ip_cu': 0,
98    'l3_length': None,
99    'ip_id': 0,
100    'ip_fragment_offset': 0,
101    'ip_ttl': 64,
102    'ip_checksum': None,
103    'ip_src_addr': '0.0.0.0',
104    'ip_dst_addr': '192.0.0.1',
105    'ip_src_mode': 'fixed',                 # ( fixed | increment | decrement | random )
106    'ip_src_step': 1,                       # ip or number
107    'ip_src_count': 1,
108    'ip_dst_mode': 'fixed',                 # ( fixed | increment | decrement | random )
109    'ip_dst_step': 1,                       # ip or number
110    'ip_dst_count': 1,
111    #L3, IPv6
112    'ipv6_traffic_class': 0,
113    'ipv6_flow_label': 0,
114    'ipv6_length': None,
115    'ipv6_next_header': None,
116    'ipv6_hop_limit': 64,
117    'ipv6_src_addr': 'fe80:0:0:0:0:0:0:12',
118    'ipv6_dst_addr': 'fe80:0:0:0:0:0:0:22',
119    'ipv6_src_mode': 'fixed',               # ( fixed | increment | decrement | random )
120    'ipv6_src_step': 1,                     # we are changing only 32 lowest bits; can be ipv6 or number
121    'ipv6_src_count': 1,
122    'ipv6_dst_mode': 'fixed',               # ( fixed | increment | decrement | random )
123    'ipv6_dst_step': 1,                     # we are changing only 32 lowest bits; can be ipv6 or number
124    'ipv6_dst_count': 1,
125    #L4, TCP
126    'l4_protocol': 'tcp',                   # ( tcp | udp )
127    'tcp_src_port': 1024,
128    'tcp_dst_port': 80,
129    'tcp_seq_num': 1,
130    'tcp_ack_num': 1,
131    'tcp_data_offset': 5,
132    'tcp_fin_flag': 0,
133    'tcp_syn_flag': 0,
134    'tcp_rst_flag': 0,
135    'tcp_psh_flag': 0,
136    'tcp_ack_flag': 0,
137    'tcp_urg_flag': 0,
138    'tcp_window': 4069,
139    'tcp_checksum': None,
140    'tcp_urgent_ptr': 0,
141    'tcp_src_port_mode': 'increment',       # ( increment | decrement | random )
142    'tcp_src_port_step': 1,
143    'tcp_src_port_count': 1,
144    'tcp_dst_port_mode': 'increment',       # ( increment | decrement | random )
145    'tcp_dst_port_step': 1,
146    'tcp_dst_port_count': 1,
147    # L4, UDP
148    'udp_src_port': 1024,
149    'udp_dst_port': 80,
150    'udp_length': None,
151    'udp_dst_port_mode': 'increment',       # ( increment | decrement | random )
152    'udp_src_port_step': 1,
153    'udp_src_port_count': 1,
154    'udp_src_port_mode': 'increment',       # ( increment | decrement | random )
155    'udp_dst_port_step': 1,
156    'udp_dst_port_count': 1,
157}
158
159# traffic_control()
160traffic_control_kwargs = {
161    'action': None,                         # ( run | stop )
162    'port_handle': None
163}
164
165# traffic_stats()
166traffic_stats_kwargs = {
167    'mode': 'aggregate',                    # ( aggregate )
168    'port_handle': None
169}
170
171
172import sys
173import os
174import socket
175import copy
176from trex_stl_lib.api import *
177from utils.common import get_number
178from collections import defaultdict
179
180
181class HLT_ERR(dict):
182    def __init__(self, log = 'Unknown error', **kwargs):
183        dict.__init__(self, {'status': 0})
184        if type(log) is dict:
185            dict.update(self, log)
186        elif type(log) is str and not log.startswith('[ERR]'):
187            self['log'] = '[ERR] ' + log
188        else:
189            self['log'] = log
190        dict.update(self, kwargs)
191
192class HLT_OK(dict):
193    def __init__(self, init_dict = {}, **kwargs):
194        dict.__init__(self, {'status': 1, 'log': None})
195        dict.update(self, init_dict)
196        dict.update(self, kwargs)
197
198def merge_kwargs(default_kwargs, user_kwargs):
199    kwargs = copy.deepcopy(default_kwargs)
200    for key, value in user_kwargs.items():
201        if key in kwargs:
202            kwargs[key] = value
203        elif key in ('save_to_yaml', 'save_to_pcap'): # internal debug arguments
204            kwargs[key] = value
205        else:
206            print("Warning: provided parameter '%s' is not supported" % key)
207    return kwargs
208
209# change MACs from HLT formats {a b c d e f} or a-b-c-d-e-f or a.b.c.d.e.f to Scapy format a:b:c:d:e:f
210def correct_macs(kwargs):
211    list_of_mac_args = ['mac_src', 'mac_dst', 'mac_src2', 'mac_dst2']
212    list_of_mac_steps = ['mac_src_step', 'mac_dst_step', 'mac_src2_step', 'mac_dst2_step']
213    for mac_arg in list_of_mac_args + list_of_mac_steps:
214        if mac_arg in kwargs:
215            if type(kwargs[mac_arg]) in (int, long) and kwargs[mac_arg] in list_of_mac_steps: # step can be number
216                continue
217            if type(kwargs[mac_arg]) is not str: raise STLError('Argument %s should be str' % mac_arg)
218            kwargs[mac_arg] = kwargs[mac_arg].replace('{', '').replace('}', '').strip().replace('-', ' ').replace(':', ' ').replace('.', ' ')
219            kwargs[mac_arg] = ':'.join(kwargs[mac_arg].split())
220            try:
221                mac2str(kwargs[mac_arg])                                # verify we are ok
222            except:
223                raise STLError('Incorrect MAC %s=%s, please use 01:23:45:67:89:10 or 01-23-45-67-89-10 or 01.23.45.67.89.10 or {01 23 45 67 89 10}' % (mac_arg, kwargs[mac_arg]))
224
225def is_true(input):
226    if input in (True, 'True', 'true', 1, '1', 'enable', 'Enable', 'Yes', 'yes', 'y', 'Y', 'enabled', 'Enabled'):
227        return True
228    return False
229
230# dict of streams per port
231# hlt_history = False: holds list of stream_id per port
232# hlt_history = True:  act as dictionary (per port) stream_id -> hlt arguments used for build
233class CStreamsPerPort(defaultdict):
234    def __init__(self, hlt_history = False):
235        self.hlt_history = hlt_history
236        if self.hlt_history:
237            defaultdict.__init__(self, dict)
238        else:
239            defaultdict.__init__(self, list)
240
241    def get_stream_list(self, ports_list = None):
242        if self.hlt_history:
243            if ports_list is None:
244                ports_list = self.keys()
245            elif not isinstance(ports_list, list):
246                ports_list = [ports_list]
247            ret = {}
248            for port in ports_list:
249                ret[port] = self[port].keys()
250            return ret
251        else:
252            return self
253
254    # add to stream_id list per port, no HLT args, res = HLT result
255    def add_streams_from_res(self, res):
256        if self.hlt_history: raise STLError('CStreamsPerPort: this object is not meant for HLT history, try init with hlt_history = False')
257        if not isinstance(res, dict): raise STLError('CStreamsPerPort: res should be dict')
258        if res.get('status') != 1: raise STLError('CStreamsPerPort: res has status %s' % res.get('status'))
259        res_streams = res.get('stream_id')
260        if not isinstance(res_streams, dict):
261            raise STLError('CStreamsPerPort: stream_id in res should be dict')
262        for port, port_stream_ids in res_streams.items():
263            if type(port_stream_ids) is not list:
264                port_stream_ids = [port_stream_ids]
265            self[port].extend(port_stream_ids)
266
267    # save HLT args to modify streams later
268    def save_stream_args(self, ports_list, stream_id, stream_hlt_args):
269        if not self.hlt_history: raise STLError('CStreamsPerPort: this object works only with HLT history, try init with hlt_history = True')
270        if type(stream_id) not in (int, long): raise STLError('CStreamsPerPort: stream_id should be number')
271        if not isinstance(stream_hlt_args, dict): raise STLError('CStreamsPerPort: stream_hlt_args should be dict')
272        if not isinstance(ports_list, list):
273            ports_list = [ports_list]
274        for port in ports_list:
275            if stream_id in self[port]:
276                self[port][stream_id].update(stream_hlt_args)
277            else:
278                self[port][stream_id] = stream_hlt_args
279
280    def remove_stream(self, ports_list, stream_id):
281        if not isinstance(ports_list, list):
282            ports_list = [ports_list]
283        if not isinstance(stream_id, dict):
284            raise STLError('CStreamsPerPort: stream_hlt_args should be dict')
285        for port in ports_list:
286            if port not in self:
287                raise STLError('CStreamsPerPort: port %s not defined' % port)
288            if stream_id not in self[port]:
289                raise STLError('CStreamsPerPort: stream_id %s not found at port %s' % (port, stream_id))
290            if self.hlt_history:
291                del self[port][stream_id]
292            else:
293                self[port].pop(stream_id)
294
295class CTRexHltApi(object):
296
297    def __init__(self, verbose = 0):
298        self.trex_client = None
299        self.verbose = verbose
300        self._streams_history = {} # streams per stream_id per port in format of HLT arguments for modify later
301
302
303###########################
304#    Session functions    #
305###########################
306
307    def connect(self, **user_kwargs):
308        kwargs = merge_kwargs(connect_kwargs, user_kwargs)
309        device = kwargs['device']
310        try:
311            device = socket.gethostbyname(device) # work with ip
312        except: # give it another try
313            try:
314                device = socket.gethostbyname(device)
315            except Exception as e:
316                return HLT_ERR('Could not translate hostname "%s" to IP: %s' % (device, e))
317
318        try:
319            # sync = RPC, async = ZMQ
320            self.trex_client = STLClient(kwargs['username'], device, sync_port = 4501, async_port = 4500, verbose_level = self.verbose)
321        except Exception as e:
322            return HLT_ERR('Could not init stateless client %s: %s' % (device, e if isinstance(e, STLError) else traceback.format_exc()))
323
324        try:
325            self.trex_client.connect()
326        except Exception as e:
327            self.trex_client = None
328            return HLT_ERR('Could not connect to device %s: %s' % (device, e if isinstance(e, STLError) else traceback.format_exc()))
329
330        # connection successfully created with server, try acquiring ports of TRex
331        try:
332            port_list = self._parse_port_list(kwargs['port_list'])
333            self.trex_client.acquire(ports = port_list, force = kwargs['break_locks'])
334        except Exception as e:
335            self.trex_client = None
336            return HLT_ERR('Could not acquire ports %s: %s' % (port_list, e if isinstance(e, STLError) else traceback.format_exc()))
337
338        # since only supporting single TRex at the moment, 1:1 map
339        port_handle = self.trex_client.get_acquired_ports()
340
341        # arrived here, all desired ports were successfully acquired
342        if kwargs['reset']:
343            # remove all port traffic configuration from TRex
344            try:
345                self.trex_client.stop(ports = port_list)
346                self.trex_client.reset(ports = port_list)
347            except Exception as e:
348                self.trex_client = None
349                return HLT_ERR('Error in reset traffic: %s' % e if isinstance(e, STLError) else traceback.format_exc())
350
351        self._streams_history = CStreamsPerPort(hlt_history = True)
352        return HLT_OK(port_handle = port_handle)
353
354    def cleanup_session(self, **user_kwargs):
355        kwargs = merge_kwargs(cleanup_session_kwargs, user_kwargs)
356        if not kwargs['maintain_lock']:
357            # release taken ports
358            port_list = kwargs['port_list'] or kwargs['port_handle'] or 'all'
359            try:
360                if port_list == 'all':
361                    port_list = self.trex_client.get_acquired_ports()
362                else:
363                    port_list = self._parse_port_list(port_list)
364            except Exception as e:
365                return HLT_ERR('Unable to determine which ports to release: %s' % e if isinstance(e, STLError) else traceback.format_exc())
366            try:
367                self.trex_client.release(port_list)
368            except Exception as e:
369                return HLT_ERR('Unable to release ports %s: %s' % (port_list, e))
370        try:
371            self.trex_client.disconnect(stop_traffic = False, release_ports = False)
372        except Exception as e:
373            return HLT_ERR('Error disconnecting: %s' % e)
374        self.trex_client = None
375        return HLT_OK()
376
377    def interface_config(self, port_handle, mode='config'):
378        if not self.trex_client:
379            return HLT_ERR('Connect first')
380        ALLOWED_MODES = ['config', 'modify', 'destroy']
381        if mode not in ALLOWED_MODES:
382            return HLT_ERR('Mode must be one of the following values: %s' % ALLOWED_MODES)
383        # pass this function for now...
384        return HLT_ERR('interface_config not implemented yet')
385
386
387###########################
388#    Traffic functions    #
389###########################
390
391    def traffic_config(self, **user_kwargs):
392        if not self.trex_client:
393            return HLT_ERR('Connect first')
394        try:
395            correct_macs(user_kwargs)
396        except Exception as e:
397            return HLT_ERR(e if isinstance(e, STLError) else traceback.format_exc())
398        kwargs = merge_kwargs(traffic_config_kwargs, user_kwargs)
399        stream_id = kwargs['stream_id']
400        mode = kwargs['mode']
401        if type(stream_id) is list:
402            if len(stream_id) > 1:
403                streams_per_port = CStreamsPerPort()
404                for each_stream_id in stream_id:
405                    user_kwargs[stream_id] = each_stream_id
406                    res = self.traffic_config(**user_kwargs)
407                    if type(res) is HLT_ERR:
408                        return res
409                    streams_per_port.add_streams_from_res(res)
410                if mode == 'create':
411                    return HLT_OK(stream_id = streams_per_port)
412                else:
413                    return HLT_OK()
414            else:
415                stream_id = stream_id[0]
416
417        port_handle = port_list = self._parse_port_list(kwargs['port_handle'])
418        ALLOWED_MODES = ['create', 'modify', 'remove', 'enable', 'disable', 'reset']
419        if mode not in ALLOWED_MODES:
420            return HLT_ERR('Mode must be one of the following values: %s' % ALLOWED_MODES)
421
422        if mode == 'reset':
423            try:
424                self.trex_client.remove_all_streams(port_handle)
425                for port in port_handle:
426                    if port in self._streams_history:
427                        del self._streams_history[port]
428                return HLT_OK()
429            except Exception as e:
430                return HLT_ERR('Could not reset streams at ports %s: %s' % (port_handle, e if isinstance(e, STLError) else traceback.format_exc()))
431
432        if mode == 'remove':
433            if stream_id is None:
434                return HLT_ERR('Please specify stream_id to remove.')
435            if type(stream_id) is str and stream_id == 'all':
436                try:
437                    self.trex_client.remove_all_streams(port_handle)
438                    for port in port_handle:
439                        if port in self._streams_history:
440                            del self._streams_history[port]
441                except Exception as e:
442                    return HLT_ERR('Could not remove all streams at ports %s: %s' % (port_handle, e if isinstance(e, STLError) else traceback.format_exc()))
443            else:
444                try:
445                    self._remove_stream(stream_id, port_handle)
446                except Exception as e:
447                    return HLT_ERR('Could not remove streams with specified by %s, error: %s' % (stream_id, e if isinstance(e, STLError) else traceback.format_exc()))
448            return HLT_OK()
449
450        #if mode == 'enable':
451        #    stream_id = kwargs.get('stream_id')
452        #    if stream_id is None:
453        #        return HLT_ERR('Please specify stream_id to enable.')
454        #    if stream_id not in self._streams_history:
455        #        return HLT_ERR('This stream_id (%s) was not used before, please create new.' % stream_id)
456        #    self._streams_history[stream_id].update(kwargs) # <- the modification
457
458        if mode == 'modify': # we remove stream and create new one with same stream_id
459            stream_id = kwargs.get('stream_id')
460            if stream_id is None:
461                return HLT_ERR('Please specify stream_id to modify.')
462
463            if len(port_handle) > 1:
464                for port in port_handle:
465                    user_kwargs[port_handle] = port
466                    res = self.traffic_config(**user_kwargs) # recurse per port, each port can have different stream with such id
467            else:
468                if type(port_handle) is list:
469                    port = port_handle[0]
470                else:
471                    port = port_handle
472                if port not in self._streams_history:
473                    return HLT_ERR('Port %s was not used/acquired' % port)
474                if stream_id not in self._streams_history[port]:
475                    return HLT_ERR('This stream_id (%s) was not used before at port %s, please create new.' % (stream_id, port))
476                kwargs.update(self._streams_history[port][stream_id])
477                kwargs.update(user_kwargs)
478            try:
479                self.trex_client.remove_streams(stream_id, port_handle)
480            except Exception as e:
481                return HLT_ERR('Could not remove stream(s) %s from port(s) %s: %s' % (stream_id, port_handle, e if isinstance(e, STLError) else traceback.format_exc()))
482
483        if mode == 'create' or mode == 'modify':
484            # create a new stream with desired attributes, starting by creating packet
485            streams_per_port = CStreamsPerPort()
486            if kwargs['bidirectional']: # two streams with opposite directions
487                del user_kwargs['bidirectional']
488                save_to_yaml = user_kwargs.get('save_to_yaml')
489                bidirect_err = 'When using bidirectional flag, '
490                if len(port_handle) != 1:
491                    return HLT_ERR(bidirect_err + 'port_handle1 should be single port handle.')
492                port_handle2 = kwargs['port_handle2']
493                if (type(port_handle2) is list and len(port_handle2) > 1) or port_handle2 is None:
494                    return HLT_ERR(bidirect_err + 'port_handle2 should be single port handle.')
495                try:
496                    if save_to_yaml and type(save_to_yaml) is str:
497                        user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_bi1.yaml')
498                    user_kwargs['port_handle'] = port_handle[0]
499                    res1 = self.traffic_config(**user_kwargs)
500                    if res1['status'] == 0:
501                        raise STLError('Could not create bidirectional stream 1: %s' % res1['log'])
502                    streams_per_port.add_streams_from_res(res1)
503                    kwargs['direction'] = 1 - kwargs['direction'] # not
504                    correct_direction(user_kwargs, kwargs)
505                    user_kwargs['mac_src'] = kwargs['mac_src2']
506                    user_kwargs['mac_dst'] = kwargs['mac_dst2']
507                    if save_to_yaml and type(save_to_yaml) is str:
508                        user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_bi2.yaml')
509                    user_kwargs['port_handle'] = port_handle2
510                    res2 = self.traffic_config(**user_kwargs)
511                    if res2['status'] == 0:
512                        raise STLError('Could not create bidirectional stream 2: %s' % res2['log'])
513                    streams_per_port.add_streams_from_res(res2)
514                except Exception as e:
515                    return HLT_ERR('Could not generate bidirectional traffic: %s' % e if isinstance(e, STLError) else traceback.format_exc())
516                if mode == 'create':
517                    return HLT_OK(stream_id = streams_per_port)
518                else:
519                    return HLT_OK()
520
521            try:
522                stream_obj = STLHltStream(**user_kwargs)
523            except Exception as e:
524                return HLT_ERR('Could not create stream: %s' % e if isinstance(e, STLError) else traceback.format_exc())
525
526            # try adding the stream per ports
527            try:
528                stream_id_arr = self.trex_client.add_streams(streams=stream_obj,
529                                                             ports=port_handle)
530                for port in port_handle:
531                    self._streams_history.save_stream_args(port_handle, stream_id_arr[0], user_kwargs)
532            except Exception as e:
533                return HLT_ERR('Could not add stream to ports: %s' % e if isinstance(e, STLError) else traceback.format_exc())
534            if mode == 'create':
535                return HLT_OK(stream_id = dict((port, stream_id_arr[0]) for port in port_handle))
536            else:
537                return HLT_OK()
538
539        return HLT_ERR('Got to the end of traffic_config, mode not implemented or forgot "return" somewhere.')
540
541    def traffic_control(self, **user_kwargs):
542        if not self.trex_client:
543            return HLT_ERR('Connect first')
544        kwargs = merge_kwargs(traffic_control_kwargs, user_kwargs)
545        action = kwargs['action']
546        port_handle = kwargs['port_handle']
547        ALLOWED_ACTIONS = ['clear_stats', 'run', 'stop', 'sync_run']
548        if action not in ALLOWED_ACTIONS:
549            return HLT_ERR('Action must be one of the following values: {actions}'.format(actions=ALLOWED_ACTIONS))
550        if type(port_handle) is not list:
551            port_handle = [port_handle]
552
553        if action == 'run':
554            try:
555                self.trex_client.start(ports = port_handle)
556            except Exception as e:
557                return HLT_ERR('Could not start traffic: %s' % e if isinstance(e, STLError) else traceback.format_exc())
558            return HLT_OK(stopped = 0)
559
560        elif action == 'stop':
561            try:
562                self.trex_client.stop(ports = port_handle)
563            except Exception as e:
564                return HLT_ERR('Could not start traffic: %s' % e if isinstance(e, STLError) else traceback.format_exc())
565            return HLT_OK(stopped = 1)
566        else:
567            return HLT_ERR("Action '{0}' is not supported yet on TRex".format(action))
568
569        # if we arrived here, this means that operation FAILED!
570        return HLT_ERR("Probably action '%s' is not implemented" % action)
571
572    def traffic_stats(self, **user_kwargs):
573        if not self.trex_client:
574            return HLT_ERR('Connect first')
575        kwargs = merge_kwargs(traffic_stats_kwargs, user_kwargs)
576        mode = kwargs['mode']
577        port_handle = kwargs['port_handle']
578        ALLOWED_MODES = ['aggregate', 'streams', 'all']
579        if mode not in ALLOWED_MODES:
580            return HLT_ERR("'mode' must be one of the following values: %s" % ALLOWED_MODES)
581        if mode == 'streams':
582            return HLT_ERR("mode 'streams' not implemented'")
583        if mode in ('all', 'aggregate'):
584            hlt_stats_dict = {}
585            try:
586                stats = self.trex_client.get_stats(port_handle)
587            except Exception as e:
588                return HLT_ERR('Could not retrieve stats: %s' % e if isinstance(e, STLError) else traceback.format_exc())
589            for port_id, stat_dict in stats.iteritems():
590                if type(port_id) in (int, long):
591                    hlt_stats_dict[port_id] = {
592                        'aggregate': {
593                            'tx': {
594                                'pkt_bit_rate': stat_dict.get('tx_bps'),
595                                'pkt_byte_count': stat_dict.get('obytes'),
596                                'pkt_count': stat_dict.get('opackets'),
597                                'pkt_rate': stat_dict.get('tx_pps'),
598                                'total_pkt_bytes': stat_dict.get('obytes'),
599                                'total_pkt_rate': stat_dict.get('tx_pps'),
600                                'total_pkts': stat_dict.get('opackets'),
601                                },
602                            'rx': {
603                                'pkt_bit_rate': stat_dict.get('rx_bps'),
604                                'pkt_byte_count': stat_dict.get('ibytes'),
605                                'pkt_count': stat_dict.get('ipackets'),
606                                'pkt_rate': stat_dict.get('rx_pps'),
607                                'total_pkt_bytes': stat_dict.get('ibytes'),
608                                'total_pkt_rate': stat_dict.get('rx_pps'),
609                                'total_pkts': stat_dict.get('ipackets'),
610                                }
611                            }
612                        }
613            return HLT_OK(hlt_stats_dict)
614
615
616    # remove streams from given port(s).
617    # stream_id can be:
618    #    * int    - exact stream_id value
619    #    * list   - list of stream_id values or strings (see below)
620    #    * string - exact stream_id value, mix of ranges/list separated by comma: 2, 4-13
621    def _remove_stream(self, stream_id, port_handle):
622        if get_number(stream_id) is not None: # exact value of int or str
623            self.trex_client.remove_streams(get_number(stream_id), port_handle)         # actual remove
624            for port in port_handle:
625                del self._streams_history[port][get_number(stream_id)]
626            return
627        if type(stream_id) is list: # list of values/strings
628            for each_stream_id in stream_id:
629                self._remove_stream(each_stream_id, port_handle)                        # recurse
630            return
631        if type(stream_id) is str: # range or list in string
632            if stream_id.find(',') != -1:
633                for each_stream_id_element in stream_id.split(','):
634                    self._remove_stream(each_stream_id_element, port_handle)            # recurse
635                return
636            if stream_id.find('-') != -1:
637                stream_id_min, stream_id_max = stream_id.split('-', 1)
638                stream_id_min = get_number(stream_id_min)
639                stream_id_max = get_number(stream_id_max)
640                if stream_id_min is None:
641                    raise STLError('_remove_stream: wrong range param %s' % stream_id_min)
642                if stream_id_max is None:
643                    raise STLError('_remove_stream: wrong range param %s' % stream_id_max)
644                if stream_id_max < stream_id_min:
645                    raise STLError('_remove_stream: right range param is smaller than left one: %s-%s' % (stream_id_min, stream_id_max))
646                for each_stream_id in xrange(stream_id_min, stream_id_max + 1):
647                    self._remove_stream(each_stream_id, port_handle)                    # recurse
648                return
649        raise STLError('_remove_stream: wrong param %s' % stream_id)
650
651
652###########################
653#    Private functions    #
654###########################
655
656    @staticmethod
657    def _parse_port_list(port_list):
658        if type(port_list) is str:
659            return [int(port) for port in port_list.strip().split()]
660        elif type(port_list) is list:
661            return [int(port) for port in port_list]
662        elif type(port) in (int, long):
663            return [int(port_list)]
664        raise STLError('port_list should be string with ports, list, or single number')
665
666def STLHltStream(**user_kwargs):
667    kwargs = merge_kwargs(traffic_config_kwargs, user_kwargs)
668    if kwargs['length_mode'] == 'imix': # several streams with given length
669        streams_arr = []
670        user_kwargs['length_mode'] = 'fixed'
671        if kwargs['l3_imix1_size'] < 32 or kwargs['l3_imix2_size'] < 32 or kwargs['l3_imix3_size'] < 32 or kwargs['l3_imix4_size'] < 32:
672            raise STLError('l3_imix*_size should be at least 32')
673        total_rate = kwargs['l3_imix1_ratio'] + kwargs['l3_imix2_ratio'] + kwargs['l3_imix3_ratio'] + kwargs['l3_imix4_ratio']
674        if total_rate == 0:
675            raise STLError('Used length_mode imix, but all the ratios are 0')
676        save_to_yaml = kwargs.get('save_to_yaml')
677        rate_pps = float(kwargs['rate_pps'])
678        if kwargs['l3_imix1_ratio'] > 0:
679            if save_to_yaml and type(save_to_yaml) is str:
680                user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_imix1.yaml')
681            user_kwargs['frame_size'] = kwargs['l3_imix1_size']
682            user_kwargs['rate_pps'] = rate_pps * kwargs['l3_imix1_ratio'] / total_rate
683            streams_arr.append(STLHltStream(**user_kwargs))
684        if kwargs['l3_imix2_ratio'] > 0:
685            if save_to_yaml and type(save_to_yaml) is str:
686                user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_imix2.yaml')
687            user_kwargs['frame_size'] = kwargs['l3_imix2_size']
688            user_kwargs['rate_pps'] = rate_pps * kwargs['l3_imix2_ratio'] / total_rate
689            streams_arr.append(STLHltStream(**user_kwargs))
690        if kwargs['l3_imix3_ratio'] > 0:
691            if save_to_yaml and type(save_to_yaml) is str:
692                user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_imix3.yaml')
693            user_kwargs['frame_size'] = kwargs['l3_imix3_size']
694            user_kwargs['rate_pps'] = rate_pps * kwargs['l3_imix3_ratio'] / total_rate
695            streams_arr.append(STLHltStream(**user_kwargs))
696        if kwargs['l3_imix4_ratio'] > 0:
697            if save_to_yaml and type(save_to_yaml) is str:
698                user_kwargs['save_to_yaml'] = save_to_yaml.replace('.yaml', '_imix4.yaml')
699            user_kwargs['frame_size'] = kwargs['l3_imix4_size']
700            user_kwargs['rate_pps'] = rate_pps * kwargs['l3_imix4_ratio'] / total_rate
701            streams_arr.append(STLHltStream(**user_kwargs))
702        return streams_arr
703
704    # packet generation
705    packet = generate_packet(**user_kwargs)
706    try:
707        transmit_mode = kwargs['transmit_mode']
708        rate_pps = kwargs['rate_pps']
709        pkts_per_burst = kwargs['pkts_per_burst']
710        if transmit_mode == 'continuous':
711            transmit_mode_class = STLTXCont(pps = rate_pps)
712        elif transmit_mode == 'single_burst':
713            transmit_mode_class = STLTXSingleBurst(pps = rate_pps, total_pkts = pkts_per_burst)
714        elif transmit_mode == 'multi_burst':
715            transmit_mode_class = STLTXMultiBurst(pps = rate_pps, total_pkts = pkts_per_burst,
716                                                  count = kwargs['burst_loop_count'], ibg = kwargs['inter_burst_gap'])
717        else:
718            raise STLError('transmit_mode %s not supported/implemented')
719    except Exception as e:
720        raise STLError('Could not create transmit_mode class %s: %s' % (transmit_mode, e if isinstance(e, STLError) else traceback.format_exc()))
721
722    # stream generation
723    try:
724        stream = STLStream(packet = packet,
725                           #enabled = True,
726                           #self_start = True,
727                           mode = transmit_mode_class,
728                           #rx_stats = rx_stats,
729                           stream_id = kwargs.get('stream_id'),
730                           name = kwargs.get('name'),
731                           )
732    except Exception as e:
733        raise STLError('Could not create stream: %s' % e if isinstance(e, STLError) else traceback.format_exc())
734
735    debug_filename = kwargs.get('save_to_yaml')
736    if type(debug_filename) is str:
737        print 'saving to %s' % debug_filename
738        stream.dump_to_yaml(debug_filename)
739    return stream
740
741def generate_packet(**user_kwargs):
742    correct_macs(user_kwargs)
743    kwargs = merge_kwargs(traffic_config_kwargs, user_kwargs)
744    correct_direction(kwargs, kwargs)
745
746    vm_cmds = []
747    fix_ipv4_checksum = False
748
749    ### L2 ###
750    if kwargs['l2_encap'] in ('ethernet_ii', 'ethernet_ii_vlan'):
751                #fields_desc = [ MACField("dst","00:00:00:01:00:00"),
752                #                MACField("src","00:00:00:02:00:00"),
753                #                XShortEnumField("type", 0x9000, ETHER_TYPES) ]
754        l2_layer = Ether(src = kwargs['mac_src'], dst = kwargs['mac_dst'])
755
756        # Eth VM
757        # WIP!!! Need 8 bytes mask and vars
758        if kwargs['mac_src_mode'] != 'fixed':
759            mac_src_count = kwargs['mac_src_count'] - 1
760            if mac_src_count < 0:
761                raise STLError('mac_src_count has to be at least 1')
762            if mac_src_count > 0:
763                mac_src = mac_str_to_num(mac2str(kwargs['mac_src']))
764                if kwargs['mac_src_mode'] == 'increment':
765                    vm_cmds.append(CTRexVmDescFlowVar(name = 'mac_src', size = 4, op = 'inc', step = kwargs['mac_src_step'],
766                                                      min_value = mac_src,
767                                                      max_value = mac_src + mac_src_count * kwargs['mac_src_step']))
768                elif kwargs['mac_src_mode'] == 'decrement':
769                    vm_cmds.append(CTRexVmDescFlowVar(name = 'mac_src', size = 4, op = 'dec', step = kwargs['mac_src_step'],
770                                                       min_value = mac_src - mac_src_count * kwargs['mac_src_step'],
771                                                       max_value = mac_src))
772                elif kwargs['mac_src_mode'] == 'random':
773                    vm_cmds.append(CTRexVmDescFlowVar(name = 'mac_src', size = 4, op = 'random', min_value = 0, max_value = 0xffffffffffffffff))
774                else:
775                    raise STLError('mac_src_mode %s is not supported' % kwargs['mac_src_mode'])
776                vm_cmds.append(STLVmWrMaskFlowVar(fv_name = 'mac_src', pkt_offset = 'Ethernet.src', pkt_cast_size = 4, mask = 0xffffffff))
777
778
779        if kwargs['l2_encap'] == 'ethernet_ii_vlan' or (kwargs['l2_encap'] == 'ethernet_ii' and vlan_in_args(user_kwargs)):
780                #fields_desc =  [ BitField("prio", 0, 3),
781                #                 BitField("id", 0, 1),
782                #                 BitField("vlan", 1, 12),
783                #                 XShortEnumField("type", 0x0000, ETHER_TYPES) ]
784            for i, vlan_kwargs in enumerate(split_vlan_args(kwargs)):
785                vlan_id = int(vlan_kwargs['vlan_id'])
786                dot1q_kwargs = {'prio': vlan_kwargs['vlan_user_priority'],
787                                'vlan': vlan_id,
788                                'id':   vlan_kwargs['vlan_cfi']}
789                vlan_protocol_tag_id = vlan_kwargs['vlan_protocol_tag_id']
790                if vlan_protocol_tag_id is not None:
791                    if type(vlan_protocol_tag_id) is str:
792                        vlan_protocol_tag_id = int(vlan_protocol_tag_id, 16)
793                    dot1q_kwargs['type'] = vlan_protocol_tag_id
794                l2_layer /= Dot1Q(**dot1q_kwargs)
795
796                # vlan VM
797                vlan_id_mode = vlan_kwargs['vlan_id_mode']
798                if vlan_id_mode != 'fixed':
799                    vlan_id_count = int(vlan_kwargs['vlan_id_count']) - 1
800                    if vlan_id_count < 0:
801                        raise STLError('vlan_id_count has to be at least 1')
802                    if vlan_id_count > 0 or vlan_id_mode == 'random':
803                        var_name = 'vlan_id%s' % i
804                        step = int(vlan_kwargs['vlan_id_step'])
805                        if vlan_id_mode == 'increment':
806                            vm_cmds.append(CTRexVmDescFlowVar(name = var_name, size = 2, op = 'inc', step = step,
807                                                              min_value = vlan_id,
808                                                              max_value = vlan_id + vlan_id_count * step))
809                        elif vlan_id_mode == 'decrement':
810                            vm_cmds.append(CTRexVmDescFlowVar(name = var_name, size = 2, op = 'dec', step = step,
811                                                               min_value = vlan_id - vlan_id_count * step,
812                                                               max_value = vlan_id))
813                        elif vlan_id_mode == 'random':
814                            vm_cmds.append(CTRexVmDescFlowVar(name = var_name, size = 2, op = 'random', min_value = 0, max_value = 0xffff))
815                        else:
816                            raise STLError('vlan_id_mode %s is not supported' % vlan_id_mode)
817                        vm_cmds.append(STLVmWrMaskFlowVar(fv_name = var_name, pkt_offset = '802|1Q:%s.vlan' % i,
818                                                          pkt_cast_size = 2, mask = 0xfff))
819
820    else:
821        raise NotImplementedError("l2_encap does not support the desired encapsulation '%s'" % kwargs['l2_encap'])
822    base_pkt = l2_layer
823
824    ### L3 ###
825    if kwargs['l3_protocol'] == 'ipv4':
826                #fields_desc = [ BitField("version" , 4 , 4),
827                #                BitField("ihl", None, 4),
828                #                XByteField("tos", 0),
829                #                ShortField("len", None),
830                #                ShortField("id", 1),
831                #                FlagsField("flags", 0, 3, ["MF","DF","evil"]),
832                #                BitField("frag", 0, 13),
833                #                ByteField("ttl", 64),
834                #                ByteEnumField("proto", 0, IP_PROTOS),
835                #                XShortField("chksum", None),
836                #                Emph(IPField("src", "16.0.0.1")),
837                #                Emph(IPField("dst", "48.0.0.1")),
838                #                PacketListField("options", [], IPOption, length_from=lambda p:p.ihl*4-20) ]
839        ip_tos = get_TOS(user_kwargs, kwargs)
840        if ip_tos < 0 or ip_tos > 255:
841            raise STLError('TOS %s is not in range 0-255' % ip_tos)
842        l3_layer = IP(tos    = ip_tos,
843                      len    = kwargs['l3_length'],
844                      id     = kwargs['ip_id'],
845                      frag   = kwargs['ip_fragment_offset'],
846                      ttl    = kwargs['ip_ttl'],
847                      chksum = kwargs['ip_checksum'],
848                      src    = kwargs['ip_src_addr'],
849                      dst    = kwargs['ip_dst_addr'],
850                      )
851        # IPv4 VM
852        if kwargs['ip_src_mode'] != 'fixed':
853            ip_src_count = kwargs['ip_src_count'] - 1
854            if ip_src_count < 0:
855                raise STLError('ip_src_count has to be at least 1')
856            if ip_src_count > 0:
857                fix_ipv4_checksum = True
858                ip_src_addr_num = ipv4_str_to_num(is_valid_ipv4(kwargs['ip_src_addr']))
859                if kwargs['ip_src_mode'] == 'increment':
860                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ip_src', size = 4, op = 'inc', step = kwargs['ip_src_step'],
861                                                      min_value = ip_src_addr_num,
862                                                      max_value = ip_src_addr_num + ip_src_count * kwargs['ip_src_step']))
863                elif kwargs['ip_src_mode'] == 'decrement':
864                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ip_src', size = 4, op = 'dec', step = kwargs['ip_src_step'],
865                                                       min_value = ip_src_addr_num - ip_src_count * kwargs['ip_src_step'],
866                                                       max_value = ip_src_addr_num))
867                elif kwargs['ip_src_mode'] == 'random':
868                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ip_src', size = 4, op = 'random', min_value = 0, max_value = 0xffffffff))
869                else:
870                    raise STLError('ip_src_mode %s is not supported' % kwargs['ip_src_mode'])
871                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='ip_src', pkt_offset = 'IP.src'))
872
873        if kwargs['ip_dst_mode'] != 'fixed':
874            ip_dst_count = kwargs['ip_dst_count'] - 1
875            if ip_dst_count < 0:
876                raise STLError('ip_dst_count has to be at least 1')
877            if ip_dst_count > 0:
878                fix_ipv4_checksum = True
879                ip_dst_addr_num = ipv4_str_to_num(is_valid_ipv4(kwargs['ip_dst_addr']))
880                if kwargs['ip_dst_mode'] == 'increment':
881                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ip_dst', size = 4, op = 'inc', step = kwargs['ip_dst_step'],
882                                                       min_value = ip_dst_addr_num,
883                                                       max_value = ip_dst_addr_num + ip_dst_count * kwargs['ip_dst_step']))
884                elif kwargs['ip_dst_mode'] == 'decrement':
885                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ip_dst', size = 4, op = 'dec', step = kwargs['ip_dst_step'],
886                                                       min_value = ip_dst_addr_num - ip_dst_count * kwargs['ip_dst_step'],
887                                                       max_value = ip_dst_addr_num))
888                elif kwargs['ip_dst_mode'] == 'random':
889                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ip_dst', size = 4, op = 'random', min_value = 0, max_value = 0xffffffff))
890                else:
891                    raise STLError('ip_dst_mode %s is not supported' % kwargs['ip_dst_mode'])
892                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='ip_dst', pkt_offset = 'IP.dst'))
893    elif kwargs['l3_protocol'] == 'ipv6':
894                #fields_desc = [ BitField("version" , 6 , 4),
895                #                BitField("tc", 0, 8), #TODO: IPv6, ByteField ?
896                #                BitField("fl", 0, 20),
897                #                ShortField("plen", None),
898                #                ByteEnumField("nh", 59, ipv6nh),
899                #                ByteField("hlim", 64),
900                #                IP6Field("dst", "::2"),
901                #                #SourceIP6Field("src", "dst"), # dst is for src @ selection
902                #                IP6Field("src", "::1") ]
903        ipv6_kwargs = {'tc': kwargs['ipv6_traffic_class'],
904                       'fl': kwargs['ipv6_flow_label'],
905                       'plen': kwargs['ipv6_length'],
906                       'hlim': kwargs['ipv6_hop_limit'],
907                       'src': kwargs['ipv6_src_addr'],
908                       'dst': kwargs['ipv6_dst_addr']}
909        if kwargs['ipv6_next_header'] is not None:
910            ipv6_kwargs['nh'] = kwargs['ipv6_next_header']
911        l3_layer = IPv6(**ipv6_kwargs)
912
913        # IPv6 VM
914        if kwargs['ipv6_src_mode'] != 'fixed':
915            ipv6_src_count = kwargs['ipv6_src_count'] - 1
916            if ipv6_src_count < 0:
917                raise STLError('ipv6_src_count has to be at least 1')
918            if ipv6_src_count > 0:
919                ipv6_src_addr_num = ipv4_str_to_num(is_valid_ipv6(kwargs['ipv6_src_addr'])[-4:])
920                ipv6_src_step = kwargs['ipv6_src_step']
921                if type(ipv6_src_step) is str: # convert ipv6 step to number
922                    ipv6_src_step = ipv4_str_to_num(is_valid_ipv6(ipv6_src_step)[-4:])
923                if kwargs['ipv6_src_mode'] == 'increment':
924                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ipv6_src', size = 4, op = 'inc', step = ipv6_src_step,
925                                                      min_value = ipv6_src_addr_num,
926                                                      max_value = ipv6_src_addr_num + ipv6_src_count * ipv6_src_step))
927                elif kwargs['ipv6_src_mode'] == 'decrement':
928                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ipv6_src', size = 4, op = 'dec', step = ipv6_src_step,
929                                                       min_value = ipv6_src_addr_num - ipv6_src_count * ipv6_src_step,
930                                                       max_value = ipv6_src_addr_num))
931                elif kwargs['ipv6_src_mode'] == 'random':
932                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ipv6_src', size = 4, op = 'random', min_value = 0, max_value = 0xffffffff))
933                else:
934                    raise STLError('ipv6_src_mode %s is not supported' % kwargs['ipv6_src_mode'])
935                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='ipv6_src', pkt_offset = 'IPv6.src', offset_fixup = 12))
936
937        if kwargs['ipv6_dst_mode'] != 'fixed':
938            ipv6_dst_count = kwargs['ipv6_dst_count'] - 1
939            if ipv6_dst_count < 0:
940                raise STLError('ipv6_dst_count has to be at least 1')
941            if ipv6_dst_count > 0:
942                ipv6_dst_addr_num = ipv4_str_to_num(is_valid_ipv6(kwargs['ipv6_dst_addr'])[-4:])
943                ipv6_dst_step = kwargs['ipv6_dst_step']
944                if type(ipv6_dst_step) is str: # convert ipv6 step to number
945                    ipv6_dst_step = ipv4_str_to_num(is_valid_ipv6(ipv6_dst_step)[-4:])
946                if kwargs['ipv6_dst_mode'] == 'increment':
947                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ipv6_dst', size = 4, op = 'inc', step = ipv6_dst_step,
948                                                      min_value = ipv6_dst_addr_num,
949                                                      max_value = ipv6_dst_addr_num + ipv6_dst_count * ipv6_dst_step))
950                elif kwargs['ipv6_dst_mode'] == 'decrement':
951                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ipv6_dst', size = 4, op = 'dec', step = ipv6_dst_step,
952                                                       min_value = ipv6_dst_addr_num - ipv6_dst_count * ipv6_dst_step,
953                                                       max_value = ipv6_dst_addr_num))
954                elif kwargs['ipv6_dst_mode'] == 'random':
955                    vm_cmds.append(CTRexVmDescFlowVar(name = 'ipv6_dst', size = 4, op = 'random', min_value = 0, max_value = 0xffffffff))
956                else:
957                    raise STLError('ipv6_dst_mode %s is not supported' % kwargs['ipv6_dst_mode'])
958                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='ipv6_dst', pkt_offset = 'IPv6.dst', offset_fixup = 12))
959
960    else:
961        raise NotImplementedError("l3_protocol '%s' is not supported by TRex yet." % kwargs['l3_protocol'])
962    base_pkt /= l3_layer
963
964    ### L4 ###
965    if kwargs['l4_protocol'] == 'tcp':
966                #fields_desc = [ ShortEnumField("sport", 20, TCP_SERVICES),
967                #                ShortEnumField("dport", 80, TCP_SERVICES),
968                #                IntField("seq", 0),
969                #                IntField("ack", 0),
970                #                BitField("dataofs", None, 4),
971                #                BitField("reserved", 0, 4),
972                #                FlagsField("flags", 0x2, 8, "FSRPAUEC"),
973                #                ShortField("window", 8192),
974                #                XShortField("chksum", None),
975                #                ShortField("urgptr", 0),
976                #                TCPOptionsField("options", {}) ]
977        tcp_flags = ('F' if kwargs['tcp_fin_flag'] else '' +
978                     'S' if kwargs['tcp_syn_flag'] else '' +
979                     'R' if kwargs['tcp_rst_flag'] else '' +
980                     'P' if kwargs['tcp_psh_flag'] else '' +
981                     'A' if kwargs['tcp_ack_flag'] else '' +
982                     'U' if kwargs['tcp_urg_flag'] else '')
983
984        l4_layer = TCP(sport   = kwargs['tcp_src_port'],
985                       dport   = kwargs['tcp_dst_port'],
986                       seq     = kwargs['tcp_seq_num'],
987                       ack     = kwargs['tcp_ack_num'],
988                       dataofs = kwargs['tcp_data_offset'],
989                       flags   = tcp_flags,
990                       window  = kwargs['tcp_window'],
991                       chksum  = kwargs['tcp_checksum'],
992                       urgptr  = kwargs['tcp_urgent_ptr'],
993                       )
994        # TCP VM
995        if kwargs['tcp_src_port_mode'] != 'fixed':
996            tcp_src_port_count = kwargs['tcp_src_port_count'] - 1
997            if tcp_src_port_count < 0:
998                raise STLError('tcp_src_port_count has to be at least 1')
999            if tcp_src_port_count > 0:
1000                fix_ipv4_checksum = True
1001                if kwargs['tcp_src_port_mode'] == 'increment':
1002                    vm_cmds.append(CTRexVmDescFlowVar(name = 'tcp_src', size = 2, op = 'inc', step = kwargs['tcp_src_port_step'],
1003                                                      min_value = kwargs['tcp_src_port'],
1004                                                      max_value = kwargs['tcp_src_port'] + tcp_src_port_count * kwargs['tcp_src_port_step']))
1005                elif kwargs['tcp_src_port_mode'] == 'decrement':
1006                    vm_cmds.append(CTRexVmDescFlowVar(name = 'tcp_src', size = 2, op = 'dec', step = kwargs['tcp_src_port_step'],
1007                                                      min_value = kwargs['tcp_src_port'] - tcp_src_port_count * kwargs['tcp_src_port_step'],
1008                                                      max_value = kwargs['tcp_src_port']))
1009                elif kwargs['tcp_src_port_mode'] == 'random':
1010                    vm_cmds.append(CTRexVmDescFlowVar(name = 'tcp_src', size = 2, op = 'random', min_value = 0, max_value = 0xffff))
1011                else:
1012                    raise STLError('tcp_src_port_mode %s is not supported' % kwargs['tcp_src_port_mode'])
1013                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='tcp_src', pkt_offset = 'TCP.sport'))
1014
1015        if kwargs['tcp_dst_port_mode'] != 'fixed':
1016            tcp_dst_port_count = kwargs['tcp_dst_port_count'] - 1
1017            if tcp_dst_port_count < 0:
1018                raise STLError('tcp_dst_port_count has to be at least 1')
1019            if tcp_dst_port_count > 0:
1020                fix_ipv4_checksum = True
1021                if kwargs['tcp_dst_port_mode'] == 'increment':
1022                    vm_cmds.append(CTRexVmDescFlowVar(name = 'tcp_dst', size = 2, op = 'inc', step = kwargs['tcp_dst_port_step'],
1023                                                      min_value = kwargs['tcp_dst_port'],
1024                                                      max_value = kwargs['tcp_dst_port'] + tcp_dst_port_count * kwargs['tcp_dst_port_step']))
1025                elif kwargs['tcp_dst_port_mode'] == 'decrement':
1026                    vm_cmds.append(CTRexVmDescFlowVar(name = 'tcp_dst', size = 2, op = 'dec', step = kwargs['tcp_dst_port_step'],
1027                                                      min_value = kwargs['tcp_dst_port'] - tcp_dst_port_count * kwargs['tcp_dst_port_step'],
1028                                                      max_value = kwargs['tcp_dst_port']))
1029                elif kwargs['tcp_dst_port_mode'] == 'random':
1030                    vm_cmds.append(CTRexVmDescFlowVar(name = 'tcp_dst', size = 2, op = 'random', min_value = 0, max_value = 0xffff))
1031                else:
1032                    raise STLError('tcp_dst_port_mode %s is not supported' % kwargs['tcp_dst_port_mode'])
1033                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='tcp_dst', pkt_offset = 'TCP.dport'))
1034
1035    elif kwargs['l4_protocol'] == 'udp':
1036                #fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES),
1037                #                ShortEnumField("dport", 53, UDP_SERVICES),
1038                #                ShortField("len", None),
1039                #                XShortField("chksum", None), ]
1040        l4_layer = UDP(sport  = kwargs['udp_src_port'],
1041                       dport  = kwargs['udp_dst_port'],
1042                       len    = kwargs['udp_length'], chksum = None)
1043        # UDP VM
1044        if kwargs['udp_src_port_mode'] != 'fixed':
1045            udp_src_port_count = kwargs['udp_src_port_count'] - 1
1046            if udp_src_port_count < 0:
1047                raise STLError('udp_src_port_count has to be at least 1')
1048            if udp_src_port_count > 0:
1049                fix_ipv4_checksum = True
1050                if kwargs['udp_src_port_mode'] == 'increment':
1051                    vm_cmds.append(CTRexVmDescFlowVar(name = 'udp_src', size = 2, op = 'inc', step = kwargs['udp_src_port_step'],
1052                                                      min_value = kwargs['udp_src_port'],
1053                                                      max_value = kwargs['udp_src_port'] + udp_src_port_count * kwargs['udp_src_port_step']))
1054                elif kwargs['udp_src_port_mode'] == 'decrement':
1055                    vm_cmds.append(CTRexVmDescFlowVar(name = 'udp_src', size = 2, op = 'dec', step = kwargs['udp_src_port_step'],
1056                                                      min_value = kwargs['udp_src_port'] - udp_src_port_count * kwargs['udp_src_port_step'],
1057                                                      max_value = kwargs['udp_src_port']))
1058                elif kwargs['udp_src_port_mode'] == 'random':
1059                    vm_cmds.append(CTRexVmDescFlowVar(name = 'udp_src', size = 2, op = 'random', min_value = 0, max_value = 0xffff))
1060                else:
1061                    raise STLError('udp_src_port_mode %s is not supported' % kwargs['udp_src_port_mode'])
1062                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='udp_src', pkt_offset = 'UDP.sport'))
1063
1064        if kwargs['udp_dst_port_mode'] != 'fixed':
1065            udp_dst_port_count = kwargs['udp_dst_port_count'] - 1
1066            if udp_dst_port_count < 0:
1067                raise STLError('udp_dst_port_count has to be at least 1')
1068            if udp_dst_port_count > 0:
1069                fix_ipv4_checksum = True
1070                if kwargs['udp_dst_port_mode'] == 'increment':
1071                    vm_cmds.append(CTRexVmDescFlowVar(name = 'udp_dst', size = 2, op = 'inc', step = kwargs['udp_dst_port_step'],
1072                                                      min_value = kwargs['udp_dst_port'],
1073                                                      max_value = kwargs['udp_dst_port'] + udp_dst_port_count * kwargs['udp_dst_port_step']))
1074                elif kwargs['udp_dst_port_mode'] == 'decrement':
1075                    vm_cmds.append(CTRexVmDescFlowVar(name = 'udp_dst', size = 2, op = 'dec', step = kwargs['udp_dst_port_step'],
1076                                                      min_value = kwargs['udp_dst_port'] - udp_dst_port_count * kwargs['udp_dst_port_step'],
1077                                                      max_value = kwargs['udp_dst_port']))
1078                elif kwargs['udp_dst_port_mode'] == 'random':
1079                    vm_cmds.append(CTRexVmDescFlowVar(name = 'udp_dst', size = 2, op = 'random', min_value = 0, max_value = 0xffff))
1080                else:
1081                    raise STLError('udp_dst_port_mode %s is not supported' % kwargs['udp_dst_port_mode'])
1082                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name='udp_dst', pkt_offset = 'UDP.dport'))
1083    else:
1084        raise NotImplementedError("l4_protocol '%s' is not supported by TRex yet." % kwargs['l3_protocol'])
1085    base_pkt /= l4_layer
1086
1087    trim_dict = {'increment': 'inc', 'decrement': 'dec', 'random': 'random'}
1088    length_mode = kwargs['length_mode']
1089    if length_mode == 'auto':
1090        payload_len = 0
1091    elif length_mode == 'fixed':
1092        if 'frame_size' in user_kwargs:
1093            payload_len = kwargs['frame_size'] - len(base_pkt)
1094        elif 'l3_length' in user_kwargs:
1095            payload_len = kwargs['l3_length'] - len(base_pkt) - len(l2_layer)
1096        else: # default
1097            payload_len = kwargs['frame_size'] - len(base_pkt)
1098    elif length_mode == 'imix':
1099        raise STLError("length_mode 'imix' should be treated at stream creating level.")
1100    elif length_mode in trim_dict:
1101        if 'frame_size_min' in user_kwargs or 'frame_size_max' in user_kwargs: # size is determined by L2, higher priority over L3 size
1102            if kwargs['frame_size_min'] < 44 or kwargs['frame_size_max'] < 44:
1103                raise STLError('frame_size_min and frame_size_max should be at least 44')
1104            if kwargs['frame_size_min'] > kwargs['frame_size_max']:
1105                raise STLError('frame_size_min is bigger than frame_size_max')
1106            if kwargs['frame_size_min'] != kwargs['frame_size_max']:
1107                fix_ipv4_checksum = True
1108                vm_cmds.append(CTRexVmDescFlowVar(name = 'pkt_len', size = 2, op = trim_dict[length_mode], step = kwargs['frame_size_step'],
1109                                                  min_value = kwargs['frame_size_min'],
1110                                                  max_value = kwargs['frame_size_max']))
1111                vm_cmds.append(CTRexVmDescTrimPktSize('pkt_len'))
1112            payload_len = kwargs['frame_size_max'] - len(base_pkt)
1113        else: # size is determined by L3
1114            if kwargs['l3_length_min'] < 40 or kwargs['l3_length_max'] < 40:
1115                raise STLError('l3_length_min and l3_length_max should be at least 40')
1116            if kwargs['l3_length_min'] > kwargs['l3_length_max']:
1117                raise STLError('l3_length_min is bigger than l3_length_max')
1118            if kwargs['l3_length_min'] != kwargs['l3_length_max']:
1119                fix_ipv4_checksum = True
1120                vm_cmds.append(CTRexVmDescFlowVar(name = 'pkt_len', size = 2, op = trim_dict[length_mode], step = kwargs['l3_length_step'],
1121                                                  min_value = kwargs['l3_length_min'] + len(l2_layer),
1122                                                  max_value = kwargs['l3_length_max'] + len(l2_layer)))
1123            payload_len = kwargs['l3_length_max'] + len(l2_layer) - len(base_pkt)
1124            vm_cmds.append(CTRexVmDescTrimPktSize('pkt_len'))
1125
1126        if l3_layer.name == 'IP' or l4_layer.name == 'UDP': # add here other things need to fix due to size change
1127            if l3_layer.name == 'IP':
1128                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name = 'pkt_len', pkt_offset = 'IP.len', add_val = -len(l2_layer)))
1129            if l4_layer.name == 'UDP':
1130                vm_cmds.append(CTRexVmDescWrFlowVar(fv_name = 'pkt_len', pkt_offset = 'UDP.len', add_val = -len(l2_layer) - len(l3_layer)))
1131    else:
1132        raise STLError('length_mode should be one of the following: %s' % ['auto', 'fixed'] + trim_dict.keys())
1133
1134    if payload_len < 0:
1135        raise STLError('Packet length is bigger than defined by frame_size* or l3_length*. We got payload size %s' % payload_len)
1136    base_pkt /= '!' * payload_len
1137
1138    pkt = STLPktBuilder()
1139    pkt.set_packet(base_pkt)
1140    if fix_ipv4_checksum and l3_layer.name == 'IP' and kwargs['ip_checksum'] is None:
1141        vm_cmds.append(CTRexVmDescFixIpv4(offset = 'IP'))
1142    if vm_cmds:
1143        split_by_field = None
1144        if kwargs['split_by_cores'] == 'single':
1145            raise STLError("split_by_cores 'single' not implemented yet")
1146        elif kwargs['split_by_cores'] == 'split':
1147            max_length = 0
1148            for cmd in vm_cmds:
1149                if isinstance(cmd, CTRexVmDescFlowVar):
1150                    if cmd.op not in ('inc', 'dec'):
1151                        continue
1152                    length = float(cmd.max_value - cmd.min_value) / cmd.step
1153                    if cmd.name == 'ip_src' and length > 7: # priority is to split by ip_src
1154                        split_by_field = 'ip_src'
1155                        break
1156                    if length > max_length:
1157                        max_length = length
1158                        split_by_field = cmd.name
1159        elif kwargs['split_by_cores'] != 'duplicate':
1160            raise STLError("split_by_cores '%s' not supported" % kwargs['split_by_cores'])
1161        pkt.add_command(CTRexScRaw(vm_cmds, split_by_field))
1162
1163    # debug (only the base packet, without VM)
1164    debug_filename = kwargs.get('save_to_pcap')
1165    if type(debug_filename) is str:
1166        pkt.dump_pkt_to_pcap(debug_filename)
1167    return pkt
1168
1169def get_TOS(user_kwargs, kwargs):
1170    TOS0 = set(['ip_precedence', 'ip_tos_field', 'ip_mbz'])
1171    TOS1 = set(['ip_precedence', 'ip_delay', 'ip_throughput', 'ip_reliability', 'ip_cost', 'ip_reserved'])
1172    TOS2 = set(['ip_dscp', 'ip_cu'])
1173    user_args = set(user_kwargs.keys())
1174    if user_args & (TOS1 - TOS0) and user_args & (TOS0 - TOS1):
1175        raise STLError('You have mixed %s and %s TOS parameters' % (TOS0, TOS1))
1176    if user_args & (TOS2 - TOS0) and user_args & (TOS0 - TOS2):
1177        raise STLError('You have mixed %s and %s TOS parameters' % (TOS0, TOS2))
1178    if user_args & (TOS2 - TOS1) and user_args & (TOS1 - TOS2):
1179        raise STLError('You have mixed %s and %s TOS parameters' % (TOS1, TOS2))
1180    if user_args & (TOS0 - TOS1 - TOS2):
1181        return (kwargs['ip_precedence'] << 5) + (kwargs['ip_tos_field'] << 2) + kwargs['ip_mbz']
1182    if user_args & (TOS1 - TOS2):
1183        return (kwargs['ip_precedence'] << 5) + (kwargs['ip_delay'] << 4) + (kwargs['ip_throughput'] << 3) + (kwargs['ip_reliability'] << 2) + (kwargs['ip_cost'] << 1) + kwargs['ip_reserved']
1184    return (kwargs['ip_dscp'] << 2) + kwargs['ip_cu']
1185
1186def vlan_in_args(user_kwargs):
1187    for arg in user_kwargs:
1188        if arg.startswith('vlan_'):
1189            return True
1190    return False
1191
1192def split_vlan_arg(vlan_arg):
1193    if type(vlan_arg) is list:
1194        return vlan_arg
1195    if type(vlan_arg) in (int, long, type(None)):
1196        return [vlan_arg]
1197    if type(vlan_arg) is str:
1198        return vlan_arg.replace('{', '').replace('}', '').strip().split()
1199    raise STLError('vlan argument invalid (expecting list, int, long, str, None): %s' % vlan_arg)
1200
1201def split_vlan_args(kwargs):
1202    vlan_args_dict = {}
1203    for arg, value in kwargs.items():
1204        if arg.startswith('vlan_'):
1205            vlan_args_dict[arg] = split_vlan_arg(value)
1206    dot1q_headers_count = max([len(x) for x in vlan_args_dict.values()])
1207    vlan_args_per_header = [{} for _ in range(dot1q_headers_count)]
1208    for arg, value in vlan_args_dict.items():
1209        for i in range(dot1q_headers_count):
1210            if len(value) > i:
1211                vlan_args_per_header[i][arg] = value[i]
1212            else:
1213                vlan_args_per_header[i][arg] = traffic_config_kwargs[arg]
1214    return vlan_args_per_header
1215
1216def correct_direction(user_kwargs, kwargs):
1217    if kwargs['direction'] == 0:
1218        return
1219    user_kwargs['mac_src'], user_kwargs['mac_dst'] = kwargs['mac_dst'], kwargs['mac_src']
1220    if kwargs['l3_protocol'] == 'ipv4':
1221        for arg in kwargs.keys():
1222            if 'ip_src_' in arg:
1223                dst_arg = 'ip_dst_' + arg[7:]
1224                user_kwargs[arg], user_kwargs[dst_arg] = kwargs[dst_arg], kwargs[arg]
1225    elif kwargs['l3_protocol'] == 'ipv6':
1226        for arg in kwargs.keys():
1227            if 'ipv6_src_' in arg:
1228                dst_arg = 'ipv6_dst_' + arg[9:]
1229                user_kwargs[arg], user_kwargs[dst_arg] = kwargs[dst_arg], kwargs[arg]
1230