Fw4 dynamically add port forwarding nftable rule on boot

on reboot I'd like to remove the previously dynamically added rule
and
dynamically add a port forwarding rule

config redirect
	option dest 'wan'
	option target 'DNAT'
	option family 'ipv4'
	option src 'lan'
	option name 'droid'
	option dest_port '32398-32400'
	option src_dport '32398-32400'
	option dest_ip '192.168.143.84'

the option dest_ip '192.168.143.84' will change on boot

which can be gotten via
GATEWAY=route | awk '/default/ {print $2;}' | tail -1 (thanks to @iplaywithtoys here)

need help with a startup code to remove previous/ insert new port forwarding rule

Off the top of my head, this might work. At least, I think the concept is sound, even if I haven't tested the execution...

  • Keep a persistent record of the gateway IP address each time (e.g. in a text file on persistent storage)
  • Next time the router reboots, calculate the new gateway IP address
  • Then retrieve the old gateway IP address from wherever you stored it
  • Edit /etc/config/firewall and replace the old gateway IP address with the new gateway IP address (the rest of the rule doesn't need to change, only the destination IP address) - sed is good for replacing text automatically.
  • Then restart the firewall service

To get the above to work, you'll need to learn a bit of shell scripting and the use of variables.

1 Like

something like this, I'm guessing

GATEWAY=route | awk '/default/ {print $2;}' | tail -1

echo $GATEWAY >> /tmp/wannn.txt

#read ip

#write to firewall using sed

/etc/init.d/firewall reload

This'll do it.

/etc/config/firewall before any change:

config redirect
        option name 'droid'
        option family 'ipv4'
        option target 'DNAT'
        option src 'lan'
        option src_dport '32398-32400'
        option dest 'wan'
        option dest_ip '192.168.192.168'
        option dest_port '32398-32400'

/etc/config/firewall after a change:

config redirect
        option name 'droid'
        option family 'ipv4'
        option target 'DNAT'
        option src 'lan'
        option src_dport '32398-32400'
        option dest 'wan'
        option dest_ip '192.168.7.1'
        option dest_port '32398-32400'

/etc/scripts/old_gateway.txt before any change:

192.168.192.168

/etc/scripts/old_gateway.txt after a change:

192.168.7.1

/etc/rc.local:

# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

# Pause to allow network to come up, or else gateway calculation might fail
sleep 10

# Retrieve previous upstream gateway address
OLD_GATEWAY=`cat /etc/scripts/old_gateway.txt`

# Obtain current upstream gateway address
NEW_GATEWAY=`route | awk '/default/ {print $2;}'`

# Replace gateway address in firewall rule(s)
sed -i s/$OLD_GATEWAY/$NEW_GATEWAY/g /etc/config/firewall

# Store new gateway address as "old" address for next reboot
echo $NEW_GATEWAY>/etc/scripts/old_gateway.txt

# Restart the firewall
/etc/init.d/firewall restart

exit 0
1 Like

Something along those lines, yes. I just posted something which I whipped up on my system and seems to work.

Be aware, there is no error checking or other safety nets beyond a 10-second delay to allow the WAN interface to come up and obtain a route. It's very crude, and not resilient at all.

1 Like

thank you, i'll use it

the only thing I see is I'll need to manually create and old gateway txt file for first run

all else looks good

appreciate it

1 Like

You're welcome. Good luck!

1 Like

Another option to consider is to use /etc/udhcpc.user or /etc/udhcpc.user.d/ which would be invoked on a WAN DHCP event and use the $router environment variable provided by udhcpc to update the firewall rule.

[ "${interface}" = "wan" ] && [ -n "${router}" ] || exit 0

# if gateway hasn’t changed, don’t update firewall rule
[ "${router}" = "$(uci get firewall.@redirect[-1].dest_ip)" ] && exit 0

uci set firewall.@redirect[-1].dest_ip="$router"
uci commit firewall

The firewall will reload on the ifup anyway.

Edit: added a check in case the IP hasn’t changed. This also assumes the redirect to be changed is the last one in the ruleset [-1]. If not, adjust accordingly.

4 Likes

Heads up: bare route gives me a symbolic name for $2, so I added the "numeric" option, route -n to force the IP address... (I suspect it depends on whether your ISP has DNS for their endpoint routers.)

Interesting. Thanks for the warning. I may need to revisit the behaviour of the route command, as well as my understanding of awk's behaviour.

I settled on that approach because I wanted something unique and the word "default" was unique. Parsing the second entry in the awk array was then easy to accomplish. When I originally tried using route -n, awk ended up returning more than one result.

I'll go back to the drawing board and see if I can come up with something a bit more robust. Thanks again.

That's an excellent suggestion, and less clunky than my crude effort. Nice.

Oh, I didn't even notice that it did the default -> 0.0.0.0 thing. I've always used something like below (probably just because I forgot about route and ifstatus always seems to be a good grab-bag for this sort of stuff, although those jsonfilter espressions can be pretty tedious).

$ ifstatus wan | jsonfilter -e '@.route[0].nexthop'

EDIT
Just playing around, this might be more robust ... doing a search through all the routes for the default.

$ ifstatus wan | jsonfilter -e '@.route[@.target="0.0.0.0"].nexthop'
61.3.190.1
3 Likes

This is the great thing about systems like these. There's always (well, usually) more than one way to accomplish the goal.

1 Like

Another way to obtain the GW address in the script:

BTW, route is obsolete, ip r is a replacement.

3 Likes

I'm old and set in my ways. Sue me.

2 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.