IXIA: Add IP random range `seed` configuration option
[yardstick.git] / yardstick / network_services / traffic_profile / trex_traffic_profile.py
1 # Copyright (c) 2016-2017 Intel Corporation
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import struct
16 import socket
17 import logging
18 from random import SystemRandom
19 import ipaddress
20
21 import six
22 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmWrFlowVar
23 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVarRepeatableRandom
24 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVar
25 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFixIpv4
26 from trex_stl_lib import api as Pkt
27
28 from yardstick.common import exceptions as y_exc
29 from yardstick.network_services.traffic_profile import base
30
31
32 SRC = 'src'
33 DST = 'dst'
34 ETHERNET = 'Ethernet'
35 IP = 'IP'
36 IPv6 = 'IPv6'
37 UDP = 'UDP'
38 DSCP = 'DSCP'
39 SRC_PORT = 'sport'
40 DST_PORT = 'dport'
41 TYPE_OF_SERVICE = 'tos'
42
43 LOG = logging.getLogger(__name__)
44
45
46 class TrexProfile(base.TrafficProfile):
47     """ This class handles Trex Traffic profile generation and execution """
48
49     PROTO_MAP = {
50         ETHERNET: ('ether_packet', Pkt.Ether),
51         IP: ('ip_packet', Pkt.IP),
52         IPv6: ('ip6_packet', Pkt.IPv6),
53         UDP: ('udp_packet', Pkt.UDP),
54     }
55
56     def _general_single_action_partial(self, protocol):
57         def f(field):
58             def partial(value):
59                 kwargs = {
60                     field: value
61                 }
62                 self._set_proto_fields(protocol, **kwargs)
63             return partial
64         return f
65
66     def _ethernet_range_action_partial(self, direction, _):
67         def partial(min_value, max_value, count):
68             # pylint: disable=unused-argument
69             stl_vm_flow_var = STLVmFlowVar(name="mac_{}".format(direction),
70                                            min_value=1,
71                                            max_value=30,
72                                            size=4,
73                                            op='inc',
74                                            step=1)
75             self.vm_flow_vars.append(stl_vm_flow_var)
76             stl_vm_wr_flow_var = STLVmWrFlowVar(
77                 fv_name='mac_{}'.format(direction),
78                 pkt_offset='Ether.{}'.format(direction))
79             self.vm_flow_vars.append(stl_vm_wr_flow_var)
80         return partial
81
82     def _ip_range_action_partial(self, direction, count=1):
83         # pylint: disable=unused-argument
84         def partial(min_value, max_value, count):
85             _, _, actual_count = self._count_ip(min_value, max_value)
86             if not actual_count:
87                 count = 1
88             elif actual_count < int(count):
89                 count = actual_count
90
91             stl_vm_flow_var = STLVmFlowVarRepeatableRandom(
92                 name="ip4_{}".format(direction),
93                 min_value=min_value,
94                 max_value=max_value,
95                 size=4,
96                 limit=int(count),
97                 seed=0x1235)
98             self.vm_flow_vars.append(stl_vm_flow_var)
99             stl_vm_wr_flow_var = STLVmWrFlowVar(
100                 fv_name='ip4_{}'.format(direction),
101                 pkt_offset='IP.{}'.format(direction))
102             self.vm_flow_vars.append(stl_vm_wr_flow_var)
103             stl_vm_fix_ipv4 = STLVmFixIpv4(offset="IP")
104             self.vm_flow_vars.append(stl_vm_fix_ipv4)
105         return partial
106
107     def _ip6_range_action_partial(self, direction, _):
108         def partial(min_value, max_value, count):
109             # pylint: disable=unused-argument
110             min_value, max_value, _ = self._count_ip(min_value, max_value)
111             stl_vm_flow_var = STLVmFlowVar(name="ip6_{}".format(direction),
112                                            min_value=min_value,
113                                            max_value=max_value,
114                                            size=8,
115                                            op='random',
116                                            step=1)
117             self.vm_flow_vars.append(stl_vm_flow_var)
118             stl_vm_wr_flow_var = STLVmWrFlowVar(
119                 fv_name='ip6_{}'.format(direction),
120                 pkt_offset='IPv6.{}'.format(direction),
121                 offset_fixup=8)
122             self.vm_flow_vars.append(stl_vm_wr_flow_var)
123         return partial
124
125     def _dscp_range_action_partial(self, *args):
126         def partial(min_value, max_value, count):
127             # pylint: disable=unused-argument
128             stl_vm_flow_var = STLVmFlowVar(name="dscp",
129                                            min_value=min_value,
130                                            max_value=max_value,
131                                            size=2,
132                                            op='inc',
133                                            step=8)
134             self.vm_flow_vars.append(stl_vm_flow_var)
135             stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='dscp',
136                                                 pkt_offset='IP.tos')
137             self.vm_flow_vars.append(stl_vm_wr_flow_var)
138         return partial
139
140     def _udp_range_action_partial(self, field, count=1):
141         # pylint: disable=unused-argument
142         def partial(min_value, max_value, count):
143             actual_count = int(max_value) - int(min_value)
144             if not actual_count:
145                 count = 1
146             elif int(count) > actual_count:
147                 count = actual_count
148
149             stl_vm_flow_var = STLVmFlowVarRepeatableRandom(
150                 name="port_{}".format(field),
151                 min_value=min_value,
152                 max_value=max_value,
153                 size=2,
154                 limit=int(count),
155                 seed=0x1235)
156             self.vm_flow_vars.append(stl_vm_flow_var)
157             stl_vm_wr_flow_var = STLVmWrFlowVar(
158                 fv_name='port_{}'.format(field),
159                 pkt_offset=self.udp[field])
160             self.vm_flow_vars.append(stl_vm_wr_flow_var)
161         return partial
162
163     def __init__(self, yaml_data):
164         super(TrexProfile, self).__init__(yaml_data)
165         self.flows = 100
166         self.pps = 100
167         self.pg_id = 0
168         self.first_run = True
169         self.streams = 1
170         self.profile_data = []
171         self.profile = None
172         self.base_pkt = None
173         self.fsize = None
174         self.trex_vm = None
175         self.vms = []
176         self.rate = None
177         self.ether_packet = None
178         self.ip_packet = None
179         self.ip6_packet = None
180         self.udp_packet = None
181         self.udp = {
182             SRC_PORT: '',
183             DST_PORT: '',
184         }
185         self.qinq_packet = None
186         self.qinq = False
187         self.vm_flow_vars = []
188         self.packets = []
189
190         self._map_proto_actions = {
191             # the tuple is (single value function, range value function, if the values should be
192             # converted to integer).
193             ETHERNET: (self._general_single_action_partial(ETHERNET),
194                        self._ethernet_range_action_partial,
195                        False,
196                        ),
197             IP: (self._general_single_action_partial(IP),
198                  self._ip_range_action_partial,
199                  False,
200                  ),
201             IPv6: (self._general_single_action_partial(IPv6),
202                    self._ip6_range_action_partial,
203                    False,
204                    ),
205             DSCP: (self._general_single_action_partial(IP),
206                    self._dscp_range_action_partial,
207                    True,
208                    ),
209             UDP: (self._general_single_action_partial(UDP),
210                   self._udp_range_action_partial,
211                   True,
212                   ),
213         }
214
215     def execute_traffic(self, traffic_generator):
216         """ Generate the stream and run traffic on the given ports """
217         raise NotImplementedError()
218
219     def _call_on_range(self, range, single_action, range_action, count=1, to_int=False):
220         def convert_to_int(val):
221             return int(val) if to_int else val
222
223         range_iter = iter(str(range).split('-'))
224         min_value = convert_to_int(next(range_iter))
225         try:
226             max_value = convert_to_int(next(range_iter))
227         except StopIteration:
228             single_action(min_value)
229         else:
230             range_action(min_value=min_value, max_value=max_value, count=count)
231
232     def _set_proto_addr(self, protocol, field, address, count=1):
233         single_action, range_action, to_int = self._map_proto_actions[protocol]
234         self._call_on_range(address,
235                             single_action(field),
236                             range_action(field, count),
237                             count=count,
238                             to_int=to_int,
239                             )
240
241     def _set_proto_fields(self, protocol, **kwargs):
242         _attr_name, _class = self.PROTO_MAP[protocol]
243
244         if not getattr(self, _attr_name):
245             setattr(self, _attr_name, _class())
246
247         _attr = getattr(self, _attr_name)
248         for key, value in six.iteritems(kwargs):
249             setattr(_attr, key, value)
250
251     def set_svlan_cvlan(self, svlan, cvlan):
252         """ set svlan & cvlan """
253         self.qinq = True
254         ether_params = {'type': 0x8100}
255         self._set_proto_fields(ETHERNET, **ether_params)
256         svlans = str(svlan['id']).split('-')
257         svlan_min = int(svlans[0])
258         svlan_max = int(svlans[1]) if len(svlans) == 2 else int(svlans[0])
259         if len(svlans) == 2:
260             svlan = self._get_random_value(svlan_min, svlan_max)
261         else:
262             svlan = svlan_min
263         cvlans = str(cvlan['id']).split('-')
264         cvlan_min = int(cvlans[0])
265         cvlan_max = int(cvlans[1]) if len(cvlans) == 2 else int(cvlans[0])
266         if len(cvlans) == 2:
267             cvlan = self._get_random_value(cvlan_min, cvlan_max)
268         else:
269             cvlan = cvlan_min
270         self.qinq_packet = Pkt.Dot1Q(vlan=svlan) / Pkt.Dot1Q(vlan=cvlan)
271
272     def set_qinq(self, qinq):
273         """ set qinq in packet """
274         self.set_svlan_cvlan(qinq['S-VLAN'], qinq['C-VLAN'])
275
276     def _set_outer_l2_fields(self, outer_l2):
277         """ setup outer l2 fields from traffic profile """
278         ether_params = {'type': 0x800}
279         self._set_proto_fields(ETHERNET, **ether_params)
280         if 'srcmac' in outer_l2:
281             self._set_proto_addr(ETHERNET, SRC, outer_l2['srcmac'])
282         if 'dstmac' in outer_l2:
283             self._set_proto_addr(ETHERNET, DST, outer_l2['dstmac'])
284         if 'QinQ' in outer_l2:
285             self.set_qinq(outer_l2['QinQ'])
286
287     def _set_outer_l3v4_fields(self, outer_l3v4):
288         """ setup outer l3v4 fields from traffic profile """
289         ip_params = {}
290         if 'proto' in outer_l3v4:
291             ip_params['proto'] = socket.getprotobyname(outer_l3v4['proto'])
292             if outer_l3v4['proto'] == 'tcp':
293                 self.udp_packet = Pkt.TCP()
294                 self.udp[DST_PORT] = 'TCP.dport'
295                 self.udp[SRC_PORT] = 'TCP.sport'
296                 tcp_params = {'flags': '', 'window': 0}
297                 self._set_proto_fields(UDP, **tcp_params)
298         if 'ttl' in outer_l3v4:
299             ip_params['ttl'] = outer_l3v4['ttl']
300         self._set_proto_fields(IP, **ip_params)
301         if 'dscp' in outer_l3v4:
302             self._set_proto_addr(DSCP, TYPE_OF_SERVICE, outer_l3v4['dscp'])
303         if 'srcip4' in outer_l3v4:
304             self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count'])
305         if 'dstip4' in outer_l3v4:
306             self._set_proto_addr(IP, DST, outer_l3v4['dstip4'], outer_l3v4['count'])
307
308     def _set_outer_l3v6_fields(self, outer_l3v6):
309         """ setup outer l3v6 fields from traffic profile """
310         ether_params = {'type': 0x86dd}
311         self._set_proto_fields(ETHERNET, **ether_params)
312         ip6_params = {}
313         if 'proto' in outer_l3v6:
314             ip6_params['proto'] = outer_l3v6['proto']
315             if outer_l3v6['proto'] == 'tcp':
316                 self.udp_packet = Pkt.TCP()
317                 self.udp[DST_PORT] = 'TCP.dport'
318                 self.udp[SRC_PORT] = 'TCP.sport'
319                 tcp_params = {'flags': '', 'window': 0}
320                 self._set_proto_fields(UDP, **tcp_params)
321         if 'ttl' in outer_l3v6:
322             ip6_params['ttl'] = outer_l3v6['ttl']
323         if 'tc' in outer_l3v6:
324             ip6_params['tc'] = outer_l3v6['tc']
325         if 'hlim' in outer_l3v6:
326             ip6_params['hlim'] = outer_l3v6['hlim']
327         self._set_proto_fields(IPv6, **ip6_params)
328         if 'srcip6' in outer_l3v6:
329             self._set_proto_addr(IPv6, SRC, outer_l3v6['srcip6'])
330         if 'dstip6' in outer_l3v6:
331             self._set_proto_addr(IPv6, DST, outer_l3v6['dstip6'])
332
333     def _set_outer_l4_fields(self, outer_l4):
334         """ setup outer l4 fields from traffic profile """
335         if 'srcport' in outer_l4:
336             self._set_proto_addr(UDP, SRC_PORT, outer_l4['srcport'], outer_l4['count'])
337         if 'dstport' in outer_l4:
338             self._set_proto_addr(UDP, DST_PORT, outer_l4['dstport'], outer_l4['count'])
339
340     @classmethod
341     def _count_ip(cls, start_ip, end_ip):
342         start = ipaddress.ip_address(six.u(start_ip))
343         end = ipaddress.ip_address(six.u(end_ip))
344         if start.version == 4:
345             return start, end, int(end) - int(start)
346         elif start.version == 6:
347             if int(start) > int(end):
348                 raise y_exc.IPv6RangeError(start_ip=str(start),
349                                            end_ip=str(end))
350             _, lo1 = struct.unpack('!QQ', start.packed)
351             _, lo2 = struct.unpack('!QQ', end.packed)
352             return lo1, lo2, lo2 - lo1
353
354     @classmethod
355     def _get_random_value(cls, min_port, max_port):
356         cryptogen = SystemRandom()
357         return cryptogen.randrange(min_port, max_port)