Skip to content

Commit 3d45a07

Browse files
committed
feat(ns-storage): persist DHCP leases
Store dnsmasq DHCP leases on /mnt/data when storage is available and keep /tmp/dhcp.leases as the compatibility path for existing readers. Switch back to /tmp before storage removal and restart victoria-metrics around fstab changes so the data mount can be released cleanly. Assisted-by: Copilot:gpt-5.4
1 parent d853f7e commit 3d45a07

9 files changed

Lines changed: 80 additions & 5 deletions

File tree

packages/ns-api/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,6 +2847,10 @@ List active DHCPv4 leases:
28472847
api-cli ns.dhcp list-active-leases
28482848
```
28492849

2850+
The method reads the dnsmasq lease file configured in
2851+
`dhcp.ns_dnsmasq.leasefile` and falls back to `/tmp/dhcp.leases` when the
2852+
option is unset.
2853+
28502854
Response example:
28512855
```json
28522856
{

packages/ns-api/files/ns.dhcp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
import json
1212
import ipaddress
13+
import os
1314
import subprocess
1415
from euci import EUci
1516
from nethsec import utils, objects
@@ -221,7 +222,11 @@ def list_active_leases():
221222
if 'mac' in ldata and 'ip' in ldata:
222223
static_leases.append(ldata['mac'].lower())
223224

224-
with open("/tmp/dhcp.leases", "r") as fp:
225+
leasefile = u.get('dhcp', 'ns_dnsmasq', 'dhcp', 'leasefile', default='/tmp/dhcp.leases')
226+
if not os.path.exists(leasefile):
227+
return ret
228+
229+
with open(leasefile, "r") as fp:
225230
for line in fp.readlines():
226231
tmp = line.split(" ")
227232
hostname = tmp[3]

packages/ns-api/files/ns.ha

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def get_device_from_ip(uci, ipaddr):
8383
return (n, uci.get('network', n, 'device', default=None))
8484
return (None, None)
8585

86+
def get_dhcp_leasefile(uci):
87+
return uci.get('dhcp', 'ns_dnsmasq', 'dhcp', 'leasefile', default='/tmp/dhcp.leases')
88+
8689
def get_main_vrrp_interface(uci):
8790
for section in utils.get_all_by_type(uci, 'keepalived', 'ipaddress'):
8891
if uci.get('keepalived', section, 'name', default=None) != 'lan_ha':
@@ -444,7 +447,7 @@ def init_local(role, primary_node_ip, backup_node_ip, virtual_ip, lan_interface,
444447
'/etc/adblock',
445448
'/etc/banip',
446449
'/etc/netifyd',
447-
'/tmp/dhcp.leases',
450+
get_dhcp_leasefile(u),
448451
'/var/ns-snort',
449452
'/tmp/banIP-backup',
450453
'/tmp/adblock-Backup'

packages/ns-storage/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
include $(TOPDIR)/rules.mk
77

88
PKG_NAME:=ns-storage
9-
PKG_VERSION:=1.0.1
9+
PKG_VERSION:=1.0.2
1010
PKG_RELEASE:=1
1111

1212
PKG_BUILD_DIR:=$(BUILD_DIR)/ns-storage-$(PKG_VERSION)
@@ -62,6 +62,7 @@ define Package/ns-storage/install
6262
$(INSTALL_BIN) ./files/ns-storage-setup-partition $(1)/usr/libexec
6363
$(INSTALL_BIN) ./files/ns-storage-setup-disk $(1)/usr/libexec
6464
$(INSTALL_BIN) ./files/ns-storage-has-free-space $(1)/usr/libexec
65+
$(INSTALL_BIN) ./files/ns-storage-dhcp-leases $(1)/usr/libexec
6566
$(INSTALL_BIN) ./files/remove-storage $(1)/usr/sbin
6667
$(INSTALL_BIN) ./files/rotate-messages $(1)/usr/sbin
6768
$(INSTALL_BIN) ./files/sync-data $(1)/usr/sbin

packages/ns-storage/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Features:
1010

1111
- device initialization and mounting
1212
- rsyslog configuration
13+
- dnsmasq DHCP lease persistence on mounted storage
1314
- logrotate for extra log files
1415
- customizable cron job to sync data once a day
1516

@@ -66,8 +67,9 @@ add-storage /dev/sda
6667
The script will mount the storage at `/mnt/data` and configure
6768
the system as follow:
6869

69-
- rsyslog will write logs also inside `/mnt/data/logs/messages` file
70-
- logrotate will rotate `/mnt/data/logs/messages` once a week (see `/etc/logrotate/data.conf` for more info)
70+
- rsyslog will write logs also inside `/mnt/data/log/messages` file
71+
- logrotate will rotate `/mnt/data/log/messages` once a week (see `/etc/logrotate/data.conf` for more info)
72+
- dnsmasq will store DHCP leases inside `/mnt/data/dnsmasq/dhcp.leases`; `/tmp/dhcp.leases` remains available as a compatibility symlink while the storage is mounted
7173

7274
### Storage status alert
7375

@@ -91,3 +93,6 @@ To remove the data storage and restore in-memory log retention only, execute:
9193
```
9294
remove-storage
9395
```
96+
97+
The `remove-storage` script switches dnsmasq back to `/tmp/dhcp.leases` before
98+
unmounting `/mnt/data`.

packages/ns-storage/files/add-storage

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ crontab -l | grep -q '/usr/sbin/sync-data' || echo '40 1 * * * /usr/sbin/sync-da
4848

4949
# check for existing OpenVPN RW connection records in /var and merge them into storage if needed
5050
/usr/libexec/ns-openvpn/openvpn-merge-connections-db
51+
52+
# Setup persistent dnsmasq leases file
53+
/usr/libexec/ns-storage-dhcp-leases

packages/ns-storage/files/ns-storage-check.init

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ storage_check()
2626
uci set rsyslog.ns_data.destination="/mnt/data/log/messages"
2727
uci commit rsyslog
2828
fi
29+
30+
/usr/libexec/ns-storage-dhcp-leases
2931
}
3032

3133
boot()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (C) 2026 Nethesis S.r.l.
4+
# SPDX-License-Identifier: GPL-2.0-only
5+
#
6+
7+
# This script is used to move the dnsmasq leases file between the storage and the tmpfs,
8+
# depending on the storage availability.
9+
10+
TMP_LEASEFILE=/tmp/dhcp.leases
11+
STORAGE_TARGET=/mnt/data
12+
STORAGE_DIR=${STORAGE_TARGET}/dnsmasq
13+
STORAGE_LEASEFILE=${STORAGE_DIR}/dhcp.leases
14+
15+
is_storage_mounted()
16+
{
17+
awk -v target="${STORAGE_TARGET}" '$2 == target {found=1} END {exit !found}' /proc/mounts
18+
}
19+
20+
get_current_leasefile()
21+
{
22+
current="$(uci -q get dhcp.ns_dnsmasq.leasefile)"
23+
if [ -n "${current}" ]; then
24+
printf '%s\n' "${current}"
25+
else
26+
printf '%s\n' "${TMP_LEASEFILE}"
27+
fi
28+
}
29+
30+
current_leasefile="$(get_current_leasefile)"
31+
if is_storage_mounted; then
32+
target_leasefile="${STORAGE_LEASEFILE}"
33+
else
34+
target_leasefile="${TMP_LEASEFILE}"
35+
fi
36+
37+
if [ "${current_leasefile}" != "${target_leasefile}" ]; then
38+
if [ "${current_leasefile}" = "${STORAGE_LEASEFILE}" ]; then
39+
# From storage to tmp
40+
cp -af "${STORAGE_LEASEFILE}" "${TMP_LEASEFILE}" || :
41+
else
42+
# From tmp to storage
43+
mkdir -p "${STORAGE_DIR}"
44+
cp -af "${TMP_LEASEFILE}" "${STORAGE_LEASEFILE}" || :
45+
fi
46+
uci set dhcp.ns_dnsmasq.leasefile="$target_leasefile"
47+
uci commit dhcp
48+
reload_config
49+
fi

packages/ns-storage/files/remove-storage

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ rm -rf /mnt/data
3434
if [ "$rom_disk" == "$data_disk" ]; then
3535
parted "/dev/${data_disk}" rm 3
3636
fi
37+
38+
# Restore dnsmasq to /tmp before the storage disappears.
39+
/usr/libexec/ns-storage-dhcp-leases

0 commit comments

Comments
 (0)