"""Interface to the traffic generator clients including NDR/PDR binary search."""
from datetime import datetime
+from math import gcd
import socket
import struct
import time
from trex.stl.api import Ether
from trex.stl.api import STLError
from trex.stl.api import UDP
+# pylint: disable=wrong-import-order
+from scapy.contrib.mpls import MPLS # flake8: noqa
+# pylint: enable=wrong-import-order
# pylint: enable=import-error
from .log import LOG
from .traffic_gen import traffic_utils as utils
from .utils import cast_integer
-
class TrafficClientException(Exception):
"""Generic traffic client exception."""
raise IndexError('Index out of bounds: %d (max=%d)' % (index, self.max_available))
return Device.int_to_ip(self.base_ip_int + index * self.step)
- def reserve_ip_range(self, count):
- """Reserve a range of count consecutive IP addresses spaced by step."""
- if self.next_free + count > self.max_available:
+ def reserve_ip_range(self, count, force_ip_reservation=False):
+ """Reserve a range of count consecutive IP addresses spaced by step.
+ force_ip_reservation parameter allows to continue the calculation of IPs when
+ the 2 sides (ports) have different size and the flow is greater than
+ the size as well.
+ """
+ if self.next_free + count > self.max_available and force_ip_reservation is False:
raise IndexError('No more IP addresses next free=%d max_available=%d requested=%d' %
(self.next_free,
self.max_available,
count))
- first_ip = self.get_ip(self.next_free)
- last_ip = self.get_ip(self.next_free + count - 1)
- self.next_free += count
+ if self.next_free + count > self.max_available and force_ip_reservation is True:
+ first_ip = self.get_ip(self.next_free)
+ last_ip = self.get_ip(self.next_free + self.max_available - 1)
+ self.next_free += self.max_available
+ else:
+ first_ip = self.get_ip(self.next_free)
+ last_ip = self.get_ip(self.next_free + count - 1)
+ self.next_free += count
return (first_ip, last_ip)
def reset_reservation(self):
self.next_free = 0
+class UdpPorts(object):
+
+ def __init__(self, src_min, src_max, dst_min, dst_max, step):
+
+ self.src_min = src_min
+ self.src_max = src_max
+ self.dst_min = dst_min
+ self.dst_max = dst_max
+ self.step = step
+
+
class Device(object):
"""Represent a port device and all information associated to it.
"""Create a new device for a given port."""
self.generator_config = generator_config
self.chain_count = generator_config.service_chain_count
- self.flow_count = generator_config.flow_count / 2
+ if generator_config.bidirectional:
+ self.flow_count = generator_config.flow_count / 2
+ else:
+ self.flow_count = generator_config.flow_count
+
self.port = port
self.switch_port = generator_config.interfaces[port].get('switch_port', None)
self.vtep_vlan = None
self.vtep_src_mac = None
self.vxlan = False
+ self.mpls = False
+ self.inner_labels = None
+ self.outer_labels = None
self.pci = generator_config.interfaces[port].pci
self.mac = None
self.dest_macs = None
self.vnis = None
self.vlans = None
self.ip_addrs = generator_config.ip_addrs[port]
- subnet = IPNetwork(self.ip_addrs)
- self.ip = subnet.ip.format()
+ self.ip_src_static = generator_config.ip_src_static
self.ip_addrs_step = generator_config.ip_addrs_step
- self.ip_block = IpBlock(self.ip, self.ip_addrs_step, self.flow_count)
+ if self.ip_addrs_step == 'random':
+ # Set step to 1 to calculate the IP range size (see check_ip_size below)
+ step = '0.0.0.1'
+ else:
+ step = self.ip_addrs_step
+ self.ip_size = self.check_ipsize(IPNetwork(self.ip_addrs).size, Device.ip_to_int(step))
+ self.ip = str(IPNetwork(self.ip_addrs).network)
+ ip_addrs_left = generator_config.ip_addrs[0]
+ ip_addrs_right = generator_config.ip_addrs[1]
+ self.ip_addrs_size = {
+ 'left': self.check_ipsize(IPNetwork(ip_addrs_left).size, Device.ip_to_int(step)),
+ 'right': self.check_ipsize(IPNetwork(ip_addrs_right).size, Device.ip_to_int(step))}
+ udp_src_port = generator_config.gen_config.udp_src_port
+ if udp_src_port is None:
+ udp_src_port = 53
+ udp_dst_port = generator_config.gen_config.udp_dst_port
+ if udp_dst_port is None:
+ udp_dst_port = 53
+ src_max, src_min = self.define_udp_range(udp_src_port, 'udp_src_port')
+ dst_max, dst_min = self.define_udp_range(udp_dst_port, 'udp_dst_port')
+ udp_src_range = int(src_max) - int(src_min) + 1
+ udp_dst_range = int(dst_max) - int(dst_min) + 1
+ lcm_port = self.lcm(udp_src_range, udp_dst_range)
+ if self.ip_src_static is True:
+ lcm_ip = self.lcm(1, min(self.ip_addrs_size['left'], self.ip_addrs_size['right']))
+ else:
+ lcm_ip = self.lcm(self.ip_addrs_size['left'], self.ip_addrs_size['right'])
+ flow_max = self.lcm(lcm_port, lcm_ip)
+ if self.flow_count > flow_max:
+ raise TrafficClientException('Trying to set unachievable traffic (%d > %d)' %
+ (self.flow_count, flow_max))
+
+ # manage udp range regarding flow count value
+ # UDP dst range is greater than FC => range will be limited to min + FC
+ if self.flow_count <= udp_dst_range:
+ dst_max = int(dst_min) + self.flow_count - 1
+ # UDP src range is greater than FC => range will be limited to min + FC
+ if self.flow_count <= udp_src_range:
+ src_max = int(src_min) + self.flow_count - 1
+ # Define IP block limit regarding flow count
+ if self.flow_count <= self.ip_size:
+ self.ip_block = IpBlock(self.ip, step, self.flow_count)
+ else:
+ self.ip_block = IpBlock(self.ip, step, self.ip_size)
+
+ if generator_config.gen_config.udp_port_step == 'random':
+ step = 1
+ else:
+ step = generator_config.gen_config.udp_port_step
+ self.udp_ports = UdpPorts(src_min, src_max, dst_min, dst_max, step)
self.gw_ip_block = IpBlock(generator_config.gateway_ips[port],
generator_config.gateway_ip_addrs_step,
self.chain_count)
self.tg_gw_ip_block = IpBlock(self.tg_gateway_ip_addrs,
generator_config.tg_gateway_ip_addrs_step,
self.chain_count)
- self.udp_src_port = generator_config.udp_src_port
- self.udp_dst_port = generator_config.udp_dst_port
+
+ @staticmethod
+ def define_udp_range(udp_port, property_name):
+ if isinstance(udp_port, int):
+ min = udp_port
+ max = min
+ elif isinstance(udp_port, tuple):
+ min = udp_port[0]
+ max = udp_port[1]
+ else:
+ raise TrafficClientException('Invalid %s property value (53 or [\'53\',\'1024\'])'
+ % property_name)
+ return max, min
+
+ @staticmethod
+ def lcm(a, b):
+ """Calculate the maximum possible value for both IP and ports,
+ eventually for maximum possible flux."""
+ if a != 0 and b != 0:
+ lcm_value = a * b // gcd(a, b)
+ return lcm_value
+ raise TypeError(" IP size or port range can't be zero !")
+
+ @staticmethod
+ def check_ipsize(ip_size, step):
+ """Check and set the available IPs, considering the step."""
+ try:
+ if ip_size % step == 0:
+ value = int(ip_size / step)
+ else:
+ value = int((ip_size / step)) + 1
+ return value
+ except ZeroDivisionError:
+ raise ZeroDivisionError("step can't be zero !")
def set_mac(self, mac):
"""Set the local MAC for this port device."""
LOG.info("Port %d: src_vtep %s, dst_vtep %s", self.port,
self.vtep_src_ip, self.vtep_dst_ip)
+ def set_mpls_peers(self, src_ip, dst_ip):
+ self.mpls = True
+ self.vtep_dst_ip = dst_ip
+ self.vtep_src_ip = src_ip
+ LOG.info("Port %d: src_mpls_vtep %s, mpls_peer_ip %s", self.port,
+ self.vtep_src_ip, self.vtep_dst_ip)
+
def set_vxlans(self, vnis):
self.vnis = vnis
LOG.info("Port %d: VNIs %s", self.port, self.vnis)
+ def set_mpls_inner_labels(self, labels):
+ self.inner_labels = labels
+ LOG.info("Port %d: MPLS Inner Labels %s", self.port, self.inner_labels)
+
+ def set_mpls_outer_labels(self, labels):
+ self.outer_labels = labels
+ LOG.info("Port %d: MPLS Outer Labels %s", self.port, self.outer_labels)
+
def set_gw_ip(self, gateway_ip):
self.gw_ip_block = IpBlock(gateway_ip,
self.generator_config.gateway_ip_addrs_step,
# example 11 flows and 3 chains => 3, 4, 4
flows_per_chain = int((self.flow_count + self.chain_count - 1) / self.chain_count)
cur_chain_flow_count = int(self.flow_count - flows_per_chain * (self.chain_count - 1))
+ force_ip_reservation = False
+ # use case example of this parameter:
+ # - static IP addresses (source & destination), netmask = /30
+ # - 4 varying UDP source ports | 1 UDP destination port
+ # - Flow count = 8
+ # --> parameter 'reserve_ip_range' should have flag 'force_ip_reservation'
+ # in order to assign the maximum available IP on each iteration
+ if self.ip_size < cur_chain_flow_count \
+ or self.ip_addrs_size['left'] != self.ip_addrs_size['right']:
+ force_ip_reservation = True
+
peer = self.get_peer_device()
self.ip_block.reset_reservation()
peer.ip_block.reset_reservation()
dest_macs = self.get_dest_macs()
for chain_idx in range(self.chain_count):
- src_ip_first, src_ip_last = self.ip_block.reserve_ip_range(cur_chain_flow_count)
- dst_ip_first, dst_ip_last = peer.ip_block.reserve_ip_range(cur_chain_flow_count)
+ src_ip_first, src_ip_last = self.ip_block.reserve_ip_range\
+ (cur_chain_flow_count, force_ip_reservation)
+ dst_ip_first, dst_ip_last = peer.ip_block.reserve_ip_range\
+ (cur_chain_flow_count, force_ip_reservation)
configs.append({
'count': cur_chain_flow_count,
'ip_dst_addr_max': dst_ip_last,
'ip_dst_count': cur_chain_flow_count,
'ip_addrs_step': self.ip_addrs_step,
- 'udp_src_port': self.udp_src_port,
- 'udp_dst_port': self.udp_dst_port,
+ 'ip_src_static': self.ip_src_static,
+ 'udp_src_port': self.udp_ports.src_min,
+ 'udp_src_port_max': self.udp_ports.src_max,
+ 'udp_src_count': int(self.udp_ports.src_max) - int(self.udp_ports.src_min) + 1,
+ 'udp_dst_port': self.udp_ports.dst_min,
+ 'udp_dst_port_max': self.udp_ports.dst_max,
+ 'udp_dst_count': int(self.udp_ports.dst_max) - int(self.udp_ports.dst_min) + 1,
+ 'udp_port_step': self.udp_ports.step,
'mac_discovery_gw': self.get_gw_ip(chain_idx),
'ip_src_tg_gw': self.tg_gw_ip_block.get_ip(chain_idx),
'ip_dst_tg_gw': peer.tg_gw_ip_block.get_ip(chain_idx),
'vlan_tag': self.vlans[chain_idx] if self.vlans else None,
'vxlan': self.vxlan,
'vtep_vlan': self.vtep_vlan if self.vtep_vlan else None,
- 'vtep_src_mac': self.mac if self.vxlan is True else None,
- 'vtep_dst_mac': self.vtep_dst_mac if self.vxlan is True else None,
+ 'vtep_src_mac': self.mac if (self.vxlan or self.mpls) else None,
+ 'vtep_dst_mac': self.vtep_dst_mac if (self.vxlan or self.mpls) else None,
'vtep_dst_ip': self.vtep_dst_ip if self.vxlan is True else None,
'vtep_src_ip': self.vtep_src_ip if self.vxlan is True else None,
- 'net_vni': self.vnis[chain_idx] if self.vxlan is True else None
+ 'net_vni': self.vnis[chain_idx] if self.vxlan is True else None,
+ 'mpls': self.mpls,
+ 'mpls_outer_label': self.outer_labels[chain_idx] if self.mpls is True else None,
+ 'mpls_inner_label': self.inner_labels[chain_idx] if self.mpls is True else None
+
})
# after first chain, fall back to the flow count for all other chains
cur_chain_flow_count = flows_per_chain
self.service_chain_count = config.service_chain_count
self.flow_count = config.flow_count
self.host_name = gen_config.host_name
-
+ self.bidirectional = config.traffic.bidirectional
self.tg_gateway_ip_addrs = gen_config.tg_gateway_ip_addrs
self.ip_addrs = gen_config.ip_addrs
self.ip_addrs_step = gen_config.ip_addrs_step or self.DEFAULT_SRC_DST_IP_STEP
gen_config.tg_gateway_ip_addrs_step or self.DEFAULT_IP_STEP
self.gateway_ip_addrs_step = gen_config.gateway_ip_addrs_step or self.DEFAULT_IP_STEP
self.gateway_ips = gen_config.gateway_ip_addrs
- self.udp_src_port = gen_config.udp_src_port
- self.udp_dst_port = gen_config.udp_dst_port
+ self.ip_src_static = gen_config.ip_src_static
self.vteps = gen_config.get('vteps')
self.devices = [Device(port, self) for port in [0, 1]]
# This should normally always be [0, 1]
(vxlans, self.config.service_chain_count))
self.devices[port_index].set_vxlans(vxlans)
+ def set_mpls_inner_labels(self, port_index, labels):
+ """Set the list of MPLS Labels to use indexed by the chain id on given port.
+
+ port_index: the port for which Labels must be set
+ Labels: a list of Labels lists indexed by chain id
+ """
+ if len(labels) != self.config.service_chain_count:
+ raise TrafficClientException('Inner MPLS list %s must have %d entries' %
+ (labels, self.config.service_chain_count))
+ self.devices[port_index].set_mpls_inner_labels(labels)
+
+ def set_mpls_outer_labels(self, port_index, labels):
+ """Set the list of MPLS Labels to use indexed by the chain id on given port.
+
+ port_index: the port for which Labels must be set
+ Labels: a list of Labels lists indexed by chain id
+ """
+ if len(labels) != self.config.service_chain_count:
+ raise TrafficClientException('Outer MPLS list %s must have %d entries' %
+ (labels, self.config.service_chain_count))
+ self.devices[port_index].set_mpls_outer_labels(labels)
+
def set_vtep_vlan(self, port_index, vlan):
"""Set the vtep vlan to use indexed by the chain id on given port.
port_index: the port for which VLAN must be set
def set_vxlan_endpoints(self, port_index, src_ip, dst_ip):
self.devices[port_index].set_vxlan_endpoints(src_ip, dst_ip)
+ def set_mpls_peers(self, port_index, src_ip, dst_ip):
+ self.devices[port_index].set_mpls_peers(src_ip, dst_ip)
+
@staticmethod
def __match_generator_profile(traffic_generator, generator_profile):
gen_config = AttrDict(traffic_generator)
get_mac_id = lambda packet: packet['binary'][60:66]
elif self.config.vxlan:
get_mac_id = lambda packet: packet['binary'][56:62]
+ elif self.config.mpls:
+ get_mac_id = lambda packet: packet['binary'][24:30]
+ # mpls_transport_label = lambda packet: packet['binary'][14:18]
else:
get_mac_id = lambda packet: packet['binary'][6:12]
for it in range(retry_count):
for packet in self.gen.packet_list:
mac_id = get_mac_id(packet).decode('latin-1')
src_mac = ':'.join(["%02x" % ord(x) for x in mac_id])
- if src_mac in mac_map and self.is_udp(packet):
- port, chain = mac_map[src_mac]
- LOG.info('Received packet from mac: %s (chain=%d, port=%d)',
- src_mac, chain, port)
- mac_map.pop(src_mac, None)
+ if self.config.mpls:
+ if src_mac in mac_map and self.is_mpls(packet):
+ port, chain = mac_map[src_mac]
+ LOG.info('Received mpls packet from mac: %s (chain=%d, port=%d)',
+ src_mac, chain, port)
+ mac_map.pop(src_mac, None)
+ else:
+ if src_mac in mac_map and self.is_udp(packet):
+ port, chain = mac_map[src_mac]
+ LOG.info('Received udp packet from mac: %s (chain=%d, port=%d)',
+ src_mac, chain, port)
+ mac_map.pop(src_mac, None)
if not mac_map:
LOG.info('End-to-end connectivity established')
pkt = Ether(packet['binary'])
return UDP in pkt
+ def is_mpls(self, packet):
+ pkt = Ether(packet['binary'])
+ return MPLS in pkt
+
def ensure_arp_successful(self):
"""Resolve all IP using ARP and throw an exception in case of failure."""
dest_macs = self.gen.resolve_arp()
if dest_macs:
# all dest macs are discovered, saved them into the generator config
- if self.config.vxlan:
+ if self.config.vxlan or self.config.mpls:
self.generator_config.set_vtep_dest_macs(0, dest_macs[0])
self.generator_config.set_vtep_dest_macs(1, dest_macs[1])
else:
self.run_config['rates'][idx] = {'rate_pps': self.__convert_rates(rate)['rate_pps']}
self.gen.clear_streamblock()
+
if self.config.no_latency_streams:
LOG.info("Latency streams are disabled")
self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional,