From 057486b092e0a4bb1989121588eb5f8afdb8e1d3 Mon Sep 17 00:00:00 2001 From: Jules Boudaud Date: Thu, 19 Dec 2019 11:18:41 +0100 Subject: NFVBENCH-158 Allow multiple UDP ports in traffic generation Change-Id: Id7c3ccad01fd9dda6c7cddb576735b429eb987a4 Signed-off-by: fmenguy --- nfvbench/cfg.default.yaml | 23 ++++++ nfvbench/traffic_client.py | 161 ++++++++++++++++++++++++++++++++++----- nfvbench/traffic_gen/trex_gen.py | 80 ++++++++++++++----- 3 files changed, 225 insertions(+), 39 deletions(-) (limited to 'nfvbench') diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml index 97f98cd..d154318 100755 --- a/nfvbench/cfg.default.yaml +++ b/nfvbench/cfg.default.yaml @@ -163,6 +163,12 @@ traffic_generator: # `ip_addrs_step`: step for generating IP sequence. Use "random" for random patterns, default is 0.0.0.1. ip_addrs: ['10.0.0.0/8', '20.0.0.0/8'] ip_addrs_step: 0.0.0.1 + + #'ip_src_static': an attribute to precise the state of source IP during the generation of traffic, It indicates whether + # the IP source variate or remain constant. Use True for constant IP and False for varying IPs. + # default value is True + ip_src_static: True + # `tg_gateway_ip_addrs` base IP for traffic generator ports in the left and right networks to the VNFs # chain count consecutive IP addresses spaced by tg_gateway_ip_addrs_step will be used # `tg_gateway_ip_addrs__step`: step for generating traffic generator gateway sequences. default is 0.0.0.1 @@ -177,10 +183,27 @@ traffic_generator: # `gateway_ip_addrs_step`: step for generating router gateway sequences. default is 0.0.0.1 gateway_ip_addrs: ['1.1.0.2', '2.2.0.2'] gateway_ip_addrs_step: 0.0.0.1 + + # UDP DEFINED VARIABLES + # TRex pick default UDP port (53) but the range of UDP source and destination ports are also + # defined from configuration file by using the following attributes: + # # `udp_src_port`: the source port for sending UDP traffic, default is picked by TRex (53) # `udp_dst_port`: the destination port for sending UDP traffic, default is picked by TRex (53) + # `udp_src_port` and `udp_dst_port` can be defined by a single port or a range. Example: + # udp_src_port: 80 + # udp_dst_port: ['1024','65000'] + # `udp_port_step`: the step between two generated ports, default is equal to '1' + # + # NOTICE: + # Following TRex functionalities, incrementation and decrementation of source port and destination + # port values occur simultaneously. + # So, in order to reach the highest possible number of packets, it's recommended that the range of source ports + # minus the range of destination ports should be different of 1 + # i.e: |range[source_port] - range[destination_port]| = 1 udp_src_port: udp_dst_port: + udp_port_step: '1' # VxLAN only: optionally specify what VLAN tag to use for the VxLAN overlay # This is used if the vxlan tunnels are running on a specific VLAN. diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py index 062d414..8ef0403 100755 --- a/nfvbench/traffic_client.py +++ b/nfvbench/traffic_client.py @@ -15,6 +15,7 @@ """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 @@ -126,16 +127,25 @@ class IpBlock(object): 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): @@ -143,6 +153,17 @@ class IpBlock(object): 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. @@ -154,7 +175,11 @@ class Device(object): """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 @@ -175,10 +200,58 @@ class Device(object): 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) @@ -186,8 +259,40 @@ class Device(object): 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.""" @@ -288,14 +393,27 @@ class Device(object): # 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, @@ -308,8 +426,14 @@ class Device(object): '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), @@ -387,7 +511,7 @@ class GeneratorConfig(object): 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 @@ -395,8 +519,7 @@ class GeneratorConfig(object): 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] diff --git a/nfvbench/traffic_gen/trex_gen.py b/nfvbench/traffic_gen/trex_gen.py index af70cde..189c3e5 100644 --- a/nfvbench/traffic_gen/trex_gen.py +++ b/nfvbench/traffic_gen/trex_gen.py @@ -387,19 +387,28 @@ class TRex(AbstractTrafficGenerator): udp_args = {} if stream_cfg['udp_src_port']: udp_args['sport'] = int(stream_cfg['udp_src_port']) + udp_args['sport_step'] = int(stream_cfg['udp_port_step']) + udp_args['sport_max'] = int(stream_cfg['udp_src_port_max']) if stream_cfg['udp_dst_port']: udp_args['dport'] = int(stream_cfg['udp_dst_port']) - pkt_base /= IP() / UDP(**udp_args) + udp_args['dport_step'] = int(stream_cfg['udp_port_step']) + udp_args['dport_max'] = int(stream_cfg['udp_dst_port_max']) + pkt_base /= IP(src=stream_cfg['ip_src_addr'], dst=stream_cfg['ip_dst_addr']) / \ + UDP(dport=udp_args['dport'], sport=udp_args['sport']) + if stream_cfg['ip_src_static'] is True: + src_max_ip_value = stream_cfg['ip_src_addr'] + else: + src_max_ip_value = stream_cfg['ip_src_addr_max'] if stream_cfg['ip_addrs_step'] == 'random': - src_fv = STLVmFlowVarRepeatableRandom( + src_fv_ip = STLVmFlowVarRepeatableRandom( name="ip_src", min_value=stream_cfg['ip_src_addr'], - max_value=stream_cfg['ip_src_addr_max'], + max_value=src_max_ip_value, size=4, seed=random.randint(0, 32767), limit=stream_cfg['ip_src_count']) - dst_fv = STLVmFlowVarRepeatableRandom( + dst_fv_ip = STLVmFlowVarRepeatableRandom( name="ip_dst", min_value=stream_cfg['ip_dst_addr'], max_value=stream_cfg['ip_dst_addr_max'], @@ -407,14 +416,14 @@ class TRex(AbstractTrafficGenerator): seed=random.randint(0, 32767), limit=stream_cfg['ip_dst_count']) else: - src_fv = STLVmFlowVar( + src_fv_ip = STLVmFlowVar( name="ip_src", min_value=stream_cfg['ip_src_addr'], - max_value=stream_cfg['ip_src_addr'], + max_value=src_max_ip_value, size=4, op="inc", step=stream_cfg['ip_addrs_step']) - dst_fv = STLVmFlowVar( + dst_fv_ip = STLVmFlowVar( name="ip_dst", min_value=stream_cfg['ip_dst_addr'], max_value=stream_cfg['ip_dst_addr_max'], @@ -422,18 +431,49 @@ class TRex(AbstractTrafficGenerator): op="inc", step=stream_cfg['ip_addrs_step']) - vm_param.extend([ - src_fv, + if stream_cfg['udp_port_step'] == 'random': + src_fv_port = STLVmFlowVarRepeatableRandom( + name="p_src", + min_value=udp_args['sport'], + max_value=udp_args['sport_max'], + size=2, + seed=random.randint(0, 32767), + limit=udp_args['udp_src_count']) + dst_fv_port = STLVmFlowVarRepeatableRandom( + name="p_dst", + min_value=udp_args['dport'], + max_value=udp_args['dport_max'], + size=2, + seed=random.randint(0, 32767), + limit=stream_cfg['udp_dst_count']) + else: + src_fv_port = STLVmFlowVar( + name="p_src", + min_value=udp_args['sport'], + max_value=udp_args['sport_max'], + size=2, + op="inc", + step=udp_args['sport_step']) + dst_fv_port = STLVmFlowVar( + name="p_dst", + min_value=udp_args['dport'], + max_value=udp_args['dport_max'], + size=2, + op="inc", + step=udp_args['dport_step']) + vm_param = [ + src_fv_ip, STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)), - dst_fv, - STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)) - ]) - - for encap in range(int(encap_level), -1, -1): - # Fixing the checksums for all encap levels - vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap), - l4_offset="UDP:{}".format(encap), - l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP)) + src_fv_port, + STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)), + dst_fv_ip, + STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)), + dst_fv_port, + STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)), + STLVmFixChecksumHw(l3_offset="IP:{}".format(encap_level), + l4_offset="UDP:{}".format(encap_level), + l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP) + ] pad = max(0, frame_size - len(pkt_base)) * 'x' return STLPktBuilder(pkt=pkt_base / pad, @@ -849,8 +889,8 @@ class TRex(AbstractTrafficGenerator): bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1]) # ports must be set in service in order to enable capture self.client.set_service_mode(ports=self.port_handle) - self.capture_id = self.client.start_capture(rx_ports=self.port_handle, - bpf_filter=bpf_filter) + self.capture_id = self.client.start_capture \ + (rx_ports=self.port_handle, bpf_filter=bpf_filter) def fetch_capture_packets(self): """Fetch capture packets in capture mode.""" -- cgit 1.2.3-korg