Page MenuHomeVyOS Platform

Title: Container commit fails for dual-stack (IPv4+IPv6) network, cascades through test_container.py smoketests
Not ApplicablePublicBUG

Description

Component: container (src/conf_mode/container.py)

Summary:

Committing a container attached to a network with both an IPv4 and an IPv6
address on the same network config node (`container network <name> prefix
<v4-prefix> + prefix <v6-prefix>`) fails with a ConfigSessionError, because
apply() calls systemctl restart vyos-container-<name>.service and that
command exits with code 1. Because the commit fails, the container/systemd
state is left inconsistent, and the smoketest's tearDown() can no longer
clean it up -- which then cascades and fails every subsequent test in
TestContainer (12 failures + 2 errors reported in one run), even though
those tests' own logic is unaffected.

Environment:

Observed in vyos-1x CI (VyOS ISO Integration Test workflow,
test_smoketest_cli job), rolling branch, on two unrelated PRs (confirming
it's not caused by either PR's own diff):

Steps to reproduce (from smoketest/scripts/cli/test_container.py::test_dual_stack_network):

set container network net-4-6 prefix 192.0.2.0/24
set container network net-4-6 prefix 2001:db8::/64
set container name dual-stack-2 image busybox:stable
set container name dual-stack-2 network net-4-6 address 192.0.2.2
set container name dual-stack-2 network net-4-6 address 2001:db8::2
# (repeat for dual-stack-3..5)
commit

Expected: commit succeeds, containers start on the dual-stack network.

Actual: commit raises ConfigSessionError:

File "conf_mode/container.py", line 709, in apply
    cmdl(['systemctl', 'restart', f'vyos-container-{name}.service'])
File "vyos/utils/process.py", line 217, in cmdl
    raise OSError(code, feedback)
PermissionError: [Errno 1] failed to run command: systemctl restart vyos-container-dual-stack-2.service
returned:
exit code: 1

Root cause analysis:

  • The vyos-container-<name>.service unit is Type=forking with ExecStart=/usr/bin/podman run --conmon-pidfile ... --cgroups=no-conmon {run_args}. The failure happens within roughly a second (no ~90s TimeoutStartSec wait visible in the timestamps), which points to podman run itself exiting immediately with an error rather than a startup timeout.
  • generate_run_arguments() (container.py ~line 522-538) builds, for a network with both an IPv4 and IPv6 address configured, both --ip <v4> and --ip6 <v6> for the *same* --network <name> argument. This dual-stack-on-one-network path is the one specific difference between the failing test (test_dual_stack_network) and the passing single-stack ones (test_ipv4_network, test_ipv6_network), which is why I suspect this specific argument combination as the trigger -- but I have not reproduced this live against podman run directly, so I can't yet confirm which exact flag/value podman rejects.
  • Because systemctl restart output isn't captured/logged (returned: is empty in the exception message), the actual podman/systemd error text isn't visible anywhere in the CI log -- someone with a reproducing VyOS instance would need to check journalctl -u vyos-container-dual-stack-2.service (or run the equivalent podman run command by hand) to see the real underlying error.

Secondary issue (error reporting, not the root cause): vyos/utils/process.py:217,
cmdl() raises OSError(code, feedback) passing the process's raw exit code as
the errno argument. Python's OSError.new auto-selects a subclass based
on that value, so an ordinary exit code of 1 gets rendered as PermissionError
(since 1 == errno.EPERM), which is misleading -- it looks like a permissions
problem when it's just "the command exited 1". This makes debugging commands
invoked via cmdl() unnecessarily confusing whenever their exit code happens
to collide with a real errno value. Worth a follow-up fix (e.g. raising a
plain OSError/custom exception without relying on the errno-based subclass
dispatch) independent of the container bug itself.

Impact: Since setUpClass/tearDownClass in TestContainer only run once per
test class, this single failure leaves systemd units/conmon processes
behind that pollute every alphabetically later test in the class, producing
a large, misleading batch of unrelated-looking smoketest failures on any PR
that happens to run the full CLI smoketest suite -- as seen on #5389 and
#5390, neither of which touch container code.

Details

Version
1.5.1
Is it a breaking change?
Unspecified (possibly destroys the router)
Issue type
Bug (incorrect behavior)

Event Timeline

Viacheslav raised the priority of this task from Normal to High.Aug 10 2026, 11:51 AM

Is not reproduced for the rolling VyOS 2026.10.07-0712-rolling and VyOS 1.5-stable-202610050418

run add container image busybox:stable

set container network net-4-6 prefix '192.0.2.0/24'
set container network net-4-6 prefix '2001:db8::/64'

set container name dual-stack-2 image 'busybox:stable'
set container name dual-stack-2 network net-4-6 address '192.0.2.2'
set container name dual-stack-2 network net-4-6 address '2001:db8::2'
set container name dual-stack-3 image 'busybox:stable'
set container name dual-stack-3 network net-4-6 address '192.0.2.3'
set container name dual-stack-3 network net-4-6 address '2001:db8::3'
set container name dual-stack-4 image 'busybox:stable'
set container name dual-stack-4 network net-4-6 address '192.0.2.4'
set container name dual-stack-4 network net-4-6 address '2001:db8::4'
set container name dual-stack-4 network net-4-6 mac '02:fe:f7:64:28:54'

Check:

vyos@r14# sudo podman exec -it dual-stack-2 ip a show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
    link/ether 02:2e:d5:49:a0:03 brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.2/24 brd 192.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8::2/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::2e:d5ff:fe49:a003/64 scope link 
       valid_lft forever preferred_lft forever
[edit]
vyos@r14# 
[edit]
vyos@r14# 
[edit]
vyos@r14# sudo podman exec -it dual-stack-3 ip a show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
    link/ether 02:0e:ce:b2:4b:a0 brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.3/24 brd 192.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8::3/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::e:ceff:feb2:4ba0/64 scope link 
       valid_lft forever preferred_lft forever
[edit]
vyos@r14# 
[edit]
vyos@r14# 
[edit]
vyos@r14# sudo podman exec -it dual-stack-4 ip a show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
    link/ether 02:fe:f7:64:28:54 brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.4/24 brd 192.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8::4/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::fe:f7ff:fe64:2854/64 scope link 
       valid_lft forever preferred_lft forever
[edit]
vyos@r14#