Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F118305725
vpp.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
36 KB
Referenced Files
None
Subscribers
None
vpp.py
View Options
#!/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'
],
'qede'
:
[
'dpdk'
,
'xdp'
],
'vmxnet3'
:
[
'xdp'
],
'virtio_net'
:
[
'xdp'
],
}
# drivers that require changing channels (half the maximum number of RX/TX queues)
ethtool_channels_change_drv
:
list
[
str
]
=
[
'ena'
,
'gve'
]
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_max_xdp_rx_queues
(
config
:
dict
):
"""
Count max number of RX queues for XDP driver
- If the interface driver is in `ethtool_channels_change_drv`
only half of the available queues are used (to avoid NIC issues)
- For other interface drivers the full number of queues is returned.
- If neither `rx` nor `combined` is set, return 1.
"""
for
key
in
(
'rx'
,
'combined'
):
value
=
config
[
'channels'
]
.
get
(
key
)
if
value
:
if
config
[
'original_driver'
]
in
ethtool_channels_change_drv
:
return
max
(
1
,
int
(
value
)
//
2
)
else
:
return
int
(
value
)
return
1
def
_check_removed_interfaces
(
config
:
dict
,
feature_name
:
str
,
interfaces_config
:
dict
):
"""
Check if removed interfaces are used in any feature configuration
Args:
config: The main configuration dictionary
feature_name: Human-readable feature name for error messages
interfaces_config: The interfaces dictionary from the feature config
Example:
_check_removed_interfaces(config, 'IPFIX monitoring', config.get('ipfix', {}).get('interface', {}))
"""
if
(
'removed_ifaces'
not
in
config
or
not
config
[
'removed_ifaces'
]
or
not
interfaces_config
):
return
for
removed_iface
in
config
[
'removed_ifaces'
]:
iface_name
=
removed_iface
.
get
(
'iface_name'
)
if
iface_name
and
iface_name
in
interfaces_config
:
raise
ConfigError
(
f
'Cannot remove interface {iface_name} - it is currently configured for {feature_name}. '
f
'Remove it from {feature_name} configuration first.'
)
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
# 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'
:
EthtoolGDrvinfo
(
iface
)
.
driver
,
}
eth_ifaces_persist
[
iface
][
'bus_id'
]
=
control_host
.
get_bus_name
(
iface
)
eth_ifaces_persist
[
iface
][
'dev_id'
]
=
control_host
.
get_dev_id
(
iface
)
eth_ifaces_persist
[
iface
][
'channels'
]
=
control_host
.
get_eth_channels
(
iface
)
# Return to config dictionary
config
[
'persist_config'
]
=
eth_ifaces_persist
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'
:
# 65535 is used as special value to request all available queues
xdp_api_params
[
'rxq_num'
]
=
65535
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
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
)
# kernel-interfaces dependency
if
effective_config
.
get
(
'kernel_interfaces'
):
for
iface
in
config
.
get
(
'kernel_interfaces'
,
{}):
set_dependents
(
'vpp_kernel_interface'
,
conf
,
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
)
# IPFIX dependency
if
conf
.
exists
([
'vpp'
,
'ipfix'
]):
set_dependents
(
'vpp_ipfix'
,
conf
)
# PPPoE dependency
if
pppoe_map_ifaces
:
config
[
'pppoe_ifaces'
]
=
pppoe_map_ifaces
set_dependents
(
'pppoe_server'
,
conf
)
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.'
)
# Check remove VPP interface that used in IPFIX
_check_removed_interfaces
(
config
,
'IPFIX monitoring'
,
config
.
get
(
'ipfix'
,
{})
.
get
(
'interface'
,
{})
)
# 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'
:
rx_queues
=
iface_config
[
'xdp_api_params'
][
'rxq_num'
]
max_rx_queues
=
_get_max_xdp_rx_queues
(
config
[
'persist_config'
][
iface
])
if
rx_queues
>
max_rx_queues
:
raise
ConfigError
(
f
'Maximum supported number of RX queues for interface {iface} is {max_rx_queues}. '
f
'Please set "xdp-options num-rx-queues" to {max_rx_queues} or fewer'
)
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, set original channels
if
driver
==
'xdp'
:
control_host
.
set_promisc
(
f
'defunct_{iface}'
,
'off'
)
control_host
.
rename_iface
(
f
'defunct_{iface}'
,
iface
)
control_host
.
xdp_remove
(
iface
)
if
iface_config
[
'original_driver'
]
in
ethtool_channels_change_drv
:
control_host
.
set_eth_channels
(
iface
,
iface_config
[
'channels'
])
# 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
Exception
:
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
)
original_driver
=
config
[
'persist_config'
][
iface
][
'original_driver'
]
effective_ifaces
=
(
config
.
get
(
'effective'
,
{})
.
get
(
'settings'
,
{})
.
get
(
'interface'
,
{})
)
# Check if the driver needs to be overridden:
# either the kernel module requires it, or the interface is being switched
# from XDP (hv_netvsc) to DPDK (T7797)
override_xdp_to_dpdk
=
(
effective_ifaces
.
get
(
iface
,
{})
.
get
(
'driver'
)
==
'xdp'
and
original_driver
==
'hv_netvsc'
)
k_module
=
(
original_driver
if
override_xdp_to_dpdk
else
iface_config
[
'kernel_module'
]
)
if
(
iface_config
[
'kernel_module'
]
in
override_drivers
or
override_xdp_to_dpdk
):
control_host
.
override_driver
(
config
[
'persist_config'
][
iface
][
'bus_id'
],
config
[
'persist_config'
][
iface
][
'dev_id'
],
override_drivers
[
k_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}'
)
# Some cloud NICs fail to load XDP if all RX queues are configured. To avoid this,
# we limit the number of queues to half of the maximum supported by the driver.
if
(
config
[
'persist_config'
][
iface
][
'original_driver'
]
in
ethtool_channels_change_drv
):
max_rx_queues
=
_get_max_xdp_rx_queues
(
config
[
'persist_config'
][
iface
]
)
channels_orig
=
config
[
'persist_config'
][
iface
][
'channels'
]
channels
=
{}
if
channels_orig
.
get
(
'rx'
):
channels
=
{
'rx'
:
max_rx_queues
,
'tx'
:
max_rx_queues
}
if
channels_orig
.
get
(
'combined'
):
channels
[
'combined'
]
=
max_rx_queues
if
channels
:
control_host
.
set_eth_channels
(
f
'defunct_{iface}'
,
channels
)
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'
,
{})
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
)
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Sat, Oct 17, 12:07 AM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4376497
Default Alt Text
vpp.py (36 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment