NFVBENCH-158 Allow multiple UDP ports in traffic generation 47/69647/3 4.2.0
authorJules Boudaud <jules.boudaud@orange.com>
Thu, 19 Dec 2019 10:18:41 +0000 (11:18 +0100)
committerfmenguy <francoisregis.menguy@orange.com>
Fri, 7 Feb 2020 09:16:50 +0000 (10:16 +0100)
Change-Id: Id7c3ccad01fd9dda6c7cddb576735b429eb987a4
Signed-off-by: fmenguy <francoisregis.menguy@orange.com>
docker/Dockerfile
docs/testing/user/userguide/advanced.rst
nfvbench/cfg.default.yaml
nfvbench/traffic_client.py
nfvbench/traffic_gen/trex_gen.py
test/test_nfvbench.py

index 50d37cf..1cb7c71 100644 (file)
@@ -2,7 +2,7 @@
 FROM ubuntu:16.04
 
 ENV TREX_VER "v2.61"
-ENV VM_IMAGE_VER "0.11"
+ENV VM_IMAGE_VER "0.12"
 ENV PYTHONIOENCODING "utf8"
 
 # Note: do not clone with --depth 1 as it will cause pbr to fail extracting the nfvbench version
index 5424a05..5307bd0 100644 (file)
@@ -263,6 +263,24 @@ These can also be written in CIDR notation to represent the subnet.
 The corresponding ``step`` is used for ranging the IP addresses from the `ip_addrs``, ``tg_gateway_ip_addrs`` and ``gateway_ip_addrs`` base addresses.
 0.0.0.1 is the default step for all IP ranges. In ``ip_addrs``, 'random' can be configured which tells NFVBench to generate random src/dst IP pairs in the traffic stream.
 
+UDP ports can be controlled with the following NFVbench configuration options:
+
+.. code-block:: bash
+
+    udp_src_port: ['1024', '65000']
+    udp_dst_port: 53
+    udp_port_step: 1
+
+``udp_src_port`` and ``udp_dst_port`` are the UDP port value used by the traffic generators.
+These can be written for unique port or range ports for all flow.
+
+The corresponding ``udp_port_step`` is used for ranging the UDP port.
+1 is the default step for all UDP ranges, 'random' can be configured which tells NFVBench to generate random src/dst UDP pairs in the traffic stream.
+
+NB:
+    Use of UDP range will increase possible values of flows (based on ip src/dst and port src/dst tuple).
+    NFVBench will calculate the least common multiple for this tuple to adapt flows generation to ``flow_count`` parameter.
+
 
 Traffic Configuration via CLI
 -----------------------------
index 97f98cd..d154318 100755 (executable)
@@ -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.
index 062d414..8ef0403 100755 (executable)
@@ -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]
index af70cde..189c3e5 100644 (file)
@@ -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."""
index fa0e098..5274557 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 #
+from mock import patch
+import pytest
+
+from .mock_trex import no_op
+
 import json
 import logging
 import sys
-
 from attrdict import AttrDict
-from mock import patch
-import pytest
-
 from nfvbench.config import config_loads
 from nfvbench.credentials import Credentials
 from nfvbench.fluentd import FluentLogHandler
@@ -32,7 +33,6 @@ from nfvbench.traffic_client import IpBlock
 from nfvbench.traffic_client import TrafficClient
 import nfvbench.traffic_gen.traffic_utils as traffic_utils
 
-from .mock_trex import no_op
 
 # just to get rid of the unused function warning
 no_op()
@@ -173,6 +173,63 @@ def test_ip_block():
     with pytest.raises(IndexError):
         ipb.get_ip(256)
 
+    ipb = IpBlock('10.0.0.0', '0.0.0.2', 128)
+    assert ipb.get_ip() == '10.0.0.0'
+    assert ipb.get_ip(1) == '10.0.0.2'
+    assert ipb.get_ip(127) == '10.0.0.254'
+    with pytest.raises(IndexError):
+        ipb.get_ip(128)
+
+    ipb = IpBlock('10.0.0.0', '0.0.0.4', 64)
+    assert ipb.get_ip() == '10.0.0.0'
+    assert ipb.get_ip(1) == '10.0.0.4'
+    assert ipb.get_ip(63) == '10.0.0.252'
+    with pytest.raises(IndexError):
+        ipb.get_ip(64)
+
+    ipb = IpBlock('10.0.0.0', '0.0.0.10', 1)
+    assert ipb.get_ip() == '10.0.0.0'
+    with pytest.raises(IndexError):
+        ipb.get_ip(1)
+
+
+def test_lcm():
+    assert Device.lcm(10, 2) == 10
+    assert Device.lcm(1, 256) == 256
+    assert Device.lcm(10, 256) == 1280
+    assert Device.lcm(Device.lcm(10, 2), Device.lcm(1, 256))
+    with pytest.raises(TypeError):
+        Device.lcm(0, 0)
+
+
+def test_check_ipsize():
+    assert Device.check_ipsize(256, 1) == 256
+    assert Device.check_ipsize(256, 3) == 86
+    assert Device.check_ipsize(256, 4) == 64
+    assert Device.check_ipsize(16, 10) == 2
+    assert Device.check_ipsize(1, 10) == 1
+    with pytest.raises(ZeroDivisionError):
+        Device.check_ipsize(256, 0)
+
+
+def test_reserve_ip_range():
+    ipb = IpBlock('10.0.0.0', '0.0.0.1', 256)
+    src_ip_first, src_ip_last = ipb.reserve_ip_range(256, False)
+    assert src_ip_first == "10.0.0.0"
+    assert src_ip_last == "10.0.0.255"
+    ipb = IpBlock('20.0.0.0', '0.0.0.1', 2)
+    src_ip_first, src_ip_last = ipb.reserve_ip_range(2, False)
+    assert src_ip_first == "20.0.0.0"
+    assert src_ip_last == "20.0.0.1"
+    ipb = IpBlock('30.0.0.0', '0.0.0.1', 2)
+    src_ip_first, src_ip_last = ipb.reserve_ip_range(256, True)
+    assert src_ip_first == "30.0.0.0"
+    assert src_ip_last == "30.0.0.1"
+    ipb = IpBlock('40.0.0.0', '0.0.0.1', 2)
+    with pytest.raises(IndexError):
+        ipb.reserve_ip_range(256, False)
+
+
 def check_stream_configs(gen_config):
     """Verify that the range for each chain have adjacent IP ranges without holes between chains."""
     config = gen_config.config
@@ -202,6 +259,78 @@ def test_device_flow_config():
     _check_device_flow_config('0.0.0.1')
     _check_device_flow_config('0.0.0.2')
 
+
+def check_udp_stream_configs(gen_config, expected):
+    """Verify that the range for each chain have adjacent UDP ports without holes between chains."""
+    config = gen_config.config
+    stream_configs = gen_config.devices[0].get_stream_configs()
+    for index in range(config['service_chain_count']):
+        stream_cfg = stream_configs[index]
+        assert stream_cfg['ip_src_addr'] == expected['ip_src_addr']
+        assert stream_cfg['ip_src_addr_max'] == expected['ip_src_addr_max']
+        assert stream_cfg['ip_dst_addr'] == expected['ip_dst_addr']
+        assert stream_cfg['ip_dst_addr_max'] == expected['ip_dst_addr_max']
+        assert stream_cfg['udp_src_port'] == expected['udp_src_port']
+        assert stream_cfg['udp_src_port_max'] == expected['udp_src_port_max']
+        assert stream_cfg['udp_src_count'] == expected['udp_src_count']
+        assert stream_cfg['udp_dst_port'] == expected['udp_dst_port']
+        assert stream_cfg['udp_dst_port_max'] == expected['udp_dst_port_max']
+        assert stream_cfg['udp_dst_count'] == expected['udp_dst_count']
+
+
+def _check_device_udp_flow_config(param, expected):
+    config = _get_dummy_tg_config('PVP', '1Mpps',
+                                  ip_src_static=param['ip_src_static'],
+                                  fc=param['flow_count'],
+                                  ip0=param['ip_src_addr'],
+                                  ip1=param['ip_dst_addr'],
+                                  src_udp=param['udp_src_port'],
+                                  dst_udp=param['udp_dst_port'])
+    gen_config = GeneratorConfig(config)
+    check_udp_stream_configs(gen_config, expected)
+
+
+def test_device_udp_flow_config():
+    param = {'ip_src_static': True,
+             'ip_src_addr': '110.0.0.0/32',
+             'ip_dst_addr': '120.0.0.0/32',
+             'udp_src_port': 53,
+             'udp_dst_port': 53,
+             'flow_count': 2}
+    expected = {'ip_src_addr': '110.0.0.0',
+                'ip_src_addr_max': '110.0.0.0',
+                'ip_dst_addr': '120.0.0.0',
+                'ip_dst_addr_max': '120.0.0.0',
+                'udp_src_port': 53,
+                'udp_src_port_max': 53,
+                'udp_src_count': 1,
+                'udp_dst_port': 53,
+                'udp_dst_port_max': 53,
+                'udp_dst_count': 1}
+    _check_device_udp_flow_config(param, expected)
+    # Overwrite the udp_src_port value to define a large range of ports
+    # instead of a single port, in order to check if the imposed
+    # flow count is respected. Notice that udp range >> flow count.
+    param['udp_src_port'] = [53, 1024]
+    param['flow_count'] = 10
+    expected['udp_src_port_max'] = 57
+    expected['udp_src_count'] = 5
+    _check_device_udp_flow_config(param, expected)
+    # Re affect the default udp_src_port values and
+    # overwrite the ip_dst_addr value to define a large range of addresses
+    # instead of a single one, in order to check if the imposed
+    # flow count is respected. Notice that the netmask allows a very larger
+    # range of possible addresses than the flow count value.
+    param['udp_src_port'] = 53
+    expected['udp_src_port_max'] = 53
+    expected['udp_src_count'] = 1
+    param['ip_src_static'] = False
+    param['ip_dst_addr'] = '120.0.0.0/24'
+    expected['ip_dst_addr'] = '120.0.0.0'
+    expected['ip_dst_addr_max'] = '120.0.0.4'
+    _check_device_udp_flow_config(param, expected)
+
+
 def test_config():
     refcfg = {1: 100, 2: {21: 100, 22: 200}, 3: None}
     res1 = {1: 10, 2: {21: 100, 22: 200}, 3: None}
@@ -290,7 +419,8 @@ def assert_ndr_pdr(stats, ndr, ndr_dr, pdr, pdr_dr):
     assert_equivalence(pdr_dr, stats['pdr']['stats']['overall']['drop_percentage'])
 
 def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
-                         ip0='10.0.0.0/8', ip1='20.0.0.0/8'):
+                         ip0='10.0.0.0/8', ip1='20.0.0.0/8',
+                         step_udp=1, src_udp=None, dst_udp=None, ip_src_static=True):
     return AttrDict({
         'traffic_generator': {'host_name': 'nfvbench_tg',
                               'default_profile': 'dummy',
@@ -302,14 +432,16 @@ def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1',
                                                                     {'port': 1, 'pci': '0.0'}]}],
                               'ip_addrs_step': step_ip,
                               'ip_addrs': [ip0, ip1],
+                              'ip_src_static': ip_src_static,
                               'tg_gateway_ip_addrs': ['1.1.0.100', '2.2.0.100'],
                               'tg_gateway_ip_addrs_step': step_ip,
                               'gateway_ip_addrs': ['1.1.0.2', '2.2.0.2'],
                               'gateway_ip_addrs_step': step_ip,
                               'mac_addrs_left': None,
                               'mac_addrs_right': None,
-                              'udp_src_port': None,
-                              'udp_dst_port': None},
+                              'udp_src_port': src_udp,
+                              'udp_dst_port': dst_udp,
+                              'udp_port_step': step_udp},
         'traffic': {'profile': 'profile_64',
                     'bidirectional': True},
         'traffic_profile': [{'name': 'profile_64', 'l2frame_size': ['64']}],