| Age | Commit message (Collapse) | Author |
|
Offer chacha20poly1305 in "encryption data-ciphers" and
"encryption data-ciphers-fallback", rendered as CHACHA20-POLY1305 and
allowed with DCO.
|
|
The ovpn Kernel module does no MSS clamping, so with a 1500 bytes MTU
full sized packets get fragmented on the underlay.
Add an "mtu" node, rendered as "tun-mtu". With "offload dco" it
defaults to 1420, unless a raw tun-mtu/link-mtu/udp-mtu option is
present. Reject "mtu" together with such an option, below 100, and
below 1280 when IPv6 runs inside the tunnel.
|
|
"failure-count" counts missed intervals, so the timeout it describes is
interval * failure-count. Server mode renders and verifies it that way;
site-to-site passed the bare count to "ping-restart", where it lands as
seconds - 60s instead of 600s with the defaults.
Render the product. That puts a timeout past the 86400 "ping-restart"
accepts within reach, so verify() rejects it and the 5-to-6 migration
clamps configurations already beyond it.
This changes behaviour on upgrade: site-to-site tunnels now tolerate a
longer silence, 600 seconds instead of 60 with the defaults.
|
|
T8977: Fix regression with is_addr_assigned and is_listen_port_bind_service
|
|
Commit a9e9c4acfa ("pki: openvpn: T3642: Migrate OpenVPN to PKI and refactor")
was the last caller of verify_diffie_hellman_length() despite the build time
testcases.
Unused code is dead code, drop it.
|
|
The Diffie-Hellman length check stringified the requested minimum keysize
into a variable it never used, then converted the original argument again
for the comparison. A non-numeric value therefore escaped as an uncaught
ValueError from the comparison rather than being reported where it was
passed in.
The argument is now typed and validated up front: a non-integer raises
TypeError, booleans included, and a non-positive integer raises
ValueError. Both indicate a logic error in the calling script and should
fail loudly instead of silently reporting an undersized key. The only
callers today are the unit tests, so the contract is free to change.
A file that exists but holds no readable parameters is user input rather
than a logic error, and now fails the check instead of propagating the
error from openssl.
The bit-count pattern becomes a raw string, and the tests pass integer
keysizes and cover the new paths.
|
|
Whether an interface belongs to a VRF was only established when a VRF was
named explicitly. A lookup for the default VRF - what most callers ask for -
thus reported an address living in an arbitrary VRF, or on a VRF device
itself, as locally assigned, and accepted a configuration the daemon cannot
bind later on. Naming the VRF device remains the way to ask for the
addresses it carries.
Probing the next address family after a failed bind was unreachable, as the
argument is validated as a literal beforehand and then resolves to exactly
one family. The early return is restored.
An interface address carries host bits and must stay valid, but the range
variants classify untrusted input handed over to nftables, which only
accepts a prefix in canonical form. Those reject host bits again.
All of it is covered by tests.
|
|
Whether an address may be bound is not the same question as whether it is
currently configured somewhere: the kernel permits binding a foreign address
once the non-local bind sysctl is set, which is how a failover address is
commonly handed over after the commit. Callers can now opt into that, the
default is unchanged.
A lookup no longer skips the VRF device itself, which carries the addresses
of its own L3 domain, and an interface name can be retrieved for an address
without the caller asking a yes/no question in the first place.
Address and range validation is split into separate checks, so a caller no
longer has to accept ranges where only an address is meaningful. The two
duplicated version checks are dropped in favour of the existing ones.
Co-authored-by: Christian Breunig <christian@breunig.cc>
|
|
configtree: T9307: serialize libvyosconfig calls across threads
|
|
ipsec: T9254: do not emit ESN transforms in IKE proposals
|
|
get_ike_cipher()
get_esp_ike_cipher() took an esn flag defaulting to True (ESP/CHILD_SA
semantics), and every IKE_SA call site had to remember to pass
esn=False. Nothing enforced that, so a future call site could reintroduce
the ESN-in-IKE bug this task fixed.
Following review feedback from hedrok, split it into two public filters:
get_esp_cipher(group_config, ike_group=None) ESP/CHILD_SA, esn=True
get_ike_cipher(group_config) IKE_SA, esn=False always
The shared implementation moves to _get_esp_ike_cipher() (no
register_filter), so a template can no longer call it directly and
skip picking one of the two entry points.
Updated call sites: peer.j2, profile.j2, remote_access.j2, l2tp.j2
(proposals -> get_ike_cipher, esp_proposals -> get_esp_cipher), and
test_template.py.
|
|
Raw options are appended after --config and therefore override what the CLI
rendered, so they have to face the same checks. A single cipher the Kernel
module does not implement makes OpenVPN fall back to the userspace data path;
"ncp-ciphers" is the 2.4 name for the negotiation list, and outside server and
pull mode "cipher" becomes the fallback cipher. "dev-type" other than tun, an
AF_UNIX "dev-node" and, in server mode, a "topology" other than subnet drop
the offload just the same.
"DEFAULT" stands for the built-in list, which OpenVPN expands to AEAD ciphers
alone before it weighs the offload, and the migration drops the offload from
whatever stays rejected.
|
|
The CLI renders "keepalive <interval> <interval * failure-count>" while its
two ranges are independent, so it can produce parameters the daemon refuses
to start on: a timeout below twice the interval, or one beyond the 12 hours
OpenVPN is about to cap ping and keepalive at.
A zero interval renders "keepalive 0 0", on which OpenVPN skips these checks
and simply runs without keepalive, so leave that combination alone.
Migrate what stays rejected, or an upgrade would leave a configuration the
router can no longer commit at boot.
|
|
"offload dco" was accepted next to tap, static keys, compression, CBC
ciphers and raw options OpenVPN refuses to offload, because 2.6 ignored the
module altogether. Those combinations now fail verify(), which would break
config load on upgrade, so drop the request instead.
The migration has to mirror verify_dco() exactly: anything it keeps that
verify() turns away leaves a router unable to commit its own configuration.
|
|
libvyosconfig is not safe for concurrent entry. ctypes releases the GIL
for each foreign call, so threads in one process can be inside the
library at the same time; two concurrent from_string calls, or
from_string racing destroy, corrupt the shared parser state and
crash the process with SIGSEGV. vyos-http-api reaches this when a
configure operation runs in the threadpool while a retrieve is served
on the event loop.
Serialize every entry point on one process-wide RLock in the _Lib
wrapper. For the entry points that report through the library's global
error buffer, read the buffer while the lock is held and keep it per
thread, so get_error() returns the caller's own error; destroy, which
can run from __del__ between a failed call and get_error(), leaves it
untouched.
|
|
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.
|
|
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.
|
|
Container interfaces are veth pairs created by the runtime. They have no
backing bus device in sysfs, so discover_physical_interfaces() never
considers them, and their MAC is assigned by the host and regenerated on
every start.
A pending node could thus never be resolved. The pass only spent both
bounded hardware waits and then asked for an hw-id that would not
survive a restart:
interface 'eth0' still has no hw-id configured after this boot's
naming pass - bind it manually, e.g. ...
Returning before write_status() leaves no status file behind, so
warn_missing_interface_hardware() in vyos-router stays quiet by itself.
setUpModule() pins the detection off for the test module - the build
container would otherwise short-circuit every existing main() test.
|
|
Reported against the previous PCIe/MAC-sorted replacement fill: on a
real, already-provisioned box (hw-id from historical probe-order
rescan, unrelated to any PCIe/MAC sort), a configured node's address
ended up silently applied to a different physical NIC. Deleting one
interface's hw-id (keeping its settings) while a different, unrelated
interface's config was fully removed in the same boot was enough - the
two freed candidates' MAC order didn't match their old name order, so
the deterministic sort swapped them. Once written back by the rescan
helper, the wrong binding became permanent and self-reinforcing on
every later boot.
There's no way to verify, from MAC and PCIe position alone, which of
several unconfigured candidates is genuinely a given node's own
hardware once its hw-id is gone. Restore strict matching: a node only
recovers its hw-id automatically when it's the sole pending node of its
type this boot and exactly one candidate exists - any other count
leaves it pending and reported rather than guessed. A candidate that
isn't matched this way is not otherwise held back - it still gets an
ordinary, settings-free bootstrap name instead of being lost, just
never inherits another node's configuration.
This necessarily changes what a combined "delete one interface fully,
clear a different one's hw-id" reboot can auto-resolve; the accompanying
test-harness change acknowledges that trade-off explicitly.
|
|
boot: T3871: rework interface renaming and ordering
|
|
non-tty stdin
|
|
utils: T9177: fix _are_same_ip() using wrong address family
|
|
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.
|
|
commit-confirm (and ~40 other call sites) hang and burn a full CPU core
indefinitely when invoked with stdin that isn't a TTY, e.g. a
non-interactive vbash session (vbash -c "... commit-confirm 10; exit").
input() raises EOFError immediately and repeatedly in that case, and the
except handler in ask_yes_no() looped straight back to input() with no
backoff or exit condition.
Check stdin.isatty() up front and return the default immediately when
it's not a TTY, matching what a user pressing Enter (accepting the
default) would already do interactively.
|
|
Multi-vendor PCIe NIC systems could lose or rename Ethernet/wireless
interfaces on boot, because naming was decided from a single per-device
udev event before all hardware had a chance to enumerate.
Replace that with one authoritative pass, run once configuration is
available during router startup: wait for configured hardware, apply
every hw-id binding, and name whatever has none yet by PCIe distance
from the root complex and MAC address - the same result every boot.
A node with its hw-id deleted (NIC replacement) or its whole
configuration removed is treated as an ordinary open slot, filled the
same way - recovering its original hardware whenever the interfaces
that vacated slots this boot also show up as candidates.
Boot now reports any interface still unresolved, and the pass is covered
by a new test suite in vyos-build named: make testifname
Assisted-by: Claude:claude-sonnet-5
|
|
Same class of pre-existing whole-file ruff findings as vyos-1x#5374's
477dd4d6b: bare except:, one unused except-clause variable, one
multi-statement if, and one unused loop variable in
python/vyos/utils/network.py.
Also renames a duplicate test_is_ipv6_link_local() in
test_utils_network.py to test_is_loopback_addr() (its actual
subject) - the duplicate name silently shadowed the first test
method, so is_ipv6_link_local() was never actually exercised.
No behavior changes to application code.
|
|
_are_same_ip() computed the address family of both arguments but
only ever passed the first one's family to inet_pton() for both,
via a computed-but-unused "s_two" local. Comparing addresses of
different families (e.g. one IPv4, one IPv6) raised instead of
returning False. Found via ruff flagging s_two as an unused local;
the function currently has no callers.
|
|
Complete the safer-subprocess migration started by the cmdl()/ifconfig
refactoring and convert every remaining vyos.utils.process.cmd() call site to
the list-based cmdl().
Drop the vyos.utils.process.cmd() implementation as it is no longer in use.
|
|
remote: T8829: fall back to GET when HEAD is not supported
|
|
utils: T9003: add list-argument variant of cmd() for safer subprocess execution
|
|
|
|
The module previously had no tests. Cover the DB logic (add/remove,
multiple connections per interface, removeAllOtherInterfaces,
setPersistentInterfaces, seed-from-file) and assert each open_* helper
acquires the serialising lock, exercised with vyos.utils.locking.Lock
patched out.
|
|
HttpC.download() always probed remote URLs with HEAD before GET to
discover redirects and Content-Length. Some APIs (e.g. AbuseIPDB)
reject HEAD with 405 Method Not Allowed, causing firewall remote-group
downloads to fail and leave empty cached list files.
Treat HEAD 405/501 as unsupported and proceed with GET using the
original URL. When HEAD does not provide Content-Length, read it from
the GET response headers instead. Validate available storage after
determining file size and before opening the destination file.
Log sanitized download errors in vyos-domain-resolver when a remote-
group list-file fetch fails. Add unit tests with mock servers that
return 405 or 501 on HEAD and 200 on GET.
|
|
A list-argument variant of cmd() for safer subprocess execution named cmdl().
Command must be a list of strings; no shell interpolation is performed, which
eliminates a class of command-injection risks present when building commands
with f-strings or other string formatting.
|
|
|
|
vpp: T8460: Use isolated cpus for VPP cpu-cores
|
|
VPP CPU core assignment now uses kernel-isolated CPUs (from /sys/devices/system/cpu/isolated) with explicit corelist-workers instead of computing offsets from available cores with skip-cores/workers. Added validation that enough CPUs are actually isolated before VPP starts, and that isolate-cpus config only references existing CPU IDs. Moved smoketest for kernel option 'isolate-cpus' to test_vpp.py
|
|
Interface names can contain dots (e.g. VLAN subinterfaces like eth0.10), which
conflicts with sysctl's dot-separated key syntax.
Change sysctl_read() and sysctl_write() to take key components as a list and
normalize each component by replacing . with / before invoking sysctl. This
fixes sysctl lookups/updates for VLAN subinterfaces.
Extend the SR-TE smoketest to cover a VLAN subinterface.
Previous error raising this issue:
vyos-configd: sysctl: cannot stat /proc/sys/net/ipv6/conf/eth0/10/seg6_enabled: No such file or directory
vyos-configd: sysctl: cannot stat /proc/sys/net/ipv6/conf/eth0/201/seg6_enabled: No such file or directory
|
|
The previous implementation of "system login" relied on Python's pwd.getpwall()
to enumerate user accounts. This forces a full walk through the NSS stack,
which is acceptable in general but problematic for our use-case. VyOS only
needs information about locally created accounts and not remote accounts
provided via AAA backends such as TACACS or RADIUS.
When TACACS servers are unreachable, NSS lookups become extremely slow due to
repeated timeouts. As a result, any operation triggering pwd.getpwall()
(including configuration commits) can stall for several minutes.
This change introduces a dedicated helper, get_local_passwd_entries(), which
reads /etc/passwd directly and avoids NSS entirely. Since only local UIDs are
relevant, this provides all required data with no external dependencies.
Performance improvement on VyOS 1.4.3 with two unreachable TACACS servers:
# set system login tacacs server 192.168.1.50 key test123
# set system login tacacs server 192.168.1.51 key test123
# time commit
Before:
real 3m29.825s
user 0m0.329s
sys 0m0.246s
After:
real 0m1.464s
user 0m0.337s
sys 0m0.195s
This significantly improves commit performance and removes sensitivity to AAA
server outages.
|
|
During upgrade, the script now checks if any `known_hosts` files exist.
If so, it prompts the user to save these SSH fingerprints, and upon
confirmation, copies the files to the new image persistence directory.
|
|
|
|
|
|
|
|
The legal team says years are not necessary so we can go ahead with it, since
it will simplify backporting.
Automatically removed using: git ls-files | grep -v libvyosconfig | xargs sed -i -E \
's/^# Copyright (19|20)[0-9]{2}(-[0-9]{4})? VyOS maintainers.*/# Copyright VyOS maintainers and contributors <maintainers@vyos.io>/g'
In addition we will error-out during "make" if someone re-adds a legacy
copyright notice
|
|
firewalls should always accept
|
|
The current implementation for SSH CA based authentication uses "set service
ssh trusted-user-ca-key ca-certificate <foo>" to define an X.509 certificate
from "set pki ca <foo> ..." - fun fact, native OpenSSH does not support X.509
certificates and only runs with OpenSSH ssh-keygen generated RSA or EC keys.
This commit changes the bahavior to support antive certificates generated using
ssh-keygen and loaded to our PKI tree. As the previous implementation
did not work at all, no migrations cript is used.
|
|
Add a new category if Jinja2 operands. We already have filters and tests, but
sometimes we would like to call a Python function without and data "|" piped
to it - that's what they call a clever-function.
{{ get_default_port(NAME) }} can be used to retrieve the value from
vyos.defaults.internal_ports[NAME] within Jinja2. We no longer need to extend
the dictionary with arbitrary data retrieved from vyos.defaults, we can now
simply register another clever-function to the Jinja2 backend.
|
|
Commit 4523e9c897b3 ("wireguard: T3763: Added check for listening port
availability") added a function to check if a port is free to use or already
occupied by a different running service. This has been done by trying to bind a
socket to said given port.
Unfortunately there is no support for IPv6 address-fdamily in both
socketserver.TCPServer or socketserver.UDPServer. This must be done manually by
deriving TCPServer and setting self.address_family for IPv6.
The new implementation gets rid of both TCPServer and UDPServer and replaces it
with a simple socket binding to a given IPv4/IPv6 address or any interface/
address if unspecified.
In addition build time tests are added for the function to check for proper
behavior during build time of vyos-1x.
|
|
|