Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F116164584
reproduce.py
All Users
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
cr0ntab
Jun 10 2026, 12:42 AM
2026-06-10 00:42:32 (UTC+0)
Size
2 KB
Referenced Files
None
Subscribers
None
reproduce.py
View Options
#!/usr/bin/env python3
#
# Minimal reproduction for the vti_updown_db.py lost-update race.
#
# This drives the exact code path the IPsec updown hook uses on an
# up-client event (src/etc/ipsec.d/vti-up-down):
#
# with open_vti_updown_db_for_create_or_update() as db:
# db.add(interface, connection, protocol)
# db.commit(supply_interface_dict)
#
# but from N processes released at the same instant by a barrier, and with a
# no-op interface_dict_supplier so it never touches a real interface (the
# named vtiN interfaces do not exist, so commit() only rewrites the state
# file and performs no `ip link` operations).
#
# strongSwan fires this hook once per CHILD_SA event, per VTI. During a
# coordinated rekey, N of them run concurrently. open_vti_updown_db_*()
# does an unlocked read-modify-write of /tmp/ipsec_vti_interfaces: every
# process reads the same copy, each writes back only its own addition, and
# the last writer's commit() wins. The rest are lost, and the hook that
# lost its add leaves that interface admin-down.
#
# Run as root on a VyOS instance:
# python3 reproduce.py
#
# Stock image: prints "LOST <N-1> of <N>". Exit code 1.
# With the patched python/vyos/utils/vti_updown_db.py in place: "0 lost". Exit 0.
import
multiprocessing
as
mp
import
os
import
sys
from
vyos.utils.vti_updown_db
import
(
open_vti_updown_db_for_create_or_update
,
VTI_WANT_UP_IFLIST
,
)
N
=
64
def
up_client
(
index
,
barrier
):
"""What the up-client hook does, minus the real interface bring-up."""
barrier
.
wait
()
# release all workers into the critical section together
with
open_vti_updown_db_for_create_or_update
()
as
db
:
db
.
add
(
f
'vti{index}'
,
f
'peer-vti{index}'
,
'v4'
)
# no-op supplier: vtiN does not exist, so commit() only rewrites the
# state file (no `ip link`, no VTIIf instantiation).
db
.
commit
(
lambda
_interface
:
{
'disable'
:
True
})
def
main
():
# Pre-create an empty DB so every worker takes the existing-file (r+)
# path. This isolates the lost-update race from the separate x+ create
# race (both are fixed by the same lock).
with
open
(
VTI_WANT_UP_IFLIST
,
'w'
):
pass
barrier
=
mp
.
Barrier
(
N
)
workers
=
[
mp
.
Process
(
target
=
up_client
,
args
=
(
i
,
barrier
))
for
i
in
range
(
N
)]
for
w
in
workers
:
w
.
start
()
for
w
in
workers
:
w
.
join
()
with
open
(
VTI_WANT_UP_IFLIST
)
as
f
:
entries
=
[
e
for
e
in
f
.
read
()
.
split
()
if
e
]
survived
=
{
e
.
split
(
':'
)[
0
]
for
e
in
entries
}
lost
=
[
f
'vti{i}'
for
i
in
range
(
N
)
if
f
'vti{i}'
not
in
survived
]
print
(
f
'fired {N} concurrent up-client events; '
f
'DB retained {len(survived)} of {N} interfaces'
)
if
lost
:
print
(
f
'LOST {len(lost)} of {N} updates '
f
'(these interfaces would be stranded admin-down): '
f
'{", ".join(lost)}'
)
sys
.
exit
(
1
)
print
(
'0 lost: all updates serialised correctly'
)
sys
.
exit
(
0
)
if
__name__
==
'__main__'
:
main
()
File Metadata
Details
Attached
Mime Type
text/x-script.python
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3711236
Default Alt Text
reproduce.py (2 KB)
Attached To
Mode
T8975: ipsec: concurrent vti-up-down hook invocations lost-update /tmp/ipsec_vti_interfaces, stranding VTI interfaces admin-down
Attached
Detach File
Event Timeline
Log In to Comment