miniupnpd: restore nftables mappings on reload - #30506
Open
Clackz wants to merge 1 commit into
Open
Conversation
The nftables variant installs its forwarding and NAT rules into the
upnp_forward, upnp_prerouting and upnp_postrouting chains. Those chains
are declared inside the main "inet fw4" table by the usr/share/nftables.d
snippets this package ships, so every firewall start or reload rebuilds
them from the templates and leaves them present but empty. The daemon
keeps running and has no way of noticing that the rules it installed are
gone, so all existing port mappings silently stop working until a client
happens to re-request them or the daemon is restarted.
Nothing else recovers from this either:
- /etc/hotplug.d/iface/20-firewall issues "fw4 -q reload" on
ifup/ifupdate, while /etc/hotplug.d/iface/50-miniupnpd exits early
both for any action other than ifup and when the external device
name is unchanged
- the daemon itself never re-installs rules it already reported as
active
The iptables variant is not affected, because its rules live in a
MINIUPNPD chain of its own which firewall3 does not flush. That is why
files/firewall3.include only has to re-add the jump rules and never
touches the daemon. For nftables the equivalent hook was added by
f1c69d0 ("miniupnpd: rework firewall4 integration") and removed again
by 4c934ae ("miniupnpd: remove uci-defaults and fw4-include files for
nftables variant"), which left the nftables variant with no way to
recover at all.
openwrt@f1c69d0e6cea
openwrt@4c934aea718c
openwrt#21931
Re-introduce the hook. The daemon is restarted rather than left alone
because a restart is what makes it re-read its lease file. The restart
is detached and delayed on purpose: fw4 runs script includes while it
still holds /var/run/fw4.lock, and a synchronous restart can make the
"nft list chain ... || fw4 reload" safeguard in the daemon's
start_service() block on that lock, which is what produced the firewall
error that motivated the removal back then.
Only IPv4 mappings are stored in the lease file, so PCP mappings and
IPv6 pinholes cannot be restored by a restart. This is a pre-existing
limitation.
Tested on bcm53xx / ImmortalWrt (fw4 + miniupnpd-nftables) with the
package built from this tree:
- before: a plain "fw4 reload" emptied both chains, left the daemon
PID unchanged and killed the five active mappings. A 120 s watch
showed no recovery, and simulated ACTION=ifup / ACTION=ifupdate on
the WAN interface left the mappings at zero as well
- with this patch: the daemon is restarted once and re-installs all
five mappings within about two seconds, with no firewall lock error
- a real WAN reconnect (ifup wan, which triggers both an ifup and an
ifupdate reload) restored all mappings the same way
Note for fw4: only type and path are valid options for a script include.
family and reload are accepted by fw3 but marked UNSUPPORTED by fw4's
parse_include(), which warns and ignores them, so the uci-defaults does
not set them even though the iptables one does.
Signed-off-by: Jianyu Zhou <clackz@qq.com>
Member
|
Ping @stangri |
openwrt-ai
reviewed
Sep 11, 2026
|
|
||
| ( sleep 1; /etc/init.d/miniupnpd restart ) >/dev/null 2>&1 & | ||
|
|
||
| exit 0 |
Collaborator
There was a problem hiding this comment.
The PR body states fw4 sources script includes (. '$path'), while line 18 here says it executes them. If it is sourced, every exit 0 in this file — this one and lines 26, 27, 28, 31, 34, 40 — terminates fw4 itself, so any script include registered after miniupnpd's is skipped. Should these be return 0?
Generated by Claude Code
Comment on lines
+38
to
+40
| leases="$(uci -q get upnpd.config.upnp_lease_file)" | ||
| [ -n "$leases" ] || leases=/var/run/miniupnpd.leases | ||
| [ -s "$leases" ] || exit 0 |
Collaborator
There was a problem hiding this comment.
nit: miniupnpd.init:156 only emits lease_file= when upnp_lease_file is set, so with the option unset the daemon keeps no lease file at all and this hardcoded fallback can only ever match a stale leftover — restarting on it kills the running daemon without restoring anything.
Suggested change
| leases="$(uci -q get upnpd.config.upnp_lease_file)" | |
| [ -n "$leases" ] || leases=/var/run/miniupnpd.leases | |
| [ -s "$leases" ] || exit 0 | |
| leases="$(uci -q get upnpd.config.upnp_lease_file)" | |
| [ -n "$leases" ] && [ -s "$leases" ] || exit 0 |
Generated by Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The nftables variant installs its forwarding/NAT rules into the
upnp_forward,upnp_preroutingandupnp_postroutingchains. Those chains are declared inside the maininet fw4table by theusr/share/nftables.d/snippets this package ships, so every firewall start/reload rebuilds them from the templates and leaves them present but empty. The daemon keeps running and has no way of noticing that the rules it installed are gone, so all existing port mappings silently stop working until a client happens to re-request them or the daemon is restarted.Nothing else recovers from this either:
/etc/hotplug.d/iface/20-firewallissuesfw4 -q reloadon ifup/ifupdate, while/etc/hotplug.d/iface/50-miniupnpdexits early both for any action other thanifupand when the external device name is unchanged.The iptables variant is not affected, because its rules live in a
MINIUPNPDchain of its own which firewall3 does not flush — that is whyfiles/firewall3.includeonly has to re-add the jump rules and never touches the daemon. For nftables the equivalent hook was added by f1c69d0 ("miniupnpd: rework firewall4 integration") and removed again by 4c934ae / #21931 ("miniupnpd: remove uci-defaults and fw4-include files for nftables variant"), which left the nftables variant with no way to recover at all. Note thatfiles/miniupnpd.defaults.nftablesis currently gone from the tree whilefiles/miniupnpd.defaults.iptablesis still there and still installed byPackage/miniupnpd-iptables/install.Re-introduce the hook for the nftables variant. The daemon is restarted rather than left alone because a restart is what makes it re-read its lease file.
Why the restart is detached and delayed
The removal in #21931 was justified with "produces a firewall error on service miniupnpd restart". That is this: fw4 executes script includes while it still holds
/var/run/fw4.lock(/sbin/fw4runsACTION=includesinside{ flock -x 1000; ... } 1000>$LOCK), andstart_service()containsso a synchronous restart can make the daemon block on the lock that fw4 still holds. Running the restart detached, one second later, avoids that. fw4 sources the include (
. '$path') after closing fd 1000, so the child inherits no lock fd; it is only the daemon's ownfw4 reloadfallback that would take the lock.Only IPv4 mappings are stored in the lease file, so PCP mappings and IPv6 pinholes cannot be restored by a restart. This is a pre-existing limitation and it is documented in the script.
fw4 include options
Only
typeandpathare valid for a script include.familyandreloadare accepted by fw3 but markedUNSUPPORTEDby fw4'sparse_include(), which warns and ignores them, so the uci-defaults does not set them even thoughminiupnpd.defaults.iptablesdoes.Run tested
bcm53xx / ImmortalWrt 25.12.2,
fw4+miniupnpd-nftablesbuilt from this tree:fw4 reloademptied both chains, left the daemon PID unchanged, and killed the five active mappings; a 120 s watch showed no recovery at all. SimulatedACTION=ifup/ACTION=ifupdateon the WAN interface left the mappings at zero as well.fw4 reloadrestarts the daemon once (confirmed in the daemon's own log: singleshutting down/startingpair) and re-installs all five mappings within about two seconds, with no fw4 lock error.ifup wan, which triggers both an ifup and an ifupdate reload) restored all mappings the same way.INSTALL_DATAis correct.Relation to other open work
#28765 also touches this package but does not address the wiped-chains case: its
50-miniupnpdkeeps the same early-exit logic. It does touch thePackage/miniupnpd-nftables/installblock, so one of the two will need a small rebase. Its uci-defaults is installed as99-miniupnpd-upnpd-migration, which does not clash with the99-miniupnpdused here. It also renames theupnp_lease_fileUCI option tolease_filein a different section; the script here reads the current name first and falls back to/var/run/miniupnpd.leases, which stays the default value there.Signed-off-by is in the commit.