diff options
-rw-r--r-- | docs/testing/user/userguide/index.rst | 1 | ||||
-rw-r--r-- | docs/testing/user/userguide/mpls.rst | 93 | ||||
-rwxr-xr-x | nfvbench/cfg.default.yaml | 80 | ||||
-rw-r--r-- | nfvbench/chain_runner.py | 25 | ||||
-rw-r--r-- | nfvbench/chaining.py | 36 | ||||
-rw-r--r-- | nfvbench/nfvbench.py | 16 | ||||
-rw-r--r-- | nfvbench/specs.py | 2 | ||||
-rwxr-xr-x | nfvbench/traffic_client.py | 84 | ||||
-rw-r--r-- | nfvbench/traffic_gen/trex_gen.py | 22 | ||||
-rw-r--r-- | nfvbench/utils.py | 4 | ||||
-rw-r--r-- | pylint.rc | 4 | ||||
-rw-r--r-- | test-requirements.txt | 1 | ||||
-rw-r--r-- | test/test_chains.py | 1 | ||||
-rw-r--r-- | test/test_nfvbench.py | 1 |
14 files changed, 321 insertions, 49 deletions
diff --git a/docs/testing/user/userguide/index.rst b/docs/testing/user/userguide/index.rst index 84c79b0..a61fa46 100644 --- a/docs/testing/user/userguide/index.rst +++ b/docs/testing/user/userguide/index.rst @@ -25,6 +25,7 @@ Table of Content examples advanced pvpl3 + mpls extchains fluentd sriov diff --git a/docs/testing/user/userguide/mpls.rst b/docs/testing/user/userguide/mpls.rst new file mode 100644 index 0000000..eaa9541 --- /dev/null +++ b/docs/testing/user/userguide/mpls.rst @@ -0,0 +1,93 @@ +========================== +MPLS encapsulation feature +========================== + +This feature allows to generate packets with standard MPLS L2VPN double stack MPLS labels, where the outer label is transport and the inner label is VPN. +The top layer of a packets encapsulated inside MPLS L2VPN seems to be an Ethernet layer with the rest of the IP stack inside. +Please refer to RFC-3031 for more details. +The whole MPLS packet structure looks like the following: + +###[ Ethernet ]### + dst = ['00:8a:96:bb:14:28'] + src = 3c:fd:fe:a3:48:7c + type = 0x8847 +###[ MPLS ]### <-------------- Outer Label + label = 16303 + cos = 1 + s = 0 + ttl = 255 +###[ MPLS ]### <-------------- Inner Label + label = 5010 + cos = 1 + s = 1 + ttl = 255 +###[ Ethernet ]### + dst = fa:16:3e:bd:02:b5 + src = 3c:fd:fe:a3:48:7c + type = 0x800 +###[ IP ]### + version = 4 + ihl = None + tos = 0x0 + len = None + id = 1 + flags = + frag = 0 + ttl = 64 + proto = udp + chksum = None + src = 16.0.0.1 + dst = 48.0.0.1 + \options \ +###[ UDP ]### + sport = 53 + dport = 53 + len = None + chksum = None + +Example: nfvbench generates mpls traffic port A ----> port B. This example assumes openstack is at the other end of the mpls tunnels. +Packets generated and sent to port B are delivered to the MPLS domain infrastructure which will transport that packet to the other end +of the MPLS transport tunnel using the outer label. At that point, the outer label is decapsulated and the inner label is used to +select the destination openstack network. After decapsulation of the inner label, the resulting L2 frame is then forwarded to the +destination VM corresponding to the destination MAC. When the VM receives the packet, it is sent back to far end port of the traffic +generator (port B) using either L2 forwarding or L3 routing though the peer virtual interface. The return packet is then encapsulated +with the inner label first then outer label to reach nfvbench on port B. + +Only 2 MPLS labels stack is supported. If more than two labels stack is required then these operations should be handled by MPLS transport +domain where nfvbench is attached next-hop mpls router and rest of the mpls domain should be configured accordingly to be able +pop/swap/push labels and deliver packet to the proper destination based on an initial transport label injected by nfvbench, VPN label +should stay unchanged until its delivered to PE (compute node). +Set nfvbench 'mpls' parameter to 'true' to enable MPLS encapsulation. +When this option is enabled internal networks 'network type' parameter value should be 'mpls' +MPLS and VxLAN encapsulations are mutual exclusive features if 'mpls' is 'true' then 'vxlan' should be set to 'false' and vise versa. +no_flow_stats, no_latency_stats, no_latency_streams parameters should be set to 'true' because these features are not supported at the moment. +In future when these features will be supported they will require special NIC hardware. + +Example of 1-chain MPLS configuration: + internal_networks: + left: + network_type: mpls + segmentation_id: 5010 + mpls_transport_labels: 16303 + physical_network: phys_sriov0 + right: + network_type: mpls + segmentation_id: 5011 + mpls_transport_labels: 16303 + physical_network: phys_sriov1 + +Example of 2-chain MPLS configuration: + internal_networks: + left: + network_type: mpls + segmentation_id: [5010, 5020] + mpls_transport_labels: [16303, 16304] + physical_network: phys_sriov0 + right: + network_type: mpls + segmentation_id: [5011, 5021] + mpls_transport_labels: [16303, 16304] + physical_network: phys_sriov1 + +Example of how to run: +nfvbench --rate 50000pps --duration 30 --mpls diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml index 2abc8dc..97f98cd 100755 --- a/nfvbench/cfg.default.yaml +++ b/nfvbench/cfg.default.yaml @@ -187,11 +187,14 @@ traffic_generator: # Leave empty if there is no VLAN tagging required, or specify the VLAN id to use # for all VxLAN tunneled traffic vtep_vlan: - # VxLAN only: local/source vteps IP addresses for port 0 and 1 ['10.1.1.230', '10.1.1.231'] + # VxLAN and MPLS only: local/source vteps IP addresses for port 0 and 1 ['10.1.1.230', '10.1.1.231'] src_vteps: # VxLAN only: remote IP address of the remote VTEPs that terminate all tunnels originating from local VTEPs dst_vtep: - + # The encapsulated L3/MPLS packet needs to traverse L3 or MPLS fabric to reach to its final dst_vtep. + # This parameter is required to resolve first next-hop MAC address if it next-hop is not its final dst_vtep. + # This parameter is mandatory for MPLS only + vtep_gateway_ips: # L2 ADDRESSING OF UDP PACKETS # Lists of dest MAC addresses to use on each traffic generator port (one dest MAC per chain) # Leave empty for PVP, PVVP, EXT with ARP @@ -369,7 +372,7 @@ loop_vm_name: 'nfvbench-loop-vm' # segmentation_id: 2001 # physical_network: phys_sriov1 # -# For multi-chaining and non shared network mode (VLAN, SRIOV, VxLAN): +# For multi-chaining and non shared network mode (VLAN, SRIOV, VxLAN, MPLS): # - the segmentation_id field if provided must be a list of values (as many as chains) # - segmentation_id auto-indexing: # the segmentation_id field can also be a single value that represents the base value from which @@ -379,23 +382,42 @@ loop_vm_name: 'nfvbench-loop-vm' # - the physical_network can be a single name (all VFs to be allocated on same physnet) # of a list of physnet names to use different PFs # -# Example of 2-chain configuration: -# internal_networks: -# left: -# segmentation_id: [2000, 2001] -# physical_network: phys_sriov0 -# right: -# segmentation_id: [2010, 2011] -# physical_network: phys_sriov1 +# Example of 2-chain VLAN configuration: +# internal_networks: +# left: +# segmentation_id: [2000, 2001] +# physical_network: phys_sriov0 +# right: +# segmentation_id: [2010, 2011] +# physical_network: phys_sriov1 +# Equivalent to (using auto-indexing): +# internal_networks: +# left: +# segmentation_id: 2000 +# physical_network: phys_sriov0 +# right: +# segmentation_id: 2010 +# physical_network: phys_sriov1 # -# Equivalent to (using auto-indexing): -# internal_networks: -# left: -# segmentation_id: 2000 -# physical_network: phys_sriov0 -# right: -# segmentation_id: 2010 -# physical_network: phys_sriov1 +# - mpls_transport_labels is used only when MPLS encapsulation is enabled (mpls: true) +# this parameter doesn't support auto-indexing because this is not a typical scenario +# expected the list of values in a range 256-1048575, one value per chain is expected +# +# In the bellow configuration example 'segmentation_id; contains the inner MPLS label for each chain +# and 'mpls_transport_labels' contains the outer transport MPLS label for each chain +# Example of 2-chain MPLS configuration: +# internal_networks: +# left: +# network_type: mpls +# segmentation_id: [2000, 2001] +# mpls_transport_labels: [10000, 10000] +# physical_network: phys_sriov0 +# right: +# network_type: mpls +# segmentation_id: [2010, 2011] +# mpls_transport_labels: [11000, 11000] +# physical_network: phys_sriov1 + internal_networks: left: @@ -405,6 +427,7 @@ internal_networks: network_type: 'vlan' segmentation_id: physical_network: + mpls_transport_labels: right: name: 'nfvbench-rnet' subnet: 'nfvbench-rsubnet' @@ -412,6 +435,7 @@ internal_networks: network_type: 'vlan' segmentation_id: physical_network: + mpls_transport_labels: middle: name: 'nfvbench-mnet' subnet: 'nfvbench-msubnet' @@ -419,6 +443,7 @@ internal_networks: network_type: 'vlan' segmentation_id: physical_network: + mpls_transport_labels: # IDLE INTERFACES: PVP, PVVP and non shared net only. # By default each test VM will have 2 virtual interfaces for looping traffic. @@ -436,7 +461,7 @@ idle_networks: # Prefix for all idle networks, the final name will append the chain ID and idle index # e.g. "nfvbench-idle-net.0.4" chain 0 idle index 4 name: 'nfvbench-idle-net' - # Subnet name to use for all idle subnetworks + # Subnet name to use for all idle subnetworks subnet: 'nfvbench-idle-subnet' # CIDR to use for all idle networks (value should not matter) cidr: '192.169.1.0/24' @@ -537,7 +562,7 @@ use_sriov_middle_net: false # external_networks: # left: ['ext-lnet', 'ext-lnet2'] # right: ['ext-rnet', 'ext-rnet2'] -# +# external_networks: left: right: @@ -578,14 +603,23 @@ edge_networks: # Use 'true' to enable VXLAN encapsulation support and sent by the traffic generator # When this option enabled internal networks 'network type' parameter value should be 'vxlan' +# VxLAN and MPLS encapsulations are mutual exclusive if 'vxlan' is true then 'mpls' should be false +# and vise versa vxlan: false - +# Use 'true' to enable MPLS encapsulation support and sent by the traffic generator +# When this option enabled internal networks 'network type' parameter value should be 'mpls' +# MPLS and VxLAN encapsulations are mutual exclusive if 'mpls' is 'true' then 'vxlan' should be set to 'false' +# and vise versa. no_flow_stats, no_latency_stats, no_latency_streams should be set to 'true' because these +# features are not supported at the moment. In future when these features will be supported they will require +# special NIC hardware. Only 2 label stack supported at the moment where one label is transport and another +# is VPN for more details please refer to 'mpls_transport_labels' and 'segmentation_id' in networks configuration +mpls: false # Use 'true' to enable VLAN tagging of packets generated and sent by the traffic generator # Leave empty or set to false if you do not want the traffic generator to insert the VLAN tag (this is # needed for example if VLAN tagging is enabled on switch (access mode) or if you want to hook # directly to a NIC). # By default is set to true (which is the nominal use case with TOR and trunk mode to Trex ports) -# If VxLAN is enabled, this option should be set to false (vlan tagging for encapsulated packets +# If VxLAN or MPLS are enabled, this option should be set to false (vlan tagging for encapsulated packets # is not supported). Use the vtep_vlan option to enable vlan tagging for the VxLAN overlay network. vlan_tagging: true diff --git a/nfvbench/chain_runner.py b/nfvbench/chain_runner.py index bb35426..62a3751 100644 --- a/nfvbench/chain_runner.py +++ b/nfvbench/chain_runner.py @@ -96,6 +96,22 @@ class ChainRunner(object): gen_config.set_vxlan_endpoints(1, src_vteps[1], dst_vtep) self.config['vxlan_gen_config'] = gen_config + if config.mpls: + # MPLS VPN is discovered from the networks + src_vteps = gen_config.gen_config.src_vteps + vtep_gateway_ips = gen_config.gen_config.vtep_gateway_ips + gen_config.set_mpls_inner_labels(0, self.chain_manager.get_chain_mpls_inner_labels(0)) + gen_config.set_mpls_inner_labels(1, self.chain_manager.get_chain_mpls_inner_labels(1)) + outer_mpls_labels_left = self.config.internal_networks.left.mpls_transport_labels + outer_mpls_labels_right = self.config.internal_networks.right.mpls_transport_labels + if outer_mpls_labels_left or outer_mpls_labels_right: + gen_config.set_mpls_outer_labels(0, outer_mpls_labels_left) + gen_config.set_mpls_outer_labels(1, outer_mpls_labels_right) + # Configuring source an remote VTEPs on TREx interfaces + gen_config.set_mpls_peers(0, src_vteps[0], vtep_gateway_ips[0]) + gen_config.set_mpls_peers(1, src_vteps[1], vtep_gateway_ips[1]) + self.config['mpls_gen_config'] = gen_config + # get an instance of the stats manager self.stats_manager = StatsManager(self) LOG.info('ChainRunner initialized') @@ -103,8 +119,8 @@ class ChainRunner(object): def __setup_traffic(self): self.traffic_client.setup() if not self.config.no_traffic: - # ARP is needed for EXT chain or VxLAN overlay unless disabled explicitly - if (self.config.service_chain == ChainType.EXT or + # ARP is needed for EXT chain or VxLAN overlay or MPLS unless disabled explicitly + if (self.config.service_chain == ChainType.EXT or self.config.mpls or self.config.vxlan or self.config.l3_router or self.config.loop_vm_arp)\ and not self.config.no_arp: self.traffic_client.ensure_arp_successful() @@ -167,10 +183,9 @@ class ChainRunner(object): LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name) self.stats_manager.create_worker() - if self.config.vxlan: - # Configure vxlan tunnels + if self.config.vxlan or self.config.mpls: + # Configure vxlan or mpls tunnels self.stats_manager.worker.config_interfaces() - self.__setup_traffic() results[self.chain_name] = {'result': self.__get_chain_result()} diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py index 71693be..b9ed48b 100644 --- a/nfvbench/chaining.py +++ b/nfvbench/chaining.py @@ -370,6 +370,15 @@ class ChainNetwork(object): return self.network['provider:segmentation_id'] + def get_mpls_inner_label(self): + """ + Extract MPLS VPN Label for this network. + + :return: MPLS VPN Label for this network + """ + + return self.network['provider:segmentation_id'] + def delete(self): """Delete this network.""" if not self.reuse and self.network: @@ -931,6 +940,20 @@ class Chain(object): port_index = -1 return self.networks[port_index].get_vxlan() + def get_mpls_inner_label(self, port_index): + """Get the MPLS VPN Label on a given port. + + port_index: left port is 0, right port is 1 + return: the mpls_label_id or None if there is no mpls + """ + # for port 1 we need to return the MPLS Label of the last network in the chain + # The networks array contains 2 networks for PVP [left, right] + # and 3 networks in the case of PVVP [left.middle,right] + if port_index: + # this will pick the last item in array + port_index = -1 + return self.networks[port_index].get_mpls_inner_label() + def get_dest_mac(self, port_index): """Get the dest MAC on a given port. @@ -1248,7 +1271,6 @@ class ChainManager(object): for chain in self.chains: instances.extend(chain.get_instances()) initial_instance_count = len(instances) - # Give additional 10 seconds per VM max_retries = (self.config.check_traffic_time_sec + (initial_instance_count - 1) * 10 + self.config.generic_poll_sec - 1) / self.config.generic_poll_sec retry = 0 @@ -1426,6 +1448,18 @@ class ChainManager(object): # no openstack raise ChainException('VxLAN is only supported with OpenStack and with admin user') + def get_chain_mpls_inner_labels(self, port_index): + """Get the list of per chain MPLS VPN Labels on a given port. + + port_index: left port is 0, right port is 1 + return: a MPLSs ID list indexed by the chain index or None if no mpls + """ + if self.chains and self.is_admin: + return [self.chains[chain_index].get_mpls_inner_label(port_index) + for chain_index in range(self.chain_count)] + # no openstack + raise ChainException('MPLS is only supported with OpenStack and with admin user') + def get_dest_macs(self, port_index): """Get the list of per chain dest MACs on a given port. diff --git a/nfvbench/nfvbench.py b/nfvbench/nfvbench.py index 168c545..50b96b6 100644 --- a/nfvbench/nfvbench.py +++ b/nfvbench/nfvbench.py @@ -233,11 +233,14 @@ class NFVBench(object): raise Exception('vif_multiqueue_size (%d) must be in [1..8]' % config.vif_multiqueue_size) - # VxLAN sanity checks - if config.vxlan: + # VxLAN and MPLS sanity checks + if config.vxlan or config.mpls: if config.vlan_tagging: config.vlan_tagging = False - LOG.info('VxLAN: vlan_tagging forced to False ' + config.no_latency_streams = True + config.no_latency_stats = True + config.no_flow_stats = True + LOG.info('VxLAN or MPLS: vlan_tagging forced to False ' '(inner VLAN tagging must be disabled)') self.config_plugin.validate_config(config, self.specs.openstack) @@ -359,6 +362,11 @@ def _parse_opts_from_cli(): action='store_true', help='Enable VxLan encapsulation') + parser.add_argument('--mpls', dest='mpls', + default=None, + action='store_true', + help='Enable MPLS encapsulation') + parser.add_argument('--no-cleanup', dest='no_cleanup', default=None, action='store_true', @@ -602,6 +610,8 @@ def main(): config.compute_nodes = opts.hypervisor if opts.vxlan: config.vxlan = True + if opts.mpls: + config.mpls = True if opts.restart: config.restart = True if opts.service_mode: diff --git a/nfvbench/specs.py b/nfvbench/specs.py index 75fe703..ec5e24e 100644 --- a/nfvbench/specs.py +++ b/nfvbench/specs.py @@ -17,11 +17,13 @@ class Encaps(object): VLAN = "VLAN" VxLAN = "VxLAN" + MPLS = "MPLS" NO_ENCAPS = "NONE" encaps_mapping = { 'VLAN': VLAN, 'VXLAN': VxLAN, + 'MPLS': MPLS, 'NONE': NO_ENCAPS } diff --git a/nfvbench/traffic_client.py b/nfvbench/traffic_client.py index a8573b0..062d414 100755 --- a/nfvbench/traffic_client.py +++ b/nfvbench/traffic_client.py @@ -26,6 +26,9 @@ from netaddr import IPNetwork 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 @@ -36,7 +39,6 @@ from .stats_collector import IterationCollector from .traffic_gen import traffic_utils as utils from .utils import cast_integer - class TrafficClientException(Exception): """Generic traffic client exception.""" @@ -158,6 +160,9 @@ class Device(object): 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 @@ -241,10 +246,25 @@ class Device(object): 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, @@ -296,11 +316,15 @@ class Device(object): '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 @@ -445,6 +469,28 @@ class GeneratorConfig(object): (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 @@ -454,6 +500,9 @@ class GeneratorConfig(object): 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) @@ -607,6 +656,9 @@ class TrafficClient(object): 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): @@ -624,11 +676,18 @@ class TrafficClient(object): 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') @@ -645,12 +704,16 @@ class TrafficClient(object): 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: @@ -676,6 +739,7 @@ class TrafficClient(object): 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, diff --git a/nfvbench/traffic_gen/trex_gen.py b/nfvbench/traffic_gen/trex_gen.py index c2e0854..af70cde 100644 --- a/nfvbench/traffic_gen/trex_gen.py +++ b/nfvbench/traffic_gen/trex_gen.py @@ -20,6 +20,9 @@ import time import traceback from itertools import count +# pylint: disable=import-error +from scapy.contrib.mpls import MPLS # flake8: noqa +# pylint: enable=import-error from nfvbench.log import LOG from nfvbench.traffic_server import TRexTrafficServer from nfvbench.utils import cast_integer @@ -363,6 +366,17 @@ class TRex(AbstractTrafficGenerator): op="random") vm_param = [vxlan_udp_src_fv, STLVmWrFlowVar(fv_name="vxlan_udp_src", pkt_offset="UDP.sport")] + elif stream_cfg['mpls'] is True: + encap_level = '0' + pkt_base = Ether(src=stream_cfg['vtep_src_mac'], dst=stream_cfg['vtep_dst_mac']) + if stream_cfg['vtep_vlan'] is not None: + pkt_base /= Dot1Q(vlan=stream_cfg['vtep_vlan']) + if stream_cfg['mpls_outer_label'] is not None: + pkt_base /= MPLS(label=stream_cfg['mpls_outer_label'], cos=1, s=0, ttl=255) + if stream_cfg['mpls_inner_label'] is not None: + pkt_base /= MPLS(label=stream_cfg['mpls_inner_label'], cos=1, s=1, ttl=255) + # Flow stats and MPLS labels randomization TBD + pkt_base /= Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst']) else: encap_level = '0' pkt_base = Ether(src=stream_cfg['mac_src'], dst=stream_cfg['mac_dst']) @@ -443,7 +457,7 @@ class TRex(AbstractTrafficGenerator): if l2frame == 'IMIX': for ratio, l2_frame_size in zip(IMIX_RATIOS, IMIX_L2_SIZES): pkt = self._create_pkt(stream_cfg, l2_frame_size) - if e2e: + if e2e or stream_cfg['mpls']: streams.append(STLStream(packet=pkt, mode=STLTXCont(pps=ratio))) else: @@ -466,8 +480,10 @@ class TRex(AbstractTrafficGenerator): else: l2frame_size = int(l2frame) pkt = self._create_pkt(stream_cfg, l2frame_size) - if e2e: + if e2e or stream_cfg['mpls']: streams.append(STLStream(packet=pkt, + # Flow stats is disabled for MPLS now + # flow_stats=STLFlowStats(pg_id=pg_id), mode=STLTXCont())) else: if stream_cfg['vxlan'] is True: @@ -648,7 +664,7 @@ class TRex(AbstractTrafficGenerator): dst_macs = [None] * chain_count dst_macs_count = 0 # the index in the list is the chain id - if self.config.vxlan: + if self.config.vxlan or self.config.mpls: arps = [ ServiceARP(ctx, src_ip=device.vtep_src_ip, diff --git a/nfvbench/utils.py b/nfvbench/utils.py index 3974fd7..c8c485f 100644 --- a/nfvbench/utils.py +++ b/nfvbench/utils.py @@ -113,8 +113,8 @@ def get_intel_pci(nic_slot=None, nic_ports=None): if nic_slot and nic_ports: dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot']) - regex = r"(?<=SlotID:%s).*?(....:..:..\..)" % nic_slot - match = re.search(regex, dmidecode, flags=re.DOTALL) + regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot) + match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL) if not match: return None @@ -197,7 +197,7 @@ indent-string=' ' max-line-length=100 # Maximum number of lines in a module -max-module-lines=1500 +max-module-lines=1600 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. @@ -361,7 +361,7 @@ max-args=12 max-attributes=32 # Maximum number of boolean expressions in a if statement -max-bool-expr=5 +max-bool-expr=6 # Maximum number of branch for function / method body max-branches=30 diff --git a/test-requirements.txt b/test-requirements.txt index 18f4952..a7e52ce 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,6 +7,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover python-subunit>=0.0.18 +scapy>=2.3.1 sphinx>=1.4.0 sphinx_rtd_theme>=0.1.9 oslosphinx>=2.5.0 # Apache-2.0 diff --git a/test/test_chains.py b/test/test_chains.py index 3cf75cb..a9df54f 100644 --- a/test/test_chains.py +++ b/test/test_chains.py @@ -72,6 +72,7 @@ def _get_chain_config(sc=ChainType.PVP, scc=1, shared_net=True, rate='1Mpps'): config.no_flow_stats = False config.no_latency_stats = False config.no_latency_streams = False + config.loop_vm_arp = True return config def test_chain_runner_ext_no_openstack(): diff --git a/test/test_nfvbench.py b/test/test_nfvbench.py index 4a8a574..fa0e098 100644 --- a/test/test_nfvbench.py +++ b/test/test_nfvbench.py @@ -341,6 +341,7 @@ def _get_dummy_tg_config(chain_type, rate, scc=1, fc=10, step_ip='0.0.0.1', def _get_traffic_client(): config = _get_dummy_tg_config('PVP', 'ndr_pdr') config['vxlan'] = False + config['mpls'] = False config['ndr_run'] = True config['pdr_run'] = True config['generator_profile'] = 'dummy' |