Page MenuHomeVyOS Platform

No OneTemporary

Size
175 KB
Referenced Files
None
Subscribers
None
diff --git a/interface-definitions/include/version/vpp-version.xml.i b/interface-definitions/include/version/vpp-version.xml.i
index 96a69c92f..205e87916 100644
--- a/interface-definitions/include/version/vpp-version.xml.i
+++ b/interface-definitions/include/version/vpp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/vpp-version.xml.i -->
-<syntaxVersion component='vpp' version='1'></syntaxVersion>
+<syntaxVersion component='vpp' version='2'></syntaxVersion>
<!-- include end -->
\ No newline at end of file
diff --git a/interface-definitions/vpp.xml.in b/interface-definitions/vpp.xml.in
index c45d89b94..911c6f668 100644
--- a/interface-definitions/vpp.xml.in
+++ b/interface-definitions/vpp.xml.in
@@ -1,1581 +1,1575 @@
<?xml version="1.0"?>
<interfaceDefinition>
<node name="vpp" owner="${vyos_conf_scripts_dir}/vpp.py">
<properties>
<help>Accelerated data-plane</help>
<priority>295</priority>
</properties>
<children>
<node name='interfaces'>
<properties>
<help>Network interfaces</help>
<priority>300</priority>
</properties>
<children>
<tagNode name="bonding" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bonding.py">
<properties>
<priority>324</priority>
<help>Bonding Interface/Link Aggregation</help>
<constraint>
<regex>bond[0-9]+</regex>
</constraint>
<constraintErrorMessage>Bonding interface must be named bondN</constraintErrorMessage>
<valueHelp>
<format>bondN</format>
<description>Bonding interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<leafNode name="hash-policy">
<properties>
<help>Bonding transmit hash policy</help>
<completionHelp>
<list>layer2 layer2+3 layer3+4</list>
</completionHelp>
<valueHelp>
<format>layer2</format>
<description>use MAC addresses to generate the hash</description>
</valueHelp>
<valueHelp>
<format>layer2+3</format>
<description>combine MAC address and IP address to make hash</description>
</valueHelp>
<valueHelp>
<format>layer3+4</format>
<description>combine IP address and port to make hash</description>
</valueHelp>
<constraint>
<regex>(layer2\+3|layer3\+4|layer2)</regex>
</constraint>
<constraintErrorMessage>hash-policy must be layer2 layer2+3 layer3+4</constraintErrorMessage>
</properties>
<defaultValue>layer2</defaultValue>
</leafNode>
#include <include/interface/mac.xml.i>
#include <include/kernel-interface-tun.xml.i>
<leafNode name="mode">
<properties>
<help>Bonding mode</help>
<completionHelp>
<list>802.3ad active-backup broadcast round-robin xor-hash</list>
</completionHelp>
<valueHelp>
<format>802.3ad</format>
<description>IEEE 802.3ad Dynamic link aggregation</description>
</valueHelp>
<valueHelp>
<format>active-backup</format>
<description>Fault tolerant: only one slave in the bond is active</description>
</valueHelp>
<valueHelp>
<format>broadcast</format>
<description>Fault tolerant: transmits everything on all slave interfaces</description>
</valueHelp>
<valueHelp>
<format>round-robin</format>
<description>Load balance: transmit packets in sequential order</description>
</valueHelp>
<valueHelp>
<format>xor-hash</format>
<description>Distribute based on MAC address</description>
</valueHelp>
<constraint>
<regex>(802.3ad|active-backup|broadcast|round-robin|xor-hash)</regex>
</constraint>
<constraintErrorMessage>mode must be 802.3ad, active-backup, broadcast, round-robin, or xor</constraintErrorMessage>
</properties>
<defaultValue>802.3ad</defaultValue>
</leafNode>
<node name="member">
<properties>
<help>Bonding member interfaces</help>
</properties>
<children>
<leafNode name="interface">
<properties>
<help>Member interface name</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces --bondable</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<multi/>
</properties>
</leafNode>
</children>
</node>
</children>
</tagNode>
<tagNode name="bridge" owner="${vyos_conf_scripts_dir}/vpp_interfaces_bridge.py">
<properties>
<priority>327</priority>
<help>Bridge domain</help>
<constraint>
<!-- Bridge domain 0 is reserved to vpp default -->
<regex>br(?!0$)[0-9]+</regex>
</constraint>
<constraintErrorMessage>Bridge domain must be named brN and br0 is reserved</constraintErrorMessage>
<valueHelp>
<format>brN</format>
<description>Bridge interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<node name="member">
<properties>
<help>Bridge member interfaces</help>
</properties>
<children>
<tagNode name="interface">
<properties>
<help>Member interface name</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
</properties>
<children>
<leafNode name="bvi">
<properties>
<help>Bridge Virtual Interface (BVI)</help>
<valueless/>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
</children>
</tagNode>
<!-- Disable as geneve interface index in the VPP always 0 and not configured -->
<!-- <tagNode name="geneve" owner="${vyos_conf_scripts_dir}/vpp_interfaces_geneve.py">
<properties>
<priority>325</priority>
<help>Generic Network Virtualization Encapsulation (GENEVE) Interface</help>
<constraint>
<regex>geneve[0-9]+</regex>
</constraint>
<constraintErrorMessage>GENEVE interface must be named geneveN</constraintErrorMessage>
<valueHelp>
<format>geneveN</format>
<description>GENEVE interface name</description>
</valueHelp>
</properties>
<children> -->
<!-- #include <include/source-address-ipv4-ipv6.xml.i> -->
<!-- #include <include/interface/tunnel-remote.xml.i> -->
<!-- #include <include/vni.xml.i> -->
<!-- #include <include/kernel-interface-tun.xml.i> -->
<!-- </children> -->
<!-- </tagNode> -->
<tagNode name="gre" owner="${vyos_conf_scripts_dir}/vpp_interfaces_gre.py">
<properties>
<priority>325</priority>
<help>Generic Network Encapsulation (GRE) Interface</help>
<constraint>
<regex>gre[0-9]+</regex>
</constraint>
<constraintErrorMessage>GRE interface must be named greN</constraintErrorMessage>
<valueHelp>
<format>greN</format>
<description>GRE interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<leafNode name="mode">
<properties>
<help>GRE tunnel mode</help>
<completionHelp>
<list>point-to-point point-to-multipoint</list>
</completionHelp>
<valueHelp>
<format>point-to-point</format>
<description>Point to point mode</description>
</valueHelp>
<valueHelp>
<format>point-to-multipoint</format>
<description>Point to multipoint mode</description>
</valueHelp>
<constraint>
<regex>(point-to-point|point-to-multipoint)</regex>
</constraint>
<constraintErrorMessage>Invalid mode, must be one of: point-to-point or point-to-multipoint</constraintErrorMessage>
</properties>
<defaultValue>point-to-point</defaultValue>
</leafNode>
<leafNode name="tunnel-type">
<properties>
<help>GRE tunnel type</help>
<completionHelp>
<list>erspan l3 teb</list>
</completionHelp>
<valueHelp>
<format>erspan</format>
<description>Encapsulated Remote Switched Port Analyzer</description>
</valueHelp>
<valueHelp>
<format>l3</format>
<description>Generic Routing Encapsulation (network layer)</description>
</valueHelp>
<valueHelp>
<format>teb</format>
<description>L2 Transparent Ethernet Bridge</description>
</valueHelp>
<constraint>
<regex>(erspan|l3|teb)</regex>
</constraint>
<constraintErrorMessage>Invalid encapsulation, must be one of: l3, teb or erspan</constraintErrorMessage>
</properties>
<defaultValue>l3</defaultValue>
</leafNode>
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/tunnel-remote.xml.i>
#include <include/kernel-interface-tun.xml.i>
</children>
</tagNode>
<tagNode name="ipip" owner="${vyos_conf_scripts_dir}/vpp_interfaces_ipip.py">
<properties>
<priority>325</priority>
<help>IP encapsulation tunnel interface</help>
<constraint>
<regex>ipip[0-9]+</regex>
</constraint>
<constraintErrorMessage>IPIP tunnel interface must be named ipipN</constraintErrorMessage>
<valueHelp>
<format>ipipN</format>
<description>IPIP tunnel interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/tunnel-remote.xml.i>
#include <include/kernel-interface-tun.xml.i>
</children>
</tagNode>
<tagNode name="loopback" owner="${vyos_conf_scripts_dir}/vpp_interfaces_loopback.py">
<properties>
<priority>325</priority>
<help>Loopback Interface</help>
<constraint>
<regex>lo[0-9]+</regex>
</constraint>
<constraintErrorMessage>Loopback interface must be named loN</constraintErrorMessage>
<valueHelp>
<format>loN</format>
<description>Loopback interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/kernel-interface-tun.xml.i>
</children>
</tagNode>
<tagNode name="vxlan" owner="${vyos_conf_scripts_dir}/vpp_interfaces_vxlan.py">
<properties>
<priority>325</priority>
<help>Virtual Extensible LAN (VXLAN) Interface</help>
<constraint>
<regex>vxlan[0-9]+</regex>
</constraint>
<constraintErrorMessage>VXLAN interface must be named vxlanN</constraintErrorMessage>
<valueHelp>
<format>vxlanN</format>
<description>VXLAN interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/source-address-ipv4-ipv6.xml.i>
#include <include/interface/tunnel-remote.xml.i>
#include <include/vni.xml.i>
#include <include/kernel-interface-tap.xml.i>
</children>
</tagNode>
<tagNode name="xconnect" owner="${vyos_conf_scripts_dir}/vpp_interfaces_xconnect.py">
<properties>
<help>Layer 2 cross connect</help>
<priority>325</priority>
<constraint>
<regex>xcon[0-9]+</regex>
</constraint>
<constraintErrorMessage>Cross connect must be named xconN</constraintErrorMessage>
<valueHelp>
<format>xconN</format>
<description>Cross connect interface name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
<node name="member">
<properties>
<help>Cross connect pair mamber interfaces</help>
</properties>
<children>
<leafNode name="interface">
<properties>
<help>Member interface name</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces --bridgeable</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<multi/>
</properties>
</leafNode>
</children>
</node>
</children>
</tagNode>
</children>
</node>
<node name="settings">
<properties>
<help>VPP settings</help>
</properties>
<children>
<node name="buffers">
<properties>
<help>Buffer settings</help>
</properties>
<children>
<leafNode name="buffers-per-numa">
<properties>
<help>Number of buffers per numa node</help>
<valueHelp>
<format>u32:16384-4294967295</format>
<description>Number of buffers</description>
</valueHelp>
<valueHelp>
<format>auto</format>
<description>Auto calculate number of buffers per numa node</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 16384-4294967295"/>
<regex>(auto)</regex>
</constraint>
</properties>
<defaultValue>auto</defaultValue>
</leafNode>
<leafNode name="data-size">
<properties>
<help>Size of buffer data area</help>
<valueHelp>
<format>u32:0-4294967295</format>
<description>Size of buffer data area</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-4294967295"/>
</constraint>
</properties>
<defaultValue>2048</defaultValue>
</leafNode>
<leafNode name="page-size">
<properties>
<help>Set the page-size for buffer allocation</help>
#include <include/unformat_log2_page_size.xml.i>
</properties>
<defaultValue>2M</defaultValue>
</leafNode>
</children>
</node>
<node name="cpu">
<properties>
<help>CPU settings</help>
</properties>
<children>
<leafNode name="corelist-workers">
<properties>
<help>List of cores worker threads</help>
<valueHelp>
<format>&lt;id&gt;</format>
<description>CPU core id</description>
</valueHelp>
<valueHelp>
<format>&lt;idN&gt;-&lt;idM&gt;</format>
<description>CPU core id range (use '-' as delimiter)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--allow-range --range 0-512"/>
</constraint>
<constraintErrorMessage>not a valid CPU core value or range</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
<leafNode name="main-core">
<properties>
<help>Main core</help>
<valueHelp>
<format>u32:0-512</format>
<description>Assign main thread to specific core</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-512"/>
</constraint>
</properties>
</leafNode>
<leafNode name="skip-cores">
<properties>
<help>Skip cores</help>
<valueHelp>
<format>u32:1-512</format>
<description>Skip cores</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-512"/>
</constraint>
</properties>
</leafNode>
<leafNode name="workers">
<properties>
<help>Create worker threads</help>
<valueHelp>
<format>u32:0-4294967295</format>
<description>Worker threads</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-512"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
<tagNode name="interface">
<properties>
<help>Interface</help>
<valueHelp>
<format>ethN</format>
<description>Interface name</description>
</valueHelp>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces --type ethernet</script>
</completionHelp>
<constraint>
<validator name="ethernet-interface"/>
</constraint>
<constraintErrorMessage>Invalid interface name</constraintErrorMessage>
</properties>
<children>
<leafNode name="driver">
<properties>
<help>Driver mode</help>
<completionHelp>
<list>dpdk xdp</list>
</completionHelp>
<valueHelp>
<format>dpdk</format>
<description>Data Plane Development Kit (DPDK)</description>
</valueHelp>
<valueHelp>
<format>xdp</format>
<description>eXpress Data Path (XDP)</description>
</valueHelp>
<constraint>
<regex>(dpdk|xdp)</regex>
</constraint>
</properties>
</leafNode>
#include <include/vpp/iface_rx_mode.xml.i>
<node name="dpdk-options">
<properties>
<help>DPDK settings</help>
</properties>
<children>
<leafNode name="num-rx-desc">
<properties>
<help>Receive ring descriptors</help>
<valueHelp>
<format>u32:256-8192</format>
<description>Number of descriptors in receive ring</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 256-8192"/>
</constraint>
</properties>
<defaultValue>1024</defaultValue>
</leafNode>
<leafNode name="num-tx-desc">
<properties>
<help>Tranceive ring descriptors</help>
<valueHelp>
<format>u32:256-8192</format>
<description>Number of descriptors in tranceive ring</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 256-8192"/>
</constraint>
</properties>
<defaultValue>1024</defaultValue>
</leafNode>
<leafNode name="num-rx-queues">
<properties>
<help>Number of receive queues</help>
<valueHelp>
<format>u32:1-8192</format>
<description>Number of receive queues</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-8192"/>
</constraint>
</properties>
</leafNode>
<leafNode name="num-tx-queues">
<properties>
<help>Number of tranceive queues</help>
<valueHelp>
<format>u32:1-8192</format>
<description>Number of tranceive queues</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-8192"/>
</constraint>
</properties>
</leafNode>
<leafNode name="promisc">
<properties>
<help>Enable promisc mode on a vpp interface</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
<node name="xdp-options">
<properties>
<help>XDP settings</help>
</properties>
<children>
- <leafNode name="no-syscall-lock">
- <properties>
- <help>Unlock multithreading in interrupt and adaptive modes</help>
- <valueless/>
- </properties>
- </leafNode>
<leafNode name="num-rx-queues">
<properties>
<help>Number of receive queues</help>
<valueHelp>
<format>u32:1-65535</format>
<description>Number of receive queues to connect to</description>
</valueHelp>
<valueHelp>
<format>all</format>
<description>All</description>
</valueHelp>
<constraint>
<regex>(all)</regex>
<validator name="numeric" argument="--range 1-65535"/>
</constraint>
</properties>
<defaultValue>all</defaultValue>
</leafNode>
<leafNode name="promisc">
<properties>
<help>Enable promisc mode on a real interface</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="rx-queue-size">
<properties>
<help>Receive queue size</help>
<valueHelp>
<format>u32:0-65535</format>
<description>Size of receive queue</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-65535"/>
</constraint>
</properties>
<defaultValue>0</defaultValue>
</leafNode>
<leafNode name="tx-queue-size">
<properties>
<help>Tranceive queue size</help>
<valueHelp>
<format>u32:0-65535</format>
<description>Size of tranceive queue</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-65535"/>
</constraint>
</properties>
<defaultValue>0</defaultValue>
</leafNode>
<leafNode name="zero-copy">
<properties>
<help>Enable zero-copy mode</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
</children>
</tagNode>
<node name="ipv6">
<properties>
<help>IPv6 settings</help>
</properties>
<children>
<leafNode name="heap-size">
<properties>
<help>IPv6 heap size</help>
#include <include/unformat_memory_size.xml.i>
</properties>
<defaultValue>32M</defaultValue>
</leafNode>
<leafNode name="hash-buckets">
<properties>
<help>IPv6 forwarding table hash buckets</help>
<valueHelp>
<format>u32:1-4294967295</format>
<description>IPv6 forwarding table hash buckets</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
<defaultValue>65536</defaultValue>
</leafNode>
</children>
</node>
<node name="ipsec">
<properties>
<help>IPsec settings</help>
</properties>
<children>
<leafNode name="interface-type">
<properties>
<help>IPsec interface type</help>
<completionHelp>
<list>ipsec ipip</list>
</completionHelp>
<valueHelp>
<format>ipsec</format>
<description>IPsec interface type</description>
</valueHelp>
<valueHelp>
<format>ipip</format>
<description>IPIP tunnel interface type</description>
</valueHelp>
<constraint>
<regex>(ipsec|ipip)</regex>
</constraint>
</properties>
<defaultValue>ipsec</defaultValue>
</leafNode>
#include <include/vpp/netlink.xml.i>
</children>
</node>
<node name="l2learn">
<properties>
<help>Level 2 MAC address learning settings</help>
</properties>
<children>
<leafNode name="limit">
<properties>
<help>Number of MAC addresses in the L2 FIB</help>
<valueHelp>
<format>u32:1-4294967295</format>
<description>Number of concurent entries</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
<defaultValue>4194304</defaultValue>
</leafNode>
</children>
</node>
<node name="lcp">
<properties>
<help>Linux control plane setting</help>
</properties>
<children>
#include <include/vpp/netlink.xml.i>
<leafNode name="ignore-kernel-routes">
<properties>
<help>Ignore kernel routes</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
<node name="logging">
<properties>
<help>Logging settings</help>
</properties>
<children>
<leafNode name="default-log-level">
<properties>
<help>default-log-level</help>
<completionHelp>
<list>alert crit debug disabled emerg error info notice warn</list>
</completionHelp>
<valueHelp>
<format>alert</format>
<description>Alert</description>
</valueHelp>
<valueHelp>
<format>crit</format>
<description>Critical</description>
</valueHelp>
<valueHelp>
<format>debug</format>
<description>Debug</description>
</valueHelp>
<valueHelp>
<format>disabled</format>
<description>Disabled</description>
</valueHelp>
<valueHelp>
<format>emerg</format>
<description>Emergency</description>
</valueHelp>
<valueHelp>
<format>error</format>
<description>Error</description>
</valueHelp>
<valueHelp>
<format>info</format>
<description>Informational</description>
</valueHelp>
<valueHelp>
<format>notice</format>
<description>Notice</description>
</valueHelp>
<valueHelp>
<format>warn</format>
<description>Warning</description>
</valueHelp>
<constraint>
<regex>(alert|crit|debug|disabled|emerg|error|info|notice|warn)</regex>
</constraint>
</properties>
</leafNode>
</children>
</node>
<node name="memory">
<properties>
<help>Memory settings</help>
</properties>
<children>
<leafNode name="main-heap-size">
<properties>
<help>Main heap size</help>
#include <include/unformat_memory_size.xml.i>
</properties>
<defaultValue>3G</defaultValue>
</leafNode>
<leafNode name="main-heap-page-size">
<properties>
<help>Main heap page size</help>
#include <include/unformat_log2_page_size.xml.i>
</properties>
<defaultValue>2M</defaultValue>
</leafNode>
</children>
</node>
<node name="nat44">
<properties>
<help>NAT settings</help>
</properties>
<children>
<node name="timeout">
<properties>
<help>NAT44 session timeouts</help>
</properties>
<children>
<leafNode name="icmp">
<properties>
<help>ICMP timeout</help>
<valueHelp>
<format>u32</format>
<description>Timeout in seconds (default: 60)</description>
</valueHelp>
</properties>
<defaultValue>60</defaultValue>
</leafNode>
<leafNode name="tcp-established">
<properties>
<help>TCP established timeout</help>
<valueHelp>
<format>u32</format>
<description>TCP established timeout in seconds (default: 7440)</description>
</valueHelp>
</properties>
<defaultValue>7440</defaultValue>
</leafNode>
<leafNode name="tcp-transitory">
<properties>
<help>TCP transitory timeout</help>
<valueHelp>
<format>u32</format>
<description>Timeout in seconds (default: 240)</description>
</valueHelp>
</properties>
<defaultValue>240</defaultValue>
</leafNode>
<leafNode name="udp">
<properties>
<help>UDP timeout</help>
<valueHelp>
<format>u32</format>
<description>Timeout in seconds (default: 300)</description>
</valueHelp>
</properties>
<defaultValue>300</defaultValue>
</leafNode>
</children>
</node>
<leafNode name="session-limit">
<properties>
<help>Maximum number of sessions per thread</help>
<valueHelp>
<format>u32</format>
<description>Number of sessions</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
<constraintErrorMessage>Number of sessions must be between 1 and 4294967295</constraintErrorMessage>
</properties>
<defaultValue>64512</defaultValue>
</leafNode>
<leafNode name="workers">
<properties>
<help>List of NAT workers</help>
<valueHelp>
<format>&lt;id&gt;</format>
<description>Worker id</description>
</valueHelp>
<valueHelp>
<format>&lt;idN&gt;-&lt;idM&gt;</format>
<description>Worker id range (use '-' as delimiter)</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--allow-range --range 0-512"/>
</constraint>
<constraintErrorMessage>Not a valid value or range</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
<leafNode name="no-forwarding">
<properties>
<help>Do not forward packets which do not match existing NAT translations (static or dynamic)</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
<node name="physmem">
<properties>
<help>Physical memory settings</help>
</properties>
<children>
<leafNode name="max-size">
<properties>
<help>Set memory size for protectable memory allocator (pmalloc) memory space</help>
#include <include/unformat_memory_size.xml.i>
</properties>
</leafNode>
</children>
</node>
<node name="statseg">
<properties>
<help>Stats settings</help>
</properties>
<children>
<leafNode name="size">
<properties>
<help>Size of stats segment</help>
#include <include/unformat_memory_size.xml.i>
</properties>
<defaultValue>128M</defaultValue>
</leafNode>
<leafNode name="page-size">
<properties>
<help>Stats page size</help>
#include <include/unformat_log2_page_size.xml.i>
</properties>
<defaultValue>2M</defaultValue>
</leafNode>
</children>
</node>
<node name="unix">
<properties>
<help>Unix settings</help>
</properties>
<children>
<leafNode name="poll-sleep-usec">
<properties>
<help>Add a fixed-sleep between main loop poll</help>
<valueHelp>
<format>u32:0-500000</format>
<description>Sleep interval in microseconds</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-500000"/>
</constraint>
</properties>
<defaultValue>0</defaultValue>
</leafNode>
</children>
</node>
</children>
</node>
<node name="sflow" owner="${vyos_conf_scripts_dir}/vpp_sflow.py">
<properties>
<help>VPP data-plane sFlow</help>
<priority>322</priority>
</properties>
<children>
<leafNode name="interface">
<properties>
<help>Interface name</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<valueHelp>
<format>txt</format>
<description>Interface name</description>
</valueHelp>
<multi/>
</properties>
</leafNode>
<leafNode name="sample-rate">
<properties>
<help>sFlow sample rate</help>
<valueHelp>
<format>u32</format>
<description>sFlow sample rate</description>
</valueHelp>
</properties>
</leafNode>
</children>
</node>
<node name="nat">
<properties>
<help>Network Address Translation (NAT) settings</help>
</properties>
<children>
<node name="cgnat" owner="${vyos_conf_scripts_dir}/vpp_nat_cgnat.py">
<properties>
<help>Carrier-grade NAT (CGNAT)</help>
<priority>331</priority>
</properties>
<children>
<node name="interface">
<properties>
<help>CGNAT interface setting</help>
</properties>
<children>
<leafNode name="inside">
<properties>
<help>CGNAT inside interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
</leafNode>
<leafNode name="outside">
<properties>
<help>CGNAT outside interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
</leafNode>
</children>
</node>
<tagNode name="rule">
<properties>
<help>Rule number for CGNAT</help>
<valueHelp>
<format>u32</format>
<description>Number of rule</description>
</valueHelp>
</properties>
<children>
<leafNode name="inside-prefix">
<properties>
<help>Inside IPv4 prefix</help>
<valueHelp>
<format>ipv4net</format>
<description>IPv4 prefix</description>
</valueHelp>
<constraint>
<validator name="ipv4-prefix"/>
</constraint>
</properties>
</leafNode>
<leafNode name="outside-prefix">
<properties>
<help>Outside IPv4 prefix</help>
<valueHelp>
<format>ipv4net</format>
<description>IPv4 prefix</description>
</valueHelp>
<constraint>
<validator name="ipv4-prefix"/>
</constraint>
</properties>
</leafNode>
#include <include/generic-description.xml.i>
</children>
</tagNode>
<node name="timeout">
<properties>
<help>Timeouts for CGNAT sessions</help>
</properties>
<children>
<leafNode name="icmp">
<properties>
<help>ICMP timeout</help>
<valueHelp>
<format>u32</format>
<description>Timeout in seconds (default: 60)</description>
</valueHelp>
</properties>
<defaultValue>60</defaultValue>
</leafNode>
<leafNode name="tcp-established">
<properties>
<help>TCP established timeout</help>
<valueHelp>
<format>u32</format>
<description>TCP established timeout in seconds (default: 7440)</description>
</valueHelp>
</properties>
<defaultValue>7440</defaultValue>
</leafNode>
<leafNode name="tcp-transitory">
<properties>
<help>TCP transitory timeout</help>
<valueHelp>
<format>u32</format>
<description>Timeout in seconds (default: 240)</description>
</valueHelp>
</properties>
<defaultValue>240</defaultValue>
</leafNode>
<leafNode name="udp">
<properties>
<help>UDP timeout</help>
<valueHelp>
<format>u32</format>
<description>Timeout in seconds (default: 300)</description>
</valueHelp>
</properties>
<defaultValue>300</defaultValue>
</leafNode>
</children>
</node>
</children>
</node>
</children>
</node>
<node name="nat44" owner="${vyos_conf_scripts_dir}/vpp_nat.py">
<properties>
<help>NAT44</help>
<priority>330</priority>
</properties>
<children>
<node name="interface">
<properties>
<help>NAT interface setting</help>
</properties>
<children>
<leafNode name="inside">
<properties>
<help>NAT inside interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
</leafNode>
<leafNode name="outside">
<properties>
<help>NAT outside interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
</leafNode>
</children>
</node>
<node name="address-pool">
<properties>
<help>NAT address pool</help>
</properties>
<children>
<node name="translation">
<properties>
<help>NAT translation pool</help>
</properties>
<children>
#include <include/vpp/nat_address_range.xml.i>
#include <include/vpp/nat_interface.xml.i>
</children>
</node>
<node name="twice-nat">
<properties>
<help>NAT twice-nat pool</help>
</properties>
<children>
#include <include/vpp/nat_address_range.xml.i>
#include <include/vpp/nat_interface.xml.i>
</children>
</node>
</children>
</node>
<node name="static">
<properties>
<help>Static NAT rules</help>
</properties>
<children>
<tagNode name="rule">
<properties>
<help>Rule number for NAT</help>
<valueHelp>
<format>u32</format>
<description>Number of NAT rule</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
<node name="external">
<properties>
<help>NAT external parameters</help>
</properties>
<children>
<leafNode name="address">
<properties>
<help>IP address</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
</constraint>
</properties>
</leafNode>
#include <include/port-number.xml.i>
</children>
</node>
<node name="local">
<properties>
<help>NAT local parameters</help>
</properties>
<children>
<leafNode name="address">
<properties>
<help>IP address</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
</constraint>
</properties>
</leafNode>
#include <include/port-number.xml.i>
</children>
</node>
<node name="options">
<properties>
<help>NAT static mapping options</help>
</properties>
<children>
<leafNode name="twice-nat">
<properties>
<help>Rewrite source IP addresses on packets sent from outside to inside</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="self-twice-nat">
<properties>
<help>Rewrite source IP addresses on packets sent only from a local address to an external address</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="out-to-in-only">
<properties>
<help>Only apply rule for traffic from outside to inside interfaces</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="twice-nat-address">
<properties>
<help>Force use of specific IP address from twice-nat address pool</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
#include <include/vpp/nat_protocol.xml.i>
</children>
</tagNode>
</children>
</node>
<node name="exclude">
<properties>
<help>Exclude packets matching these rules from NAT</help>
</properties>
<children>
<tagNode name="rule">
<properties>
<help>Rule number</help>
<valueHelp>
<format>u32</format>
<description>Number of rule</description>
</valueHelp>
</properties>
<children>
<leafNode name="local-address">
<properties>
<help>IP address of the internal (local) device</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
</constraint>
</properties>
</leafNode>
<leafNode name="local-port">
<properties>
<help>Port number used by connection on internal device</help>
<valueHelp>
<format>u32:1-65535</format>
<description>Numeric IP port</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-65535"/>
</constraint>
<constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
</properties>
</leafNode>
#include <include/vpp/nat_protocol.xml.i>
<leafNode name="external-interface">
<properties>
<help>External interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
</leafNode>
#include <include/generic-description.xml.i>
</children>
</tagNode>
</children>
</node>
</children>
</node>
<node name="acl" owner="${vyos_conf_scripts_dir}/vpp_acl.py">
<properties>
<help>Access Control Lists (ACLs)</help>
<priority>332</priority>
</properties>
<children>
<node name="ip">
<properties>
<help>Access Control List by IPv4/IPv6</help>
</properties>
<children>
<tagNode name="tag-name">
<properties>
<help>ACL tag name</help>
<valueHelp>
<format>txt</format>
<description>Tag name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
<help>Rule number for Access control element (ACE)</help>
<valueHelp>
<format>u32</format>
<description>Number of ACE</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
<constraintErrorMessage>Rule number must be between 1 and 4294967295</constraintErrorMessage>
</properties>
<children>
#include <include/generic-description.xml.i>
<leafNode name="action">
<properties>
<help>Rule action</help>
<completionHelp>
<list>permit deny permit-reflect</list>
</completionHelp>
<valueHelp>
<format>permit</format>
<description>Permit matching traffic</description>
</valueHelp>
<valueHelp>
<format>deny</format>
<description>Deny matching traffic</description>
</valueHelp>
<valueHelp>
<format>permit-reflect</format>
<description>Permit the matching outbound traffic and allow the reverse traffic</description>
</valueHelp>
<constraint>
<regex>(permit|deny|permit-reflect)</regex>
</constraint>
</properties>
</leafNode>
<node name="source">
<properties>
<help>Source parameters</help>
</properties>
<children>
#include <include/vpp/acl_prefix.xml.i>
#include <include/vpp/acl_port_range.xml.i>
</children>
</node>
<node name="destination">
<properties>
<help>Destination parameters</help>
</properties>
<children>
#include <include/vpp/acl_prefix.xml.i>
#include <include/vpp/acl_port_range.xml.i>
</children>
</node>
<leafNode name="protocol">
<properties>
<help>Protocol</help>
<completionHelp>
<script>${vyos_completion_dir}/list_protocols.sh</script>
<list>all</list>
</completionHelp>
<valueHelp>
<format>all</format>
<description>All IP protocols</description>
</valueHelp>
<valueHelp>
<format>&lt;protocol&gt;</format>
<description>IP protocol name</description>
</valueHelp>
<constraint>
<validator name="ip-protocol"/>
</constraint>
</properties>
<defaultValue>all</defaultValue>
</leafNode>
<node name="tcp-flags">
<properties>
<help>TCP flags</help>
</properties>
<children>
#include <include/vpp/acl_tcp_flags.xml.i>
<node name="not">
<properties>
<help>Match flags not set</help>
</properties>
<children>
#include <include/vpp/acl_tcp_flags.xml.i>
</children>
</node>
</children>
</node>
</children>
</tagNode>
</children>
</tagNode>
<tagNode name="interface">
<properties>
<help>Apply an ACL to an interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
<node name="input">
<properties>
<help>Input direction</help>
</properties>
<children>
#include <include/vpp/acl_common_interface_ip_rule.xml.i>
</children>
</node>
<node name="output">
<properties>
<help>Output direction</help>
</properties>
<children>
#include <include/vpp/acl_common_interface_ip_rule.xml.i>
</children>
</node>
</children>
</tagNode>
</children>
</node>
<node name="macip">
<properties>
<help>Access Control List by mac address</help>
</properties>
<children>
<tagNode name="tag-name">
<properties>
<help>ACL tag name</help>
<valueHelp>
<format>txt</format>
<description>ACL name</description>
</valueHelp>
</properties>
<children>
#include <include/generic-description.xml.i>
<tagNode name="rule">
<properties>
<help>Rule number for Access control element (ACE)</help>
<valueHelp>
<format>u32</format>
<description>Number of ACE</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
<constraintErrorMessage>Rule number must be between 1 and 4294967295</constraintErrorMessage>
</properties>
<children>
#include <include/generic-description.xml.i>
<leafNode name="action">
<properties>
<help>Rule action</help>
<completionHelp>
<list>permit deny</list>
</completionHelp>
<valueHelp>
<format>permit</format>
<description>Permit matching traffic</description>
</valueHelp>
<valueHelp>
<format>deny</format>
<description>Deny matching traffic</description>
</valueHelp>
<constraint>
<regex>(permit|deny)</regex>
</constraint>
</properties>
</leafNode>
#include <include/vpp/acl_prefix.xml.i>
<leafNode name="prefix">
<properties>
<help>Source IP prefix</help>
</properties>
</leafNode>
<leafNode name="mac-address">
<properties>
<help>Source MAC address</help>
<valueHelp>
<format>macaddr</format>
<description>MAC address</description>
</valueHelp>
<constraint>
<validator name="mac-address"/>
</constraint>
</properties>
</leafNode>
<leafNode name="mac-mask">
<properties>
<help>Source MAC mask (default ff:ff:ff:ff:ff:ff)</help>
<valueHelp>
<format>macaddr</format>
<description>MAC mask</description>
</valueHelp>
<constraint>
<validator name="mac-address"/>
</constraint>
</properties>
<defaultValue>ff:ff:ff:ff:ff:ff</defaultValue>
</leafNode>
</children>
</tagNode>
</children>
</tagNode>
<tagNode name="interface">
<properties>
<help>Apply an ACL to an input interface</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
<children>
<leafNode name="tag-name">
<properties>
<help>ACL tag name</help>
<completionHelp>
<path>vpp acl macip tag-name</path>
</completionHelp>
</properties>
</leafNode>
</children>
</tagNode>
</children>
</node>
</children>
</node>
<tagNode name="kernel-interfaces" owner="${vyos_conf_scripts_dir}/vpp_kernel-interfaces.py">
<properties>
<help>VPP kernel interface settings</help>
<priority>328</priority>
<valueHelp>
<format>vpptapN</format>
<description>Kernel interface name</description>
</valueHelp>
<valueHelp>
<format>vpptunN</format>
<description>Kernel interface name</description>
</valueHelp>
<constraint>
<regex>vpp(tun|tap)\d+</regex>
</constraint>
<constraintErrorMessage>Kernel interface must start with vpp and include tun or tap N</constraintErrorMessage>
</properties>
<children>
#include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/generic-description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/mtu-68-16000.xml.i>
#include <include/vpp/vif.xml.i>
#include <include/vpp/iface_rx_mode.xml.i>
</children>
</tagNode>
</children>
</node>
</interfaceDefinition>
diff --git a/python/vyos/vpp/control_vpp.py b/python/vyos/vpp/control_vpp.py
index 42e7bf6c1..41b4573a5 100644
--- a/python/vyos/vpp/control_vpp.py
+++ b/python/vyos/vpp/control_vpp.py
@@ -1,505 +1,505 @@
#
# Copyright (C) VyOS Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from collections.abc import Callable
from functools import wraps
from re import search as re_search, MULTILINE as re_M
from systemd import journal
from time import sleep
from typing import TypeVar, ParamSpec, Literal
from vpp_papi import VPPApiClient
from vpp_papi import VPPIOError, VPPValueError
# define types for static type checkers
AnyType = TypeVar('AnyType')
AnyParam = ParamSpec('AnyParam')
class VPPControl:
"""Control VPP network stack"""
class _Decorators:
"""Decorators for VPPControl"""
@classmethod
def api_call(
cls, decorated_func: Callable[AnyParam, AnyType]
) -> Callable[AnyParam, AnyType]:
"""Check if API is connected before API call
Args:
decorated_func: function to decorate
Raises:
VPPIOError: Connection to API is not established
"""
@wraps(decorated_func)
def api_safe_wrapper(
cls, *args: AnyParam.args, **kwargs: AnyParam.kwargs
) -> AnyType:
if not cls.connected:
raise VPPIOError(2, 'VPP API is not connected')
return decorated_func(cls, *args, **kwargs)
return api_safe_wrapper
@classmethod
def check_retval(
cls, decorated_func: Callable[AnyParam, AnyType]
) -> Callable[AnyParam, AnyType]:
"""Check retval from API response
Args:
decorated_func: function to decorate
Raises:
VPPValueError: raised when retval is not 0
"""
@wraps(decorated_func)
def check_retval_wrapper(
cls, *args: AnyParam.args, **kwargs: AnyParam.kwargs
) -> AnyType:
return_value = decorated_func(cls, *args, **kwargs)
if not return_value.retval == 0:
raise VPPValueError(f'VPP API call failed: {return_value.retval}')
return return_value
return check_retval_wrapper
def __init__(self, attempts: int = 5, interval: int = 1000) -> None:
"""Create VPP API connection
Args:
attempts (int, optional): attempts to connect. Defaults to 5.
interval (int, optional): interval between attempts in ms. Defaults to 1000.
Raises:
VPPIOError: Connection to API cannot be established
"""
self.__vpp_api_client = VPPApiClient()
# connect with interval
while attempts:
try:
attempts -= 1
self.__vpp_api_client.connect('vpp-vyos')
break
except (ConnectionRefusedError, FileNotFoundError) as err:
error_message = f'VPP API connection timeout: {err}'
journal.send(error_message, priority=journal.LOG_ERR)
sleep(interval / 1000)
# raise exception if connection was not successful in the end
if not self.__vpp_api_client.transport.connected:
raise VPPIOError(2, 'Cannot connect to VPP API')
def __del__(self) -> None:
"""Disconnect from VPP API (destructor)"""
self.disconnect()
def disconnect(self) -> None:
"""Disconnect from VPP API"""
if self.__vpp_api_client.transport.connected:
self.__vpp_api_client.disconnect()
@_Decorators.check_retval
@_Decorators.api_call
def cli_cmd(self, command: str):
"""Send raw CLI command
Args:
command (str): command to send
Returns:
vpp_papi.vpp_serializer.cli_inband_reply: CLI reply class
"""
return self.__vpp_api_client.api.cli_inband(cmd=command)
@_Decorators.api_call
def get_mac(self, ifname: str) -> str:
"""Find MAC address by interface name in VPP
Args:
ifname (str): interface name inside VPP
Returns:
str: MAC address
"""
for iface in self.__vpp_api_client.api.sw_interface_dump():
if iface.interface_name == ifname:
return iface.l2_address.mac_string
return ''
@_Decorators.api_call
def get_sw_if_index(self, ifname: str) -> int | None:
"""Find interface index by interface name in VPP
Args:
ifname (str): interface name inside VPP
Returns:
int | None: Interface index or None (if was not fount)
"""
for iface in self.__vpp_api_client.api.sw_interface_dump():
if iface.interface_name == ifname:
return iface.sw_if_index
return None
@_Decorators.api_call
def get_interface_name(self, index: int) -> str | None:
"""Find interface name by interface index in VPP
Args:
index (int): interface index inside VPP
Returns:
str | None: Interface name or None (if was not found)
"""
for iface in self.__vpp_api_client.api.sw_interface_dump():
if iface.sw_if_index == index:
return iface.interface_name
return None
@_Decorators.check_retval
@_Decorators.api_call
def lcp_pair_add(
self,
iface_name_vpp: str,
iface_name_kernel: str,
iface_type: Literal['tun', 'tap', ''] = '',
) -> None:
"""Create LCP interface pair between VPP and kernel
Args:
iface_name_vpp (str): interface name in VPP
iface_name_kernel (str): interface name in kernel
iface_type (Literal['tun', 'tap', ''], optional): Use explicit interface type in kernel. Defaults to ''.
"""
iface_index = self.get_sw_if_index(iface_name_vpp)
if iface_index:
api_call_args: dict[str, bool | int | str] = {
'is_add': True,
'sw_if_index': iface_index,
'host_if_name': iface_name_kernel,
}
if iface_type:
iface_type_resolve = {'tun': 1, 'tap': 0}
api_call_args['host_if_type'] = iface_type_resolve[iface_type]
return self.__vpp_api_client.api.lcp_itf_pair_add_del_v2(**api_call_args)
@_Decorators.check_retval
@_Decorators.api_call
def lcp_pair_del(self, iface_name_vpp: str, iface_name_kernel: str) -> None:
"""Delete LCP interface pair between VPP and kernel
Args:
iface_name_vpp (str): interface name in VPP
iface_name_kernel (str): interface name in kernel
"""
iface_index = self.get_sw_if_index(iface_name_vpp)
if iface_index:
return self.__vpp_api_client.api.lcp_itf_pair_add_del_v2(
is_add=False, sw_if_index=iface_index, host_if_name=iface_name_kernel
)
@_Decorators.api_call
def lcp_pair_find(
self,
kernel_name: str = '',
vpp_index_hw: int | None = None,
vpp_index_kernel: int | None = None,
vpp_name_hw: str = '',
vpp_name_kernel: str = '',
) -> dict[str, str | int] | None:
"""Find LCP pair details
Args:
kernel_name (str, optional): Interface name in the kernel. Defaults to ''.
vpp_index_hw (int | None, optional): Interface index in VPP (hardware). Defaults to None.
vpp_index_kernel (int | None, optional): Interface index in VPP (kernel). Defaults to None.
vpp_name_hw (str, optional): Interface name in VPP (hardware). Defaults to ''.
vpp_name_kernel (str, optional): Interface name in VPP (to kernel). Defaults to ''.
Returns:
dict[str, str | int] | None: LCP pair details
"""
filter_dict = {}
for filter_name, filter_value in locals().items():
if filter_value:
filter_dict[filter_name] = filter_value
# Get list of pairs
lcp_pairs = self.lcp_pairs_list()
# Check each pair
for pair in lcp_pairs:
pair_found = False
# For each item provided in function arguments
for filter_name, filter_value in filter_dict.items():
# Stop if filter value is not as in a current pair
if filter_name in pair and pair[filter_name] != filter_value:
pair_found = False
break
# Set flag to True and check the next filter value
pair_found = True
if pair_found:
return pair
return None
@_Decorators.api_call
def lcp_pairs_list(self) -> list[dict[str, str | int]]:
"""List all LCP pairs
Returns:
list[dict[str, str | int]]: LCP pairs details
"""
lcp_pairs_details = []
lcp_pairs = self.__vpp_api_client.api.lcp_itf_pair_get()[1]
vpp_ifaces = self.__vpp_api_client.api.sw_interface_dump()
for pair in lcp_pairs:
pair_details = {
'kernel_name': pair.host_if_name,
'vpp_index_hw': pair.phy_sw_if_index,
'vpp_index_kernel': pair.host_sw_if_index,
}
for vpp_iface in vpp_ifaces:
if vpp_iface.sw_if_index == pair_details['vpp_index_hw']:
pair_details['vpp_name_hw'] = vpp_iface.interface_name
if vpp_iface.sw_if_index == pair_details['vpp_index_kernel']:
pair_details['vpp_name_kernel'] = vpp_iface.interface_name
lcp_pairs_details.append(pair_details)
return lcp_pairs_details
@_Decorators.check_retval
@_Decorators.api_call
def lcp_resync(self) -> None:
"""Resynchronize objects between kernel and VPP via Netlink
This clears all routes in VPP configured by LCP and re-creates them
based on the current state of the kernel.
"""
return self.__vpp_api_client.api.lcp_nl_resync()
@_Decorators.check_retval
@_Decorators.api_call
def iface_rxmode(self, iface_name: str, rx_mode: str) -> None:
"""Set interface rx-mode in VPP
Args:
iface_name (str): interface name in VPP
rx_mode (str): mode (polling, interrupt, adaptive)
"""
modes_dict: dict[str, int] = {'polling': 1, 'interrupt': 2, 'adaptive': 3}
if rx_mode not in modes_dict:
raise VPPValueError(f'Mode {rx_mode} is not known')
iface_index = self.get_sw_if_index(iface_name)
return self.__vpp_api_client.api.sw_interface_set_rx_mode(
sw_if_index=iface_index, mode=modes_dict[rx_mode]
)
@_Decorators.api_call
def get_pci_addr(self, ifname: str) -> str:
"""Find PCI address of interface by interface name in VPP
Args:
ifname (str): interface name inside VPP
Returns:
str: PCI address
"""
hw_info = self.cli_cmd(f'show hardware-interfaces {ifname}').reply
regex_filter = r'^\s+pci: device (?P<device>\w+:\w+) subsystem (?P<subsystem>\w+:\w+) address (?P<address>\w+:\w+:\w+\.\w+) numa (?P<numa>\w+)$'
re_obj = re_search(regex_filter, hw_info, re_M)
# return empty string if no interface or no PCI info was found
if not hw_info or not re_obj:
return ''
address = re_obj.groupdict().get('address', '')
# we need to modify address to match kernel style
# for example: 0000:06:14.00 -> 0000:06:14.0
address_chunks: list[str] = address.split('.')
address_normalized: str = f'{address_chunks[0]}.{int(address_chunks[1])}'
return address_normalized
@_Decorators.check_retval
@_Decorators.api_call
def xdp_iface_create(
self,
host_if: str,
name: str,
rxq_num: int = 0,
rxq_size: int = 0,
txq_size: int = 0,
mode: Literal['auto', 'copy', 'zero-copy'] = 'auto',
flags: Literal['no_syscall_lock', ''] = '',
) -> None:
"""Create XDP interface
Args:
host_if (str): name of an interface in kernel
name (str): name of an interface in VPP
rxq_num (int, optional): Number of receive queues to connect to. Defaults to 0 (all).
rxq_size (int, optional): Size of receive queue. Defaults to 0.
txq_size (int, optional): Size of tranceive queue. Defaults to 0.
mode (Literal['auto', 'copy', 'zero-copy', optional): Zero-copy mode. Defaults to 'auto'.
flags (Literal['no_syscall_lock', ''], optional): Syscall lock mode. Defaults to ''.
"""
api_call_args: dict[str, int | str] = {
'host_if': host_if,
'name': name,
'rxq_num': rxq_num,
'rxq_size': rxq_size,
'txq_size': txq_size,
}
if mode != 'auto':
mode_resolve: dict[str, int] = {'auto': 0, 'copy': 1, 'zero-copy': 2}
api_call_args['mode'] = mode_resolve[mode]
- if flags == 'no_systcall_lock':
+ if flags == 'no_syscall_lock':
api_call_args['flags'] = 1
return self.__vpp_api_client.api.af_xdp_create_v3(**api_call_args)
@_Decorators.check_retval
@_Decorators.api_call
def xdp_iface_delete(self, iface_name_vpp: str) -> None:
"""Delete XDP interface
Args:
iface_name_vpp (str): Name of an interface in VPP
"""
iface_index = self.get_sw_if_index(iface_name_vpp)
if iface_index:
api_call_args: dict[str, int] = {'sw_if_index': iface_index}
return self.__vpp_api_client.api.af_xdp_delete(**api_call_args)
@_Decorators.check_retval
@_Decorators.api_call
def set_iface_mac(self, iface_name_vpp: str, mac_address: str) -> None:
"""Set MAC address of an interface
Args:
iface_name_vpp (str): Name of an interface in VPP
mac_address (str): MAC address
"""
iface_index = self.get_sw_if_index(iface_name_vpp)
api_call_args: dict[str, str | int] = {
'sw_if_index': iface_index,
'mac_address': mac_address,
}
return self.__vpp_api_client.api.sw_interface_set_mac_address(**api_call_args)
@_Decorators.check_retval
@_Decorators.api_call
def set_iface_mtu(self, iface_name_vpp: str, mtu: int) -> None:
"""Set MTU for interface
Args:
iface_name_vpp (str): Name of an interface in VPP
mtu (int): MTU
"""
iface_index = self.get_sw_if_index(iface_name_vpp)
api_call_args: dict[str, str | int] = {'sw_if_index': iface_index, 'mtu': mtu}
return self.__vpp_api_client.api.hw_interface_set_mtu(**api_call_args)
@_Decorators.api_call
def get_sw_if_dev_type(self, ifname: str) -> int | None:
"""Find interface device type by interface name in VPP
Args:
ifname (str): interface name inside VPP
Returns:
int | None: Interface device type or None (if was not fount)
"""
for iface in self.__vpp_api_client.api.sw_interface_dump():
if iface.interface_name == ifname:
return iface.interface_dev_type
return None
@_Decorators.api_call
def enable_disable_nat44_forwarding(self, enable: bool) -> None:
"""Enable/disable NAT44 forwarding
Args:
enable (bool): True if enable, False if disable
"""
self.__vpp_api_client.api.nat44_forwarding_enable_disable(enable=enable)
@_Decorators.api_call
def set_nat44_session_limit(self, session_limit: int) -> None:
"""Set NAT44 session limit
Args:
session_limit (int): Maximum number of sessions per thread
"""
self.__vpp_api_client.api.nat44_set_session_limit(
session_limit=session_limit,
)
@_Decorators.api_call
def set_nat_workers(self, workers: int) -> None:
"""Set NAT44 session limit
Args:
workers (int): Bitmask of workers list
"""
self.__vpp_api_client.api.nat_set_workers(worker_mask=workers)
@property
def connected(self) -> bool:
"""Check if VPP API is connected
Returns:
bool: True if connected, False if not
"""
return self.__vpp_api_client.transport.connected
@property
@_Decorators.api_call
def api(self):
"""Call API
Returns:
Callable[AnyParam, AnyType]: API functions
"""
return self.__vpp_api_client.api
@_Decorators.api_call
def map_pppoe_interface(self, ifname: str, is_add: bool) -> None:
"""Create or delete PPPoE mapping between data-plain and control-plane interfaces
Args:
ifname (str): name of an interface in kernel
is_add (bool): create or delete mapping
"""
vpp_pair = self.lcp_pair_find(kernel_name=ifname)
if vpp_pair:
vpp_pair_name = vpp_pair['vpp_name_kernel']
self.__vpp_api_client.api.pppoe_add_del_cp(
dp_sw_if_index=self.get_sw_if_index(ifname),
cp_sw_if_index=self.get_sw_if_index(vpp_pair_name),
is_add=is_add,
)
diff --git a/smoketest/config-tests/vpp b/smoketest/config-tests/vpp
index e42568965..46856fe25 100644
--- a/smoketest/config-tests/vpp
+++ b/smoketest/config-tests/vpp
@@ -1,49 +1,49 @@
set interfaces ethernet eth0 description 'WAN'
set interfaces ethernet eth0 offload gro
set interfaces ethernet eth0 offload gso
set interfaces ethernet eth0 offload sg
set interfaces ethernet eth0 offload tso
set interfaces ethernet eth1 address '192.0.2.1/30'
set interfaces ethernet eth1 address '192.0.2.21/30'
set interfaces ethernet eth1 description 'WAN'
set interfaces ethernet eth2
set interfaces ethernet eth3
set interfaces ethernet eth4 description 'Bonding'
set interfaces loopback lo
set system config-management commit-revisions '100'
set system console device ttyS0 speed '115200'
set system host-name 'r16'
set system login user vyos authentication encrypted-password '$6$rounds=656000$FZBlGpWsmSrV2Rvq$YPNAPtk4k6u99FAMxR6cw4DUPCgOomwCgRZRSO5rAoJK8RlMSCkVAFVF3ozL/3mZMfxcuCnwvd5HX6f9V5KzO.'
set system name-server '203.0.113.1'
set system option kernel memory hugepage-size 2M hugepage-count '2048'
set system option time-format '24-hour'
set system syslog local facility all level 'info'
set system syslog local facility local7 level 'debug'
set vpp interfaces bonding bond0 hash-policy 'layer2+3'
set vpp interfaces bonding bond0 member interface 'eth4'
set vpp interfaces bonding bond0 mode '802.3ad'
set vpp interfaces gre gre10 description 'Layer-3'
set vpp interfaces gre gre10 mode 'point-to-point'
set vpp interfaces gre gre10 remote '192.0.2.2'
set vpp interfaces gre gre10 source-address '192.0.2.1'
set vpp interfaces gre gre10 tunnel-type 'l3'
set vpp interfaces gre gre11 description 'L2-bridge-to-eth2'
set vpp interfaces gre gre11 remote '192.0.2.123'
set vpp interfaces gre gre11 source-address '192.0.2.1'
set vpp interfaces gre gre11 tunnel-type 'teb'
set vpp interfaces gre gre12 description 'Multipoint'
set vpp interfaces gre gre12 mode 'point-to-multipoint'
set vpp interfaces gre gre12 remote '0.0.0.0'
set vpp interfaces gre gre12 source-address '192.0.2.21'
set vpp interfaces gre gre12 tunnel-type 'l3'
set vpp interfaces ipip ipip20 remote '192.0.2.2'
set vpp interfaces ipip ipip20 source-address '192.0.2.1'
set vpp interfaces loopback lo12 description 'Loop12'
set vpp interfaces vxlan vxlan10 remote '192.0.2.2'
set vpp interfaces vxlan vxlan10 source-address '192.0.2.1'
set vpp interfaces vxlan vxlan10 vni '10'
set vpp settings interface eth1 driver 'dpdk'
set vpp settings interface eth2 driver 'dpdk'
-set vpp settings interface eth3 driver 'dpdk'
+set vpp settings interface eth3 driver 'xdp'
set vpp settings interface eth4 driver 'dpdk'
set vpp settings unix poll-sleep-usec '12'
diff --git a/smoketest/configs/vpp b/smoketest/configs/vpp
index 472c2919b..e334b36bb 100644
--- a/smoketest/configs/vpp
+++ b/smoketest/configs/vpp
@@ -1,128 +1,131 @@
interfaces {
ethernet eth0 {
description "WAN"
offload {
gro
gso
sg
tso
}
}
ethernet eth1 {
address "192.0.2.1/30"
address "192.0.2.21/30"
description "WAN"
}
ethernet eth2 {
}
ethernet eth3 {
}
ethernet eth4 {
description "Bonding"
}
loopback lo {
}
}
system {
config-management {
commit-revisions "100"
}
console {
device ttyS0 {
speed "115200"
}
}
host-name "r16"
login {
user vyos {
authentication {
encrypted-password "$6$rounds=656000$FZBlGpWsmSrV2Rvq$YPNAPtk4k6u99FAMxR6cw4DUPCgOomwCgRZRSO5rAoJK8RlMSCkVAFVF3ozL/3mZMfxcuCnwvd5HX6f9V5KzO."
}
}
}
name-server "203.0.113.1"
option {
kernel {
memory {
hugepage-size 2M {
hugepage-count "2048"
}
}
}
time-format "24-hour"
}
syslog {
global {
facility all {
level "info"
}
facility local7 {
level "debug"
}
}
}
}
vpp {
interfaces {
bonding bond0 {
hash-policy "layer2+3"
member {
interface "eth4"
}
mode "802.3ad"
}
gre gre10 {
description "Layer-3"
mode "point-to-point"
remote "192.0.2.2"
source-address "192.0.2.1"
tunnel-type "l3"
}
gre gre11 {
description "L2-bridge-to-eth2"
remote "192.0.2.123"
source-address "192.0.2.1"
tunnel-type "teb"
}
gre gre12 {
description "Multipoint"
mode "point-to-multipoint"
remote "0.0.0.0"
source-address "192.0.2.21"
tunnel-type "l3"
}
ipip ipip20 {
remote "192.0.2.2"
source-address "192.0.2.1"
}
loopback lo12 {
description "Loop12"
}
vxlan vxlan10 {
remote "192.0.2.2"
source-address "192.0.2.1"
vni "10"
}
}
settings {
interface eth1 {
driver "dpdk"
}
interface eth2 {
driver "dpdk"
}
interface eth3 {
- driver "dpdk"
+ driver "xdp"
+ xdp-options {
+ no-syscall-lock
+ }
}
interface eth4 {
driver "dpdk"
}
unix {
poll-sleep-usec "12"
}
}
}
// Warning: Do not remove the following line.
// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@11:dhcpv6-server@6:dns-dynamic@4:dns-forwarding@4:firewall@18:flow-accounting@2:https@7:ids@1:interfaces@33:ipoe-server@4:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@2:nat@8:nat66@3:nhrp@1:ntp@3:openconnect@3:openvpn@4:ospf@2:pim@1:policy@8:pppoe-server@11:pptp@5:qos@3:quagga@12:reverse-proxy@2:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@28:vpp@1:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2"
// Release version: 1.5-rolling-202502030007
diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py
index 37485bd0d..03b2fc673 100755
--- a/smoketest/scripts/cli/test_vpp.py
+++ b/smoketest/scripts/cli/test_vpp.py
@@ -1,1483 +1,1483 @@
#!/usr/bin/env python3
#
# Copyright (C) VyOS Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import re
import unittest
from collections import defaultdict
from json import loads
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
from vyos.utils.process import rc_cmd
from vyos.utils.system import sysctl_read
PROCESS_NAME = 'vpp_main'
VPP_CONF = '/run/vpp/vpp.conf'
base_path = ['vpp']
driver = 'dpdk'
interface = 'eth1'
def get_vpp_config():
config = defaultdict(dict)
current_section = None
with open(VPP_CONF, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'): # Ignore empty lines and comments
continue
section_match = re.match(r'([a-zA-Z0-9_-]+)\s*{', line)
if section_match:
current_section = section_match.group(1)
config[current_section] = {}
continue
if line == '}': # End of section
current_section = None
continue
key_value_match = re.match(r'([a-zA-Z0-9_-]+)\s+(.+)', line)
if key_value_match:
key, value = key_value_match.groups()
if current_section:
config[current_section][key] = value
else:
config[key] = value
return config
def get_address(interface):
rc, data = rc_cmd(f'ip --json address show dev {interface}')
if rc == 0:
data = loads(data)
if isinstance(data, list) and len(data) > 0:
ip_address = data[0]['addr_info'][0]['local']
return ip_address
class TestVPP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
super(TestVPP, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
def setUp(self):
self.cli_set(base_path + ['settings', 'interface', interface, 'driver', driver])
self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', '10'])
def tearDown(self):
try:
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
finally:
# Ensure these cleanup operations always run
self.cli_delete(base_path)
self.cli_commit()
# delete address for Ethernet interface
self.cli_delete(['interfaces', 'ethernet', interface, 'address'])
self.cli_commit()
self.assertFalse(os.path.exists(VPP_CONF))
self.assertFalse(process_named_running(PROCESS_NAME))
def test_01_vpp_basic(self):
main_core = '0'
poll_sleep = '0'
# Main core must be verified
# expect raise ConfigError
self.cli_set(base_path + ['settings', 'cpu', 'main-core', '99'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', poll_sleep])
# commit changes
self.cli_commit()
config_entries = (
f'poll-sleep-usec {poll_sleep}',
f'main-core {main_core}',
'plugin default { disable }',
'plugin dpdk_plugin.so { enable }',
'plugin linux_cp_plugin.so { enable }',
'dev 0000:00:00.0',
'uio-bind-force',
)
# Check configured options
config = read_file(VPP_CONF)
for config_entry in config_entries:
self.assertIn(config_entry, config)
# route-no-paths is not present in the output
# looks like vpp bug
_, out = rc_cmd('sudo vppctl show lcp')
required_str = 'lcp route-no-paths on'
self.assertIn(required_str, out)
self.cli_set(base_path + ['settings', 'lcp', 'ignore-kernel-routes'])
self.cli_commit()
# check disabled 'route no path'
_, out = rc_cmd('sudo vppctl show lcp')
required_str = 'lcp route-no-paths off'
self.assertIn(required_str, out)
def test_02_vpp_vxlan(self):
vni = '23'
interface_vxlan = f'vxlan{vni}'
interface_kernel = f'vpptap{vni}'
new_interface_kernel = f'vpptap1{vni}'
source_address = '192.0.2.1'
new_source_address = '192.0.2.3'
remote_address = '192.0.2.254'
kernel_address = '203.0.113.1'
self.cli_set(['interfaces', 'ethernet', interface, 'address', '192.0.2.1/24'])
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'source-address', source_address]
)
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan, 'vni', vni])
# remote and source address must not be the same
# expect raise ConfigError
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', source_address]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', remote_address]
)
self.cli_set(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/24']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check vxlan interface
_, out = rc_cmd('sudo vppctl show vxlan tunnel')
required_str = f'[0] instance 23 src {source_address} dst {remote_address} src_port 4789 dst_port 4789 vni {vni}'
self.assertIn(required_str, out)
# update vxlan interface
self.cli_set(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'source-address',
new_source_address,
]
)
# source address of the tunnel interface should be configured
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(
[
'interfaces',
'ethernet',
interface,
'vif',
vni,
'address',
f'{new_source_address}/24',
]
)
self.cli_commit()
# check gre interface after update
_, out = rc_cmd('sudo vppctl show vxlan tunnel')
required_str = (
f'[0] instance {vni} src {new_source_address} dst {remote_address}'
)
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# change vpp settings
self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', '5'])
self.cli_commit()
config = read_file(VPP_CONF)
self.assertIn('poll-sleep-usec 5', config)
# delete vxlan kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update vxlan kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete vxlan kernel-interface
self.cli_delete(
base_path + ['interfaces', 'vxlan', interface_vxlan, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete vxlan interface
self.cli_delete(base_path + ['interfaces', 'vxlan', interface_vxlan])
self.cli_commit()
# delete vif Ethernet interface
self.cli_delete(['interfaces', 'ethernet', interface, 'vif'])
self.cli_commit()
def test_03_vpp_gre(self):
interface_gre = 'gre12'
interface_kernel = 'vpptun12'
new_interface_kernel = 'vpptun123'
source_address = '192.0.2.1'
new_source_address = '192.0.2.2'
remote_address = '192.0.2.254'
kernel_address = '10.0.0.0'
self.cli_set(
base_path
+ ['interfaces', 'gre', interface_gre, 'source-address', source_address]
)
self.cli_set(
base_path + ['interfaces', 'gre', interface_gre, 'remote', remote_address]
)
self.cli_set(
base_path
+ ['interfaces', 'gre', interface_gre, 'kernel-interface', interface_kernel]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/31']
)
# source address of the tunnel interface should be configured
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(
['interfaces', 'ethernet', interface, 'address', f'{source_address}/24']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check gre interface
_, out = rc_cmd('sudo vppctl show gre tunnel')
required_str = f'[0] instance 12 src {source_address} dst {remote_address}'
self.assertIn(required_str, out)
# update gre interface
self.cli_set(
base_path
+ ['interfaces', 'gre', interface_gre, 'source-address', new_source_address]
)
self.cli_set(
['interfaces', 'ethernet', interface, 'address', f'{new_source_address}/24']
)
self.cli_commit()
# check gre interface after update
_, out = rc_cmd('sudo vppctl show gre tunnel')
required_str = f'[0] instance 12 src {new_source_address} dst {remote_address}'
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# delete gre kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ ['interfaces', 'gre', interface_gre, 'kernel-interface', interface_kernel]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update gre kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'gre',
interface_gre,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete kernel interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete gre kernel-interface
self.cli_delete(
base_path + ['interfaces', 'gre', interface_gre, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete gre interface
self.cli_delete(base_path + ['interfaces', 'gre', interface_gre])
self.cli_commit()
@unittest.skip('Skipping this test geneve index always is 0')
def test_04_vpp_geneve(self):
vni = '2'
# Must be 'geneve0' to pass smoketest
# As geneve interfaces cannot be named with "instance" suffix
interface_geneve = 'geneve0'
interface_kernel = f'vpptun{vni}'
new_interface_kernel = f'vpptun1{vni}'
source_address = '192.0.2.1'
new_source_address = '192.0.2.2'
remote_address = '203.0.113.10'
kernel_address = '10.0.0.1'
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'source-address',
source_address,
]
)
self.cli_set(
base_path
+ ['interfaces', 'geneve', interface_geneve, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'geneve', interface_geneve, 'vni', vni])
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/31']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check geneve interface
_, out = rc_cmd('sudo vppctl show geneve tunnel')
required_str = f'[0] lcl {source_address} rmt {remote_address} vni {vni}'
self.assertIn(required_str, out)
# update geneve interface
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'source-address',
new_source_address,
]
)
self.cli_commit()
# check geneve interface after update
_, out = rc_cmd('sudo vppctl show geneve tunnel')
required_str = f'[0] lcl {new_source_address} rmt {remote_address} vni {vni}'
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# delete geneve kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update gemeve kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete geneve kernel-interface
self.cli_delete(
base_path + ['interfaces', 'geneve', interface_geneve, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete geneve interface
self.cli_set(base_path + ['interfaces', 'geneve', interface_geneve])
self.cli_commit()
def test_05_vpp_loopback(self):
interface_loopback = 'lo11'
interface_kernel = 'vpptun11'
new_interface_kernel = 'vpptun12'
kernel_address = '192.0.2.54'
self.cli_set(base_path + ['interfaces', 'loopback', interface_loopback])
self.cli_set(
base_path
+ [
'interfaces',
'loopback',
interface_loopback,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/25']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check loopback interface
_, out = rc_cmd('sudo vppctl show interface loop11')
required_str = 'loop11'
self.assertIn(required_str, out)
# delete loopback kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'loopback',
interface_loopback,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update loopback kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'loopback',
interface_loopback,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete loopback kernel-interface
self.cli_delete(
base_path
+ ['interfaces', 'loopback', interface_loopback, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete loopback interface
self.cli_delete(base_path + ['interfaces', 'loopback', interface_loopback])
self.cli_commit()
@unittest.skip('Skipping temporary bonding, sometimes get recursion T7117')
def test_06_vpp_bonding(self):
interface_bond = 'bond23'
interface_kernel = 'vpptun23'
hash = 'layer3+4'
mode = '802.3ad'
description = 'Interface-Bonding'
vlans = ['123', '456']
vlan_description = 'My-vlan-123'
self.cli_set(
base_path
+ [
'interfaces',
'bonding',
interface_bond,
'member',
'interface',
interface,
]
)
self.cli_set(
base_path + ['interfaces', 'bonding', interface_bond, 'hash-policy', hash]
)
self.cli_set(
base_path + ['interfaces', 'bonding', interface_bond, 'mode', mode]
)
# commit changes
self.cli_commit()
# Check for interface state "BondEthernet23 up"
_, out = rc_cmd('sudo vppctl show interface')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
self.assertRegex(
normalized_out,
r'BondEthernet23\s+\d+\s+up',
"Interface BondEthernet23 is not in the expected state 'up'.",
)
# set kernel interface
self.cli_set(
base_path
+ [
'interfaces',
'bonding',
interface_bond,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'description', description]
)
for vlan in vlans:
self.cli_set(
base_path
+ [
'kernel-interfaces',
interface_kernel,
'vif',
vlan,
'description',
vlan_description,
]
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}.{vlan}'))
current_alias = read_file(f'/sys/class/net/{interface_kernel}/ifalias')
vlan_alias = read_file(f'/sys/class/net/{interface_kernel}.{vlan}/ifalias')
self.assertEqual(current_alias, description)
self.assertEqual(vlan_alias, vlan_description)
# check bonding interface
_, out = rc_cmd('sudo vppctl show bond details')
required_enries = (
'BondEthernet23',
'mode: lacp',
'load balance: l34',
'number of active members: 0',
'number of members: 1',
f'{interface}',
'device instance: 0',
'interface id: 23',
)
for entry in required_enries:
self.assertIn(entry, out)
# check interface state
_, out = rc_cmd('sudo vppctl show interface')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
# Check for interface state "BondEthernet23 up"
self.assertRegex(
normalized_out,
r'BondEthernet23\s+\d+\s+up',
"Interface BondEthernet23 is not in the expected state 'up'.",
)
# delete vpp kernel-interface vlan
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel, 'vif'])
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}.{vlan}'))
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete bonding kernel-interface
self.cli_delete(
base_path + ['interfaces', 'bonding', interface_bond, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete bonding interface
self.cli_delete(base_path + ['interfaces', 'bonding'])
self.cli_commit()
# check deleting bonding interface
_, out = rc_cmd('sudo vppctl show interface')
self.assertNotIn('BondEthernet23', out)
def test_07_vpp_bridge(self):
fake_member = 'eth2'
members = [interface]
interface_bridge = 'br10'
vni = '23'
interface_vxlan = f'vxlan{vni}'
source_address = '192.0.2.1'
remote_address = '192.0.2.254'
self.cli_set(['interfaces', 'ethernet', interface, 'address', '192.0.2.1/24'])
for member in members:
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
member,
]
)
# commit changes
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
# Perform assertions based on the normalized output
self.assertIn('BD-ID Index BSN Age(min)', normalized_out)
self.assertIn('10 1 0 off', normalized_out)
self.assertIn('Learning U-Forwrd UU-Flood Flooding', normalized_out)
self.assertIn('on on flood on', normalized_out)
self.assertIn('Interface If-idx ISN', normalized_out)
# Check Interface, If-idx, ISN
self.assertRegex(out, r'\s*eth1\s+\d+\s+\d+')
# Set non exist member
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
fake_member,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
fake_member,
]
)
# Add VXLAN to the bridge
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'source-address', source_address]
)
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan, 'vni', vni])
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
interface_vxlan,
]
)
# commit changes
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
# Perform assertions based on the normalized output
self.assertIn('BD-ID Index BSN Age(min)', normalized_out)
self.assertIn('10 1 0 off', normalized_out)
self.assertIn('Learning U-Forwrd UU-Flood Flooding', normalized_out)
self.assertIn('on on flood on', normalized_out)
self.assertIn('Interface If-idx ISN', normalized_out)
# Check Interface, If-idx, ISN
self.assertRegex(out, r'\s*eth1\s+\d+\s+\d+')
self.assertRegex(out, r'\s*vxlan_tunnel23\s+\d+\s+\d+')
# Add check dependency ethernet => bridge
self.cli_set(
base_path + ['settings', 'interface', interface, 'dpdk-options', 'promisc']
)
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
self.assertRegex(out, r'\s*eth1\s+\d+\s+\d+')
self.assertRegex(out, r'\s*vxlan_tunnel23\s+\d+\s+\d+')
# Add Loopback BVI to the bridge
self.cli_set(base_path + ['interfaces', 'loopback', f'lo{vni}'])
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
f'lo{vni}',
'bvi',
]
)
# commit changes
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
self.assertIn('10 1 0 off', normalized_out)
self.assertRegex(out, r'\bloop23\s+\d+\s+\d+\s+\d+\s+\*\s+')
def test_08_vpp_ipip(self):
interface_ipip = 'ipip12'
interface_kernel = 'vpptun12'
new_interface_kernel = 'vpptun123'
source_address = '192.0.2.1'
new_source_address = '192.0.2.2'
remote_address = '192.0.2.5'
kernel_address = '10.0.0.0'
self.cli_set(['interfaces', 'ethernet', interface, 'address', '192.0.2.1/24'])
self.cli_set(
base_path
+ ['interfaces', 'ipip', interface_ipip, 'source-address', source_address]
)
self.cli_set(
base_path + ['interfaces', 'ipip', interface_ipip, 'remote', remote_address]
)
self.cli_set(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/31']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check ipip interface
_, out = rc_cmd('sudo vppctl show ipip tunnel')
required_str = f'[0] instance 12 src {source_address} dst {remote_address}'
self.assertIn(required_str, out)
# update ipip interface
self.cli_set(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'source-address',
new_source_address,
]
)
# source address of the tunnel interface should be configured
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(
['interfaces', 'ethernet', interface, 'address', f'{new_source_address}/24']
)
self.cli_commit()
# check ipip interface after update
_, out = rc_cmd('sudo vppctl show ipip tunnel')
required_str = f'[0] instance 12 src {new_source_address} dst {remote_address}'
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# delete ipip kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update ipip kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete kernel interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete ipip kernel-interface
self.cli_delete(
base_path + ['interfaces', 'ipip', interface_ipip, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete ipip interface
self.cli_delete(base_path + ['interfaces', 'ipip', interface_ipip])
self.cli_commit()
def test_09_vpp_xconnect(self):
vni = '23'
interface_vxlan = f'vxlan{vni}'
interface_xconnect = f'xcon{vni}'
source_address = '192.0.2.1'
remote_address = '192.0.2.254'
self.cli_set(['interfaces', 'ethernet', interface, 'address', '192.0.2.1/24'])
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'source-address', source_address]
)
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan, 'vni', vni])
# Add xconneect
self.cli_set(
base_path
+ [
'interfaces',
'xconnect',
interface_xconnect,
'member',
'interface',
interface,
]
)
# Cross connect interfaces require 2 interfaces
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(
base_path
+ [
'interfaces',
'xconnect',
interface_xconnect,
'member',
'interface',
interface_vxlan,
]
)
# commit changes
self.cli_commit()
# check interface mode
_, out = rc_cmd('sudo vppctl show mode')
required_str_list = [
f'l2 xconnect {interface} vxlan_tunnel{vni}',
f'l2 xconnect vxlan_tunnel{vni} {interface}',
]
for required_string in required_str_list:
self.assertIn(required_string, out)
# delete xconnect interface
self.cli_delete(base_path + ['interfaces', 'xconnect', interface_xconnect])
self.cli_commit()
# check delete xconnect interface
_, out = rc_cmd('sudo vppctl show mode')
for required_string in required_str_list:
self.assertNotIn(required_string, out)
def test_10_vpp_driver_options(self):
dpdk_options = {
'num-rx-desc': '512',
'num-tx-desc': '512',
'num-rx-queues': '1',
'num-tx-queues': '1',
}
main_core = '0'
workers = '1'
base_interface_path = base_path + ['settings', 'interface', interface]
for option, value in dpdk_options.items():
self.cli_set(base_interface_path + ['dpdk-options', option, value])
# rx/tx queue configuration expect VPP workers to be set
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
self.cli_set(base_path + ['settings', 'cpu', 'workers', workers])
# DPDK driver expect only dpdk-options and not xdp-options to be set
# expect raise ConfigError
- self.cli_set(base_interface_path + ['xdp-options', 'no-syscall-lock'])
+ self.cli_set(base_interface_path + ['xdp-options', 'zero-copy'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete xdp-options and apply commit
self.cli_delete(base_interface_path + ['xdp-options'])
self.cli_commit()
# check dpdk options in config file
config = read_file(VPP_CONF)
for option, value in dpdk_options.items():
self.assertIn(f'{option} {value}', config)
def test_11_vpp_cpu_settings(self):
main_core = '2'
workers = '1'
skip_cores = '1'
self.cli_set(base_path + ['settings', 'cpu', 'workers', workers])
# "cpu workers" reqiures main-core to be set
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
self.cli_set(base_path + ['settings', 'cpu', 'skip-cores', '99'])
# "cpu skip-cores" cannot be more than number of available CPUs - 1
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'skip-cores', skip_cores])
self.cli_commit()
config_entries = (
f'skip-cores {skip_cores}',
f'main-core {main_core}',
f'workers {workers}',
'dev 0000:00:00.0',
)
# Check configured options
config = read_file(VPP_CONF)
for config_entry in config_entries:
self.assertIn(config_entry, config)
def test_12_vpp_cpu_corelist_workers(self):
main_core = '0'
corelist_workers = ['3']
for worker in corelist_workers:
self.cli_set(base_path + ['settings', 'cpu', 'corelist-workers', worker])
# "cpu corelist-workers" reqiures main-core to be set
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
# corelist-workers and workers cannot be used at the same time
# expect raise ConfigError
self.cli_set(base_path + ['settings', 'cpu', 'workers', '2'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(base_path + ['settings', 'cpu', 'workers'])
# verify corelist-workers are set not correctly
# expect raise ConfigError
self.cli_set(base_path + ['settings', 'cpu', 'corelist-workers', '99-101'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(base_path + ['settings', 'cpu', 'corelist-workers', '99-101'])
self.cli_commit()
config_entries = (
f'main-core {main_core}',
f'corelist-workers {",".join(corelist_workers)}',
'dev 0000:00:00.0',
)
# Check configured options
config = read_file(VPP_CONF)
for config_entry in config_entries:
self.assertIn(config_entry, config)
def test_13_1_buffer_page_size(self):
sizes = ['4K', '2M']
for size in sizes:
self.cli_set(base_path + ['settings', 'buffers', 'page-size', size])
self.cli_commit()
conf = get_vpp_config()
self.assertEqual(conf['buffers']['page-size'], size)
def test_13_2_statseg_page_size(self):
sizes = ['4K', '2M']
for size in sizes:
self.cli_set(base_path + ['settings', 'statseg', 'page-size', size])
self.cli_commit()
conf = get_vpp_config()
self.assertEqual(conf['statseg']['page-size'], size)
def test_13_3_mem_page_size(self):
sizes = ['4K', '2M']
for size in sizes:
self.cli_set(
base_path + ['settings', 'memory', 'main-heap-page-size', size]
)
self.cli_commit()
conf = get_vpp_config()
self.assertEqual(conf['memory']['main-heap-page-size'], size)
def test_14_vpp_ipsec_xfrm_nl(self):
base_ipsec = base_path + ['settings', 'ipsec']
batch_delay = '250'
batch_size = '150'
rx_buffer_zise = '1024'
self.cli_set(base_ipsec + ['netlink', 'batch-delay-ms', batch_delay])
self.cli_set(base_ipsec + ['netlink', 'batch-size', batch_size])
self.cli_set(base_ipsec + ['netlink', 'rx-buffer-size', rx_buffer_zise])
self.cli_commit()
config_entries = (
'linux-xfrm-nl',
'enable-route-mode-ipsec',
'interface ipsec',
f'nl-batch-delay-ms {batch_delay}',
f'nl-batch-size {batch_size}',
f'nl-rx-buffer-size {rx_buffer_zise}',
)
# Check configured options
config = read_file(VPP_CONF)
for config_entry in config_entries:
self.assertIn(config_entry, config)
# set IPsec tunnel-type ipip
self.cli_set(base_ipsec + ['interface-type', 'ipip'])
self.cli_commit()
config = read_file(VPP_CONF)
self.assertIn('interface ipip', config)
def test_15_vpp_cgnat(self):
base_cgnat = base_path + ['nat', 'cgnat']
iface_out = 'eth0'
iface_inside = 'eth1'
timeout_udp = '150'
timeout_icmp = '30'
timeout_tcp_est = '600'
timeout_tcp_trans = '120'
inside_prefix = '100.64.0.0/24'
outside_prefix = '192.0.2.1/32'
self.cli_set(base_path + ['settings', 'interface', iface_out, 'driver', driver])
self.cli_set(base_cgnat + ['interface', 'inside', iface_inside])
self.cli_set(base_cgnat + ['interface', 'outside', iface_out])
self.cli_set(base_cgnat + ['rule', '100', 'inside-prefix', inside_prefix])
self.cli_set(base_cgnat + ['rule', '100', 'outside-prefix', outside_prefix])
self.cli_set(base_cgnat + ['timeout', 'icmp', timeout_icmp])
self.cli_set(base_cgnat + ['timeout', 'tcp-established', timeout_tcp_est])
self.cli_set(base_cgnat + ['timeout', 'tcp-transitory', timeout_tcp_trans])
self.cli_set(base_cgnat + ['timeout', 'udp', timeout_udp])
self.cli_commit()
# Check interfaces
_, out = rc_cmd('sudo vppctl show det44 interfaces')
self.assertIn(f'{iface_inside} in', out)
self.assertIn(f'{iface_out} out', out)
# Check mappings
_, out = rc_cmd('sudo vppctl show det44 mappings')
self.assertIn(inside_prefix, out)
self.assertIn(outside_prefix, out)
# Check timeouts
_, out = rc_cmd('sudo vppctl show det44 timeouts')
self.assertIn(f'udp timeout: {timeout_udp}sec', out)
self.assertIn(f'tcp established timeout: {timeout_tcp_est}sec', out)
self.assertIn(f'tcp transitory timeout: {timeout_tcp_trans}sec', out)
self.assertIn(f'icmp timeout: {timeout_icmp}sec', out)
def test_16_vpp_nat(self):
base_nat = base_path + ['nat44']
base_nat_settings = base_path + ['settings', 'nat44']
exclude_local_addr = '100.64.0.52'
exclude_local_port = '22'
iface_out = 'eth0'
iface_inside = 'eth1'
timeout_udp = '150'
timeout_icmp = '30'
timeout_tcp_est = '600'
timeout_tcp_trans = '120'
translation_pool = '192.0.2.1-192.0.2.2'
static_ext_addr = '192.0.2.55'
static_local_addr = '100.64.0.55'
sess_limit = '64000'
self.cli_set(base_path + ['settings', 'interface', iface_out, 'driver', driver])
self.cli_set(base_nat + ['interface', 'inside', iface_inside])
self.cli_set(base_nat + ['interface', 'outside', iface_out])
self.cli_set(
base_nat + ['address-pool', 'translation', 'address', translation_pool]
)
self.cli_set(
base_nat + ['exclude', 'rule', '100', 'local-address', exclude_local_addr]
)
self.cli_set(
base_nat + ['exclude', 'rule', '100', 'local-port', exclude_local_port]
)
# cannot set local-port without specifying protocol
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_nat + ['exclude', 'rule', '100', 'protocol', 'tcp'])
self.cli_set(
base_nat + ['static', 'rule', '100', 'external', 'address', static_ext_addr]
)
self.cli_set(
base_nat + ['static', 'rule', '100', 'local', 'address', static_local_addr]
)
self.cli_set(base_nat_settings + ['no-forwarding'])
self.cli_set(base_nat_settings + ['session-limit', sess_limit])
self.cli_set(base_nat_settings + ['timeout', 'icmp', timeout_icmp])
self.cli_set(
base_nat_settings + ['timeout', 'tcp-established', timeout_tcp_est]
)
self.cli_set(
base_nat_settings + ['timeout', 'tcp-transitory', timeout_tcp_trans]
)
self.cli_set(base_nat_settings + ['timeout', 'udp', timeout_udp])
self.cli_commit()
# Check addresses
_, out = rc_cmd('sudo vppctl show nat44 addresses')
self.assertIn(translation_pool.split('-')[0], out)
self.assertIn(translation_pool.split('-')[1], out)
# Check interfaces
_, out = rc_cmd('sudo vppctl show nat44 interfaces')
self.assertIn(f'{iface_inside} in', out)
self.assertIn(f'{iface_out} out', out)
# Check mappings
_, out = rc_cmd('sudo vppctl show nat44 static mappings')
self.assertIn(
f'local {static_local_addr} external {static_ext_addr} vrf 0', out
)
self.assertIn(f'{exclude_local_addr}:{exclude_local_port} vrf 0', out)
# Check timeouts
_, out = rc_cmd('sudo vppctl show nat timeouts')
self.assertIn(f'udp timeout: {timeout_udp}sec', out)
self.assertIn(f'tcp-established timeout: {timeout_tcp_est}sec', out)
self.assertIn(f'tcp-transitory timeout: {timeout_tcp_trans}sec', out)
self.assertIn(f'icmp timeout: {timeout_icmp}sec', out)
# Summary
_, out = rc_cmd('sudo vppctl show nat44 summary')
self.assertIn(f'max translations per thread: {sess_limit} fib 0', out)
def test_17_vpp_sflow(self):
base_sflow = ['system', 'sflow']
self.cli_set(base_path + ['sflow', 'interface', interface])
self.cli_set(base_sflow + ['interface', interface])
self.cli_set(base_sflow + ['server', '127.0.0.1'])
self.cli_set(base_sflow + ['vpp'])
self.cli_commit()
# Check sFlow
_, out = rc_cmd('sudo vppctl show sflow')
expected_entries = (
'sflow sampling-direction ingress',
f'sflow enable {interface}',
'interfaces enabled: 1',
)
for expected_entry in expected_entries:
self.assertIn(expected_entry, out)
self.cli_delete(base_sflow)
self.cli_commit()
def test_18_resource_limits(self):
max_map_count = '100000'
shmmax = '55555555555555'
hr_path = ['system', 'option', 'resource-limits']
# Check if max-map-count has default auto calculated value
# but not less than '65530'
self.assertEqual(sysctl_read('vm.max_map_count'), '65530')
# The same is with: kernel.shmmax = '8589934592'
self.assertEqual(sysctl_read('kernel.shmmax'), '8589934592')
# Change max-map-count, shmmax and check
self.cli_set(hr_path + ['max-map-count', max_map_count])
self.cli_set(hr_path + ['shmmax', shmmax])
self.cli_commit()
self.assertEqual(sysctl_read('vm.max_map_count'), max_map_count)
self.assertEqual(sysctl_read('kernel.shmmax'), shmmax)
# We expect max-map-count and shmmax will return auto calculated values
self.cli_delete(hr_path + ['max-map-count'])
self.cli_delete(hr_path + ['shmmax'])
self.cli_commit()
self.assertEqual(sysctl_read('vm.max_map_count'), '65530')
self.assertEqual(sysctl_read('kernel.shmmax'), '8589934592')
def test_19_vpp_pppoe_mapping(self):
config_file = '/run/accel-pppd/pppoe.conf'
pool = "TEST-POOL"
vni = '23'
pppoe_base = ['service', 'pppoe-server']
self.cli_set(['interfaces', 'ethernet', interface, 'vif', vni])
# Basic pppoe-server config
self.cli_set(pppoe_base + ['authentication', 'mode', 'noauth'])
self.cli_set(pppoe_base + ['gateway-address', '192.0.2.1'])
self.cli_set(pppoe_base + ['client-ip-pool', pool, 'range', '192.0.2.0/24'])
self.cli_set(pppoe_base + ['default-pool', pool])
# Enable PPPoE control-plane integration with VPP
self.cli_set(pppoe_base + ['interface', interface, 'vpp-cp'])
self.cli_set(pppoe_base + ['interface', f'{interface}.{vni}', 'vpp-cp'])
self.cli_commit()
# Validate configuration values
config = read_file(config_file)
# Validate configuration
self.assertIn(f'interface={interface},vpp-cp=true', config)
self.assertIn(f'interface={interface}.{vni},vpp-cp=true', config)
# Check pppoe mapping
_, out = rc_cmd('sudo vppctl show pppoe control-plane binding')
self.assertRegex(out, rf'{interface}\s+tap4096')
self.assertRegex(out, rf'{interface}.{vni}\s+tap4096.23')
# check if dependency is called and mapping is correct after changes in vpp script
self.cli_set(
base_path + ['settings', 'interface', interface, 'dpdk-options', 'promisc']
)
self.cli_commit()
# Check pppoe mapping
_, out = rc_cmd('sudo vppctl show pppoe control-plane binding')
self.assertRegex(out, rf'{interface}\s+tap4096')
self.assertRegex(out, rf'{interface}.{vni}\s+tap4096.23')
# delete PPPoE config
self.cli_delete(pppoe_base)
# delete vif Ethernet interface
self.cli_delete(['interfaces', 'ethernet', interface, 'vif'])
self.cli_commit()
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py
index 81502c0b4..c4142437b 100755
--- a/src/conf_mode/vpp.py
+++ b/src/conf_mode/vpp.py
@@ -1,795 +1,797 @@
#!/usr/bin/env python3
#
# Copyright (C) VyOS Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from pathlib import Path
from pyroute2.iproute import IPRoute
try:
from vpp_papi import VPPIOError, VPPValueError
except ImportError: # pylint: disable=import-error
VPPIOError = VPPValueError = None
from vyos import ConfigError
from vyos import airbag
from vyos.base import Warning
from vyos.config import Config, config_dict_merge
from vyos.configdep import set_dependents, call_dependents
from vyos.configdict import node_changed, leaf_node_changed
from vyos.ifconfig import Section
from vyos.logger import getLogger
from vyos.template import render
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.kernel import check_kmod
from vyos.utils.kernel import unload_kmod
from vyos.utils.kernel import list_loaded_modules
from vyos.utils.process import call
from vyos.vpp import VPPControl
from vyos.vpp import control_host
from vyos.vpp.config_deps import deps_xconnect_dict
from vyos.vpp.config_verify import (
verify_dev_driver,
verify_vpp_minimum_cpus,
verify_vpp_minimum_memory,
verify_vpp_settings_cpu,
verify_vpp_settings_cpu_corelist_workers,
verify_vpp_cpu_main_core,
verify_vpp_settings_cpu_skip_cores,
verify_vpp_settings_cpu_workers,
verify_vpp_nat44_workers,
verify_vpp_memory,
verify_vpp_statseg_size,
verify_vpp_interfaces_dpdk_num_queues,
verify_routes_count,
verify_vpp_main_heap_size,
verify_vpp_buffers,
)
from vyos.vpp.config_resource_checks import memory
from vyos.vpp.config_filter import iface_filter_eth
from vyos.vpp.utils import EthtoolGDrvinfo
from vyos.vpp.configdb import JSONStorage
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'
vpp_log = getLogger(
service_name, format='%(filename)s[%(process)d]: %(message)s', address='/dev/log'
)
dependency_interface_type_map = {
'vpp_interfaces_bonding': 'bonding',
'vpp_interfaces_bridge': 'bridge',
'vpp_interfaces_ethernet': 'ethernet',
'vpp_interfaces_geneve': 'geneve',
'vpp_interfaces_gre': 'gre',
'vpp_interfaces_ipip': 'ipip',
'vpp_interfaces_loopback': 'loopback',
'vpp_interfaces_vxlan': 'vxlan',
'vpp_interfaces_xconnect': 'xconnect',
}
# dict of drivers that needs to be overrided
override_drivers: dict[str, str] = {
'hv_netvsc': 'uio_hv_generic',
'ena': 'vfio-pci',
}
# drivers that does not use PCIe addresses
not_pci_drv: list[str] = ['hv_netvsc']
# drivers that support interrupt RX mode for DPDK and XDP
drivers_support_interrupt: dict[str, list] = {
'atlantic': ['dpdk', 'xdp'],
'bnx2x': ['dpdk'],
'e1000': ['dpdk'],
'ena': ['dpdk', 'xdp'],
'i40e': ['dpdk', 'xdp'],
'ice': ['dpdk', 'xdp'],
'igb': ['xdp'],
'igc': ['dpdk', 'xdp'],
'ixgbe': ['dpdk', 'xdp'],
- 'ixgbevf': ['dpdk'],
'qede': ['dpdk', 'xdp'],
'vmxnet3': ['xdp'],
'virtio_net': ['xdp'],
}
def _load_module(module_name: str):
"""
Load a kernel module
Args:
module_name (str): Name of the module to load.
"""
if module_name in list_loaded_modules():
vpp_log.info(f"Module '{module_name}' is alrady loaded")
return
try:
check_kmod(module_name)
vpp_log.info(f"Module '{module_name}' loaded successfully")
except Exception as e:
vpp_log.error(f"Failed to load module '{module_name}': {e}")
raise
def _unload_module(module_name: str):
"""
Unload a kernel module
Args:
module_name (str): Name of the module to unload.
"""
if module_name not in list_loaded_modules():
vpp_log.info(f"Module '{module_name}' is not loaded")
return
try:
unload_kmod(module_name)
vpp_log.info(f"Module '{module_name}' unloaded successfully")
except Exception as e:
vpp_log.error(f"Failed to unload module '{module_name}': {e}")
raise
def _get_workers_count(cpu_settings: dict) -> int:
if 'corelist_workers' in cpu_settings:
corelist_workers = []
for worker_range in cpu_settings['corelist_workers']:
core_numbers = worker_range.split('-')
corelist_workers.extend(
range(int(core_numbers[0]), int(core_numbers[-1]) + 1)
)
return len(corelist_workers)
return int(cpu_settings.get('workers', 0))
def _normalize_buffers(config: dict):
"""Replace 'auto' buffers_per_numa with calculated value"""
if config['settings']['buffers']['buffers_per_numa'] == 'auto':
workers = _get_workers_count(config['settings'].get('cpu', {}))
buffers = memory.buffers_required(config['settings'], workers)
config['settings']['buffers']['buffers_per_numa'] = str(buffers)
def get_config(config=None):
# use persistent config to store interfaces data between executions
# this is required because some interfaces after they are connected
# to VPP is really hard or impossible to restore without knowing
# their original parameters (like IDs)
persist_config = JSONStorage('vpp_conf')
eth_ifaces_persist: dict[str, dict[str, str]] = persist_config.read(
'eth_ifaces', {}
)
if config:
conf = config
else:
conf = Config()
base = ['vpp']
base_settings = ['vpp', 'settings']
# find interfaces removed from VPP
effective_config = conf.get_config_dict(
base,
key_mangling=('-', '_'),
effective=True,
get_first_key=True,
no_tag_node_value_mangle=True,
)
xconn_members = deps_xconnect_dict(conf)
removed_ifaces = []
tmp = node_changed(conf, base_settings + ['interface'])
if tmp:
for removed_iface in tmp:
to_append = {
'iface_name': removed_iface,
'driver': effective_config['settings']['interface'][removed_iface][
'driver'
],
}
removed_ifaces.append(to_append)
# add an interface to a list of interfaces that need
# to be reinitialized after the commit
set_dependents('ethernet', conf, removed_iface)
# Get interfaces that are used in PPPoe for control-plane integration
pppoe_conf = conf.get_config_dict(
['service', 'pppoe-server'],
key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True,
)
pppoe_map_ifaces = [
ifname
for ifname, iface_conf in pppoe_conf.get('interface', {}).items()
if 'vpp_cp' in iface_conf
]
if not conf.exists(base):
return {
'removed_ifaces': removed_ifaces,
'xconn_members': xconn_members,
'persist_config': eth_ifaces_persist,
**({'pppoe_ifaces': pppoe_map_ifaces} if pppoe_map_ifaces else {}),
}
config = conf.get_config_dict(
base,
get_first_key=True,
key_mangling=('-', '_'),
no_tag_node_value_mangle=True,
)
# Get default values which we need to conditionally update into the
# dictionary retrieved.
default_values = conf.get_config_defaults(**config.kwargs, recursive=True)
# delete driver-incompatible defaults
for iface, iface_config in config.get('settings', {}).get('interface', {}).items():
if iface_config.get('driver') == 'dpdk':
del default_values['settings']['interface'][iface]['xdp_options']
elif iface_config.get('driver') == 'xdp':
del default_values['settings']['interface'][iface]['dpdk_options']
config = config_dict_merge(default_values, config)
# Ignore default XML values if config doesn't exists
if not conf.exists(base_settings + ['ipsec']):
del config['settings']['ipsec']
# add running config
if effective_config:
default_values_effective = conf.get_config_defaults(
**effective_config.kwargs, recursive=True
)
effective_config = config_dict_merge(default_values_effective, effective_config)
# Buffer normalization (auto → computed)
_normalize_buffers(effective_config)
config['effective'] = effective_config
if 'settings' in config:
if 'interface' in config['settings']:
for iface, iface_config in config['settings']['interface'].items():
# Driver must be configured to continue
if 'driver' not in iface_config:
raise ConfigError(
f'"driver" must be configured for {iface} interface!'
)
old_driver = leaf_node_changed(
conf, base_settings + ['interface', iface, 'driver']
)
# Get current kernel module, required for extra verification and
# logic for VMBus interfaces
config['settings']['interface'][iface]['kernel_module'] = (
EthtoolGDrvinfo(iface).driver
)
# filter unsupported config nodes
iface_filter_eth(conf, iface)
set_dependents('ethernet', conf, iface)
# Interfaces with changed driver should be removed/readded
if old_driver and old_driver[0] == 'dpdk':
removed_ifaces.append(
{
'iface_name': iface,
'driver': 'dpdk',
}
)
# Get PCI address or device ID
if iface_config['driver'] == 'dpdk':
if 'dpdk_options' not in iface_config:
iface_config['dpdk_options'] = {}
# Check in a persistent config first
id_from_persisten_conf = eth_ifaces_persist.get(iface, {}).get(
'dev_id'
)
if id_from_persisten_conf:
iface_config['dpdk_options']['dev_id'] = id_from_persisten_conf
else:
try:
iface_to_search = iface
if old_driver and old_driver[0] == 'xdp':
iface_to_search = f'defunct_{iface}'
iface_config['dpdk_options']['dev_id'] = (
control_host.get_dev_id(iface_to_search)
)
except Exception:
# Return empty address if all attempts failed
# We will catch this in verify()
iface_config['dpdk_options']['dev_id'] = ''
# prepare XDP interface parameters
if iface_config['driver'] == 'xdp':
xdp_api_params = {
'rxq_size': int(iface_config['xdp_options']['rx_queue_size']),
'txq_size': int(iface_config['xdp_options']['tx_queue_size']),
}
if iface_config['xdp_options']['num_rx_queues'] == 'all':
xdp_api_params['rxq_num'] = 0
else:
xdp_api_params['rxq_num'] = int(
iface_config['xdp_options']['num_rx_queues']
)
if 'zero-copy' in iface_config['xdp_options']:
xdp_api_params['mode'] = 'zero-copy'
- if 'zero-copy' in iface_config['xdp_options']:
+ if iface_config.get('rx_mode') in ('interrupt', 'adaptive') and any(
+ key in config['settings'].get('cpu', {})
+ for key in ('workers', 'corelist_workers')
+ ):
xdp_api_params['flags'] = 'no_syscall_lock'
iface_config['xdp_api_params'] = xdp_api_params
# Buffer normalization (auto → computed)
_normalize_buffers(config)
if removed_ifaces:
config['removed_ifaces'] = removed_ifaces
config['xconn_members'] = xconn_members
# Dependencies
for dependency, interface_type in dependency_interface_type_map.items():
# if conf.exists(base + ['interfaces', interface_type]):
if effective_config.get('interfaces', {}).get(interface_type):
for iface, iface_config in (
config.get('interfaces', {}).get(interface_type, {}).items()
):
# filter unsupported config nodes
if interface_type == 'ethernet':
iface_filter_eth(conf, iface)
set_dependents(dependency, conf, iface)
# Save important info about all interfaces that cannot be retrieved later
# Add new interfaces (only if they are first time seen in a config)
for iface, iface_config in config.get('settings', {}).get('interface', {}).items():
if iface not in effective_config.get('settings', {}).get('interface', {}):
eth_ifaces_persist[iface] = {
'original_driver': config['settings']['interface'][iface][
'kernel_module'
],
}
eth_ifaces_persist[iface]['bus_id'] = control_host.get_bus_name(iface)
eth_ifaces_persist[iface]['dev_id'] = control_host.get_dev_id(iface)
# NAT dependency
if conf.exists(['vpp', 'nat44']):
set_dependents('vpp_nat', conf)
if conf.exists(['vpp', 'nat', 'cgnat']):
set_dependents('vpp_nat_cgnat', conf)
# sFlow dependency
if conf.exists(['vpp', 'sflow']):
set_dependents('vpp_sflow', conf)
# ACL dependency
if conf.exists(['vpp', 'acl']):
set_dependents('vpp_acl', conf)
# PPPoE dependency
if pppoe_map_ifaces:
config['pppoe_ifaces'] = pppoe_map_ifaces
set_dependents('pppoe_server', conf)
# Return to config dictionary
config['persist_config'] = eth_ifaces_persist
return config
def verify(config):
# Cannot remove interface if PPPoE control-plane is still enabled
removed_ifaces = [iface['iface_name'] for iface in config.get('removed_ifaces', [])]
pppoe_removed_ifaces = [
p
for p in config.get('pppoe_ifaces', [])
for r in removed_ifaces
if p == r or p.startswith(f'{r}.')
]
if pppoe_removed_ifaces:
raise ConfigError(
f'{", ".join(pppoe_removed_ifaces)} still in use by the PPPoE server. '
'Disable PPPoE control-plane integration with VPP before proceeding.'
)
# bail out early - looks like removal from running config
if not config or ('removed_ifaces' in config and 'settings' not in config):
return None
if 'settings' not in config:
raise ConfigError('"settings interface" is required but not set!')
if 'interface' not in config['settings']:
raise ConfigError('"settings interface" is required but not set!')
# check if the system meets minimal requirements
verify_vpp_minimum_memory()
# check if Ethernet interfaces exist
ethernet_ifaces = Section.interfaces('ethernet')
for iface in config['settings']['interface'].keys():
if iface not in ethernet_ifaces:
raise ConfigError(f'Interface {iface} does not exist or is not Ethernet!')
# Resource usage checks
cpu_settings = config['settings'].get('cpu', {})
if not any(key in cpu_settings for key in ('workers', 'corelist_workers')):
verify_vpp_minimum_cpus()
if 'cpu' in config['settings']:
# Check whether the workers and corelist-workers are configured properly
verify_vpp_settings_cpu(cpu_settings)
# Check if there are enough CPU cores to skip according to config
if 'skip_cores' in cpu_settings:
skip_cores = int(cpu_settings['skip_cores'])
verify_vpp_settings_cpu_skip_cores(skip_cores)
# Check if there are enough CPU cores to add workers
if 'workers' in cpu_settings:
verify_vpp_settings_cpu_workers(cpu_settings)
if 'main_core' in cpu_settings:
verify_vpp_cpu_main_core(cpu_settings)
# Check the CPU main core not falling to the corelist-workers
if 'corelist_workers' in cpu_settings:
verify_vpp_settings_cpu_corelist_workers(cpu_settings)
workers = _get_workers_count(config['settings'].get('cpu', {}))
if 'workers' in config['settings']['nat44']:
verify_vpp_nat44_workers(
workers=workers, nat44_workers=config['settings']['nat44']['workers']
)
verify_vpp_main_heap_size(config['settings'])
verify_vpp_statseg_size(config['settings'])
# Check buffers
verify_vpp_buffers(config['settings'], workers)
# Check if available memory is enough for current VPP config
verify_vpp_memory(config)
# ensure DPDK/XDP settings are properly configured
for iface, iface_config in config['settings']['interface'].items():
# check if selected driver is supported, but only for new interfaces
if iface not in config.get('effective', {}).get('settings', {}).get(
'interface', {}
):
if not verify_dev_driver(iface, iface_config['driver']):
raise ConfigError(
f'Driver {iface_config["driver"]} is not compatible with interface {iface}!'
)
if iface_config['driver'] == 'xdp' and 'xdp_options' in iface_config:
if iface_config['xdp_options']['num_rx_queues'] != 'all':
Warning(f'Not all RX queues will be connected to VPP for {iface}!')
if iface_config['driver'] == 'xdp' and 'dpdk_options' in iface_config:
raise ConfigError('DPDK options are not applicable for XDP driver!')
if iface_config['driver'] == 'dpdk' and 'xdp_options' in iface_config:
raise ConfigError('XDP options are not applicable for DPDK driver!')
if iface_config['driver'] == 'dpdk' and 'dpdk_options' in iface_config:
if 'num_rx_queues' in iface_config['dpdk_options']:
rx_queues = int(iface_config['dpdk_options']['num_rx_queues'])
verify_vpp_interfaces_dpdk_num_queues(
qtype='receive', num_queues=rx_queues, workers=workers
)
if 'num_tx_queues' in iface_config['dpdk_options']:
tx_queues = int(iface_config['dpdk_options']['num_tx_queues'])
verify_vpp_interfaces_dpdk_num_queues(
qtype='transmit', num_queues=tx_queues, workers=workers
)
# RX-mode verification
rx_mode = iface_config.get('rx_mode')
if rx_mode and rx_mode != 'polling':
# By default drivers operate in polling mode. Not all NIC drivers support
# RX mode interrupt and adaptive
driver = config.get('persist_config').get(iface).get('original_driver')
if (
driver not in drivers_support_interrupt
or iface_config['driver'] not in drivers_support_interrupt[driver]
):
raise ConfigError(
f'RX mode {rx_mode} is not supported for interface {iface}'
)
# check GRE tunnels as part of the bridge, only tunnel-type teb is allowed
# set vpp interfaces bridge br1 member interface gre1
# set vpp interfaces gre gre1 tunnel-type teb
if 'interfaces' in config:
if 'bridge' in config['interfaces']:
for iface, iface_config in config['interfaces']['bridge'].items():
if 'member' in iface_config:
for member in iface_config['member'].get('interface', []):
if member.startswith('gre'):
if (
'gre' in config['interfaces']
and config['interfaces']['gre']
.get(member, {})
.get('tunnel_type')
!= 'teb'
):
raise ConfigError(
f'Only tunnel-type teb is allowed for GRE interfaces in bridge {iface}'
)
# Only one multipoint GRE tunnel is allowed from the same source address
# set vpp interfaces gre gre0 mode 'point-to-multipoint'
# set vpp interfaces gre gre0 remote '0.0.0.0'
# set vpp interfaces gre gre0 source-address '192.0.2.1'
# set vpp interfaces gre gre1 mode 'point-to-multipoint'
# set vpp interfaces gre gre1 remote '0.0.0.0'
# set vpp interfaces gre gre1 source-address '192.0.2.1'
if 'gre' in config['interfaces']:
for iface, iface_config in config['interfaces']['gre'].items():
if iface_config['mode'] == 'point-to-multipoint':
for other_iface, other_iface_config in config['interfaces'][
'gre'
].items():
if (
other_iface_config['mode'] == 'point-to-multipoint'
and other_iface_config['source_address']
== iface_config['source_address']
and iface != other_iface
):
raise ConfigError(
'Only one multipoint GRE tunnel is allowed from the same source address'
)
# Check if deleted interfaces are not xconnect memebrs
for iface_config in config.get('removed_ifaces', []):
if iface_config['iface_name'] in config.get('xconn_members', {}):
raise ConfigError(
f'Interface {iface_config["iface_name"]} is an xconnect member and cannot be removed'
)
verify_routes_count(config['settings'], workers)
def generate(config):
if not config or ('removed_ifaces' in config and 'settings' not in config):
# Remove old config and return
service_conf.unlink(missing_ok=True)
return None
render(service_conf, 'vpp/startup.conf.j2', config['settings'])
render(systemd_override, 'vpp/override.conf.j2', config)
return None
def initialize_interface(iface, driver, iface_config) -> None:
# DPDK - rescan PCI to use a proper driver
if driver == 'dpdk' and iface_config['original_driver'] not in not_pci_drv:
control_host.pci_rescan(iface_config['dev_id'])
# rename to the proper name
iface_new_name: str = control_host.get_eth_name(iface_config['dev_id'])
control_host.rename_iface(iface_new_name, iface)
# XDP - rename an interface, disable promisc and XDP
if driver == 'xdp':
control_host.set_promisc(f'defunct_{iface}', 'off')
control_host.rename_iface(f'defunct_{iface}', iface)
control_host.xdp_remove(iface)
# Rename Mellanox NIC to a normal name
try:
if control_host.get_eth_driver(f'defunct_{iface}') == 'mlx5_core':
control_host.rename_iface(f'defunct_{iface}', iface)
except FileNotFoundError:
pass
# Replace a driver with original for VMBus interfaces and rename it
if driver == 'dpdk' and iface_config['original_driver'] in override_drivers:
control_host.override_driver(iface_config['bus_id'], iface_config['dev_id'])
iface_new_name: str = control_host.get_eth_name(iface_config['dev_id'])
control_host.rename_iface(iface_new_name, iface)
def apply(config):
# modrpobe modules
modules = ('vfio_iommu_type1', 'vfio_pci', 'vfio_pci_core', 'vfio')
# Open persistent config
# It is required for operations with interfaces
persist_config = JSONStorage('vpp_conf')
if not config or ('removed_ifaces' in config and 'settings' not in config):
# Cleanup persistent config
persist_config.delete()
# And stop the service
call(f'systemctl stop {service_name}.service')
# Unlod modules (modprobe -r)
for module in modules:
_unload_module(module)
else:
# Some interfaces required extra preparation before VPP can be started
if 'settings' in config and 'interface' in config.get('settings'):
# modprobe vfio
if any(
iface_config.get('driver') == 'dpdk'
for iface_config in config['settings']['interface'].values()
):
for module in modules:
_load_module(module)
for iface, iface_config in config['settings']['interface'].items():
if iface_config['driver'] == 'dpdk':
# ena interfaces require noiommu mode
if iface_config['kernel_module'] == 'ena':
control_host.unsafe_noiommu_mode(True)
if iface_config['kernel_module'] in override_drivers:
control_host.override_driver(
config['persist_config'][iface]['bus_id'],
config['persist_config'][iface]['dev_id'],
override_drivers[iface_config['kernel_module']],
)
call('systemctl daemon-reload')
call(f'systemctl restart {service_name}.service')
# Initialize interfaces removed from VPP
for iface in config.get('removed_ifaces', []):
initialize_interface(
iface['iface_name'],
iface['driver'],
config['persist_config'][iface['iface_name']],
)
# Remove what is not in the config anymore
if iface['iface_name'] not in config.get('settings', {}).get('interface', {}):
del config['persist_config'][iface['iface_name']]
if 'settings' in config and 'interface' in config.get('settings'):
# connect to VPP
# must be performed multiple attempts because API is not available
# immediately after the service restart
try:
vpp_control = VPPControl(attempts=20, interval=500)
# preconfigure LCP plugin
if 'ignore_kernel_routes' in config.get('settings', {}).get('lcp', {}):
vpp_control.cli_cmd('lcp param route-no-paths off')
else:
vpp_control.cli_cmd('lcp param route-no-paths on')
# add interfaces
iproute = IPRoute()
for iface, iface_config in config['settings']['interface'].items():
# promisc option for DPDK interfaces
if iface_config['driver'] == 'dpdk':
if 'promisc' in iface_config['dpdk_options']:
if_index = vpp_control.get_sw_if_index(iface)
vpp_control.api.sw_interface_set_promisc(
sw_if_index=if_index, promisc_on=True
)
# add XDP interfaces
if iface_config['driver'] == 'xdp':
control_host.rename_iface(iface, f'defunct_{iface}')
vpp_control.xdp_iface_create(
host_if=f'defunct_{iface}',
name=iface,
**iface_config['xdp_api_params'],
)
# replicate MAC address of a real interface
real_mac = control_host.get_eth_mac(f'defunct_{iface}')
vpp_control.set_iface_mac(iface, real_mac)
if 'promisc' in iface_config['xdp_options']:
control_host.set_promisc(f'defunct_{iface}', 'on')
control_host.set_status(f'defunct_{iface}', 'up')
control_host.flush_ip(f'defunct_{iface}')
# Rename Mellanox interfaces to hide them and create LCP properly
if (
iface in Section.interfaces()
and control_host.get_eth_driver(iface) == 'mlx5_core'
):
control_host.rename_iface(iface, f'defunct_{iface}')
control_host.set_status(f'defunct_{iface}', 'up')
control_host.flush_ip(f'defunct_{iface}')
# Create lcp
if iface not in Section.interfaces():
vpp_control.lcp_pair_add(iface, iface)
# For unknown reasons, if multiple interfaces later try to be
# initialized by configuration scripts, some of them may stuck
# in an endless UP/DOWN loop
# We found two workarounds - pause initialization (requires
# main code modifications).
# And this one
dev_index = iproute.link_lookup(ifname=iface)[0]
iproute.link('set', index=dev_index, state='up')
# Set rx-mode. Should be configured after interface state set to UP
rx_mode = iface_config.get('rx_mode')
if rx_mode:
# to hardware side
vpp_control.iface_rxmode(iface, rx_mode)
# to kernel side
lcp_name = vpp_control.lcp_pair_find(vpp_name_hw=iface).get(
'vpp_name_kernel'
)
vpp_control.iface_rxmode(lcp_name, rx_mode)
# Syncronize routes via LCP
vpp_control.lcp_resync()
# NAT44 settings
nat44_settings = config['settings'].get('nat44', {})
enable_forwarding = True
if 'no_forwarding' in nat44_settings:
enable_forwarding = False
vpp_control.enable_disable_nat44_forwarding(enable_forwarding)
vpp_control.set_nat44_session_limit(
int(nat44_settings.get('session_limit'))
)
if nat44_settings.get('workers'):
bitmask = 0
for worker_range in nat44_settings['workers']:
worker_numbers = worker_range.split('-')
for wid in range(
int(worker_numbers[0]), int(worker_numbers[-1]) + 1
):
bitmask |= 1 << wid
vpp_control.set_nat_workers(bitmask)
except (VPPIOError, VPPValueError) as e:
# if cannot connect to VPP or an error occurred then
# we need to stop vpp service and initialize interfaces
call(f'systemctl stop {service_name}.service')
for iface, iface_config in config['settings']['interface'].items():
initialize_interface(
iface, iface_config['driver'], config['persist_config'][iface]
)
raise ConfigError(
f'An error occurred: {e}. '
'VPP service will be restarted with the previous configuration'
)
# Save persistent config
if 'persist_config' in config and config['persist_config']:
persist_config.write('eth_ifaces', config['persist_config'])
# reinitialize interfaces, but not during the first boot
if boot_configuration_complete():
call_dependents()
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)
diff --git a/src/migration-scripts/vpp/1-to-2 b/src/migration-scripts/vpp/1-to-2
new file mode 100644
index 000000000..822799cc6
--- /dev/null
+++ b/src/migration-scripts/vpp/1-to-2
@@ -0,0 +1,34 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+# Delete 'vpp settings interface ethX xdp-options no-syscall-lock'
+# since it is set automatically
+
+from vyos.configtree import ConfigTree
+
+base = ['vpp', 'settings', 'interface']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base):
+ # Nothing to do
+ return
+
+ for iface_name in config.list_nodes(base):
+ xdp_options_base = base + [iface_name, 'xdp-options']
+ if config.exists(xdp_options_base + ['no-syscall-lock']):
+ # Delete no-syscall-lock option from configuration
+ config.delete(xdp_options_base + ['no-syscall-lock'])
+ if config.exists(xdp_options_base) and len(config.list_nodes(xdp_options_base)) == 0:
+ config.delete(xdp_options_base)

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 17, 12:16 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4416499
Default Alt Text
(175 KB)

Event Timeline