| Age | Commit message (Collapse) | Author |
|
op-mode: T9354: replace "reset connection" with "reconnect interface"
|
|
T8977: Fix regression with is_addr_assigned and is_listen_port_bind_service
|
|
(#5504)
The Router Solicitation kicked off when an IPv6 autoconf PPPoE link comes up
is best effort - the peer may answer late or not at all. rdisc6(8) then blocks
for the duration of all retries and exits non-zero, which aborted the commit:
FileNotFoundError: [Errno 2] failed to run command: rdisc6 --single --retry 3 pppoe1000
returned: Soliciting ff02::2 (ff02::2) on pppoe1000...
Timed out.
No response.
exit code: 2
Run rdisc6(8) detached in a transient systemd unit, so neither the delay nor
the exit code reaches the commit. The outcome is logged to the journal.
|
|
accel-ppp: T9357: do not require local users when any-login is configured
|
|
create_private_key() took one "size" for every key type, but the number
means entirely different things per type: for RSA and DSA it is a modulus
size, for EC it selects a curve. CodeQL is not path sensitive across the
key_type comparison, so it follows the 256 an EC caller passes into the
RSA and DSA branches that caller can never reach, and reports pki.py as
generating 256 bit RSA and DSA keys.
Build elliptic curve keys in create_ec_private_key() instead and route the
op mode generator and the OpenVPN smoketest there, so no curve size ever
enters the function that also builds RSA and DSA keys.
create_private_key() now refuses a key type it does not build rather than
returning None, so a caller cannot drift back into the old shape
unnoticed.
|
|
The "default to SECP256R1" branch stored an instance where the other branch
stored the class, so the shared call at the end invoked the instance and
raised "'SECP256R1' object is not callable". Any curve size without a
matching SECP<size>R1 - the very case the default exists for - aborted
instead of falling back.
Take the default through getattr() so both paths carry the class.
|
|
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.
|
|
The list of listening sockets is a snapshot, and the owning process was
looked up afterwards. A process exiting in between, or one belonging to
another user when we do not run privileged, raised an exception that
escaped and aborted the conf-mode script asking whether a port is taken.
Neither case can be the service being looked for, so such a socket is
skipped now.
|
|
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>
|
|
Committing "service pppoe-server authentication any-login" failed with
"Authentication mode local requires local users to be configured!" even
though any-login accepts any username/password combination, making local
user accounts pointless.
verify_accel_ppp_authentication() raised unconditionally whenever
authentication mode was "local" - the default - without ever consulting
the any-login option. Skip that requirement when any-login is set, while
keeping the password and rate-limit validation for users that are
configured.
|
|
"connect interface" and "disconnect interface" cannot be combined to restart a
dialer interface when the CLI session runs over that very interface, yet the
command which does cover it was filed under an unrelated top-level node and
was hard to find.
It is now "reconnect interface <ifname>", mirroring the existing connect and
disconnect commands. "reset connection" is removed, no alias is kept.
The removed command was reachable through the REST API as a "reset" path. A
dedicated "/reconnect" endpoint takes its place, so the operation stays
available to API consumers.
|
|
vpp: T9146: Enable DPDK no-multi-seg when a full frame fits the buffer
|
|
ppp: T9060: do not derive a link-local address from a synthetic MAC
|
|
A modem is on the bus while still probing, so a dial could start too early and
leave the interface without a session until something re-dialled it.
|
|
A PPP link has no hardware address. RFC 5072 defines the IPv6 interface
identifier of such a link as the one negotiated via IPV6CP, and that
negotiation is also what guarantees its uniqueness - which is why duplicate
address detection is skipped for the link-local address on PPP links.
VyOS additionally derived an EUI-64 link-local address from a synthetic MAC
address and assigned it to PPPoE and SSTP client interfaces. The peer knows
nothing about that address and its uniqueness was never verified. With two
link-local addresses present, RFC 6724 source address selection falls through
to rule 8 and may pick the synthetic one, so DHCPv6 prefix delegation and
router solicitations leave from an address the BRAS never negotiated. ISPs
validating the source address silently drop them and delegation stalls.
Both interface types now carry only the address negotiated via IPV6CP. A
leftover address on an established session is removed on commit.
|
|
vxlan: T9319: add VXLAN-GBP support
|
|
Two things kept an operator disconnect from taking effect.
vyos-netlinkd re-dials every configured WWAN interface that has lost its
session. It cannot tell a session lost to RF signal loss from one that was
taken down on purpose, so the disconnect was undone on the next reconcile
pass, at most 30 seconds later:
$ disconnect interface wwan0
08:53:33 t+10s state=registered
08:53:43 t+20s state=registered
08:53:53 t+30s state=connected <- re-dialled
08:54:03 t+40s state=connected
vyos-netlinkd: WWAN interface wwan0 is not connected, re-dialling...
vyos-netlinkd: Re-dialled WWAN interface wwan0
Record the intent instead of inferring it from the session state. op-mode
"disconnect" drops a marker in /run/vyos-wwan, which _reconcile_wwan() honours
and "connect" removes again. conf_mode removes it too, so a commit keeps
re-asserting the configured state - the marker only survives for as long as
nobody says otherwise, and never across a reboot.
|
|
/etc/cron.d/vyos-wwan re-ran the conf_mode script every 5 minutes via
op-mode "connect interface wwanN" to re-establish a session lost to RF
signal loss. Re-entering conf_mode from cron is unsafe: the cron job knows
nothing about the boot commit or about a commit in flight, so when a tick
lands before the running config is populated, get_config() returns nothing
but {'ifname', 'deleted'} and apply() takes the teardown branch instead of
dialling. It then stops the DHCP clients, stops ModemManager and unlinks its
own cron file, leaving wwan0 down with no way to recover until the next
commit:
CRON[1869]: (root) CMD (/usr/libexec/vyos/vyos-check-wwan.py)
systemd[1]: Stopping DHCP client on wwan0...
systemd[1]: Stopping Modem Manager...
This is timing dependent - it only bites when a */5 tick coincides with
boot - which is why it presents as an intermittently dead WWAN interface.
Move the re-dial into vyos-netlinkd, which already tracks these interfaces
for DHCP and already gates all of its work behind boot_configuration_complete()
and commit_in_progress2(). A re-dial can therefore no longer land on a
half-applied configuration. It also dials ModemManager directly rather than
re-entering conf_mode, so the teardown branch is no longer reachable from
the recovery path at all.
|
|
- Reduced rule processing of source-validation rules through hashable objects
|
|
|
|
- Refactor source-validation to use vmaps
- Update broken source-validation tests
- Fix source-validation failing to remove rules when interface is removed (T9153)
|
|
|
|
|
|
|
|
Expose the Group Policy extension as a valueless gbp CLI node and pass
it to iproute2 when creating VXLAN links. Recreate links when GBP
changes and reject configurations combining GBP with GPE.
Reject conflicting GBP settings on a shared UDP port, accounting for
address families and underlay VRF socket bindings. Warn that external
mode carries GBP in tunnel metadata instead of the packet mark.
Add smoketest coverage for GBP creation, removal and re-enabling,
retained interface settings, rejection without changing the running
tunnel, shared ports, separate address families, and external mode.
|
|
configtree: T9307: serialize libvyosconfig calls across threads
|
|
GeoIP (country-code/ASN) match sets were named
GEOIP_{CC|ASN}{6}_{hook}_{name}_{rule} with no source/destination
discriminator, unlike the sibling FQDN-match naming which already
appends one. A rule matching GeoIP on both source and destination
computed the same set name for both sides, so geoip_update() silently
overwrote one side's IP-range list with the other's, and the generated
rule matched both saddr and daddr against the same single set -
silently dropping one of the two configured constraints with no error
at commit or runtime.
Append the existing source/destination prefix (already in scope,
matching the FQDN convention) to the set name in firewall.py, and
thread the same discriminator through geoip.py's set-name construction
so the rule's match expression and the set it references always agree.
Also updates the non-load-bearing geoip_sets() name construction in
conf_mode/firewall.py and conf_mode/policy_route.py for consistency.
|
|
test_dhcp_vrf and test_dhcpv6_vrf resolved the DHCP client's PID, then read
the VRF's process list and asserted the PID appeared in it. The client
re-executes itself at moments of its own choosing, so between those two
steps it can come back with a new PID - the process is in the VRF, it simply
is not the process we asked about:
AssertionError: '13502' not found in '14322 dhclient\n
14324 dhclient-script\n
14485 dhclient...'
Observed failing and passing on the same image, so it costs a rerun rather
than telling anyone anything.
get_process_cmdline() already fights the same race for the /proc read.
Resolve the PID and read the VRF's list as one step and retry, so a restart
costs a retry instead of a failure.
The probe itself was also loose: substring-matching the raw output of
"ip vrf pids <name>" lets a PID check be satisfied by the process name
column or by a longer PID containing ours. Add
vyos.utils.network.get_vrf_pids(), which parses the command's
"<pid> <name>" lines into (pid, process_name) tuples and returns an empty
list for an unknown VRF, and compare the PID field as an int.
|
|
T8264: openvpn dco lifecycle
|
|
ipsec: T9254: do not emit ESN transforms in IKE proposals
|
|
assertDcoDataPath() checks the link kind, the operating mode and that the
daemon is up, and none of those change when the offload carries nothing.
A client whose server is not up yet never opens its tun, so VyOS creates the
"ovpn" device for it and OpenVPN has to adopt that device once the server
appears. Get the adoption wrong and the daemon takes the device but not its
ifindex: dco_new_peer() fails, the daemon exits and deletes the interface on
its way out, systemd starts it again and the second attempt creates a device
of its own. The tunnel comes up either way, so the state it ends in proves
nothing - the restart it cost does.
Ask the Kernel for its peers as well, which is the one thing no amount of
userspace bookkeeping can fake.
|
|
apply() restarts the daemon and then creates the interface itself, as a
tun device. OpenVPN declines the offload when it finds a device of the
wrong type, so DCO worked only until the first reconfiguration.
The Kernel module takes its operating mode at creation time and iproute2
can neither set nor report it, so go through netlink - multipoint for a
server, point-to-point otherwise, matching what OpenVPN asks for itself.
A server adopting a point-to-point device rejects every client, and the
attribute is dropped silently if it is ever unrecognised, so read the
mode back.
A raw option does not change this. verify() turns away the ones known to
drop the offload, and beyond those "openvpn-option" is documented as the
user's own responsibility - so an offloaded interface is created either
way. A raw option that does decline the offload now leaves the daemon
unable to open the device it is given, which is at least visible, rather
than quietly carrying traffic in userspace.
|
|
EthernetIf.remove() snapshotted Section.sub_interfaces() once and then removed
every entry of that list. For Q-in-Q setups the list contains both the vif-s
(eth0.10) and its vif-c children (eth0.10.20), as Section.sub_interfaces()
matches every configured interface whose name starts with "<ifname>.".
Removing eth0.10 cascades in the Kernel and also deletes eth0.10.20. Once the
loop reached eth0.10.20, Interface.__init__() no longer found it and - as
"create" defaults to True, re-created it via a bare "ip link add dev eth0.10.20"
without any type. This failed and left a stranded interface behind which in
turn broke all subsequent smoketests.
The same cascade silently killed DHCP clients running on sub-interfaces of any
other interface type, as only the interface itself got its addresses flushed:
* deletion of a bond or bridge interface carrying vif/vif-s/vif-c
* deletion of an entire vif-s, as get_removed_vlans() only populates
"vif_c_remove" for a vif-s which still exists in the new configuration
Move the sub-interface teardown from EthernetIf.remove() into the base class
Interface.remove(), where it benefits every interface type. The list is walked
deepest first (vif-c before vif-s/vif), which is what T7813 requires to stop
the DHCP client and return the lease, and interfaces which vanished in the
meantime are skipped so they are never accidentally re-created.
As a side effect of this move, an ethernet interface is now only placed into
A/D state after the base class has released the addresses - previously the
lease of the interface itself was returned over an already downed link.
|
|
ethernet: T9228: only warn about unsupported NIC features when node changed
|
|
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.
|
|
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.
|
|
configquery: T8409: resolve op-mode config paths independent of edit level
|
|
firewall: T9157: add fib-type match for destination/source
|
|
EthernetIf.update() re-applies every ethernet knob on every commit touching
the interface. The capability messages (offload, flow-control, ring-buffer,
coalesce, switchdev) are emitted whenever the CLI-desired state differs from
what the driver reports - and on drivers where a feature is fixed
(virtio_net, vmxnet3, vif, veth, ...) that is permanently true. The message
is correct the first time, but it then re-appears on unrelated commits like
an interface description or IP address change.
Record in interfaces_ethernet.py which of the affected CLI nodes were
altered - the mechanism already used for speed_duplex_changed - and pass it
to the setters via a "warn" argument. Settings are still applied
unconditionally, only the message is gated.
While at it, consolidate the seven offload setters onto a common helper and
use Warning() instead of print().
|
|
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.
|