diff options
| author | Oleksandr Kuchmystyi <o.kuchmystyi@vyos.io> | 2026-05-19 12:09:56 +0300 |
|---|---|---|
| committer | Oleksandr Kuchmystyi <o.kuchmystyi@vyos.io> | 2026-05-22 17:50:22 +0300 |
| commit | 81e8f14c5b9b322a998e6530184b07e9e9187ff5 (patch) | |
| tree | 0637a42ef04642439d38f02c1241dfec3c4187ee /src/system | |
| parent | 51ab39e51bb925307c0f9ad1d905d450ceecc0f5 (diff) | |
| download | vyos-1x-81e8f14c5b9b322a998e6530184b07e9e9187ff5.tar.gz vyos-1x-81e8f14c5b9b322a998e6530184b07e9e9187ff5.zip | |
snmp: T8538: Persist engineBoots counter across reboots
Per RFC 3414 section 2.2 (Replay Protection), the `snmpEngineBoots`
counter must be stored in non-volatile storage and incremented on
every snmpd restart. VyOS was not persisting this value, causing
it to reset to 1 after every reboot.
SNMP managers cache the engineBoots value from previous sessions.
When VyOS resets the counter to 1 after reboot, managers reject
incoming SNMPv3 trap packets as "too old", producing errors such as:
```
usm: Message too old.
reboot count invalid
```
This change introduces `/config/snmp/engineboots.count` as a disk-backed
persist file and it uses to sync the counter into snmpd's conf
before the daemon starts.
Diffstat (limited to 'src/system')
| -rw-r--r-- | src/system/sync-snmp-engine-boots.py | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/src/system/sync-snmp-engine-boots.py b/src/system/sync-snmp-engine-boots.py new file mode 100644 index 000000000..4213325c4 --- /dev/null +++ b/src/system/sync-snmp-engine-boots.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# +# Copyright (C) VyOS Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Called via systemd ExecStartPost= after snmpd starts. +# Reads the live engineBoots value from /var/lib/snmp/snmpd.conf and +# writes it to /config/snmp/engineboots.count only if the value differs. +# Fixes T8538: ensures the persistent counter is always in sync with +# what snmpd actually used, so the next restart increments correctly. + +import os +import logging +import contextlib + +import vyos.opmode + +from vyos.utils.file import read_file +from vyos.utils.file import write_file + +SNMPD_CONF = '/var/lib/snmp/snmpd.conf' +PERSIST_FILE = '/config/snmp/engineboots.count' + +# Configure logging +logger = logging.getLogger(__name__) +logger.addHandler(logging.StreamHandler()) +logger.setLevel(logging.DEBUG) + + +def _read_snmpd_engine_boots() -> int | None: + """Return the engineBoots value from snmpd's persistent conf, or None.""" + + content = read_file(SNMPD_CONF, defaultonfailure='', sudo=True) + for line in content.splitlines(): + if line.startswith('engineBoots'): + parts = line.split() + if len(parts) < 2: + continue + _, value, *_ = parts + with contextlib.suppress(ValueError): + return int(value) + + return None + + +def _read_persist_engine_boots() -> int | None: + """Return the currently saved engineBoots counter, or None.""" + + raw = read_file(PERSIST_FILE, defaultonfailure='') + with contextlib.suppress(ValueError): + return int(raw.strip()) + + return None + + +if __name__ == '__main__': + snmpd_boots = _read_snmpd_engine_boots() + if snmpd_boots is None: + raise vyos.opmode.DataUnavailable( + f'Could not read engineBoots from {SNMPD_CONF}' + ) + + logger.debug(f'engineBoots from snmpd: {snmpd_boots}') + + persist_boots = _read_persist_engine_boots() + logger.debug(f'engineBoots from persist file: {persist_boots}') + + if persist_boots == snmpd_boots: + logger.debug('engineBoots already in sync, nothing to do') + else: + os.makedirs(os.path.dirname(PERSIST_FILE), exist_ok=True) + write_file(PERSIST_FILE, str(snmpd_boots)) + logger.debug(f'engineBoots updated: {persist_boots} -> {snmpd_boots}') |
