From 8f402c2ba47ed3ccbf94f9f037ec6e18d6b975ea Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 26 Jun 2023 11:24:01 +0000 Subject: T1797: Add initial vpp configuration Add initial configuration mode for VPP (PoC) set vpp cpu corelist-workers '2' set vpp cpu main-core '1' set vpp interface eth1 num-rx-desc '256' set vpp interface eth1 num-rx-queues '512' set vpp interface eth1 num-tx-desc '256' set vpp interface eth1 num-tx-queues '512' set vpp interface eth1 pci '0000:02:00.0' set vpp interface eth1 rx-mode 'polling' set vpp interface eth2 pci '0000:08:00.0' Limitation: - 'set vpp interface ethX pci auto' works only per first commit, then interface detached from default stack and creates tun interface 'ethX' to communicate with default stack. In this case we can't get PCI address via ethtool for 'tun' interfaces. But we can set pci address manualy. - Interface sync between default stack and VPP-DPDK stack After vpp change it doesn't trigger iproute2 for changes (should be written later) I.e. if we change something in vpp per each commit it restarts vpp.service it gets empty interface config as we don't configure vpp directly and it should be configured via iproute2 But then if we do any change on interface (for example description) it gets IP address, MTU, state, etc. --- data/templates/vpp/override.conf.j2 | 14 ++ data/templates/vpp/startup.conf.j2 | 116 ++++++++++++ debian/control | 4 + interface-definitions/vpp.xml.in | 342 ++++++++++++++++++++++++++++++++++++ python/vyos/ethtool.py | 3 +- python/vyos/vpp.py | 28 +++ src/conf_mode/vpp.py | 145 +++++++++++++++ 7 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 data/templates/vpp/override.conf.j2 create mode 100644 data/templates/vpp/startup.conf.j2 create mode 100644 interface-definitions/vpp.xml.in create mode 100644 python/vyos/vpp.py create mode 100755 src/conf_mode/vpp.py diff --git a/data/templates/vpp/override.conf.j2 b/data/templates/vpp/override.conf.j2 new file mode 100644 index 000000000..a2c2b04ed --- /dev/null +++ b/data/templates/vpp/override.conf.j2 @@ -0,0 +1,14 @@ +[Unit] +After= +After=vyos-router.service +ConditionPathExists= +ConditionPathExists=/run/vpp/vpp.conf + +[Service] +EnvironmentFile= +ExecStart= +ExecStart=/usr/bin/vpp -c /run/vpp/vpp.conf +WorkingDirectory= +WorkingDirectory=/run/vpp +Restart=always +RestartSec=10 diff --git a/data/templates/vpp/startup.conf.j2 b/data/templates/vpp/startup.conf.j2 new file mode 100644 index 000000000..f33539fba --- /dev/null +++ b/data/templates/vpp/startup.conf.j2 @@ -0,0 +1,116 @@ +# Generated by /usr/libexec/vyos/conf_mode/vpp.py + +unix { + nodaemon + log /var/log/vpp.log + full-coredump + cli-listen /run/vpp/cli.sock + gid vpp + # exec /etc/vpp/bootstrap.vpp +{% if unix is vyos_defined %} +{% if unix.poll_sleep_usec is vyos_defined %} + poll-sleep-usec {{ unix.poll_sleep_usec }} +{% endif %} +{% endif %} +} + +{% if cpu is vyos_defined %} +cpu { +{% if cpu.main_core is vyos_defined %} + main-core {{ cpu.main_core }} +{% endif %} +{% if cpu.corelist_workers is vyos_defined %} + corelist-workers {{ cpu.corelist_workers | join(',') }} +{% endif %} +{% if cpu.skip_cores is vyos_defined %} + skip-cores {{ cpu.skip_cores }} +{% endif %} +{% if cpu.workers is vyos_defined %} + workers {{ cpu.workers }} +{% endif %} +} +{% endif %} + +{# ip heap-size does not work now (23.06-rc2~1-g3a4e62ad4) #} +{# vlib_call_all_config_functions: unknown input `ip heap-size 32M ' #} +{% if ip is vyos_defined %} +#ip { +#{% if ip.heap_size is vyos_defined %} +# heap-size {{ ip.heap_size }}M +#{% endif %} +#} +{% endif %} + +{% if ip6 is vyos_defined %} +ip6 { +{% if ip6.hash_buckets is vyos_defined %} + hash-buckets {{ ip6.hash_buckets }} +{% endif %} +{% if ip6.heap_size is vyos_defined %} + heap-size {{ ip6.heap_size }}M +{% endif %} +} +{% endif %} + +{% if l2learn is vyos_defined %} +l2learn { +{% if l2learn.limit is vyos_defined %} + limit {{ l2learn.limit }} +{% endif %} +} +{% endif %} + +{% if logging is vyos_defined %} +logging { +{% if logging.default_log_level is vyos_defined %} + default-log-level {{ logging.default_log_level }} +{% endif %} +} +{% endif %} + +{% if physmem is vyos_defined %} +physmem { +{% if physmem.max_size is vyos_defined %} + max-size {{ physmem.max_size.upper() }} +{% endif %} +} +{% endif %} + +plugins { + path /usr/lib/x86_64-linux-gnu/vpp_plugins/ + plugin default { disable } + plugin dpdk_plugin.so { enable } + plugin linux_cp_plugin.so { enable } + plugin linux_nl_plugin.so { enable } +} + +linux-cp { + lcp-sync + lcp-auto-subint +} + +dpdk { + # Whitelist the fake PCI address 0000:00:00.0 + # This prevents all devices from being added to VPP-DPDK by default + dev 0000:00:00.0 +{% for iface, iface_config in interface.items() %} +{% if iface_config.pci is vyos_defined %} + dev {{ iface_config.pci }} { + name {{ iface }} +{% if iface_config.num_rx_desc is vyos_defined %} + num-rx-desc {{ iface_config.num_rx_desc }} +{% endif %} +{% if iface_config.num_tx_desc is vyos_defined %} + num-tx-desc {{ iface_config.num_tx_desc }} +{% endif %} +{% if iface_config.num_rx_queues is vyos_defined %} + num-rx-queues {{ iface_config.num_rx_queues }} +{% endif %} +{% if iface_config.num_tx_queues is vyos_defined %} + num-tx-queues {{ iface_config.num_tx_queues }} +{% endif %} + } +{% endif %} +{% endfor %} + uio-bind-force +} diff --git a/debian/control b/debian/control index 8cbad99d9..e9e560442 100644 --- a/debian/control +++ b/debian/control @@ -87,6 +87,7 @@ Depends: libqmi-utils, libstrongswan-extra-plugins (>=5.9), libstrongswan-standard-plugins (>=5.9), + libvppinfra, libvyosconfig0, lldpd, lm-sensors, @@ -173,6 +174,9 @@ Depends: uidmap, usb-modeswitch, usbutils, + vpp, + vpp-plugin-core, + vpp-plugin-dpdk, vyatta-bash, vyatta-cfg, vyos-http-api-tools, diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in new file mode 100644 index 000000000..51ab776c3 --- /dev/null +++ b/interface-definitions/vpp.xml.in @@ -0,0 +1,342 @@ + + + + + Accelerated data-plane + 1280 + + + + + CPU settings + + + + + List of cores worker threads + + <id> + CPU core id + + + <idN>-<idM> + CPU core id range (use '-' as delimiter) + + + + + not a valid CPU core value or range + + + + + + Main core + + u32:0-512 + Assign main thread to specific core + + + + + + + + + Skip cores + + u32:0-512 + Skip cores + + + + + + + + + Create worker threads + + u32:0-4294967295 + Worker threads + + + + + + + + + + + Interface + + ethN + Interface name + + + ((eth|lan)[0-9]+|(eno|ens|enp|enx).+) + + Invalid interface name + + + + + Number of receive ring descriptors + + u32:256-8192 + Number of receive ring descriptors + + + + + + + + + Number of tranceive ring descriptors + + u32:256-8192 + Number of tranceive ring descriptors + + + + + + + + + Number of receive ring descriptors + + u32:256-8192 + Number of receive queues + + + + + + + + + Number of tranceive ring descriptors + + u32:256-8192 + Number of tranceive queues + + + + + + + + + PCI address allocation + + auto + Auto detect PCI address + + + <xxxx:xx:xx.x> + Set Peripheral Component Interconnect (PCI) address + + + (auto|[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]) + + + auto + + + + Receive packet processing mode + + polling interrupt adaptive + + + polling + Constantly check for new data + + + interrupt + Interrupt mode + + + adaptive + Adaptive mode + + + (polling|interrupt|adaptive) + + + + + + + + IP settings + + + + + IPv4 heap size + + u32:0-4294967295 + Amount of memory (in Mbytes) dedicated to the destination IP lookup table + + + + + + 32 + + + + + + IPv6 settings + + + + + IPv6 heap size + + u32:0-4294967295 + Amount of memory (in Mbytes) dedicated to the destination IP lookup table + + + + + + 32 + + + + IPv6 forwarding table hash buckets + + u32:1-4294967295 + IPv6 forwarding table hash buckets + + + + + + 65536 + + + + + + Level 2 MAC address learning settings + + + + + Number of MAC addresses in the L2 FIB + + u32:1-4294967295 + Number of concurent entries + + + + + + 4194304 + + + + + + Loggint settings + + + + + default-log-level + + alert crit debug disabled emerg err info notice warn + + + alert + Alert + + + crit + Critical + + + debug + Debug + + + disabled + Disabled + + + emerg + Emergency + + + err + Error + + + info + Informational + + + notice + Notice + + + warn + Warning + + + (alert|crit|debug|disabled|emerg|err|info|notice|warn) + + + + + + + + Memory settings + + + + + Set memory size for protectable memory allocator (pmalloc) memory space + + <number>m + Megabyte + + + <number>g + Gigabyte + + + + + + + + Unix settings + + + + + Add a fixed-sleep between main loop poll + + u32:0-4294967295 + Number of receive queues + + + + + + 0 + + + + + + diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 68234089c..9b7da89fa 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -21,7 +21,8 @@ from vyos.util import popen # These drivers do not support using ethtool to change the speed, duplex, or # flow control settings _drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront', - 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf'] + 'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf', + 'tun'] class Ethtool: """ diff --git a/python/vyos/vpp.py b/python/vyos/vpp.py new file mode 100644 index 000000000..decc6c087 --- /dev/null +++ b/python/vyos/vpp.py @@ -0,0 +1,28 @@ +# Copyright 2023 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +from vyos.util import call + + +def lcp_create_host_interface(ifname): + """LCP reprepsents a connection point between VPP dataplane + and the host stack + """ + return call(f'vppctl lcp create {ifname} host-if {ifname}') + + +def set_interface_rx_mode(ifname, mode): + """Rx mode""" + return call(f'sudo vppctl set interface rx-mode {ifname} {mode}') diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py new file mode 100755 index 000000000..aa6c14e89 --- /dev/null +++ b/src/conf_mode/vpp.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from pathlib import Path + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.ifconfig import Section +from vyos.ifconfig import EthernetIf +from vyos.ifconfig import interface +from vyos.util import call +from vyos.util import rc_cmd +from vyos.template import render +from vyos.xml import defaults + +from vyos import ConfigError +from vyos import vpp +from vyos import airbag + +airbag.enable() + +service_name = 'vpp' +service_conf = Path(f'/run/vpp/{service_name}.conf') +systemd_override = '/run/systemd/system/vpp.service.d/10-override.conf' + + +def _get_pci_address_by_interface(iface): + from vyos.util import rc_cmd + rc, out = rc_cmd(f'ethtool -i {iface}') + if rc == 0: + output_lines = out.split('\n') + for line in output_lines: + if 'bus-info' in line: + return line.split(None, 1)[1].strip() + + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['vpp'] + base_ethernet = ['interfaces', 'ethernet'] + if not conf.exists(base): + return None + + config = conf.get_config_dict(base, + get_first_key=True, + key_mangling=('-', '_'), + no_tag_node_value_mangle=True) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + if 'interface' in default_values: + del default_values['interface'] + config = dict_merge(default_values, config) + + if 'interface' in config: + for iface, iface_config in config['interface'].items(): + default_values_iface = defaults(base + ['interface']) + config['interface'][iface] = dict_merge(default_values_iface, config['interface'][iface]) + + # Get PCI address auto + for iface, iface_config in config['interface'].items(): + if iface_config['pci'] == 'auto': + config['interface'][iface]['pci'] = _get_pci_address_by_interface(iface) + + config['other_interfaces'] = conf.get_config_dict(base_ethernet, key_mangling=('-', '_'), + get_first_key=True, no_tag_node_value_mangle=True) + + return config + + +def verify(config): + # bail out early - looks like removal from running config + if not config: + return None + + if 'interface' not in config: + raise ConfigError(f'"interface" is required but not set!') + + if 'cpu' in config: + if 'corelist_workers' in config['cpu'] and 'main_core' not in config['cpu']: + raise ConfigError(f'"cpu main-core" is required but not set!') + + +def generate(config): + if not config: + # Remove old config and return + service_conf.unlink(missing_ok=True) + return None + + render(service_conf, 'vpp/startup.conf.j2', config) + render(systemd_override, 'vpp/override.conf.j2', config) + + return None + + +def apply(config): + if not config: + print(f'systemctl stop {service_name}.service') + call(f'systemctl stop {service_name}.service') + return + else: + print(f'systemctl restart {service_name}.service') + call(f'systemctl restart {service_name}.service') + + call('systemctl daemon-reload') + + call('sudo sysctl -w vm.nr_hugepages=4096') + for iface, _ in config['interface'].items(): + # Create lcp + if iface not in Section.interfaces(): + vpp.lcp_create_host_interface(iface) + + # update interface config + #e = EthernetIf(iface) + #e.update(config['other_interfaces'][iface]) + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3