| Age | Commit message (Collapse) | Author |
|
bond: T9269: keep the bond MAC when appending a member interface
|
|
A multi node with a single value is a plain string in the JSON rendering
of the config tree, and only becomes a list once it holds several values.
leaf_node_changed() tested for a string before diffing the lists, so
appending a value reported the already configured one as gone.
Adding a second bond member thus produced an "interface_remove" entry for
the first, and BondIf.update() released it right after enslaving the new
one, leaving the bond with the new member only.
Normalize to a list and reuse list_diff(). Only the string to list append
changes, from [old] to []. interface.py was immune as it re-applies
list_diff() to address_old, protocols_static_arp.py was not.
|
|
T9290: Fix typo for the image.py file
|
|
Typo "writeable" should be "writable"
|
|
dict_to_key_paths() yields the path leading to an empty dict, which is a
valueless leaf node. An empty dict at the top level means "no
configuration" and yielded a single zero-component path, making
dict_to_paths_values() report an option named ''.
A bare "interfaces ethernet eth1" - as found in a container, where no
hw-id is deployed - could therefore not be added to a bond:
Cannot add interface "eth1" to bond, it has a "" assigned!
interfaces_ethernet.py recorded the same phantom path in
bond_blocked_changes. configdiff.py was immune, owner([]) is falsy.
|
|
Commit 6ebb8cd26 ("bond: T7571: fix inconsistent MAC address behaviour")
re-assigns the first member's MAC address to the bond, as newer Kernels
no longer do so themselves. The flag driving it was initialized
unconditionally and an already enslaved member hits "continue" without
clearing it, so the step ran again on every append and handed the bond
the newly added member's address:
bond0 aa:c1:ab:c3:62:48
eth1 aa:c1:ab:98:48:0f
eth2 aa:c1:ab:c3:62:48
Only adopt a member MAC while the bond has no member at all, which is
what T7571 intended.
The added smoketest covers this together with T9269 in leaf_node_changed:
it commits one member, appends a second, and verifies both stay enslaved,
the pre-existing member stays admin up, and the bond MAC does not move.
|
|
op_mode_config_dict() rewrote the absolute path passed in by the caller
into one relative to VYATTA_EDIT_LEVEL. list_strip() returns an empty
list as soon as a path component mismatches, so from e.g. [edit qos
policy] the path "qos interface eth1" became the current edit level
itself and cli-shell-api dumped an unrelated subtree:
[edit qos policy]
vyos@vyos# run show qos shaper interface eth1
WARNING: Specified configuration path is not valid
QoS is not applied to eth1!
Relative lookups are unreliable even when the edit level is a proper
prefix of the path, as cli-shell-api validates the path against
VYATTA_TEMPLATE_LEVEL and rejects a relative tag node value.
Keep the absolute path and reset both level variables for the child
process instead.
|
|
Each lease event emitted some fifteen daemon.info lines tracing internal steps
rather than recording a change. Add a "debug" level to logmsg(), silenced unless
/tmp/vyos.dhclient.debug exists, following the flag file convention used for FRR
and ifconfig. Demoting alone would not help, as journald shows all priorities by
default. Fifteen tracing messages move to debug; the sixteen recording an actual
change stay at info.
Also fix logmsg(): "warn" had no case arm although it is used, and the global
LOG_PRIO leaked the preceding call's priority. It is now local.
|
|
get_dhcp_route_interfaces() only walked "protocols static route <prefix>
dhcp-interface", so routes configured below "protocols static table <id> route
<prefix> dhcp-interface" never made it into the interface list. Both nodes share
the same include/static/static-route.xml.i definition and both are rendered from
the DHCP lease via the get_dhcp_router() filter in staticd.frr.j2, so they depend
on the current lease in exactly the same way.
As a result the affected interfaces were missing from the DHCP gateway cache used
for FRR change detection. A lease event on an interface referenced only by a
table based route would compare equal to the cached state and the re-render
requested by the dhclient hook was skipped, leaving the stale gateway in the
rendered configuration.
Iterate the route sections of every table in addition to the top level ones.
Only IPv4 routes are considered, as "dhcp-interface" does not exist for route6.
Named VRFs have no table node, but the collection is shared and stays correct
there as well.
|
|
The default route derived from "interfaces <type> <ifname> address dhcp" is
rendered from the DHCP lease file, so it is only known at lease time. Until now
the sole runtime path into staticd was the one-shot vtysh injection in
dhclient-enter-hooks.d/03-vyos-ipwrapper. That injection races the FRR reload of
the very commit which started the DHCP client: dhclient runs with "-nw", thus the
commit does not wait for a lease, and when BOUND arrives mid-reload frr_alive()
can report FRR as down. The route is then installed into the kernel only and FRR
never learns about it. Nothing recovers afterwards, as FRRender.generate()
short-circuits on an unchanged configuration dict.
The self-healing re-render used by "protocols static route <prefix>
dhcp-interface" was gated on /tmp/static_dhcp_interfaces, which never lists plain
"address dhcp" interfaces - protocols_static.py does not even run on an
interface-only commit, as no config-mode dependency points to it.
Derive the DHCP dependent interface list from the configuration dict itself, both
for the default VRF and for every named VRF, and use it for FRR change
detection. Drop the interface list gate in the dhclient exit hook so any lease
event requests a re-render.
Also poll for the lease and for the rendered route in the affected smoketests
instead of relying on a fixed sleep.
|
|
|
|
sysctl_write() reports success through its return code, but the underlying
sysctl(8) call inherited stderr. net.ipv4.neigh.default.gc_thresh* is not
network namespace aware, so a container printed six "cannot stat ... No such
file or directory" lines on every boot.
Capture the output and keep the return code as the only interface to the
caller.
|
|
A container has no hardware of its own - vendor, model, serial and UUID are
read from /sys/class/dmi/id and thus describe the host system. Listing them
below "System type: container" reads as a property of the node.
Leave the hardware_* keys unset when running as a container and omit the
entire block in both "show version" and the airbag bug report. The latter
moves the fields into an optional HARDWARE section, as str.format() has no
conditionals.
|
|
A container is never booted on its own - is_live_boot() parses the Kernel
cmdline of the host system, which on an installed host contains a BOOT_IMAGE
pointing to /boot/<version>/vmlinuz. A container was thus reported to be
running from an "installed image".
Report "container image" for containers and keep the livecd against installed
image distinction for everything else.
|
|
A container shares the Kernel with its host and inherits its DMI and CPUID
data, thus hvinfo reports the hypervisor of the host system - a VyOS container
on a KVM host was described as "KVM guest".
Check is_running_as_container() first and report "container", otherwise fall
back to hvinfo as before. Note that boot_via and the hardware vendor, model,
serial and UUID fields are still derived from the host.
|
|
Kernel cmdline and serial console updates rewrite the GRUB configuration on
the persistence partition. A container has no GRUB at all, and on a system
where the persistence partition is not mounted disk.find_persistence()
returns an empty string, so the code reads '//boot/grub/grub.cfg' and dies
with FileNotFoundError.
Extend is_running_as_container() to also honor /run/.containerenv (Podman)
and the container= variable systemd sets for PID 1, and add if_not_container()
plus if_persistence() decorators guarding all grub_util entry points.
|
|
T9261: Python interpreter startup makes the CLI sluggish on low-cache CPUs
|
|
pki: T9225: Support ED25519 and ED448 signatures on public keys in PKI
|
|
get_esp_ike_cipher() renders both IKE_SA proposals and CHILD_SA
proposals. It unconditionally appended the -esn / -noesn variants,
which are only meaningful for ESP: ESN is Transform Type 5 per
RFC 7296 section 3.3.2 and is defined for CHILD_SAs only.
Emitting ESN in an IKE_SA proposal breaks interoperability with
implementations that reject the malformed payload without replying
at all. Observed against a Cisco FTD: the IKE_SA_INIT is silently
discarded, no NO_PROPOSAL_CHOSEN and no INVALID_KE_PAYLOAD, so the
tunnel never comes up and nothing on the initiator side explains it.
Add an explicit esn parameter defaulting to True so ESP behaviour is
unchanged, and pass esn=False at the four IKE call sites: peer.j2,
profile.j2, remote_access.j2 and l2tp.j2. The five esp_proposals call
sites keep ESN.
ike_group could have served as the discriminator, since IKE call sites
do not pass it, but that coupling is incidental: the argument only
exists to resolve PFS.
Smoke test assertions on the proposals lines are updated accordingly;
the esp_proposals assertions are left untouched.
|
|
Disabling DPDK multi-segment buffers (no-multi-seg) lets VPP receive each
frame into a single buffer, increasing forwarding throughput. The frame
must then fit the buffer 'data-size', so 'no-multi-seg' is computed: it is
enabled only while the largest MTU across all VPP-bound interfaces, plus
its L2 framing overhead (26 bytes), fits 'data-size'. Otherwise VPP keeps
multi-seg and Jumbo frames still work without a larger buffer.
Changing the MTU of a VPP-bound interface can flip the flag, so
interfaces_ethernet reconfigures VPP when it changes, via an initial
dependency (set_dependents_initial) to avoid a cycle with the existing
vpp -> ethernet dependency.
|
|
|
|
Follow-up sweep to f696b2d65. Every conf-mode script not handled in-daemon by
vyos-configd, every op-mode script and every Python validator is a fresh
interpreter, so module-scope imports of heavy dependencies are paid over and
over - which is what makes the CLI feel sluggish on CPUs with little cache.
configsource.py imported vyos.vyconf_session, and with it google.protobuf, for
a class that is only instantiated when the vyconf backend is active. opmode.py
imported ConfigTreeQuery for a single decorator most op-mode scripts never use.
template.py imported jinja2 at module scope although Environment is only touched
in the lru_cache'd _get_environment(), while most callers want just the
is_ipv4/is_ipv6 predicates. ethtool.py pulled pyroute2 via vyos.netlink for two
calls in Ethtool.__init__. ifconfig/interface.py pulled the whole cryptography
stack via vyos.pki for set_eapol() alone. Also deferred: netaddr, jmespath,
tabulate, hurry.filesize, vyos.ifconfig in qos, and cracklib - the last of which
also opens its dictionary database on import.
import vyos.opmode 34.0 -> 4.6 ms (-86%), 206 -> 71 modules
import vyos.ifconfig 132.9 -> 30.8 ms (-77%), 618 -> 193 modules
import vyos.config 40.7 -> 20.6 ms (-49%), 194 -> 130 modules
import vyos.qos 134.1 -> 12.5 ms (-91%), 631 -> 124 modules
import vyos.template 18.8 -> 6.3 ms (-66%), 153 -> 89 modules
import vyos.firewall 22.2 -> 12.2 ms (-45%), 168 -> 114 modules
Deferring textwrap in base.py and logging in configtree.py was measured too and
made no difference; both were left alone.
|
|
Every ConfigTree re-opened libvyosconfig with cdll.LoadLibrary and re-declared
all 31 function prototypes inline in __init__. DiffTree and the twelve
module-level helpers repeated the pattern per call, and since a DiffTree builds
six further trees internally, a single one performed seven dlopens and roughly
200 argtypes/restype assignments. referencetree.py had its own copy.
Declare the prototypes once in a _PROTOTYPES table and hand out an lru_cache'd
wrapper keyed on libpath. Symbols are resolved on first use rather than up
front, because not every build of the library exports every entry point and the
previous code only failed on symbols actually called. referencetree.py shares
the cache and picks up LIBPATH from configtree, so it now honours the
/tmp/libvyosconfig/_build development path instead of hardcoding the install
path.
ConfigTree() 72.3 -> 6.3 us (-91%, 11.5x)
DiffTree() 397.3 -> 34.7 us (-91%, 11.4x)
Two prototypes were wrong and are fixed by declaring them in one place.
validate_tree_filter had no restype, so ctypes defaulted to c_int and truncated
the returned tree pointer to 32 bits before ConfigTree(address=...) saw it;
harmless while the heap stays below 4 GB, corrupt above it. get_subtree
declared two argtypes but is called with three, leaving with_node unchecked.
|
|
Importing vyos.config dragged in the whole HTTP and crypto stack. vyos.version
imported requests and urllib3 for get_remote_version(), plus vyos.system.image
for a single call in get_full_version_data() - and that reaches vyos.system.grub
and vyos.template, so every consumer paid for jinja2 as well. vyconf_session
imported vyos.remote, and therefore paramiko and cryptography, for a
get_config_file() used in two methods. All of these move to their call sites.
vyos/utils/__init__.py eagerly imported all 18 submodules, so 'from
vyos.utils.dict import get_sub_dict' also loaded auth, network and process.
It now resolves them on first attribute access via a module-level __getattr__.
No caller relied on the eager import; the common 'from vyos.utils.<sub> import
<name>' form is unaffected.
import vyos.config 129.6 -> 47.7 ms (-63%, 2.7x), 575 -> 194 modules
import vyos.ifconfig 205.0 -> 149.7 ms (-27%, 1.4x)
This is per process, so it is paid again by every conf-mode script not handled
in-daemon by vyos-configd, every op-mode script, and every smoketest.
|
|
VyOS supports both amd64 and arm64 CPU architectures, but not every smoketest
is applicable to both of them. Add a cpu_arch() decorator to vyos.utils.cpu
which takes a list of supported CPU architectures - if the test is run on any
other architecture it is skipped with a message naming both the supported and
the current architecture. Tests without the decorator run everywhere.
Use it to replace the ad-hoc IS_ARM64 checks in test_kernel_options.py and to
guard the x86 only intel and intel_qat module sets in test_module_load.py,
which so far failed on arm64.
|
|
ha: T9166: add IPv6 support for HA peer links
|
|
T9146: add initial set/call_dependents as exempt from possible cycle
|
|
The introduction of util get_kernel_boot_arg() has many uses, however,
it is not appropriate for use in get_kernel_serial_console: since it
searches right to left, it picks up an incorrect and incomplete entry;
consequently get_kernel_serial_console returns a None tuple, which voids
selection of console type on install.
Restore original implementation of get_kernel_serial_console.
|
|
The category of initial set/call_dependents only apply when the script
is called natively, and not as a dependency. This allows an exemption
from a possible dependency cycle.
|
|
Accept IPv6 source and remote addresses for Kea high availability and
bracket IPv6 endpoints in generated peer URLs.
|
|
firewall: T9242: Add mac-address-mask support
|
|
http-api: T9223: add VRF option to traceroute REST API endpoint
|
|
|
|
http-api: T9224: add ping endpoint to REST API
|
|
- Add mac-address-mask support to firewall rules
- Add smoketest for mac-address-mask
|
|
kdump: T8868: Implement kernel crash dump support
|
|
- Replaced direct file reads from `/proc/cmdline` with `get_kernel_boot_arg()`
for fetching kernel command-line arguments in multiple modules.
- Improved memory information retrieval by utilizing `get_memory_info()`.
|
|
Add new CLI subtree 'system option kdump' with memory reservation and
local dump path configuration. Also add 'show system kdump' and
'show system kdump dumps' operation mode commands to get
status of the configuration and list of dumps.
Extend 'show tech-support report' and 'generate tech-support archive' with
kdump service status, active config files and the recently kernel dump file.
|
|
detach_member() never disabled promiscuous mode when a member left a
VPP bond or the bond was deleted, unlike add_member() which always
enabled it. Disable it on detach, except for members that still have
their own vif/vif-s sub-interfaces configured, since VPP requires
promiscuous mode to pass their VLAN-tagged traffic.
|
|
interfaces: T9158: add option to enable/disable rx offload
|
|
|
|
Commit 4d621da33 ("firewall: T9210: fix ruff findings in python/vyos/firewall.py")
narrowed fqdn_resolve() exception handling in from a bare except: statement to
except gaierror:, claiming "no behavior change." That let non-gaierror OSErrors
(e.g. transient network errors right after commit) escape uncaught, crashing
the unguarded main loop of the vyos-domain-resolver daemon, the process that
fills in D_* (domain-group) nftables sets.
Change except clause back to OSError, as gaierror is a subclass.
|
|
T9169: update for vyos1x-config refactor
|
|
boot: T3871: rework interface renaming and ordering
|
|
non-tty stdin
|
|
Split out of #5372 (T9157) at sarthurdev's request: that PR bundled
unrelated ruff findings with the fib-type-match feature because it
happened to touch this file.
- fqdn_resolve(): bare except -> except gaierror (E722)
- geoip inverse-match check: != None -> is not None (E711)
- 3x extraneous f prefix on string literals without placeholders
(F541): hook_name, pkttype, last
No behavior change.
|
|
utils: T9177: fix _are_same_ip() using wrong address family
|
|
utils: T9185: fix ask_yes_no() busy-looping forever on non-tty stdin
|
|
jestabro (and dmbaturin previously, on #5362) pointed out that silently
returning `default` on a non-TTY stdin still violates ask_yes_no()'s
interactive-only contract, even though it stops the CPU-spin. Per T9185,
sageframe's original incident confirms why: a caller that forgets an
explicit non-interactive guard gets no error and no visible hang, just
a process quietly pegging a core for days.
Raise EOFError immediately instead, so a missing guard fails loudly and
fast rather than either spinning or silently succeeding. Existing callers
that are meant to run non-interactively already check no_prompt/-y before
calling this (config_mgmt.py, backend.py) and are unaffected.
|
|
|