Skip to content

Instantly share code, notes, and snippets.

@AcidSlide
Last active October 20, 2025 16:37
Show Gist options
  • Select an option

  • Save AcidSlide/cd646a8cc81534ad4d3f48075310fe9b to your computer and use it in GitHub Desktop.

Select an option

Save AcidSlide/cd646a8cc81534ad4d3f48075310fe9b to your computer and use it in GitHub Desktop.
/etc/init.d/nft2ipset: An OPTIMIZED version for nftables set to ipset synchronizer for use with OpenWRT/mwan3
#!/bin/sh
#check if the script is already running
PID=$$
SCRIPT="$(basename $0)"
TMPDIR="/tmp"
MONITORPIDFILE="$TMPDIR/$SCRIPT-$$.nftmonitorpid"
MONITORFIFO="$TMPDIR/$SCRIPT-$$.nftmonitorfifo"
mkfifo "$MONITORFIFO"
# Extract all ipset names from MWAN3
SET_NAMES=$(uci show mwan3 | grep '\.ipset=' | awk -F '=' '{gsub(/[ "\047]/, "", $2); print $2}' | sed ':a; N; $!ba; s/\n/|/g')
cleanup () {
# Cleanup nft monitor subprocess
if [ -f "$MONITORPIDFILE" ]; then
MONITORPID="$(cat "$MONITORPIDFILE")"
if [ "$MONITORPID" -gt 1 ]; then
kill "$MONITORPID"
fi
fi
# Remove pid file and fifo
rm "$MONITORFIFO" "$MONITORPIDFILE"
}
trap cleanup TERM INT EXIT
create_or_update_ipset() {
# Determine ipset parameters
local DEF="$1"
local NAME="$(echo "$DEF" | cut -d' ' -f2)"
local OPTS=""
local FAMILY="inet"
if echo "$DEF" | grep -q "ipv6_addr"; then
FAMILY="inet6"
OPTS="$OPTS family $FAMILY"
fi
local TIMEOUT="$(echo "$DEF" | sed -r 's/.*timeout ([0-9]*)s.*/\1/; t; s/.*/0/')"
if [ -n "$TIMEOUT" -a "$TIMEOUT" -gt 0 ]; then
OPTS="$OPTS timeout $TIMEOUT"
fi
# Create or update ipset from nftables set
if [ "$(ipset list -n "$NAME")" = "$NAME" ]; then
CUR="$(ipset list -t "$NAME")"
if ! ( echo "$CUR" | grep -q "family $FAMILY"); then
( ipset destroy "$NAME" 2>&1 | logger -t "$SCRIPT" ) || logger -t "$SCRIPT" "WARNING: Could not destroy ipset with family != $FAMILY"
elif ! ( echo "$CUR" | grep -q "timeout $TIMEOUT"); then
# Swap current iteration of the ipset with a new iteration due to timeout mismatch
ipset create "_$NAME" hash:ip $OPTS
ipset swap "_$NAME" "$NAME"
ipset destroy "_$NAME"
logger -t "$SCRIPT" "Replaced ipset $NAME with new iteration with timeout $TIMEOUT"
fi
fi
if [ "$(ipset list -n "$NAME")" != "$NAME" ]; then
# Create a new ipset with options matching the nftables set
ipset create "$NAME" hash:ip $OPTS
# Restart mwan3 if this ipset is used by it, it is already running but the set name is not found in active rule output
if [ $? = 0 ] && grep -q "option ipset '$NAME'" /etc/config/mwan3 2>/dev/null && ( service | grep mwan3 | grep running ) && ( ! (mwan3 rules | grep -q "match-set $NAME" ) ); then
mwan3 restart
fi
logger -t "$SCRIPT" "Created new ipset $NAME with timeout $TIMEOUT"
fi
# Add already existing entries to the set
echo "$DEF" | sed -re 's/.*elements = \{ ([^\}]+) \}.*/\1/g; t; s/.*//g' | tr ',' '\n' | sed -re 's/^[ ]+//g;s/expires/timeout/g;s/s$//g' | while read LINE; do
if [ -n "$LINE" ]; then
ipset -q add "$NAME" $LINE && logger -t "$SCRIPT" "Added $LINE to $NAME upon ipset creation/update" || true
fi
done
}
# Check if ipsets exist for only MWAN3-related nftsets
nft -nT list sets | tr '\n' ' ' | awk '{$1=$1;print}' | sed -r 's/(set|table)/\n\1/g' | grep -E "^set ($SET_NAMES) " | while read DEF; do
create_or_update_ipset "$DEF"
done
# Start monitoring nftables but filter only relevant sets
nft -nT monitor > "$MONITORFIFO" 2>&1 &
echo $! > "$MONITORPIDFILE"
while read LINE; do
if echo "$LINE" | grep -q "add element inet fw4"; then
# Extract the set name and check if it's in SET_NAMES
NAME="$(echo "$LINE" | cut -d' ' -f 5)"
if echo "$SET_NAMES" | grep -qw "$NAME"; then
# Check if ipset exists or create otherwise
if [ "$(ipset list -n "$NAME" 2>/dev/null)" != "$NAME" ]; then
DEF="$(nft -nT list sets | tr '\n' ' ' | awk '{$1=$1;print}' | sed -r 's/(set|table)/\n\1/g' | grep "^set $NAME")"
create_or_update_ipset "$DEF"
fi
# Add element to ipset
IP="$(echo "$LINE" | cut -d' ' -f 7)"
EXPIRES="$(echo "$LINE" | sed -re 's/.*expires ([0-9]+)s.*/\1/; t; s/.*/0/')"
ADDOPTS=""
if [ "$EXPIRES" -gt 0 ]; then
ADDOPTS="timeout $EXPIRES"
fi
if ipset -q test "$NAME" "$IP"; then
ipset -q del "$NAME" "$IP"
ipset -q add "$NAME" "$IP" $ADDOPTS
else
ipset -q add "$NAME" "$IP" $ADDOPTS
logger -t "$SCRIPT" "Added $IP to ipset $NAME $ADDOPTS"
fi
fi
elif echo "$LINE" | grep -q "add set inet fw4"; then
# Extract the set name and check if it's in SET_NAMES
NAME="$(echo "$LINE" | cut -d' ' -f 5)"
if echo "$SET_NAMES" | grep -qw "$NAME"; then
DEF="$(nft -nT list sets | tr '\n' ' ' | awk '{$1=$1;print}' | sed -r 's/(set|table)/\n\1/g' | grep "^set $NAME")"
create_or_update_ipset "$DEF"
fi
elif echo "$LINE" | grep -q "delete set inet fw4"; then
# Extract the set name and check if it's in SET_NAMES
NAME="$(echo "$LINE" | cut -d' ' -f 5)"
if echo "$SET_NAMES" | grep -qw "$NAME"; then
ipset clear "$NAME"
ipset destroy "$NAME" 2>&1 | logger -t "$SCRIPT"
fi
fi
done < "$MONITORFIFO"
EOT
}
start_service() {
write_script "$SCRIPTPATH"
chmod +x "$SCRIPTPATH"
procd_open_instance
procd_set_param command "$SCRIPTPATH"
procd_set_param respawn
procd_close_instance
}
service_stopped() {
# Remove the script file
rm -f "$SCRIPTPATH"
# Kill any running nft -nT monitor processes
pkill -f "nft -nT monitor" 2>/dev/null
}
# vim: ts=2 sw=2 et
@AcidSlide
Copy link
Author

If anybody wants a pre-compiled dnsmasq-full with IPSETS, i only currently build 3 different architectures (because I only have those types of routers hahaha). And I can provide a download links for those.

  • mipsel_24kc
  • aarch64_cortex-a53
  • x86_64

@leleb
Copy link

leleb commented Feb 27, 2025

Sorry, I'm not sure I understood the instructions: is it necessary to replace dnsmasq with dnsmasq-full?

Only dnsmasq-full can have IPSETs but honestly right now I don't know where to get a dnsmasq-full that is pre-compiled with IPSET enabled. I do my own openwrt builds and on the options when building dnsmasq-full I enable "IPSET Support" which is disabled by default by OpenWRT.

See this forum post: https://forum.openwrt.org/t/dnsmasq-full-ipset-support-removed-in-23-05-and-master/150274/21?page=2

I am running mwan3 with the" workaround script in Openwrt 24.10 x86 (with default dnsmasq) and it seems to work.
Do you mean that it shouldn't ?
From what I read here
openwrt/packages#22474 (comment)
"...Removing the need to recompile dnsmasq-full with ipset and enabling mwan3 to work with dnsmasq's set functionality again..."

@AcidSlide
Copy link
Author

AcidSlide commented Feb 27, 2025

I am running mwan3 with the" workaround script in Openwrt 24.10 x86 (with default dnsmasq) and it seems to work. Do you mean that it shouldn't ? From what I read here openwrt/packages#22474 (comment) "...Removing the need to recompile dnsmasq-full with ipset and enabling mwan3 to work with dnsmasq's set functionality again..."

Oh really? Didn't know it works even without IPSETs enabled and even with just dnsmasq . Then if it works it works.

Does my updated script works for you? If it does, I'll update my comment and remove mentioning it as a requirement.

@AcidSlide
Copy link
Author

Posted latest version as of 2025-07-12

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment