1 # Copyright (c) 2016-2017 Intel Corporation
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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 """ Trex Traffic Profile definitions """
16 from __future__ import absolute_import
20 from random import SystemRandom
24 from yardstick.network_services.traffic_profile.base import TrafficProfile
25 from trex_stl_lib.trex_stl_client import STLStream
26 from trex_stl_lib.trex_stl_streams import STLFlowLatencyStats
27 from trex_stl_lib.trex_stl_streams import STLTXCont
28 from trex_stl_lib.trex_stl_streams import STLProfile
29 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmWrFlowVar
30 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVarRepeatableRandom
31 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVar
32 from trex_stl_lib.trex_stl_packet_builder_scapy import STLPktBuilder
33 from trex_stl_lib.trex_stl_packet_builder_scapy import STLScVmRaw
34 from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFixIpv4
35 from trex_stl_lib import api as Pkt
46 TYPE_OF_SERVICE = 'tos'
48 LOG = logging.getLogger(__name__)
51 class TrexProfile(TrafficProfile):
52 """ This class handles Trex Traffic profile generation and execution """
55 ETHERNET: ('ether_packet', Pkt.Ether),
56 IP: ('ip_packet', Pkt.IP),
57 IPv6: ('ip6_packet', Pkt.IPv6),
58 UDP: ('udp_packet', Pkt.UDP),
61 def _general_single_action_partial(self, protocol):
67 self._set_proto_fields(protocol, **kwargs)
71 def _ethernet_range_action_partial(self, direction, _):
72 def partial(min_value, max_value, count):
73 stl_vm_flow_var = STLVmFlowVar(name="mac_{}".format(direction),
79 self.vm_flow_vars.append(stl_vm_flow_var)
80 stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='mac_{}'.format(direction),
81 pkt_offset='Ether.{}'.format(direction))
82 self.vm_flow_vars.append(stl_vm_wr_flow_var)
85 def _ip_range_action_partial(self, direction, count=1):
86 def partial(min_value, max_value, count):
87 ip1 = int(ipaddress.IPv4Address(min_value))
88 ip2 = int(ipaddress.IPv4Address(max_value))
89 actual_count = (ip2 - ip1)
92 elif actual_count < int(count):
95 stl_vm_flow_var = STLVmFlowVarRepeatableRandom(name="ip4_{}".format(direction),
101 self.vm_flow_vars.append(stl_vm_flow_var)
102 stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='ip4_{}'.format(direction),
103 pkt_offset='IP.{}'.format(direction))
104 self.vm_flow_vars.append(stl_vm_wr_flow_var)
105 stl_vm_fix_ipv4 = STLVmFixIpv4(offset="IP")
106 self.vm_flow_vars.append(stl_vm_fix_ipv4)
109 def _ip6_range_action_partial(self, direction, _):
110 def partial(min_value, max_value, count):
111 min_value, max_value = self._get_start_end_ipv6(min_value, max_value)
112 stl_vm_flow_var = STLVmFlowVar(name="ip6_{}".format(direction),
118 self.vm_flow_vars.append(stl_vm_flow_var)
119 stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='ip6_{}'.format(direction),
120 pkt_offset='IPv6.{}'.format(direction),
122 self.vm_flow_vars.append(stl_vm_wr_flow_var)
125 def _dscp_range_action_partial(self, *_):
126 def partial(min_value, max_value, count):
127 stl_vm_flow_var = STLVmFlowVar(name="dscp",
133 self.vm_flow_vars.append(stl_vm_flow_var)
134 stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='dscp',
136 self.vm_flow_vars.append(stl_vm_wr_flow_var)
138 def _udp_range_action_partial(self, field, count=1):
139 def partial(min_value, max_value, count):
140 actual_count = int(max_value) - int(min_value)
143 elif int(count) > actual_count:
146 stl_vm_flow_var = STLVmFlowVarRepeatableRandom(name="port_{}".format(field),
152 self.vm_flow_vars.append(stl_vm_flow_var)
153 stl_vm_wr_flow_var = STLVmWrFlowVar(fv_name='port_{}'.format(field),
154 pkt_offset=self.udp[field])
155 self.vm_flow_vars.append(stl_vm_wr_flow_var)
158 def __init__(self, yaml_data):
159 super(TrexProfile, self).__init__(yaml_data)
163 self.first_run = True
165 self.profile_data = []
172 self.ether_packet = None
173 self.ip_packet = None
174 self.ip6_packet = None
175 self.udp_packet = None
180 self.qinq_packet = None
182 self.vm_flow_vars = []
185 self._map_proto_actions = {
186 # the tuple is (single value function, range value function, if the values should be
187 # converted to integer).
188 ETHERNET: (self._general_single_action_partial(ETHERNET),
189 self._ethernet_range_action_partial,
192 IP: (self._general_single_action_partial(IP),
193 self._ip_range_action_partial,
196 IPv6: (self._general_single_action_partial(IPv6),
197 self._ip6_range_action_partial,
200 DSCP: (self._general_single_action_partial(IP),
201 self._dscp_range_action_partial,
204 UDP: (self._general_single_action_partial(UDP),
205 self._udp_range_action_partial,
210 def execute_traffic(self, traffic_generator):
211 """ Generate the stream and run traffic on the given ports """
212 raise NotImplementedError()
214 def _call_on_range(self, range, single_action, range_action, count=1, to_int=False):
215 def convert_to_int(val):
216 return int(val) if to_int else val
218 range_iter = iter(str(range).split('-'))
219 min_value = convert_to_int(next(range_iter))
221 max_value = convert_to_int(next(range_iter))
222 except StopIteration:
223 single_action(min_value)
225 range_action(min_value=min_value, max_value=max_value, count=count)
227 def _set_proto_addr(self, protocol, field, address, count=1):
228 single_action, range_action, to_int = self._map_proto_actions[protocol]
229 self._call_on_range(address,
230 single_action(field),
231 range_action(field, count),
236 def _set_proto_fields(self, protocol, **kwargs):
237 _attr_name, _class = self.PROTO_MAP[protocol]
239 if not getattr(self, _attr_name):
240 setattr(self, _attr_name, _class())
242 _attr = getattr(self, _attr_name)
243 for key, value in six.iteritems(kwargs):
244 setattr(_attr, key, value)
246 def set_svlan_cvlan(self, svlan, cvlan):
247 """ set svlan & cvlan """
249 ether_params = {'type': 0x8100}
250 self._set_proto_fields(ETHERNET, **ether_params)
251 svlans = str(svlan['id']).split('-')
252 svlan_min = int(svlans[0])
253 svlan_max = int(svlans[1]) if len(svlans) == 2 else int(svlans[0])
255 svlan = self._get_random_value(svlan_min, svlan_max)
258 cvlans = str(cvlan['id']).split('-')
259 cvlan_min = int(cvlans[0])
260 cvlan_max = int(cvlans[1]) if len(cvlans) == 2 else int(cvlans[0])
262 cvlan = self._get_random_value(cvlan_min, cvlan_max)
265 self.qinq_packet = Pkt.Dot1Q(vlan=svlan) / Pkt.Dot1Q(vlan=cvlan)
267 def set_qinq(self, qinq):
268 """ set qinq in packet """
269 self.set_svlan_cvlan(qinq['S-VLAN'], qinq['C-VLAN'])
271 def _set_outer_l2_fields(self, outer_l2):
272 """ setup outer l2 fields from traffic profile """
273 ether_params = {'type': 0x800}
274 self._set_proto_fields(ETHERNET, **ether_params)
275 if 'srcmac' in outer_l2:
276 self._set_proto_addr(ETHERNET, SRC, outer_l2['srcmac'])
277 if 'dstmac' in outer_l2:
278 self._set_proto_addr(ETHERNET, DST, outer_l2['dstmac'])
279 if 'QinQ' in outer_l2:
280 self.set_qinq(outer_l2['QinQ'])
282 def _set_outer_l3v4_fields(self, outer_l3v4):
283 """ setup outer l3v4 fields from traffic profile """
285 if 'proto' in outer_l3v4:
286 ip_params['proto'] = socket.getprotobyname(outer_l3v4['proto'])
287 if outer_l3v4['proto'] == 'tcp':
288 self.udp_packet = Pkt.TCP()
289 self.udp[DST_PORT] = 'TCP.dport'
290 self.udp[SRC_PORT] = 'TCP.sport'
291 tcp_params = {'flags': '', 'window': 0}
292 self._set_proto_fields(UDP, **tcp_params)
293 if 'ttl' in outer_l3v4:
294 ip_params['ttl'] = outer_l3v4['ttl']
295 self._set_proto_fields(IP, **ip_params)
296 if 'dscp' in outer_l3v4:
297 self._set_proto_addr(DSCP, TYPE_OF_SERVICE, outer_l3v4['dscp'])
298 if 'srcip4' in outer_l3v4:
299 self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count'])
300 if 'dstip4' in outer_l3v4:
301 self._set_proto_addr(IP, DST, outer_l3v4['dstip4'], outer_l3v4['count'])
303 def _set_outer_l3v6_fields(self, outer_l3v6):
304 """ setup outer l3v6 fields from traffic profile """
305 ether_params = {'type': 0x86dd}
306 self._set_proto_fields(ETHERNET, **ether_params)
308 if 'proto' in outer_l3v6:
309 ip6_params['proto'] = outer_l3v6['proto']
310 if outer_l3v6['proto'] == 'tcp':
311 self.udp_packet = Pkt.TCP()
312 self.udp[DST_PORT] = 'TCP.dport'
313 self.udp[SRC_PORT] = 'TCP.sport'
314 tcp_params = {'flags': '', 'window': 0}
315 self._set_proto_fields(UDP, **tcp_params)
316 if 'ttl' in outer_l3v6:
317 ip6_params['ttl'] = outer_l3v6['ttl']
318 if 'tc' in outer_l3v6:
319 ip6_params['tc'] = outer_l3v6['tc']
320 if 'hlim' in outer_l3v6:
321 ip6_params['hlim'] = outer_l3v6['hlim']
322 self._set_proto_fields(IPv6, **ip6_params)
323 if 'srcip6' in outer_l3v6:
324 self._set_proto_addr(IPv6, SRC, outer_l3v6['srcip6'])
325 if 'dstip6' in outer_l3v6:
326 self._set_proto_addr(IPv6, DST, outer_l3v6['dstip6'])
328 def _set_outer_l4_fields(self, outer_l4):
329 """ setup outer l4 fields from traffic profile """
330 if 'srcport' in outer_l4:
331 self._set_proto_addr(UDP, SRC_PORT, outer_l4['srcport'], outer_l4['count'])
332 if 'dstport' in outer_l4:
333 self._set_proto_addr(UDP, DST_PORT, outer_l4['dstport'], outer_l4['count'])
335 def generate_imix_data(self, packet_definition):
336 """ generate packet size for a given traffic profile """
339 if not packet_definition:
341 imix = packet_definition.get('framesize')
345 imix_data[int(size[:-1])] = int(data)
346 imix_sum = sum(imix_data.values())
348 raise SystemExit("Error in IMIX data")
350 imix_data[64] = imix_data.get(64, 0) + (100 - imix_sum)
353 for size in imix_data:
354 count = int(imix_data[size])
356 avg_size += round(size * count / 100, 2)
357 pps = round(self.pps * count / 100, 0)
358 imix_count[size] = pps
359 self.rate = round(1342177280 / avg_size, 0) * 2
360 logging.debug("Imax: %s rate: %s", imix_count, self.rate)
363 def get_streams(self, profile_data):
364 """ generate trex stream
369 self.pps = self.params['traffic_profile'].get('frame_rate', 100)
370 for packet_name in profile_data:
371 outer_l2 = profile_data[packet_name].get('outer_l2')
372 imix_data = self.generate_imix_data(outer_l2)
374 imix_data = {64: self.pps}
375 self.generate_vm(profile_data[packet_name])
376 for size in imix_data:
377 self._generate_streams(size, imix_data[size])
378 self._generate_profile()
381 def generate_vm(self, packet_definition):
382 """ generate trex vm with flows setup """
383 self.ether_packet = Pkt.Ether()
384 self.ip_packet = Pkt.IP()
385 self.ip6_packet = None
386 self.udp_packet = Pkt.UDP()
387 self.udp[DST_PORT] = 'UDP.dport'
388 self.udp[SRC_PORT] = 'UDP.sport'
390 self.vm_flow_vars = []
391 outer_l2 = packet_definition.get('outer_l2', None)
392 outer_l3v4 = packet_definition.get('outer_l3v4', None)
393 outer_l3v6 = packet_definition.get('outer_l3v6', None)
394 outer_l4 = packet_definition.get('outer_l4', None)
396 self._set_outer_l2_fields(outer_l2)
398 self._set_outer_l3v4_fields(outer_l3v4)
400 self._set_outer_l3v6_fields(outer_l3v6)
402 self._set_outer_l4_fields(outer_l4)
403 self.trex_vm = STLScVmRaw(self.vm_flow_vars)
405 def generate_packets(self):
406 """ generate packets from trex TG """
407 base_pkt = self.base_pkt
408 size = self.fsize - 4
409 pad = max(0, size - len(base_pkt)) * 'x'
410 self.packets = [STLPktBuilder(pkt=base_pkt / pad,
411 vm=vm) for vm in self.vms]
413 def _create_single_packet(self, size=64):
415 ether_packet = self.ether_packet
416 ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet
417 udp_packet = self.udp_packet
419 qinq_packet = self.qinq_packet
420 base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet
422 base_pkt = ether_packet / ip_packet / udp_packet
423 pad = max(0, size - len(base_pkt)) * 'x'
424 packet = STLPktBuilder(pkt=base_pkt / pad, vm=self.trex_vm)
427 def _create_single_stream(self, packet_size, pps, isg=0):
428 packet = self._create_single_packet(packet_size)
431 stl_flow = STLFlowLatencyStats(pg_id=self.pg_id)
432 stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps),
435 stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps))
438 def _generate_streams(self, packet_size, pps):
439 self.streams.append(self._create_single_stream(packet_size, pps))
441 def _generate_profile(self):
442 self.profile = STLProfile(self.streams)
445 def _get_start_end_ipv6(cls, start_ip, end_ip):
447 ip1 = socket.inet_pton(socket.AF_INET6, start_ip)
448 ip2 = socket.inet_pton(socket.AF_INET6, end_ip)
449 hi1, lo1 = struct.unpack('!QQ', ip1)
450 hi2, lo2 = struct.unpack('!QQ', ip2)
451 if ((hi1 << 64) | lo1) > ((hi2 << 64) | lo2):
452 raise SystemExit("IPv6: start_ip is greater then end_ip")
453 max_p1 = abs(int(lo1) - int(lo2))
455 except Exception as ex_error:
456 raise SystemExit(ex_error)
458 return base_p1, max_p1 + base_p1
461 def _get_random_value(cls, min_port, max_port):
462 cryptogen = SystemRandom()
463 return cryptogen.randrange(min_port, max_port)