summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2026-06-24 14:48:01 +0300
committerGitHub <noreply@github.com>2026-06-24 14:48:01 +0300
commitd3826867554eb9dbd30d5c8c781516a8afcfd96b (patch)
tree5dfe88e7ff6454e6d0ff59653ee857bf8aa9c9ee /python
parente0114625c08a2a9826822958f53724995580716d (diff)
parent66d984ae1b25928335650d04a28a497702ba8280 (diff)
downloadvyos-1x-d3826867554eb9dbd30d5c8c781516a8afcfd96b.tar.gz
vyos-1x-d3826867554eb9dbd30d5c8c781516a8afcfd96b.zip
Merge pull request #5264 from rnavarro/fix/vti-updown-db-flock-race
ipsec: T8975: lock vti-up-down state DB to prevent lost updates under concurrent rekey
Diffstat (limited to 'python')
-rw-r--r--python/vyos/ifconfig/vti.py8
-rw-r--r--python/vyos/utils/vti_updown_db.py74
2 files changed, 47 insertions, 35 deletions
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index 030aa1ed7..01d724593 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -15,7 +15,7 @@
from vyos.ifconfig.interface import Interface
from vyos.utils.dict import dict_search
-from vyos.utils.vti_updown_db import vti_updown_db_exists, open_vti_updown_db_readonly
+from vyos.utils.vti_updown_db import open_vti_updown_db_readonly
@Interface.register
class VTIIf(Interface):
@@ -64,14 +64,14 @@ class VTIIf(Interface):
"""
Set interface administrative state to be 'up' or 'down'.
- The interface will only be brought 'up' if ith is attached to an
+ The interface will only be brought 'up' if it is attached to an
active ipsec site-to-site connection or remote access connection.
"""
if state == 'down' or self.bypass_vti_updown_db:
super().set_admin_state(state)
- elif vti_updown_db_exists():
+ else:
with open_vti_updown_db_readonly() as db:
- if db.wantsInterfaceUp(self.ifname):
+ if db is not None and db.wantsInterfaceUp(self.ifname):
super().set_admin_state(state)
def get_mac(self):
diff --git a/python/vyos/utils/vti_updown_db.py b/python/vyos/utils/vti_updown_db.py
index 2931df86c..390db5195 100644
--- a/python/vyos/utils/vti_updown_db.py
+++ b/python/vyos/utils/vti_updown_db.py
@@ -18,55 +18,67 @@ import os
from contextlib import contextmanager
from syslog import syslog
+from vyos.utils.locking import Lock
+
VTI_WANT_UP_IFLIST = '/tmp/ipsec_vti_interfaces'
+VTI_UPDOWN_LOCK_NAME = 'ipsec_vti_updown'
def vti_updown_db_exists():
""" Returns true if the database exists """
return os.path.exists(VTI_WANT_UP_IFLIST)
@contextmanager
-def open_vti_updown_db_for_create_or_update():
- """ Opens the database for reading and writing, creating the database if it does not exist """
- if vti_updown_db_exists():
- f = open(VTI_WANT_UP_IFLIST, 'r+')
- else:
- f = open(VTI_WANT_UP_IFLIST, 'x+')
+def _vti_updown_db_lock():
+ """Serialise access to the VTI up/down DB across the concurrent updown-hook
+ invocations (one per VTI) that strongSwan fires during a coordinated rekey,
+ which would otherwise lost-update the shared state file."""
+ lock = Lock(VTI_UPDOWN_LOCK_NAME)
+ lock.acquire() # timeout=0 -> block until acquired
try:
- db = VTIUpDownDB(f)
- yield db
+ yield
finally:
- f.close()
+ lock.release()
+
+
+@contextmanager
+def open_vti_updown_db_for_create_or_update():
+ """ Opens the database for reading and writing, creating the database if it does not exist """
+ with _vti_updown_db_lock():
+ mode = 'r+' if vti_updown_db_exists() else 'x+'
+ with open(VTI_WANT_UP_IFLIST, mode) as f:
+ yield VTIUpDownDB(f)
@contextmanager
def open_vti_updown_db_for_update():
""" Opens the database for reading and writing, returning an error if it does not exist """
- f = open(VTI_WANT_UP_IFLIST, 'r+')
- try:
- db = VTIUpDownDB(f)
- yield db
- finally:
- f.close()
+ with _vti_updown_db_lock():
+ with open(VTI_WANT_UP_IFLIST, 'r+') as f:
+ yield VTIUpDownDB(f)
@contextmanager
def open_vti_updown_db_readonly():
- """ Opens the database for reading, returning an error if it does not exist """
- f = open(VTI_WANT_UP_IFLIST, 'r')
- try:
- db = VTIUpDownDB(f)
- yield db
- finally:
- f.close()
+ """Opens the database for reading. Yields None if the database does not exist."""
+ with _vti_updown_db_lock():
+ if not vti_updown_db_exists():
+ yield None
+ return
+ with open(VTI_WANT_UP_IFLIST, 'r') as f:
+ yield VTIUpDownDB(f)
def remove_vti_updown_db():
- """ Brings down any interfaces referenced by the database and removes the database """
- # We need to process the DB first to bring down any interfaces still up
- with open_vti_updown_db_for_update() as db:
- db.removeAllOtherInterfaces([])
- # this usage of commit will only ever bring down interfaces,
- # do not need to provide a functional interface dict supplier
- db.commit(lambda _: None)
-
- os.unlink(VTI_WANT_UP_IFLIST)
+ """Brings down any interfaces referenced by the database and removes the database, if it exists."""
+ with _vti_updown_db_lock():
+ if not vti_updown_db_exists():
+ return
+ # We hold the lock already; open the file directly rather than via the
+ # locking context manager to avoid re-acquiring (which would deadlock).
+ with open(VTI_WANT_UP_IFLIST, 'r+') as f:
+ db = VTIUpDownDB(f)
+ db.removeAllOtherInterfaces([])
+ # this usage of commit will only ever bring down interfaces,
+ # do not need to provide a functional interface dict supplier
+ db.commit(lambda _: None)
+ os.unlink(VTI_WANT_UP_IFLIST)
class VTIUpDownDB:
# The VTI Up-Down DB is a text-based database of space-separated "ifspecs".