3f72c6df9Simaromfrom .trex_stl_exceptions import *
4f72c6df9Simaromfrom .trex_stl_types import verify_exclusive_arg, validate_type
5f72c6df9Simaromfrom .trex_stl_packet_builder_interface import CTrexPktBuilderInterface
61975220bSYaroslav Brustinovfrom .trex_stl_packet_builder_scapy import *
7995267dbSimaromfrom collections import OrderedDict, namedtuple
9c59a02f8Simaromfrom scapy.utils import ltoa
108691f401Simaromfrom scapy.error import Scapy_Exception
11995267dbSimaromimport random
12995267dbSimaromimport yaml
13995267dbSimaromimport base64
14f5a5e50bSimaromimport string
15e8ba50a8Simaromimport traceback
16a420b4c4Simaromimport copy
17f72c6df9Simaromimport imp
20d1360da9Simarom# base class for TX mode
21d1360da9Simaromclass STLTXMode(object):
222be2f7e9SHanoh Haim    """ mode rate speed """
232be2f7e9SHanoh Haim
24bc7f0b85Simarom    def __init__ (self, pps = None, bps_L1 = None, bps_L2 = None, percentage = None):
252be2f7e9SHanoh Haim        """
26a627a1d4SDavidBlock        Speed can be given in packets per second (pps), L2/L1 bps, or port percent
27a627a1d4SDavidBlock        Use only one unit.
282be2f7e9SHanoh Haim        you can enter pps =10000 oe bps_L1=10
292be2f7e9SHanoh Haim
302be2f7e9SHanoh Haim        :parameters:
312be2f7e9SHanoh Haim            pps : float
32a627a1d4SDavidBlock               Packets per second
332be2f7e9SHanoh Haim
342be2f7e9SHanoh Haim            bps_L1 : float
35a627a1d4SDavidBlock               Bits per second L1 (with IPG)
362be2f7e9SHanoh Haim
372be2f7e9SHanoh Haim            bps_L2 : float
38a627a1d4SDavidBlock               Bits per second L2 (Ethernet-FCS)
392be2f7e9SHanoh Haim
402be2f7e9SHanoh Haim            percentage : float
41a627a1d4SDavidBlock               Link interface percent (0-100). Example: 10 is 10% of the port link setup
422be2f7e9SHanoh Haim
435817a450SHanoh Haim        .. code-block:: python
445817a450SHanoh Haim
45138686f3SYaroslav Brustinov            # STLTXMode Example
465817a450SHanoh Haim
47138686f3SYaroslav Brustinov            mode = STLTXCont(pps = 10)
485817a450SHanoh Haim
49138686f3SYaroslav Brustinov            mode = STLTXCont(bps_L1 = 10000000) #10mbps L1
505817a450SHanoh Haim
51138686f3SYaroslav Brustinov            mode = STLTXCont(bps_L2 = 10000000) #10mbps L2
52138686f3SYaroslav Brustinov
53138686f3SYaroslav Brustinov            mode = STLTXCont(percentage = 10)   #10%
542be2f7e9SHanoh Haim
552be2f7e9SHanoh Haim        """
562be2f7e9SHanoh Haim
57d1360da9Simarom        args = [pps, bps_L1, bps_L2, percentage]
59d1360da9Simarom        # default
60d1360da9Simarom        if all([x is None for x in args]):
61d1360da9Simarom            pps = 1.0
62d1360da9Simarom        else:
63d1360da9Simarom            verify_exclusive_arg(args)
65d1360da9Simarom        self.fields = {'rate': {}}
67bc7f0b85Simarom        if pps is not None:
68bc7f0b85Simarom            validate_type('pps', pps, [float, int])
70d1360da9Simarom            self.fields['rate']['type']  = 'pps'
71d1360da9Simarom            self.fields['rate']['value'] = pps
73bc7f0b85Simarom        elif bps_L1 is not None:
74bc7f0b85Simarom            validate_type('bps_L1', bps_L1, [float, int])
76d1360da9Simarom            self.fields['rate']['type']  = 'bps_L1'
77d1360da9Simarom            self.fields['rate']['value'] = bps_L1
79bc7f0b85Simarom        elif bps_L2 is not None:
80bc7f0b85Simarom            validate_type('bps_L2', bps_L2, [float, int])
82d1360da9Simarom            self.fields['rate']['type']  = 'bps_L2'
83d1360da9Simarom            self.fields['rate']['value'] = bps_L2
85bc7f0b85Simarom        elif percentage is not None:
86bc7f0b85Simarom            validate_type('percentage', percentage, [float, int])
87bc7f0b85Simarom            if not (percentage > 0 and percentage <= 100):
88bc7f0b85Simarom                raise STLArgumentError('percentage', percentage)
90d1360da9Simarom            self.fields['rate']['type']  = 'percentage'
91d1360da9Simarom            self.fields['rate']['value'] = percentage
95995267dbSimarom    def to_json (self):
96995267dbSimarom        return self.fields
99995267dbSimarom# continuous mode
100995267dbSimaromclass STLTXCont(STLTXMode):
101a627a1d4SDavidBlock    """ Continuous mode """
103301d6ca6SYaroslav Brustinov    def __init__ (self, **kwargs):
1042be2f7e9SHanoh Haim        """
105a627a1d4SDavidBlock        Continuous mode
1062be2f7e9SHanoh Haim
1072be2f7e9SHanoh Haim         see :class:`trex_stl_lib.trex_stl_streams.STLTXMode` for rate
1095817a450SHanoh Haim        .. code-block:: python
1105817a450SHanoh Haim
111138686f3SYaroslav Brustinov            # STLTXCont Example
112138686f3SYaroslav Brustinov
113138686f3SYaroslav Brustinov            mode = STLTXCont(pps = 10)
1142be2f7e9SHanoh Haim
1152be2f7e9SHanoh Haim        """
116301d6ca6SYaroslav Brustinov        super(STLTXCont, self).__init__(**kwargs)
1182be2f7e9SHanoh Haim
1197e1049aeSHanoh Haim        self.fields['type'] = 'continuous'
121a842b4cfSYaroslav Brustinov    @staticmethod
122a842b4cfSYaroslav Brustinov    def __str__ ():
12396e96afaSimarom        return "Continuous"
125995267dbSimarom# single burst mode
126995267dbSimaromclass STLTXSingleBurst(STLTXMode):
1272be2f7e9SHanoh Haim    """ Single burst mode """
129301d6ca6SYaroslav Brustinov    def __init__ (self, total_pkts = 1, **kwargs):
1302be2f7e9SHanoh Haim        """
131a627a1d4SDavidBlock        Single burst mode
1322be2f7e9SHanoh Haim
1332be2f7e9SHanoh Haim            :parameters:
1342be2f7e9SHanoh Haim                 total_pkts : int
135a627a1d4SDavidBlock                    Number of packets for this burst
1362be2f7e9SHanoh Haim
1372be2f7e9SHanoh Haim         see :class:`trex_stl_lib.trex_stl_streams.STLTXMode` for rate
1382be2f7e9SHanoh Haim
1395817a450SHanoh Haim        .. code-block:: python
1405817a450SHanoh Haim
141138686f3SYaroslav Brustinov            # STLTXSingleBurst Example
142138686f3SYaroslav Brustinov
143138686f3SYaroslav Brustinov            mode = STLTXSingleBurst( pps = 10, total_pkts = 1)
1442be2f7e9SHanoh Haim
1452be2f7e9SHanoh Haim        """
1462be2f7e9SHanoh Haim
148995267dbSimarom        if not isinstance(total_pkts, int):
149995267dbSimarom            raise STLArgumentError('total_pkts', total_pkts)
151301d6ca6SYaroslav Brustinov        super(STLTXSingleBurst, self).__init__(**kwargs)
153995267dbSimarom        self.fields['type'] = 'single_burst'
154995267dbSimarom        self.fields['total_pkts'] = total_pkts
156a842b4cfSYaroslav Brustinov    @staticmethod
157a842b4cfSYaroslav Brustinov    def __str__ ():
15896e96afaSimarom        return "Single Burst"
160995267dbSimarom# multi burst mode
161995267dbSimaromclass STLTXMultiBurst(STLTXMode):
162a627a1d4SDavidBlock    """ Multi-burst mode """
164995267dbSimarom    def __init__ (self,
165995267dbSimarom                  pkts_per_burst = 1,
1666c3a6dd6SHanoh Haim                  ibg = 0.0,   # usec not SEC
167d1360da9Simarom                  count = 1,
168301d6ca6SYaroslav Brustinov                  **kwargs):
1692be2f7e9SHanoh Haim        """
170a627a1d4SDavidBlock        Multi-burst mode
1712be2f7e9SHanoh Haim
1722be2f7e9SHanoh Haim        :parameters:
1732be2f7e9SHanoh Haim
1742be2f7e9SHanoh Haim             pkts_per_burst: int
175a627a1d4SDavidBlock                Number of packets per burst
1762be2f7e9SHanoh Haim
1772be2f7e9SHanoh Haim              ibg : float
178a627a1d4SDavidBlock                Inter-burst gap in usec 1,000,000.0 is 1 sec
1792be2f7e9SHanoh Haim
1802be2f7e9SHanoh Haim              count : int
181a627a1d4SDavidBlock                Number of bursts
1822be2f7e9SHanoh Haim
1832be2f7e9SHanoh Haim         see :class:`trex_stl_lib.trex_stl_streams.STLTXMode` for rate
1842be2f7e9SHanoh Haim
1855817a450SHanoh Haim        .. code-block:: python
1865817a450SHanoh Haim
187138686f3SYaroslav Brustinov            # STLTXMultiBurst Example
188138686f3SYaroslav Brustinov
189138686f3SYaroslav Brustinov            mode = STLTXMultiBurst(pps = 10, pkts_per_burst = 1,count 10, ibg=10.0)
1902be2f7e9SHanoh Haim
1912be2f7e9SHanoh Haim        """
1922be2f7e9SHanoh Haim
194995267dbSimarom        if not isinstance(pkts_per_burst, int):
195995267dbSimarom            raise STLArgumentError('pkts_per_burst', pkts_per_burst)
197995267dbSimarom        if not isinstance(ibg, (int, float)):
198995267dbSimarom            raise STLArgumentError('ibg', ibg)
200995267dbSimarom        if not isinstance(count, int):
201995267dbSimarom            raise STLArgumentError('count', count)
203301d6ca6SYaroslav Brustinov        super(STLTXMultiBurst, self).__init__(**kwargs)
205995267dbSimarom        self.fields['type'] = 'multi_burst'
206995267dbSimarom        self.fields['pkts_per_burst'] = pkts_per_burst
207995267dbSimarom        self.fields['ibg'] = ibg
208a69c4dd8Simarom        self.fields['count'] = count
210a842b4cfSYaroslav Brustinov    @staticmethod
211a842b4cfSYaroslav Brustinov    def __str__ ():
21296e96afaSimarom        return "Multi Burst"
2141e98c58dSHanoh HaimSTLStreamDstMAC_CFG_FILE=0
2151e98c58dSHanoh HaimSTLStreamDstMAC_PKT     =1
2161e98c58dSHanoh HaimSTLStreamDstMAC_ARP     =2
2171e98c58dSHanoh Haim
21889d643b9SIdo Barneaclass STLFlowStatsInterface(object):
219ca3f7e0fSIdo Barnea    def __init__ (self, pg_id):
220f0ab9ebaSIdo Barnea        self.fields = {}
2213eb4f868Simarom        self.fields['enabled']         = True
222ca3f7e0fSIdo Barnea        self.fields['stream_id']       = pg_id
224f0ab9ebaSIdo Barnea    def to_json (self):
225a627a1d4SDavidBlock        """ Dump as json"""
226f0ab9ebaSIdo Barnea        return dict(self.fields)
2271e98c58dSHanoh Haim
228f0ab9ebaSIdo Barnea    @staticmethod
229f0ab9ebaSIdo Barnea    def defaults ():
230f0ab9ebaSIdo Barnea        return {'enabled' : False}
23289d643b9SIdo Barnea
23389d643b9SIdo Barneaclass STLFlowStats(STLFlowStatsInterface):
23489d643b9SIdo Barnea    """ Define per stream basic stats
23589d643b9SIdo Barnea
23689d643b9SIdo Barnea    .. code-block:: python
237138686f3SYaroslav Brustinov
238138686f3SYaroslav Brustinov        # STLFlowStats Example
23989d643b9SIdo Barnea
2400968caf0SHanoh Haim        flow_stats = STLFlowStats(pg_id = 7)
24189d643b9SIdo Barnea
24289d643b9SIdo Barnea    """
24389d643b9SIdo Barnea
24489d643b9SIdo Barnea    def __init__(self, pg_id):
24589d643b9SIdo Barnea        super(STLFlowStats, self).__init__(pg_id)
24689d643b9SIdo Barnea        self.fields['rule_type'] = 'stats'
24789d643b9SIdo Barnea
24889d643b9SIdo Barnea
24989d643b9SIdo Barneaclass STLFlowLatencyStats(STLFlowStatsInterface):
25089d643b9SIdo Barnea    """ Define per stream basic stats + latency, jitter, packet reorder/loss
25189d643b9SIdo Barnea
25289d643b9SIdo Barnea    .. code-block:: python
253138686f3SYaroslav Brustinov
254138686f3SYaroslav Brustinov        # STLFlowLatencyStats Example
25589d643b9SIdo Barnea
2560968caf0SHanoh Haim        flow_stats = STLFlowLatencyStats(pg_id = 7)
25789d643b9SIdo Barnea
25889d643b9SIdo Barnea    """
25989d643b9SIdo Barnea
26089d643b9SIdo Barnea    def __init__(self, pg_id):
26189d643b9SIdo Barnea        super(STLFlowLatencyStats, self).__init__(pg_id)
26289d643b9SIdo Barnea        self.fields['rule_type'] = 'latency'
26389d643b9SIdo Barnea
26489d643b9SIdo Barnea
265995267dbSimaromclass STLStream(object):
266a627a1d4SDavidBlock    """ One stream object. Includes mode, Field Engine mode packet template and Rx stats
2672be2f7e9SHanoh Haim
2685817a450SHanoh Haim        .. code-block:: python
269138686f3SYaroslav Brustinov
270138686f3SYaroslav Brustinov            # STLStream Example
2715817a450SHanoh Haim
272d82201e2SHanoh Haim
2732be2f7e9SHanoh Haim            base_pkt =  Ether()/IP(src="",dst="")/UDP(dport=12,sport=1025)
2742be2f7e9SHanoh Haim            pad = max(0, size - len(base_pkt)) * 'x'
2752be2f7e9SHanoh Haim
2762be2f7e9SHanoh Haim            STLStream( isg = 10.0, # star in delay
2772be2f7e9SHanoh Haim                       name    ='S0',
2782be2f7e9SHanoh Haim                       packet = STLPktBuilder(pkt = base_pkt/pad),
2792be2f7e9SHanoh Haim                       mode = STLTXSingleBurst( pps = 10, total_pkts = 1),
2802be2f7e9SHanoh Haim                       next = 'S1'), # point to next stream
2812be2f7e9SHanoh Haim
282d82201e2SHanoh Haim
2832be2f7e9SHanoh Haim    """
285995267dbSimarom    def __init__ (self,
2860f03cc46SYaroslav Brustinov                  name = None,
287995267dbSimarom                  packet = None,
288d1360da9Simarom                  mode = STLTXCont(pps = 1),
289995267dbSimarom                  enabled = True,
290995267dbSimarom                  self_start = True,
291995267dbSimarom                  isg = 0.0,
292ca3f7e0fSIdo Barnea                  flow_stats = None,
293f5a5e50bSimarom                  next = None,
2941e98c58dSHanoh Haim                  stream_id = None,
2958f6067d8Simarom                  action_count = 0,
2964ae35508SHanoh Haim                  random_seed =0,
2971e98c58dSHanoh Haim                  mac_src_override_by_pkt=None,
2981e98c58dSHanoh Haim                  mac_dst_override_mode=None    #see  STLStreamDstMAC_xx
2991e98c58dSHanoh Haim                  ):
3002be2f7e9SHanoh Haim        """
3012be2f7e9SHanoh Haim        Stream object
3022be2f7e9SHanoh Haim
3032be2f7e9SHanoh Haim        :parameters:
3042be2f7e9SHanoh Haim
3052be2f7e9SHanoh Haim                  name  : string
306a627a1d4SDavidBlock                       Name of the stream. Required if this stream is dependent on another stream, and another stream needs to refer to this stream by name.
3072be2f7e9SHanoh Haim
3085817a450SHanoh Haim                  packet :  STLPktBuilder see :class:`trex_stl_lib.trex_stl_packet_builder_scapy.STLPktBuilder`
309a627a1d4SDavidBlock                       Template packet and field engine program. Example: packet = STLPktBuilder(pkt = base_pkt/pad)
3102be2f7e9SHanoh Haim
311d82201e2SHanoh Haim                  mode :  :class:`trex_stl_lib.trex_stl_streams.STLTXCont` or :class:`trex_stl_lib.trex_stl_streams.STLTXSingleBurst`  or  :class:`trex_stl_lib.trex_stl_streams.STLTXMultiBurst`
3122be2f7e9SHanoh Haim
3132be2f7e9SHanoh Haim                  enabled : bool
314a627a1d4SDavidBlock                      Indicates whether the stream is enabled.
3152be2f7e9SHanoh Haim
3162be2f7e9SHanoh Haim                  self_start : bool
317a627a1d4SDavidBlock                      If False, another stream activates it.
3182be2f7e9SHanoh Haim
3192be2f7e9SHanoh Haim                  isg : float
320a627a1d4SDavidBlock                     Inter-stream gap in usec. Time to wait until the stream sends the first packet.
3212be2f7e9SHanoh Haim
322d82201e2SHanoh Haim                  flow_stats : :class:`trex_stl_lib.trex_stl_streams.STLFlowStats`
323a627a1d4SDavidBlock                      Per stream statistic object. See: STLFlowStats
3242be2f7e9SHanoh Haim
3252be2f7e9SHanoh Haim                  next : string
326a627a1d4SDavidBlock                      Name of the stream to activate.
3272be2f7e9SHanoh Haim
3282be2f7e9SHanoh Haim                  stream_id :
329a627a1d4SDavidBlock                        For use by HLTAPI.
3302be2f7e9SHanoh Haim
3312be2f7e9SHanoh Haim                  action_count : uint16_t
332a627a1d4SDavidBlock                        If there is a next stream, number of loops before stopping. Default: 0 (unlimited).
3332be2f7e9SHanoh Haim
3342be2f7e9SHanoh Haim                  random_seed: uint16_t
335a627a1d4SDavidBlock                       If given, the seed for this stream will be this value. Useful if you need a deterministic random value.
3362be2f7e9SHanoh Haim
3372be2f7e9SHanoh Haim                  mac_src_override_by_pkt : bool
338a627a1d4SDavidBlock                        Template packet sets src MAC.
3392be2f7e9SHanoh Haim
3402be2f7e9SHanoh Haim                  mac_dst_override_mode=None : STLStreamDstMAC_xx
341a627a1d4SDavidBlock                        Template packet sets dst MAC.
3422be2f7e9SHanoh Haim        """
3432be2f7e9SHanoh Haim
345995267dbSimarom        # type checking
346bc7f0b85Simarom        validate_type('mode', mode, STLTXMode)
347f72c6df9Simarom        validate_type('packet', packet, (type(None), CTrexPktBuilderInterface))
34889d643b9SIdo Barnea        validate_type('flow_stats', flow_stats, (type(None), STLFlowStatsInterface))
349bc7f0b85Simarom        validate_type('enabled', enabled, bool)
350bc7f0b85Simarom        validate_type('self_start', self_start, bool)
351bc7f0b85Simarom        validate_type('isg', isg, (int, float))
352f72c6df9Simarom        validate_type('stream_id', stream_id, (type(None), int))
3534ae35508SHanoh Haim        validate_type('random_seed',random_seed,int);
355f5a5e50bSimarom        if (type(mode) == STLTXCont) and (next != None):
356a627a1d4SDavidBlock            raise STLError("Continuous stream cannot have a next stream ID")
358f5a5e50bSimarom        # tag for the stream and next - can be anything
359e9392617Simarom        self.name = name
360f5a5e50bSimarom        self.next = next
362a842b4cfSYaroslav Brustinov        self.mac_src_override_by_pkt = mac_src_override_by_pkt # save for easy construct code from stream object
363a842b4cfSYaroslav Brustinov        self.mac_dst_override_mode = mac_dst_override_mode
3640e70a929Simarom        self.id = stream_id
3661e98c58dSHanoh Haim
367f5a5e50bSimarom        self.fields = {}
3691e98c58dSHanoh Haim        int_mac_src_override_by_pkt = 0;
3701e98c58dSHanoh Haim        int_mac_dst_override_mode   = 0;
3711e98c58dSHanoh Haim
3721e98c58dSHanoh Haim
3731e98c58dSHanoh Haim        if mac_src_override_by_pkt == None:
3741e98c58dSHanoh Haim            int_mac_src_override_by_pkt=0
3751e98c58dSHanoh Haim            if packet :
376afefddfaSYaroslav Brustinov                if packet.is_default_src_mac ()==False:
3771e98c58dSHanoh Haim                    int_mac_src_override_by_pkt=1
3781e98c58dSHanoh Haim
3791e98c58dSHanoh Haim        else:
3801e98c58dSHanoh Haim            int_mac_src_override_by_pkt = int(mac_src_override_by_pkt);
3811e98c58dSHanoh Haim
3821e98c58dSHanoh Haim        if mac_dst_override_mode == None:
3831e98c58dSHanoh Haim            int_mac_dst_override_mode   = 0;
3841e98c58dSHanoh Haim            if packet :
385afefddfaSYaroslav Brustinov                if packet.is_default_dst_mac ()==False:
3861e98c58dSHanoh Haim                    int_mac_dst_override_mode=STLStreamDstMAC_PKT
3871e98c58dSHanoh Haim        else:
3881e98c58dSHanoh Haim            int_mac_dst_override_mode = int(mac_dst_override_mode);
3891e98c58dSHanoh Haim
3901e98c58dSHanoh Haim
3911d62dfcaSimarom        self.is_default_mac = not (int_mac_src_override_by_pkt or int_mac_dst_override_mode)
3931e98c58dSHanoh Haim        self.fields['flags'] = (int_mac_src_override_by_pkt&1) +  ((int_mac_dst_override_mode&3)<<1)
3941e98c58dSHanoh Haim
3951e98c58dSHanoh Haim        self.fields['action_count'] = action_count
3961e98c58dSHanoh Haim
397995267dbSimarom        # basic fields
398995267dbSimarom        self.fields['enabled'] = enabled
399995267dbSimarom        self.fields['self_start'] = self_start
400995267dbSimarom        self.fields['isg'] = isg
4024ae35508SHanoh Haim        if random_seed !=0 :
4034ae35508SHanoh Haim            self.fields['random_seed'] = random_seed # optional
4044ae35508SHanoh Haim
405995267dbSimarom        # mode
406995267dbSimarom        self.fields['mode'] = mode.to_json()
40796e96afaSimarom        self.mode_desc      = str(mode)
410bc7f0b85Simarom        # packet
411995267dbSimarom        self.fields['packet'] = {}
412995267dbSimarom        self.fields['vm'] = {}
414995267dbSimarom        if not packet:
415d82201e2SHanoh Haim            packet = STLPktBuilder(pkt = Ether()/IP())
4170af59f17SHanoh Haim        self.scapy_pkt_builder = packet
418995267dbSimarom        # packet builder
419995267dbSimarom        packet.compile()
421995267dbSimarom        # packet and VM
422995267dbSimarom        self.fields['packet'] = packet.dump_pkt()
423995267dbSimarom        self.fields['vm']     = packet.get_vm_data()
425a5788f0eSimarom        self.pkt = base64.b64decode(self.fields['packet']['binary'])
427ec369cd7Simarom        # this is heavy, calculate lazy
428ec369cd7Simarom        self.packet_desc = None
430ca3f7e0fSIdo Barnea        if not flow_stats:
431ea61eabfSIdo Barnea            self.fields['flow_stats'] = STLFlowStats.defaults()
432e8ba50a8Simarom        else:
433ca3f7e0fSIdo Barnea            self.fields['flow_stats'] = flow_stats.to_json()
436995267dbSimarom    def __str__ (self):
437cd4cf991Simarom        s =  "Stream Name: {0}\n".format(self.name)
438cd4cf991Simarom        s += "Stream Next: {0}\n".format(self.next)
439cd4cf991Simarom        s += "Stream JSON:\n{0}\n".format(json.dumps(self.fields, indent = 4, separators=(',', ': '), sort_keys = True))
440cd4cf991Simarom        return s
442995267dbSimarom    def to_json (self):
4432be2f7e9SHanoh Haim        """
444a627a1d4SDavidBlock        Return json format
4452be2f7e9SHanoh Haim        """
446e9392617Simarom        return dict(self.fields)
448995267dbSimarom    def get_id (self):
4492be2f7e9SHanoh Haim        """ Get the stream id after resolution  """
450f5a5e50bSimarom        return self.id
4531d62dfcaSimarom    def has_custom_mac_addr (self):
4541d62dfcaSimarom        """ Return True if src or dst MAC were set as custom """
4551d62dfcaSimarom        return not self.is_default_mac
457f5a5e50bSimarom    def get_name (self):
4582be2f7e9SHanoh Haim        """ Get the stream name """
459f5a5e50bSimarom        return self.name
461f5a5e50bSimarom    def get_next (self):
4622be2f7e9SHanoh Haim        """ Get next stream object """
463f5a5e50bSimarom        return self.next
466d4791e05Simarom    def has_flow_stats (self):
467d4791e05Simarom        """ Return True if stream was configured with flow stats """
468d4791e05Simarom        return self.fields['flow_stats']['enabled']
470a5788f0eSimarom    def get_pkt (self):
4712be2f7e9SHanoh Haim        """ Get packet as string """
472a5788f0eSimarom        return self.pkt
47496e96afaSimarom    def get_pkt_len (self, count_crc = True):
4752be2f7e9SHanoh Haim       """ Get packet number of bytes  """
4760e70a929Simarom       pkt_len = len(self.get_pkt())
47796e96afaSimarom       if count_crc:
47896e96afaSimarom           pkt_len += 4
48096e96afaSimarom       return pkt_len
4830e70a929Simarom    def get_pkt_type (self):
484a627a1d4SDavidBlock        """ Get packet description. Example: IP:UDP """
4850e70a929Simarom        if self.packet_desc == None:
486d82201e2SHanoh Haim            self.packet_desc = STLPktBuilder.pkt_layers_desc_from_buffer(self.get_pkt())
4880e70a929Simarom        return self.packet_desc
49096e96afaSimarom    def get_mode (self):
49196e96afaSimarom        return self.mode_desc
4930e70a929Simarom    @staticmethod
4940e70a929Simarom    def get_rate_from_field (rate_json):
4952be2f7e9SHanoh Haim        """ Get rate from json  """
4960e70a929Simarom        t = rate_json['type']
4970e70a929Simarom        v = rate_json['value']
4990e70a929Simarom        if t == "pps":
5000e70a929Simarom            return format_num(v, suffix = "pps")
5010e70a929Simarom        elif t == "bps_L1":
5020e70a929Simarom            return format_num(v, suffix = "bps (L1)")
5030e70a929Simarom        elif t == "bps_L2":
5040e70a929Simarom            return format_num(v, suffix = "bps (L2)")
5050e70a929Simarom        elif t == "percentage":
5060e70a929Simarom            return format_num(v, suffix = "%")
5080e70a929Simarom    def get_rate (self):
5090e70a929Simarom        return self.get_rate_from_field(self.fields['mode']['rate'])
5110af59f17SHanoh Haim    def to_pkt_dump (self):
512a627a1d4SDavidBlock        """ Print packet description from Scapy  """
51342c621b9SHanoh Haim        if self.name:
514f72c6df9Simarom            print("Stream Name: ",self.name)
5150af59f17SHanoh Haim        scapy_b = self.scapy_pkt_builder;
516d82201e2SHanoh Haim        if scapy_b and isinstance(scapy_b,STLPktBuilder):
5170af59f17SHanoh Haim            scapy_b.to_pkt_dump()
5180af59f17SHanoh Haim        else:
519f72c6df9Simarom            print("Nothing to dump")
5200af59f17SHanoh Haim
5210af59f17SHanoh Haim
523f5a5e50bSimarom    def to_yaml (self):
524a627a1d4SDavidBlock        """ Convert to YAML  """
525e9392617Simarom        y = {}
527e9392617Simarom        if self.name:
528e9392617Simarom            y['name'] = self.name
530e9392617Simarom        if self.next:
531e9392617Simarom            y['next'] = self.next
533a420b4c4Simarom        y['stream'] = copy.deepcopy(self.fields)
535d1360da9Simarom        # some shortcuts for YAML
536d1360da9Simarom        rate_type  = self.fields['mode']['rate']['type']
537d1360da9Simarom        rate_value = self.fields['mode']['rate']['value']
539d1360da9Simarom        y['stream']['mode'][rate_type] = rate_value
540d1360da9Simarom        del y['stream']['mode']['rate']
542e9392617Simarom        return y
543a842b4cfSYaroslav Brustinov
544a842b4cfSYaroslav Brustinov    # returns the Python code (text) to build this stream, inside the code it will be in variable "stream"
545a842b4cfSYaroslav Brustinov    def to_code (self):
546a627a1d4SDavidBlock        """ Convert to Python code as profile  """
547a842b4cfSYaroslav Brustinov        packet = Ether(self.pkt)
5489de93958SYaroslav Brustinov        layer = packet
5491975220bSYaroslav Brustinov        imports_arr = []
5501975220bSYaroslav Brustinov        # remove checksums, add imports if needed
5511975220bSYaroslav Brustinov        while layer:
5521975220bSYaroslav Brustinov            layer_class = layer.__class__.__name__
5531975220bSYaroslav Brustinov            try: # check if class can be instantiated
5541975220bSYaroslav Brustinov                eval('%s()' % layer_class)
5551975220bSYaroslav Brustinov            except NameError: # no such layer
5561975220bSYaroslav Brustinov                found_import = False
5571975220bSYaroslav Brustinov                for module_path, module in sys.modules.items():
5581975220bSYaroslav Brustinov                    import_string = 'from %s import %s' % (module_path, layer_class)
5591975220bSYaroslav Brustinov                    if import_string in imports_arr:
5601975220bSYaroslav Brustinov                        found_import = True
5611975220bSYaroslav Brustinov                        break
5621975220bSYaroslav Brustinov                    if not module_path.startswith(('scapy.layers', 'scapy.contrib')):
5631975220bSYaroslav Brustinov                        continue
5641975220bSYaroslav Brustinov                    check_layer = getattr(module, layer_class, None)
5651975220bSYaroslav Brustinov                    if not check_layer:
5661975220bSYaroslav Brustinov                        continue
5671975220bSYaroslav Brustinov                    try:
5681975220bSYaroslav Brustinov                        check_layer()
5691975220bSYaroslav Brustinov                        imports_arr.append(import_string)
5701975220bSYaroslav Brustinov                        found_import = True
5711975220bSYaroslav Brustinov                        break
5721975220bSYaroslav Brustinov                    except: # can't by instantiated
5731975220bSYaroslav Brustinov                        continue
5741975220bSYaroslav Brustinov                if not found_import:
5751975220bSYaroslav Brustinov                    raise STLError('Could not determine import of layer %s' % layer.name)
5769de93958SYaroslav Brustinov            for chksum_name in ('cksum', 'chksum'):
5779de93958SYaroslav Brustinov                if chksum_name in layer.fields:
5789de93958SYaroslav Brustinov                    del layer.fields[chksum_name]
5799de93958SYaroslav Brustinov            layer = layer.payload
5809de93958SYaroslav Brustinov        packet.hide_defaults()          # remove fields with default values
581a842b4cfSYaroslav Brustinov        payload = packet.getlayer('Raw')
582a842b4cfSYaroslav Brustinov        packet_command = packet.command()
5831975220bSYaroslav Brustinov
584a842b4cfSYaroslav Brustinov        imports = '\n'.join(imports_arr)
585a842b4cfSYaroslav Brustinov        if payload:
586a842b4cfSYaroslav Brustinov            payload.remove_payload() # fcs etc.
587a842b4cfSYaroslav Brustinov            data = payload.fields.get('load', '')
5895e727474Simarom            good_printable = [c for c in string.printable if ord(c) not in range(32)]
5905e727474Simarom            good_printable.remove("'")
5925e727474Simarom            if type(data) is str:
59389a2be82Simarom                new_data = ''.join([c if c in good_printable else r'\x{0:02x}'.format(ord(c)) for c in data])
5945e727474Simarom            else:
59589a2be82Simarom                new_data = ''.join([chr(c) if chr(c) in good_printable else r'\x{0:02x}'.format(c) for c in data])
5972bcbca45Simarom            payload_start = packet_command.find("Raw(load=")
598a842b4cfSYaroslav Brustinov            if payload_start != -1:
599a842b4cfSYaroslav Brustinov                packet_command = packet_command[:payload_start-1]
600a842b4cfSYaroslav Brustinov        layers = packet_command.split('/')
602a842b4cfSYaroslav Brustinov        if payload:
603a842b4cfSYaroslav Brustinov            if len(new_data) and new_data == new_data[0] * len(new_data):
604a842b4cfSYaroslav Brustinov                layers.append("Raw(load='%s' * %s)" % (new_data[0], len(new_data)))
605a842b4cfSYaroslav Brustinov            else:
606a842b4cfSYaroslav Brustinov                layers.append("Raw(load='%s')" % new_data)
608a842b4cfSYaroslav Brustinov        packet_code = 'packet = (' + (' / \n          ').join(layers) + ')'
609a842b4cfSYaroslav Brustinov        vm_list = []
610a842b4cfSYaroslav Brustinov        for inst in self.fields['vm']['instructions']:
611a842b4cfSYaroslav Brustinov            if inst['type'] == 'flow_var':
612d82201e2SHanoh Haim                vm_list.append("STLVmFlowVar(name='{name}', size={size}, op='{op}', init_value={init_value}, min_value={min_value}, max_value={max_value}, step={step})".format(**inst))
613a842b4cfSYaroslav Brustinov            elif inst['type'] == 'write_flow_var':
614d82201e2SHanoh Haim                vm_list.append("STLVmWrFlowVar(fv_name='{name}', pkt_offset={pkt_offset}, add_val={add_value}, is_big={is_big_endian})".format(**inst))
615a842b4cfSYaroslav Brustinov            elif inst['type'] == 'write_mask_flow_var':
616a842b4cfSYaroslav Brustinov                inst = copy.copy(inst)
617a842b4cfSYaroslav Brustinov                inst['mask'] = hex(inst['mask'])
618d82201e2SHanoh Haim                vm_list.append("STLVmWrMaskFlowVar(fv_name='{name}', pkt_offset={pkt_offset}, pkt_cast_size={pkt_cast_size}, mask={mask}, shift={shift}, add_value={add_value}, is_big={is_big_endian})".format(**inst))
619a842b4cfSYaroslav Brustinov            elif inst['type'] == 'fix_checksum_ipv4':
620d82201e2SHanoh Haim                vm_list.append("STLVmFixIpv4(offset={pkt_offset})".format(**inst))
621a842b4cfSYaroslav Brustinov            elif inst['type'] == 'trim_pkt_size':
622d82201e2SHanoh Haim                vm_list.append("STLVmTrimPktSize(fv_name='{name}')".format(**inst))
623a842b4cfSYaroslav Brustinov            elif inst['type'] == 'tuple_flow_var':
624a842b4cfSYaroslav Brustinov                inst = copy.copy(inst)
625a842b4cfSYaroslav Brustinov                inst['ip_min'] = ltoa(inst['ip_min'])
626a842b4cfSYaroslav Brustinov                inst['ip_max'] = ltoa(inst['ip_max'])
627d82201e2SHanoh Haim                vm_list.append("STLVmTupleGen(name='{name}', ip_min='{ip_min}', ip_max='{ip_max}', port_min={port_min}, port_max={port_max}, limit_flows={limit_flows}, flags={flags})".format(**inst))
628b424cd3dSHanoh Haim            elif inst['type'] == 'flow_var_rand_limit':
629b424cd3dSHanoh Haim                vm_list.append("STLVmFlowVarRepetableRandom(name='{name}', size={size}, limit={limit}, seed={seed}, min_value={min_value}, max_value={max_value})".format(**inst))
630b424cd3dSHanoh Haim
631d82201e2SHanoh Haim        vm_code = 'vm = STLScVmRaw([' + ',\n                 '.join(vm_list) + '], split_by_field = %s)' % STLStream.__add_quotes(self.fields['vm'].get('split_by_var'))
632a842b4cfSYaroslav Brustinov        stream_params_list = []
633d82201e2SHanoh Haim        stream_params_list.append('packet = STLPktBuilder(pkt = packet, vm = vm)')
634a842b4cfSYaroslav Brustinov        if default_STLStream.name != self.name:
635a842b4cfSYaroslav Brustinov            stream_params_list.append('name = %s' % STLStream.__add_quotes(self.name))
636a842b4cfSYaroslav Brustinov        if default_STLStream.fields['enabled'] != self.fields['enabled']:
637a842b4cfSYaroslav Brustinov            stream_params_list.append('enabled = %s' % self.fields['enabled'])
638a842b4cfSYaroslav Brustinov        if default_STLStream.fields['self_start'] != self.fields['self_start']:
639a842b4cfSYaroslav Brustinov            stream_params_list.append('self_start = %s' % self.fields['self_start'])
640a842b4cfSYaroslav Brustinov        if default_STLStream.fields['isg'] != self.fields['isg']:
641a842b4cfSYaroslav Brustinov            stream_params_list.append('isg = %s' % self.fields['isg'])
642ca3f7e0fSIdo Barnea        if default_STLStream.fields['flow_stats'] != self.fields['flow_stats']:
643ea61eabfSIdo Barnea            stream_params_list.append('flow_stats = STLFlowStats(%s)' % self.fields['flow_stats']['stream_id'])
644a842b4cfSYaroslav Brustinov        if default_STLStream.next != self.next:
645a842b4cfSYaroslav Brustinov            stream_params_list.append('next = %s' % STLStream.__add_quotes(self.next))
646a842b4cfSYaroslav Brustinov        if default_STLStream.id != self.id:
647a842b4cfSYaroslav Brustinov            stream_params_list.append('stream_id = %s' % self.id)
648a842b4cfSYaroslav Brustinov        if default_STLStream.fields['action_count'] != self.fields['action_count']:
649a842b4cfSYaroslav Brustinov            stream_params_list.append('action_count = %s' % self.fields['action_count'])
650a842b4cfSYaroslav Brustinov        if 'random_seed' in self.fields:
651a842b4cfSYaroslav Brustinov            stream_params_list.append('random_seed = %s' % self.fields.get('random_seed', 0))
652a842b4cfSYaroslav Brustinov        if default_STLStream.mac_src_override_by_pkt != self.mac_src_override_by_pkt:
653a842b4cfSYaroslav Brustinov            stream_params_list.append('mac_src_override_by_pkt = %s' % self.mac_src_override_by_pkt)
654a842b4cfSYaroslav Brustinov        if default_STLStream.mac_dst_override_mode != self.mac_dst_override_mode:
655a842b4cfSYaroslav Brustinov            stream_params_list.append('mac_dst_override_mode = %s' % self.mac_dst_override_mode)
656a842b4cfSYaroslav Brustinov
657a842b4cfSYaroslav Brustinov        mode_args = ''
658a842b4cfSYaroslav Brustinov        for key, value in self.fields['mode'].items():
659a842b4cfSYaroslav Brustinov            if key not in ('rate', 'type'):
660a842b4cfSYaroslav Brustinov                mode_args += '%s = %s, ' % (key, value)
661a842b4cfSYaroslav Brustinov        mode_args += '%s = %s' % (self.fields['mode']['rate']['type'], self.fields['mode']['rate']['value'])
662a842b4cfSYaroslav Brustinov        if self.mode_desc == STLTXCont.__str__():
663a842b4cfSYaroslav Brustinov            stream_params_list.append('mode = STLTXCont(%s)' % mode_args)
664a842b4cfSYaroslav Brustinov        elif self.mode_desc == STLTXSingleBurst().__str__():
665a842b4cfSYaroslav Brustinov            stream_params_list.append('mode = STLTXSingleBurst(%s)' % mode_args)
666a842b4cfSYaroslav Brustinov        elif self.mode_desc == STLTXMultiBurst().__str__():
667a842b4cfSYaroslav Brustinov            stream_params_list.append('mode = STLTXMultiBurst(%s)' % mode_args)
668a842b4cfSYaroslav Brustinov        else:
669a842b4cfSYaroslav Brustinov            raise STLError('Could not determine mode: %s' % self.mode_desc)
670a842b4cfSYaroslav Brustinov
671a842b4cfSYaroslav Brustinov        stream = "stream = STLStream(" + ',\n                   '.join(stream_params_list) + ')'
672a842b4cfSYaroslav Brustinov        return '\n'.join([imports, packet_code, vm_code, stream])
673a842b4cfSYaroslav Brustinov
674a842b4cfSYaroslav Brustinov    # add quoted for string, or leave as is if other type
675a842b4cfSYaroslav Brustinov    @staticmethod
676a842b4cfSYaroslav Brustinov    def __add_quotes(arg):
677a842b4cfSYaroslav Brustinov        if type(arg) is str:
678a842b4cfSYaroslav Brustinov            return "'%s'" % arg
679a842b4cfSYaroslav Brustinov        return arg
680a842b4cfSYaroslav Brustinov
681a842b4cfSYaroslav Brustinov    # used to replace non-printable characters with hex
682a842b4cfSYaroslav Brustinov    @staticmethod
683a842b4cfSYaroslav Brustinov    def __replchars_to_hex(match):
684a842b4cfSYaroslav Brustinov        return r'\x{0:02x}'.format(ord(match.group()))
685a842b4cfSYaroslav Brustinov
6860f03cc46SYaroslav Brustinov    def dump_to_yaml (self, yaml_file = None):
687a627a1d4SDavidBlock        """ Print as yaml  """
6880f03cc46SYaroslav Brustinov        yaml_dump = yaml.dump([self.to_yaml()], default_flow_style = False)
6900f03cc46SYaroslav Brustinov        # write to file if provided
6910f03cc46SYaroslav Brustinov        if yaml_file:
6920f03cc46SYaroslav Brustinov            with open(yaml_file, 'w') as f:
6930f03cc46SYaroslav Brustinov                f.write(yaml_dump)
6940f03cc46SYaroslav Brustinov
6950f03cc46SYaroslav Brustinov        return yaml_dump
697e8ba50a8Simaromclass YAMLLoader(object):
699e8ba50a8Simarom    def __init__ (self, yaml_file):
700e8ba50a8Simarom        self.yaml_path = os.path.dirname(yaml_file)
701e8ba50a8Simarom        self.yaml_file = yaml_file
704e8ba50a8Simarom    def __parse_packet (self, packet_dict):
706e8ba50a8Simarom        packet_type = set(packet_dict).intersection(['binary', 'pcap'])
707e8ba50a8Simarom        if len(packet_type) != 1:
708a627a1d4SDavidBlock            raise STLError("Packet section must contain either 'binary' or 'pcap'")
710e8ba50a8Simarom        if 'binary' in packet_type:
711e8ba50a8Simarom            try:
712e8ba50a8Simarom                pkt_str = base64.b64decode(packet_dict['binary'])
713e8ba50a8Simarom            except TypeError:
714e8ba50a8Simarom                raise STLError("'binary' field is not a valid packet format")
716d82201e2SHanoh Haim            builder = STLPktBuilder(pkt_buffer = pkt_str)
718e8ba50a8Simarom        elif 'pcap' in packet_type:
719e8ba50a8Simarom            pcap = os.path.join(self.yaml_path, packet_dict['pcap'])
721e8ba50a8Simarom            if not os.path.exists(pcap):
722e8ba50a8Simarom                raise STLError("'pcap' - cannot find '{0}'".format(pcap))
724d82201e2SHanoh Haim            builder = STLPktBuilder(pkt = pcap)
726e8ba50a8Simarom        return builder
729e8ba50a8Simarom    def __parse_mode (self, mode_obj):
7303eb4f868Simarom        if not mode_obj:
7313eb4f868Simarom            return None
733d1360da9Simarom        rate_parser = set(mode_obj).intersection(['pps', 'bps_L1', 'bps_L2', 'percentage'])
734d1360da9Simarom        if len(rate_parser) != 1:
735d1360da9Simarom            raise STLError("'rate' must contain exactly one from 'pps', 'bps_L1', 'bps_L2', 'percentage'")
737d1360da9Simarom        rate_type  = rate_parser.pop()
738d1360da9Simarom        rate = {rate_type : mode_obj[rate_type]}
740e8ba50a8Simarom        mode_type = mode_obj.get('type')
742e8ba50a8Simarom        if mode_type == 'continuous':
743d1360da9Simarom            mode = STLTXCont(**rate)
745e8ba50a8Simarom        elif mode_type == 'single_burst':
746e8ba50a8Simarom            defaults = STLTXSingleBurst()
747d1360da9Simarom            mode = STLTXSingleBurst(total_pkts  = mode_obj.get('total_pkts', defaults.fields['total_pkts']),
748d1360da9Simarom                                    **rate)
750e8ba50a8Simarom        elif mode_type == 'multi_burst':
751e8ba50a8Simarom            defaults = STLTXMultiBurst()
752bc7f0b85Simarom            mode = STLTXMultiBurst(pkts_per_burst = mode_obj.get('pkts_per_burst', defaults.fields['pkts_per_burst']),
753e8ba50a8Simarom                                   ibg            = mode_obj.get('ibg', defaults.fields['ibg']),
754d1360da9Simarom                                   count          = mode_obj.get('count', defaults.fields['count']),
755d1360da9Simarom                                   **rate)
757e8ba50a8Simarom        else:
758e8ba50a8Simarom            raise STLError("mode type can be 'continuous', 'single_burst' or 'multi_burst")
761e8ba50a8Simarom        return mode
765ca3f7e0fSIdo Barnea    def __parse_flow_stats (self, flow_stats_obj):
7673eb4f868Simarom        # no such object
768ca3f7e0fSIdo Barnea        if not flow_stats_obj or flow_stats_obj.get('enabled') == False:
7693eb4f868Simarom            return None
771ca3f7e0fSIdo Barnea        pg_id = flow_stats_obj.get('stream_id')
772ca3f7e0fSIdo Barnea        if pg_id == None:
773a627a1d4SDavidBlock            raise STLError("Enabled RX stats section must contain 'stream_id' field")
775ea61eabfSIdo Barnea        return STLFlowStats(pg_id = pg_id)
778e8ba50a8Simarom    def __parse_stream (self, yaml_object):
779e8ba50a8Simarom        s_obj = yaml_object['stream']
781e8ba50a8Simarom        # parse packet
782e8ba50a8Simarom        packet = s_obj.get('packet')
783e8ba50a8Simarom        if not packet:
784e8ba50a8Simarom            raise STLError("YAML file must contain 'packet' field")
786e8ba50a8Simarom        builder = self.__parse_packet(packet)
789e8ba50a8Simarom        # mode
7903eb4f868Simarom        mode = self.__parse_mode(s_obj.get('mode'))
7923eb4f868Simarom        # rx stats
793ca3f7e0fSIdo Barnea        flow_stats = self.__parse_flow_stats(s_obj.get('flow_stats'))
796a842b4cfSYaroslav Brustinov        defaults = default_STLStream
797e8ba50a8Simarom        # create the stream
798e8ba50a8Simarom        stream = STLStream(name       = yaml_object.get('name'),
799e8ba50a8Simarom                           packet     = builder,
800e8ba50a8Simarom                           mode       = mode,
801ca3f7e0fSIdo Barnea                           flow_stats   = flow_stats,
802e8ba50a8Simarom                           enabled    = s_obj.get('enabled', defaults.fields['enabled']),
803e8ba50a8Simarom                           self_start = s_obj.get('self_start', defaults.fields['self_start']),
804e8ba50a8Simarom                           isg        = s_obj.get('isg', defaults.fields['isg']),
8051e98c58dSHanoh Haim                           next       = yaml_object.get('next'),
8061e98c58dSHanoh Haim                           action_count = s_obj.get('action_count', defaults.fields['action_count']),
8071e98c58dSHanoh Haim                           mac_src_override_by_pkt = s_obj.get('mac_src_override_by_pkt', 0),
8081e98c58dSHanoh Haim                           mac_dst_override_mode = s_obj.get('mac_src_override_by_pkt', 0)
8091e98c58dSHanoh Haim                           )
811e8ba50a8Simarom        # hack the VM fields for now
812e8ba50a8Simarom        if 'vm' in s_obj:
813a94f1cddSimarom            stream.fields['vm'].update(s_obj['vm'])
815e8ba50a8Simarom        return stream
818e8ba50a8Simarom    def parse (self):
819f5a5e50bSimarom        with open(self.yaml_file, 'r') as f:
820e8ba50a8Simarom            # read YAML and pass it down to stream object
821f5a5e50bSimarom            yaml_str = f.read()
823a94f1cddSimarom            try:
82479005906SYaroslav Brustinov                objects = yaml.safe_load(yaml_str)
825a94f1cddSimarom            except yaml.parser.ParserError as e:
826a94f1cddSimarom                raise STLError(str(e))
828e8ba50a8Simarom            streams = [self.__parse_stream(object) for object in objects]
830e8ba50a8Simarom            return streams
833e8ba50a8Simarom# profile class
834e8ba50a8Simaromclass STLProfile(object):
8352be2f7e9SHanoh Haim    """ Describe a list of streams
8362be2f7e9SHanoh Haim
8375817a450SHanoh Haim        .. code-block:: python
8382be2f7e9SHanoh Haim
839138686f3SYaroslav Brustinov            # STLProfile Example
840138686f3SYaroslav Brustinov
841138686f3SYaroslav Brustinov            profile =  STLProfile( [ STLStream( isg = 10.0, # star in delay
8422be2f7e9SHanoh Haim                                        name    ='S0',
8432be2f7e9SHanoh Haim                                        packet = STLPktBuilder(pkt = base_pkt/pad),
8442be2f7e9SHanoh Haim                                        mode = STLTXSingleBurst( pps = 10, total_pkts = self.burst_size),
8452be2f7e9SHanoh Haim                                        next = 'S1'), # point to next stream
8462be2f7e9SHanoh Haim
8472be2f7e9SHanoh Haim                             STLStream( self_start = False, # stream is  disabled enable trow S0
8482be2f7e9SHanoh Haim                                        name    ='S1',
8492be2f7e9SHanoh Haim                                        packet  = STLPktBuilder(pkt = base_pkt1/pad),
8502be2f7e9SHanoh Haim                                        mode    = STLTXSingleBurst( pps = 10, total_pkts = self.burst_size),
8512be2f7e9SHanoh Haim                                        next    = 'S2' ),
8522be2f7e9SHanoh Haim
8532be2f7e9SHanoh Haim                             STLStream(  self_start = False, # stream is  disabled enable trow S0
8542be2f7e9SHanoh Haim                                         name   ='S2',
8552be2f7e9SHanoh Haim                                         packet = STLPktBuilder(pkt = base_pkt2/pad),
8562be2f7e9SHanoh Haim                                         mode = STLTXSingleBurst( pps = 10, total_pkts = self.burst_size )
8572be2f7e9SHanoh Haim                                        )
8582be2f7e9SHanoh Haim                            ]).get_streams()
8592be2f7e9SHanoh Haim
8602be2f7e9SHanoh Haim
8612be2f7e9SHanoh Haim
8622be2f7e9SHanoh Haim    """
8632be2f7e9SHanoh Haim
864e8ba50a8Simarom    def __init__ (self, streams = None):
8652be2f7e9SHanoh Haim        """
8662be2f7e9SHanoh Haim
8672be2f7e9SHanoh Haim            :parameters:
8682be2f7e9SHanoh Haim
869d82201e2SHanoh Haim                  streams  : list of :class:`trex_stl_lib.trex_stl_streams.STLStream`
8702be2f7e9SHanoh Haim                       a list of stream objects
8712be2f7e9SHanoh Haim
8722be2f7e9SHanoh Haim        """
8732be2f7e9SHanoh Haim
8742be2f7e9SHanoh Haim
875e8ba50a8Simarom        if streams == None:
876e8ba50a8Simarom            streams = []
878e8ba50a8Simarom        if not type(streams) == list:
879e8ba50a8Simarom            streams = [streams]
881e8ba50a8Simarom        if not all([isinstance(stream, STLStream) for stream in streams]):
8820f03cc46SYaroslav Brustinov            raise STLArgumentError('streams', streams, valid_values = STLStream)
884e8ba50a8Simarom        self.streams = streams
885aa334e0eSimarom        self.meta = None
888e8ba50a8Simarom    def get_streams (self):
889a627a1d4SDavidBlock        """ Get the list of streams"""
890e8ba50a8Simarom        return self.streams
892e8ba50a8Simarom    def __str__ (self):
893e8ba50a8Simarom        return '\n'.join([str(stream) for stream in self.streams])
89532b6b284Simarom    def is_pauseable (self):
8964eacb570Simarom        return all([x.get_mode() == "Continuous" for x in self.get_streams()])
8981d62dfcaSimarom    def has_custom_mac_addr (self):
8991d62dfcaSimarom        return any([x.has_custom_mac_addr() for x in self.get_streams()])
901300ca9bcSimarom    def has_flow_stats (self):
90232b6b284Simarom        return any([x.has_flow_stats() for x in self.get_streams()])
904e8ba50a8Simarom    @staticmethod
905e8ba50a8Simarom    def load_yaml (yaml_file):
906a627a1d4SDavidBlock        """ Load (from YAML file) a profile with a number of streams"""
9072be2f7e9SHanoh Haim
908e8ba50a8Simarom        # check filename
909e8ba50a8Simarom        if not os.path.isfile(yaml_file):
910e8ba50a8Simarom            raise STLError("file '{0}' does not exists".format(yaml_file))
912e8ba50a8Simarom        yaml_loader = YAMLLoader(yaml_file)
913e8ba50a8Simarom        streams = yaml_loader.parse()
915aa334e0eSimarom        profile = STLProfile(streams)
916aa334e0eSimarom        profile.meta = {'type': 'yaml'}
918aa334e0eSimarom        return profile
920aa334e0eSimarom    @staticmethod
921aa334e0eSimarom    def get_module_tunables(module):
922aa334e0eSimarom        # remove self and variables
923aa334e0eSimarom        func = module.register().get_streams
924aa334e0eSimarom        argc = func.__code__.co_argcount
925aa334e0eSimarom        tunables = func.__code__.co_varnames[1:argc]
927aa334e0eSimarom        # fetch defaults
928f72c6df9Simarom        defaults = func.__defaults__
929df1e9dbaSYaroslav Brustinov        if defaults is None:
930df1e9dbaSYaroslav Brustinov            return {}
931aa334e0eSimarom        if len(defaults) != (argc - 1):
932aa334e0eSimarom            raise STLError("Module should provide default values for all arguments on get_streams()")
934aa334e0eSimarom        output = {}
935aa334e0eSimarom        for t, d in zip(tunables, defaults):
936aa334e0eSimarom            output[t] = d
938aa334e0eSimarom        return output
941e8ba50a8Simarom    @staticmethod
94211425470Simarom    def load_py (python_file, direction = 0, port_id = 0, **kwargs):
943a627a1d4SDavidBlock        """ Load from Python profile """
9442be2f7e9SHanoh Haim
945e8ba50a8Simarom        # check filename
946e8ba50a8Simarom        if not os.path.isfile(python_file):
947a627a1d4SDavidBlock            raise STLError("File '{0}' does not exist".format(python_file))
949e8ba50a8Simarom        basedir = os.path.dirname(python_file)
950457c257eSYaroslav Brustinov        sys.path.insert(0, basedir)
952e8ba50a8Simarom        try:
953e8ba50a8Simarom            file    = os.path.basename(python_file).split('.')[0]
954f72c6df9Simarom            module = __import__(file, globals(), locals(), [], 0)
955f72c6df9Simarom            imp.reload(module) # reload the update
957aa334e0eSimarom            t = STLProfile.get_module_tunables(module)
958384a2f8eSimarom            #for arg in kwargs:
959384a2f8eSimarom            #    if not arg in t:
960384a2f8eSimarom            #        raise STLError("Profile {0} does not support tunable '{1}' - supported tunables are: '{2}'".format(python_file, arg, t))
96211425470Simarom            streams = module.register().get_streams(direction = direction,
96311425470Simarom                                                    port_id = port_id,
96411425470Simarom                                                    **kwargs)
965aa334e0eSimarom            profile = STLProfile(streams)
967aa334e0eSimarom            profile.meta = {'type': 'python',
968aa334e0eSimarom                            'tunables': t}
970aa334e0eSimarom            return profile
972e8ba50a8Simarom        except Exception as e:
973e8ba50a8Simarom            a, b, tb = sys.exc_info()
9747d7cb50dSimarom            x =''.join(traceback.format_list(traceback.extract_tb(tb)[1:])) + a.__name__ + ": " + str(b) + "\n"
976e8ba50a8Simarom            summary = "\nPython Traceback follows:\n\n" + x
977e8ba50a8Simarom            raise STLError(summary)
980e8ba50a8Simarom        finally:
981e8ba50a8Simarom            sys.path.remove(basedir)
984a5788f0eSimarom    # loop_count = 0 means loop forever
985cd4cf991Simarom    @staticmethod
986dbe6b188Simarom    def load_pcap (pcap_file,
987dbe6b188Simarom                   ipg_usec = None,
988dbe6b188Simarom                   speedup = 1.0,
989dbe6b188Simarom                   loop_count = 1,
990dbe6b188Simarom                   vm = None,
991dbe6b188Simarom                   packet_hook = None,
992e4c8e44bSYaroslav Brustinov                   split_mode = None,
993e4c8e44bSYaroslav Brustinov                   min_ipg_usec = None):
994a627a1d4SDavidBlock        """ Convert a pcap file with a number of packets to a list of connected streams.
9952be2f7e9SHanoh Haim
9962be2f7e9SHanoh Haim        packet1->packet2->packet3 etc
9972be2f7e9SHanoh Haim
9982be2f7e9SHanoh Haim                :parameters:
9992be2f7e9SHanoh Haim
10002be2f7e9SHanoh Haim                  pcap_file  : string
1001a627a1d4SDavidBlock                       Name of the pcap file
10022be2f7e9SHanoh Haim
10032be2f7e9SHanoh Haim                  ipg_usec   : float
100475ce59e5Simarom                       Inter packet gap in usec. If IPG is None, IPG is taken from pcap file
10052be2f7e9SHanoh Haim
10062be2f7e9SHanoh Haim                  speedup   : float
1007a627a1d4SDavidBlock                       When reading the pcap file, divide IPG by this "speedup" factor. Resulting IPG is sped up by this factor.
10082be2f7e9SHanoh Haim
10092be2f7e9SHanoh Haim                  loop_count : uint16_t
1010a627a1d4SDavidBlock                       Number of loops to repeat the pcap file
10112be2f7e9SHanoh Haim
10122be2f7e9SHanoh Haim                  vm        :  list
1013a627a1d4SDavidBlock                        List of Field engine instructions
10142be2f7e9SHanoh Haim
101563bf6abaSimarom                  packet_hook : Callable or function
101663bf6abaSimarom                        will be applied to every packet
101894a7c25cSYaroslav Brustinov                  split_mode : str
1019dbe6b188Simarom                        should this PCAP be split to two profiles based on IPs / MACs
1020dbe6b188Simarom                        used for dual mode
1021dbe6b188Simarom                        can be 'MAC' or 'IP'
1023e4c8e44bSYaroslav Brustinov                  min_ipg_usec   : float
1024e4c8e44bSYaroslav Brustinov                       Minumum inter packet gap in usec. Used to guard from too small IPGs.
1025e4c8e44bSYaroslav Brustinov
10262be2f7e9SHanoh Haim                 :return: STLProfile
10272be2f7e9SHanoh Haim
10282be2f7e9SHanoh Haim        """
1030cd4cf991Simarom        # check filename
1031cd4cf991Simarom        if not os.path.isfile(pcap_file):
1032cd4cf991Simarom            raise STLError("file '{0}' does not exists".format(pcap_file))
10335cfeb192SYaroslav Brustinov        if speedup <= 0:
10345cfeb192SYaroslav Brustinov            raise STLError('Speedup should not be negative.')
10355cfeb192SYaroslav Brustinov        if min_ipg_usec and min_ipg_usec < 0:
10365cfeb192SYaroslav Brustinov            raise STLError('min_ipg_usec should not be negative.')
10375cfeb192SYaroslav Brustinov
1039e4c8e44bSYaroslav Brustinov        # make sure IPG is not less than 0.001 usec
1040e4c8e44bSYaroslav Brustinov        if (ipg_usec is not None and (ipg_usec < 0.001 * speedup) and
1041e4c8e44bSYaroslav Brustinov                              (min_ipg_usec is None or min_ipg_usec < 0.001)):
1042db9145d2Simarom            raise STLError("ipg_usec cannot be less than 0.001 usec: '{0}'".format(ipg_usec))
10447d0f9e5eSimarom        if loop_count < 0:
10457d0f9e5eSimarom            raise STLError("'loop_count' cannot be negative")
10488691f401Simarom        try:
1050dbe6b188Simarom            if split_mode is None:
1051dbe6b188Simarom                pkts = PCAPReader(pcap_file).read_all()
1052bcea0262Simarom                if len(pkts) == 0:
1053bcea0262Simarom                    raise STLError("'{0}' does not contain any packets".format(pcap_file))
1055dbe6b188Simarom                return STLProfile.__pkts_to_streams(pkts,
1056dbe6b188Simarom                                                    ipg_usec,
1057e4c8e44bSYaroslav Brustinov                                                    min_ipg_usec,
1058dbe6b188Simarom                                                    speedup,
1059dbe6b188Simarom                                                    loop_count,
1060dbe6b188Simarom                                                    vm,
1061dbe6b188Simarom                                                    packet_hook)
1062dbe6b188Simarom            else:
1063dbe6b188Simarom                pkts_a, pkts_b = PCAPReader(pcap_file).read_all(split_mode = split_mode)
1064bcea0262Simarom                if (len(pkts_a) + len(pkts_b)) == 0:
1065bcea0262Simarom                    raise STLError("'{0}' does not contain any packets".format(pcap_file))
1067b65b65e7SYaroslav Brustinov                # swap the packets if a is empty, or the ts of first packet in b is earlier
1068b65b65e7SYaroslav Brustinov                if not pkts_a:
1069b65b65e7SYaroslav Brustinov                    pkts_a, pkts_b = pkts_b, pkts_a
1070b65b65e7SYaroslav Brustinov                elif (ipg_usec is None) and pkts_b:
1071b65b65e7SYaroslav Brustinov                    meta = pkts_a[0][1]
1072b65b65e7SYaroslav Brustinov                    start_time_a = meta[0] * 1e6 + meta[1]
1073b65b65e7SYaroslav Brustinov                    meta = pkts_b[0][1]
1074b65b65e7SYaroslav Brustinov                    start_time_b = meta[0] * 1e6 + meta[1]
1075b65b65e7SYaroslav Brustinov                    if start_time_b < start_time_a:
1076b65b65e7SYaroslav Brustinov                        pkts_a, pkts_b = pkts_b, pkts_a
1077b65b65e7SYaroslav Brustinov
1078dbe6b188Simarom                profile_a = STLProfile.__pkts_to_streams(pkts_a,
1079dbe6b188Simarom                                                         ipg_usec,
1080e4c8e44bSYaroslav Brustinov                                                         min_ipg_usec,
1081dbe6b188Simarom                                                         speedup,
1082dbe6b188Simarom                                                         loop_count,
1083dbe6b188Simarom                                                         vm,
1084dbe6b188Simarom                                                         packet_hook,
1085dbe6b188Simarom                                                         start_delay_usec = 10000)
1087dbe6b188Simarom                profile_b = STLProfile.__pkts_to_streams(pkts_b,
1088dbe6b188Simarom                                                         ipg_usec,
1089e4c8e44bSYaroslav Brustinov                                                         min_ipg_usec,
1090dbe6b188Simarom                                                         speedup,
1091dbe6b188Simarom                                                         loop_count,
1092dbe6b188Simarom                                                         vm,
1093dbe6b188Simarom                                                         packet_hook,
1094dbe6b188Simarom                                                         start_delay_usec = 10000)
1096dbe6b188Simarom                return profile_a, profile_b
10998691f401Simarom        except Scapy_Exception as e:
1100dbe6b188Simarom            raise STLError("failed to open PCAP file {0}: '{1}'".format(pcap_file, str(e)))
1103dbe6b188Simarom    @staticmethod
1104e4c8e44bSYaroslav Brustinov    def __pkts_to_streams (pkts, ipg_usec, min_ipg_usec, speedup, loop_count, vm, packet_hook, start_delay_usec = 0):
1106dbe6b188Simarom        streams = []
110763bf6abaSimarom        if packet_hook:
110863bf6abaSimarom            pkts = [(packet_hook(cap), meta) for (cap, meta) in pkts]
1110cd4cf991Simarom        for i, (cap, meta) in enumerate(pkts, start = 1):
1111268c7f12Simarom            # IPG - if not provided, take from cap
11125cfeb192SYaroslav Brustinov            if ipg_usec is None:
1113deb1ce15SYaroslav Brustinov                packet_time = meta[0] * 1e6 + meta[1]
1114deb1ce15SYaroslav Brustinov                if i == 1:
11155cfeb192SYaroslav Brustinov                    prev_time = packet_time
11165cfeb192SYaroslav Brustinov                isg = (packet_time - prev_time) / float(speedup)
11175cfeb192SYaroslav Brustinov                if min_ipg_usec and isg < min_ipg_usec:
11185cfeb192SYaroslav Brustinov                    isg = min_ipg_usec
1119e4c8e44bSYaroslav Brustinov                prev_time = packet_time
11205cfeb192SYaroslav Brustinov            else: # user specified ipg
11215cfeb192SYaroslav Brustinov                if min_ipg_usec:
11225cfeb192SYaroslav Brustinov                    isg = min_ipg_usec
11235cfeb192SYaroslav Brustinov                else:
11245cfeb192SYaroslav Brustinov                    isg = ipg_usec / float(speedup)
1126268c7f12Simarom            # handle last packet
1127268c7f12Simarom            if i == len(pkts):
1128a5788f0eSimarom                next = 1
1129a5788f0eSimarom                action_count = loop_count
1130268c7f12Simarom            else:
1131268c7f12Simarom                next = i + 1
1132a5788f0eSimarom                action_count = 0
1134cd4cf991Simarom            streams.append(STLStream(name = i,
1135d82201e2SHanoh Haim                                     packet = STLPktBuilder(pkt_buffer = cap, vm = vm),
11360e70a929Simarom                                     mode = STLTXSingleBurst(total_pkts = 1, percentage = 100),
11375cfeb192SYaroslav Brustinov                                     self_start = True if (i == 1) else False,
11385cfeb192SYaroslav Brustinov                                     isg = isg,  # usec
1139a5788f0eSimarom                                     action_count = action_count,
1140268c7f12Simarom                                     next = next))
1142e4c8e44bSYaroslav Brustinov
1143aa334e0eSimarom        profile = STLProfile(streams)
1144aa334e0eSimarom        profile.meta = {'type': 'pcap'}
1146aa334e0eSimarom        return profile
1150e8ba50a8Simarom    @staticmethod
115111425470Simarom    def load (filename, direction = 0, port_id = 0, **kwargs):
1152a627a1d4SDavidBlock        """ Load a profile by its type. Supported types are:
11532be2f7e9SHanoh Haim           * py
11542be2f7e9SHanoh Haim           * yaml
11552be2f7e9SHanoh Haim           * pcap file that converted to profile automaticly
11562be2f7e9SHanoh Haim
1157a627a1d4SDavidBlock           :Parameters:
1158f803e5b2Simarom              filename  : string as filename
1159f803e5b2Simarom              direction : profile's direction (if supported by the profile)
116011425470Simarom              port_id   : which port ID this profile is being loaded to
1161f803e5b2Simarom              kwargs    : forward those key-value pairs to the profile
11622be2f7e9SHanoh Haim
11632be2f7e9SHanoh Haim        """
11642be2f7e9SHanoh Haim
1165e8ba50a8Simarom        x = os.path.basename(filename).split('.')
1166e8ba50a8Simarom        suffix = x[1] if (len(x) == 2) else None
1168e8ba50a8Simarom        if suffix == 'py':
116911425470Simarom            profile = STLProfile.load_py(filename, direction, port_id, **kwargs)
1171e8ba50a8Simarom        elif suffix == 'yaml':
1172e8ba50a8Simarom            profile = STLProfile.load_yaml(filename)
1174cd4cf991Simarom        elif suffix in ['cap', 'pcap']:
1175268c7f12Simarom            profile = STLProfile.load_pcap(filename, speedup = 1, ipg_usec = 1e6)
1177e8ba50a8Simarom        else:
1178e8ba50a8Simarom            raise STLError("unknown profile file type: '{0}'".format(suffix))
1180aa334e0eSimarom        profile.meta['stream_count'] = len(profile.get_streams()) if isinstance(profile.get_streams(), list) else 1
1181e8ba50a8Simarom        return profile
1183aa334e0eSimarom    @staticmethod
1184aa334e0eSimarom    def get_info (filename):
1185aa334e0eSimarom        profile = STLProfile.load(filename)
1186aa334e0eSimarom        return profile.meta
11880af59f17SHanoh Haim    def dump_as_pkt (self):
1189a627a1d4SDavidBlock        """ Dump the profile as Scapy packet. If the packet is raw, convert it to Scapy before dumping it."""
11900af59f17SHanoh Haim        cnt=0;
11910af59f17SHanoh Haim        for stream in self.streams:
1192f72c6df9Simarom            print("=======================")
1193f72c6df9Simarom            print("Stream %d" % cnt)
1194f72c6df9Simarom            print("=======================")
11950af59f17SHanoh Haim            cnt = cnt +1
11960af59f17SHanoh Haim            stream.to_pkt_dump()
1198c07bd86fSimarom    def dump_to_yaml (self, yaml_file = None):
1199a627a1d4SDavidBlock        """ Convert the profile to yaml """
1200e8ba50a8Simarom        yaml_list = [stream.to_yaml() for stream in self.streams]
1201e8ba50a8Simarom        yaml_str = yaml.dump(yaml_list, default_flow_style = False)
1203e8ba50a8Simarom        # write to file if provided
1204e8ba50a8Simarom        if yaml_file:
1205e8ba50a8Simarom            with open(yaml_file, 'w') as f:
1206e8ba50a8Simarom                f.write(yaml_str)
1208e8ba50a8Simarom        return yaml_str
1210a842b4cfSYaroslav Brustinov    def dump_to_code (self, profile_file = None):
1211a627a1d4SDavidBlock        """ Convert the profile to Python native profile. """
1212a842b4cfSYaroslav Brustinov        profile_dump = '''# !!! Auto-generated code !!!
1213a842b4cfSYaroslav Brustinovfrom trex_stl_lib.api import *
1214a842b4cfSYaroslav Brustinov
1215a842b4cfSYaroslav Brustinovclass STLS1(object):
1216f803e5b2Simarom    def get_streams(self, direction = 0, **kwargs):
1217a842b4cfSYaroslav Brustinov        streams = []
1218a842b4cfSYaroslav Brustinov'''
1219a842b4cfSYaroslav Brustinov        for stream in self.streams:
1220a842b4cfSYaroslav Brustinov            profile_dump += ' '*8 + stream.to_code().replace('\n', '\n' + ' '*8) + '\n'
1221a842b4cfSYaroslav Brustinov            profile_dump += ' '*8 + 'streams.append(stream)\n'
1222a842b4cfSYaroslav Brustinov        profile_dump += '''
1223a842b4cfSYaroslav Brustinov        return streams
1224a842b4cfSYaroslav Brustinov
1225a842b4cfSYaroslav Brustinovdef register():
1226a842b4cfSYaroslav Brustinov    return STLS1()
1227a842b4cfSYaroslav Brustinov'''
1228a842b4cfSYaroslav Brustinov        # write to file if provided
1229a842b4cfSYaroslav Brustinov        if profile_file:
1230a842b4cfSYaroslav Brustinov            with open(profile_file, 'w') as f:
1231a842b4cfSYaroslav Brustinov                f.write(profile_dump)
1232a842b4cfSYaroslav Brustinov
1233a842b4cfSYaroslav Brustinov        return profile_dump
1234a842b4cfSYaroslav Brustinov
1235a842b4cfSYaroslav Brustinov
1237a5788f0eSimarom    def __len__ (self):
1238a5788f0eSimarom        return len(self.streams)
1241dbe6b188Simaromclass PCAPReader(object):
1242dbe6b188Simarom    def __init__ (self, pcap_file):
1243dbe6b188Simarom        self.pcap_file = pcap_file
1245dbe6b188Simarom    def read_all (self, split_mode = None):
1246dbe6b188Simarom        if split_mode is None:
1247dbe6b188Simarom            return RawPcapReader(self.pcap_file).read_all()
1249dbe6b188Simarom        # we need to split
1250dbe6b188Simarom        self.pcap = rdpcap(self.pcap_file)
1251dbe6b188Simarom        self.graph = Graph()
1253dbe6b188Simarom        self.pkt_groups = [ [], [] ]
1255dbe6b188Simarom        if split_mode == 'MAC':
1256dbe6b188Simarom            self.generate_mac_groups()
1257dbe6b188Simarom        elif split_mode == 'IP':
1258dbe6b188Simarom            self.generate_ip_groups()
1259dbe6b188Simarom        else:
1260dbe6b188Simarom            raise STLError('unknown split mode for PCAP')
1262dbe6b188Simarom        return self.pkt_groups
1265dbe6b188Simarom    # generate two groups based on MACs
1266dbe6b188Simarom    def generate_mac_groups (self):
1267dbe6b188Simarom        for i, pkt in enumerate(self.pcap):
1268dbe6b188Simarom            if not isinstance(pkt, (Ether, Dot3) ):
1269dbe6b188Simarom                raise STLError("Packet #{0} has an unknown L2 format: {1}".format(i, type(pkt)))
1270dbe6b188Simarom            mac_src = pkt.fields['src']
1271dbe6b188Simarom            mac_dst = pkt.fields['dst']
1272dbe6b188Simarom            self.graph.add(mac_src, mac_dst)
1274dbe6b188Simarom        # split the graph to two groups
1275dbe6b188Simarom        mac_groups = self.graph.split()
1277dbe6b188Simarom        for pkt in self.pcap: