1 # Copyright (c) 2019 Viosoft 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.
18 from collections import Counter
21 from yardstick.benchmark.contexts.base import Context
22 from yardstick.common.process import check_if_process_failed
23 from yardstick.network_services import constants
24 from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNF
25 from yardstick.network_services.vnf_generic.vnf.vpp_helpers import \
26 VppSetupEnvHelper, VppConfigGenerator
28 LOG = logging.getLogger(__name__)
31 class CryptoAlg(Enum):
32 """Encryption algorithms."""
33 AES_CBC_128 = ('aes-cbc-128', 'AES-CBC', 16)
34 AES_CBC_192 = ('aes-cbc-192', 'AES-CBC', 24)
35 AES_CBC_256 = ('aes-cbc-256', 'AES-CBC', 32)
36 AES_GCM_128 = ('aes-gcm-128', 'AES-GCM', 20)
38 def __init__(self, alg_name, scapy_name, key_len):
39 self.alg_name = alg_name
40 self.scapy_name = scapy_name
41 self.key_len = key_len
45 """Integrity algorithms."""
46 SHA1_96 = ('sha1-96', 'HMAC-SHA1-96', 20)
47 SHA_256_128 = ('sha-256-128', 'SHA2-256-128', 32)
48 SHA_384_192 = ('sha-384-192', 'SHA2-384-192', 48)
49 SHA_512_256 = ('sha-512-256', 'SHA2-512-256', 64)
50 AES_GCM_128 = ('aes-gcm-128', 'AES-GCM', 20)
52 def __init__(self, alg_name, scapy_name, key_len):
53 self.alg_name = alg_name
54 self.scapy_name = scapy_name
55 self.key_len = key_len
58 class VipsecApproxSetupEnvHelper(VppSetupEnvHelper):
59 DEFAULT_IPSEC_VNF_CFG = {
60 'crypto_type': 'SW_cryptodev',
62 'worker_config': '1C/1T',
66 def __init__(self, vnfd_helper, ssh_helper, scenario_helper):
67 super(VipsecApproxSetupEnvHelper, self).__init__(
68 vnfd_helper, ssh_helper, scenario_helper)
70 def _get_crypto_type(self):
71 vnf_cfg = self.scenario_helper.options.get('vnf_config',
72 self.DEFAULT_IPSEC_VNF_CFG)
73 return vnf_cfg.get('crypto_type', 'SW_cryptodev')
75 def _get_crypto_algorithms(self):
76 vpp_cfg = self.scenario_helper.all_options.get('vpp_config', {})
77 return vpp_cfg.get('crypto_algorithms', 'aes-gcm')
79 def _get_n_tunnels(self):
80 vpp_cfg = self.scenario_helper.all_options.get('vpp_config', {})
81 return vpp_cfg.get('tunnels', 1)
83 def _get_n_connections(self):
85 flow_cfg = self.scenario_helper.all_options['flow']
86 return flow_cfg['count']
88 raise KeyError("Missing flow definition in scenario section" +
89 " of the task definition file")
91 def _get_flow_src_start_ip(self):
92 node_name = self.find_encrypted_data_interface()["node_name"]
94 flow_cfg = self.scenario_helper.all_options['flow']
95 src_ips = flow_cfg['src_ip']
96 dst_ips = flow_cfg['dst_ip']
98 raise KeyError("Missing flow definition in scenario section" +
99 " of the task definition file")
101 for src, dst in zip(src_ips, dst_ips):
102 flow_src_start_ip, _ = src.split('-')
103 flow_dst_start_ip, _ = dst.split('-')
105 if node_name == "vnf__0":
106 return flow_src_start_ip
107 elif node_name == "vnf__1":
108 return flow_dst_start_ip
110 def _get_flow_dst_start_ip(self):
111 node_name = self.find_encrypted_data_interface()["node_name"]
113 flow_cfg = self.scenario_helper.all_options['flow']
114 src_ips = flow_cfg['src_ip']
115 dst_ips = flow_cfg['dst_ip']
117 raise KeyError("Missing flow definition in scenario section" +
118 " of the task definition file")
120 for src, dst in zip(src_ips, dst_ips):
121 flow_src_start_ip, _ = src.split('-')
122 flow_dst_start_ip, _ = dst.split('-')
124 if node_name == "vnf__0":
125 return flow_dst_start_ip
126 elif node_name == "vnf__1":
127 return flow_src_start_ip
129 def build_config(self):
130 vnf_cfg = self.scenario_helper.options.get('vnf_config',
131 self.DEFAULT_IPSEC_VNF_CFG)
132 rxq = vnf_cfg.get('rxq', 1)
133 phy_cores = vnf_cfg.get('worker_threads', 1)
134 # worker_config = vnf_cfg.get('worker_config', '1C/1T').split('/')[1].lower()
136 vpp_cfg = self.create_startup_configuration_of_vpp()
137 self.add_worker_threads_and_rxqueues(vpp_cfg, phy_cores, rxq)
138 self.add_pci_devices(vpp_cfg)
140 frame_size_cfg = self.scenario_helper.all_options.get('framesize', {})
141 uplink_cfg = frame_size_cfg.get('uplink', {})
142 downlink_cfg = frame_size_cfg.get('downlink', {})
143 framesize = min(self.calculate_frame_size(uplink_cfg),
144 self.calculate_frame_size(downlink_cfg))
146 vpp_cfg.add_dpdk_no_multi_seg()
148 crypto_algorithms = self._get_crypto_algorithms()
149 if crypto_algorithms == 'aes-gcm':
150 self.add_dpdk_cryptodev(vpp_cfg, 'aesni_gcm', phy_cores)
151 elif crypto_algorithms == 'cbc-sha1':
152 self.add_dpdk_cryptodev(vpp_cfg, 'aesni_mb', phy_cores)
154 vpp_cfg.add_dpdk_dev_default_rxd(2048)
155 vpp_cfg.add_dpdk_dev_default_txd(2048)
156 self.apply_config(vpp_cfg, True)
157 self.update_vpp_interface_data()
159 def setup_vnf_environment(self):
160 resource = super(VipsecApproxSetupEnvHelper,
161 self).setup_vnf_environment()
163 self.start_vpp_service()
164 # for QAT device DH895xCC, the number of VFs is required as 32
165 if self._get_crypto_type() == 'HW_cryptodev':
166 sriov_numvfs = self.get_sriov_numvfs(
167 self.find_encrypted_data_interface()["vpci"])
168 if sriov_numvfs != 32:
169 self.crypto_device_init(
170 self.find_encrypted_data_interface()["vpci"], 32)
172 self._update_vnfd_helper(self.sys_cores.get_cpu_layout())
173 self.update_vpp_interface_data()
174 self.iface_update_numa()
179 def calculate_frame_size(frame_cfg):
183 imix_count = {size.upper().replace('B', ''): int(weight)
184 for size, weight in frame_cfg.items()}
185 imix_sum = sum(imix_count.values())
188 packets_total = sum([int(size) * weight
189 for size, weight in imix_count.items()])
190 return packets_total / imix_sum
192 def check_status(self):
193 ipsec_created = False
194 cmd = "vppctl show int"
195 _, stdout, _ = self.ssh_helper.execute(cmd)
196 entries = re.split(r"\n+", stdout)
197 tmp = [re.split(r"\s\s+", entry, 5) for entry in entries]
200 if isinstance(item, list):
201 if item[0] and item[0] != 'local0':
202 if "ipsec" in item[0] and not ipsec_created:
204 if len(item) > 2 and item[2] == 'down':
208 def get_vpp_statistics(self):
209 cmd = "vppctl show int {intf}"
211 for interface in self.vnfd_helper.interfaces:
212 iface_name = self.get_value_by_interface_key(
213 interface["virtual-interface"]["ifname"], "vpp_name")
214 command = cmd.format(intf=iface_name)
215 _, stdout, _ = self.ssh_helper.execute(command)
217 self.parser_vpp_stats(interface["virtual-interface"]["ifname"],
219 self.ssh_helper.execute("vppctl clear interfaces")
223 def parser_vpp_stats(interface, iface_name, stats):
229 entries = re.split(r"\n+", stats)
230 tmp = [re.split(r"\s\s+", entry, 5) for entry in entries]
233 if isinstance(item, list):
234 if item[0] == iface_name and len(item) >= 5:
235 if item[3] == 'rx packets':
236 packets_in = int(item[4])
237 elif item[4] == 'rx packets':
238 packets_in = int(item[5])
240 if item[1] == 'tx packets':
241 packets_fwd = int(item[2])
242 elif item[1] == 'drops' or item[1] == 'rx-miss':
243 packets_dropped = int(item[2])
244 if packets_dropped == 0 and packets_in > 0 and packets_fwd > 0:
245 packets_dropped = abs(packets_fwd - packets_in)
247 result[interface] = {
248 'packets_in': packets_in,
249 'packets_fwd': packets_fwd,
250 'packets_dropped': packets_dropped,
255 def create_ipsec_tunnels(self):
256 self.initialize_ipsec()
258 # TODO generate the same key
259 crypto_algorithms = self._get_crypto_algorithms()
260 if crypto_algorithms == 'aes-gcm':
261 encr_alg = CryptoAlg.AES_GCM_128
262 auth_alg = IntegAlg.AES_GCM_128
263 encr_key = 'LNYZXMBQDKESNLREHJMS'
264 auth_key = 'SWGLDTYZSQKVBZZMPIEV'
265 elif crypto_algorithms == 'cbc-sha1':
266 encr_alg = CryptoAlg.AES_CBC_128
267 auth_alg = IntegAlg.SHA1_96
268 encr_key = 'IFEMSHYLCZIYFUTT'
269 auth_key = 'PEALEIPSCPTRHYJSDXLY'
271 self.execute_script("enable_dpdk_traces.vat", json_out=False)
272 self.execute_script("enable_vhost_user_traces.vat", json_out=False)
273 self.execute_script("enable_memif_traces.vat", json_out=False)
275 node_name = self.find_encrypted_data_interface()["node_name"]
276 n_tunnels = self._get_n_tunnels()
277 n_connections = self._get_n_connections()
278 flow_dst_start_ip = self._get_flow_dst_start_ip()
279 if node_name == "vnf__0":
280 self.vpp_create_ipsec_tunnels(
281 self.find_encrypted_data_interface()["local_ip"],
282 self.find_encrypted_data_interface()["peer_intf"]["local_ip"],
283 self.find_encrypted_data_interface()["ifname"],
284 n_tunnels, n_connections, encr_alg, encr_key, auth_alg,
285 auth_key, flow_dst_start_ip)
286 elif node_name == "vnf__1":
287 self.vpp_create_ipsec_tunnels(
288 self.find_encrypted_data_interface()["local_ip"],
289 self.find_encrypted_data_interface()["peer_intf"]["local_ip"],
290 self.find_encrypted_data_interface()["ifname"],
291 n_tunnels, n_connections, encr_alg, encr_key, auth_alg,
292 auth_key, flow_dst_start_ip, 20000, 10000)
294 def find_raw_data_interface(self):
296 return self.vnfd_helper.find_virtual_interface(vld_id="uplink_0")
298 return self.vnfd_helper.find_virtual_interface(vld_id="downlink_0")
300 def find_encrypted_data_interface(self):
301 return self.vnfd_helper.find_virtual_interface(vld_id="ciphertext")
303 def create_startup_configuration_of_vpp(self):
304 vpp_config_generator = VppConfigGenerator()
305 vpp_config_generator.add_unix_log()
306 vpp_config_generator.add_unix_cli_listen()
307 vpp_config_generator.add_unix_nodaemon()
308 vpp_config_generator.add_unix_coredump()
309 vpp_config_generator.add_dpdk_socketmem('1024,1024')
310 vpp_config_generator.add_dpdk_no_tx_checksum_offload()
311 vpp_config_generator.add_dpdk_log_level('debug')
312 for interface in self.vnfd_helper.interfaces:
313 vpp_config_generator.add_dpdk_uio_driver(
314 interface["virtual-interface"]["driver"])
315 vpp_config_generator.add_heapsize('4G')
316 # TODO Enable configuration depend on VPP version
317 vpp_config_generator.add_statseg_size('4G')
318 vpp_config_generator.add_plugin('disable', ['default'])
319 vpp_config_generator.add_plugin('enable', ['dpdk_plugin.so'])
320 vpp_config_generator.add_ip6_hash_buckets('2000000')
321 vpp_config_generator.add_ip6_heap_size('4G')
322 vpp_config_generator.add_ip_heap_size('4G')
323 return vpp_config_generator
325 def add_worker_threads_and_rxqueues(self, vpp_cfg, phy_cores,
327 thr_count_int = phy_cores
328 cpu_count_int = phy_cores
329 num_mbufs_int = 32768
333 if_list = [self.find_encrypted_data_interface()["ifname"],
334 self.find_raw_data_interface()["ifname"]]
335 for if_key in if_list:
338 self.get_value_by_interface_key(if_key, 'numa_node'))
341 numa_cnt_mc = Counter(numa_list).most_common()
343 if numa_cnt_mc and numa_cnt_mc[0][0] is not None and \
344 numa_cnt_mc[0][0] != -1:
345 numa = numa_cnt_mc[0][0]
346 elif len(numa_cnt_mc) > 1 and numa_cnt_mc[0][0] == -1:
347 numa = numa_cnt_mc[1][0]
352 smt_used = self.sys_cores.is_smt_enabled()
356 cpu_main = self.sys_cores.cpu_list_per_node_str(numa, skip_cnt=1,
358 cpu_wt = self.sys_cores.cpu_list_per_node_str(numa, skip_cnt=2,
359 cpu_cnt=cpu_count_int,
363 thr_count_int = 2 * cpu_count_int
365 if rx_queues is None:
366 rxq_count_int = int(thr_count_int / 2)
368 rxq_count_int = rx_queues
370 if rxq_count_int == 0:
373 num_mbufs_int = num_mbufs_int * rxq_count_int
375 vpp_cfg.add_cpu_main_core(cpu_main)
376 vpp_cfg.add_cpu_corelist_workers(cpu_wt)
377 vpp_cfg.add_dpdk_dev_default_rxq(rxq_count_int)
378 vpp_cfg.add_dpdk_num_mbufs(num_mbufs_int)
380 def add_pci_devices(self, vpp_cfg):
381 pci_devs = [self.find_encrypted_data_interface()["vpci"],
382 self.find_raw_data_interface()["vpci"]]
383 vpp_cfg.add_dpdk_dev(*pci_devs)
385 def add_dpdk_cryptodev(self, vpp_cfg, sw_pmd_type, count):
386 crypto_type = self._get_crypto_type()
387 smt_used = self.sys_cores.is_smt_enabled()
388 cryptodev = self.find_encrypted_data_interface()["vpci"]
389 socket_id = self.get_value_by_interface_key(
390 self.find_encrypted_data_interface()["ifname"], "numa_node")
393 thr_count_int = count * 2
394 if crypto_type == 'HW_cryptodev':
395 vpp_cfg.add_dpdk_cryptodev(thr_count_int, cryptodev)
397 vpp_cfg.add_dpdk_sw_cryptodev(sw_pmd_type, socket_id,
400 thr_count_int = count
401 if crypto_type == 'HW_cryptodev':
402 vpp_cfg.add_dpdk_cryptodev(thr_count_int, cryptodev)
404 vpp_cfg.add_dpdk_sw_cryptodev(sw_pmd_type, socket_id,
407 def initialize_ipsec(self):
408 flow_src_start_ip = self._get_flow_src_start_ip()
410 self.set_interface_state(
411 self.find_encrypted_data_interface()["ifname"], 'up')
412 self.set_interface_state(self.find_raw_data_interface()["ifname"],
414 self.vpp_interfaces_ready_wait()
415 self.vpp_set_interface_mtu(
416 self.find_encrypted_data_interface()["ifname"])
417 self.vpp_set_interface_mtu(self.find_raw_data_interface()["ifname"])
418 self.vpp_interfaces_ready_wait()
420 self.set_ip(self.find_encrypted_data_interface()["ifname"],
421 self.find_encrypted_data_interface()["local_ip"], 24)
422 self.set_ip(self.find_raw_data_interface()["ifname"],
423 self.find_raw_data_interface()["local_ip"],
426 self.add_arp_on_dut(self.find_encrypted_data_interface()["ifname"],
427 self.find_encrypted_data_interface()["peer_intf"][
429 self.find_encrypted_data_interface()["peer_intf"][
431 self.add_arp_on_dut(self.find_raw_data_interface()["ifname"],
432 self.find_raw_data_interface()["peer_intf"][
434 self.find_raw_data_interface()["peer_intf"][
437 self.vpp_route_add(flow_src_start_ip, 8,
438 self.find_raw_data_interface()["peer_intf"][
440 self.find_raw_data_interface()["ifname"])
443 class VipsecApproxVnf(SampleVNF):
444 """ This class handles vIPSEC VNF model-driver definitions """
450 def __init__(self, name, vnfd, setup_env_helper_type=None,
451 resource_helper_type=None):
452 if setup_env_helper_type is None:
453 setup_env_helper_type = VipsecApproxSetupEnvHelper
454 super(VipsecApproxVnf, self).__init__(
455 name, vnfd, setup_env_helper_type,
456 resource_helper_type)
459 # we can't share ssh paramiko objects to force new connection
460 self.ssh_helper.drop_connection()
461 # kill before starting
462 self.setup_helper.kill_vnf()
464 self.setup_helper.create_ipsec_tunnels()
466 def wait_for_instantiate(self):
467 time.sleep(self.WAIT_TIME)
469 status = self.setup_helper.check_status()
470 if not self._vnf_process.is_alive() and not status:
471 raise RuntimeError("%s VNF process died." % self.APP_NAME)
472 LOG.info("Waiting for %s VNF to start.. ", self.APP_NAME)
473 time.sleep(self.WAIT_TIME_FOR_SCRIPT)
474 status = self.setup_helper.check_status()
476 LOG.info("%s VNF is up and running.", self.APP_NAME)
478 return self._vnf_process.exitcode
481 self.setup_helper.kill_vnf()
483 self.resource_helper.stop_collect()
484 if self._vnf_process is not None:
485 # be proper and join first before we kill
486 LOG.debug("joining before terminate %s", self._vnf_process.name)
487 self._vnf_process.join(constants.PROCESS_JOIN_TIMEOUT)
488 self._vnf_process.terminate()
490 def collect_kpi(self):
491 # we can't get KPIs if the VNF is down
492 check_if_process_failed(self._vnf_process, 0.01)
493 physical_node = Context.get_physical_node_from_server(
494 self.scenario_helper.nodes[self.name])
495 result = {"physical_node": physical_node}
496 result["collect_stats"] = self.setup_helper.get_vpp_statistics()
497 LOG.debug("%s collect KPIs %s", self.APP_NAME, result)