Skip to content

Instantly share code, notes, and snippets.

@Kishi85
Last active December 6, 2025 15:45
Show Gist options
  • Select an option

  • Save Kishi85/b7f379f9aa19f4878af28b8e1a8887ab to your computer and use it in GitHub Desktop.

Select an option

Save Kishi85/b7f379f9aa19f4878af28b8e1a8887ab to your computer and use it in GitHub Desktop.
/etc/init.d/nft2ipset: An nftables set to ipset synchronizer for use with OpenWRT/mwan3
#!/bin/sh /etc/rc.common
# Start before firewall and mwan3 which are at Prio 19
START=18
APP=nft2ipset
USE_PROCD=1
SCRIPTPATH="/tmp/nft2ipset"
write_script() {
cat > "$1" <<'EOT'
#!/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"
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 QUIT ABRT
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 all currently existing nftsets or create otherwise
nft -nT list sets | tr '\n' ' ' | awk '{$1=$1;print}' | sed -r 's/ (set|table)/\n\1/g' | grep "^set" | while read DEF; do
NAME="$(echo "$DEF" | cut -d' ' -f2)"
if [ -z "$NAME" ] || ! grep -q "option ipset '$NAME'" /etc/config/mwan3; then
logger -t "$SCRIPT" "Ignored set '$NAME' as it is invalid or not used by mwan3"
else
create_or_update_ipset "$DEF"
fi
done
# Monitor nftables rule changes
nft -nT monitor > "$MONITORFIFO" 2>&1 &
echo $! > "$MONITORPIDFILE"
while read LINE; do
# Update ipsets according to specified nft monitor option (this should be always one operation per line)
if echo "$LINE" | grep -q "add element inet"; then
NAME="$(echo "$LINE" | cut -d' ' -f 5)"
if [ -z "$NAME" ] || ! grep -q "option ipset '$NAME'" /etc/config/mwan3; then
logger -t "$SCRIPT" "Ignored set '$NAME' as it is invalid or not used by mwan3"
else
# Check if ipset exists or create otherwise
if [ "$(ipset list -n $NAME)" != "$NAME" ]; then
DEF="$(nft -tnT 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
# Refresh the entry by deleting it first if already existing
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"; then
NAME="$(echo "$LINE" | cut -d' ' -f 5)"
if [ -z "$NAME" ] || ! grep -q "option ipset '$NAME'" /etc/config/mwan3; then
logger -t "$SCRIPT" "Ignored set '$NAME' as it is invalid or not used by mwan3"
else
# Create or update ipset
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"; then
NAME="$(echo "$LINE" | cut -d' ' -f 5)"
if [ -z "$NAME" ] || ! grep -q "option ipset '$NAME'" /etc/config/mwan3; then
logger -t "$SCRIPT" "Ignored set '$NAME' as it is invalid or not used by mwan3"
else
# Clear and try to delete removed ipset (This will fail if it is in use by any iptables rule)
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_set_param pidfile /var/run/nft2ipset.pid
procd_close_instance
}
service_stopped() {
rm "$SCRIPTPATH"
}
# vim: ts=2 sw=2 et
@varunpalekar
Copy link

When I am using mwan3 with tailscale and above solution, then also it's exit node functionality not working.
It took nearly 2 days got to know that mwan3 iptables can be the problem.
Finally need to remove mwan3.

Also created a script to replicate failover functionality using another script which you can use trigger using crontab. Sharing below if it help someone

#!/bin/bash
set -e
CHECK_IP=${CHECK_IP:-8.8.8.8}
PRIMARY_IF=${PRIMARY_IF:-prmwan}
BACKUP_IF=${BACKUP_IF:-bkpwan}
ACTIVE_METRIC=5
FAILOVER_METRIC=10

HEALTH_CHECK_FAIL=2

# Check if number of ping failed then retun false with interface
is_up() {
  network_name=$1
  interface=$(get_interface $network_name)
  if ping -I "$interface" -c$HEALTH_CHECK_FAIL "$CHECK_IP" &>/dev/null
  then
    echo true
  else
    echo false
  fi
}

is_primary_up() {
  is_up $PRIMARY_IF
}

is_backup_up() {
  is_ip $BACKUP_IF
}

# Get interface from network name
get_interface() {
  network_name=$1
  interface=""

  if [ $(uci get network.$network_name.proto) == "pppoe" ]; then
    interface=pppoe-"$network_name"
  else
    interface=$(uci get network.$network_name.device)
  fi
  echo $interface
}

activate_primary() {
  primary_metric=$(uci get network.$PRIMARY_IF.metric)
  backup_metric=$(uci get network.$BACKUP_IF.metric)
  if [ "$primary_metric" != "$ACTIVE_METRIC" ] || [ "$backup_metric" != "$FAILOVER_METRIC" ]; then
    uci set network.$PRIMARY_IF.metric=$ACTIVE_METRIC
    uci set network.$BACKUP_IF.metric=$FAILOVER_METRIC
    uci commit network
    /etc/init.d/network reload
    echo "Making $PRIMARY_IF active"
  fi
}

activate_backup() {
  primary_metric=$(uci get network.$PRIMARY_IF.metric)
  backup_metric=$(uci get network.$BACKUP_IF.metric)
  if [ "$backup_metric" != "$ACTIVE_METRIC" ] || [ "$primary_metric" != "$FAILOVER_METRIC" ] ; then
    uci set network.$BACKUP_IF.metric=$ACTIVE_METRIC
    uci set network.$PRIMARY_IF.metric=$FAILOVER_METRIC
    uci commit network
    /etc/init.d/network reload
    echo "Making $BACKUP_IF active"
  fi
}



if [ "$(is_primary_up)" = "true" ]; then
  activate_primary
else
  if [ "$(is_backup_up)" = "true" ]; then
    activate_backup
  fi
fi

@xurubin
Copy link

xurubin commented Oct 6, 2024

BTW there are a couple of places where the code uses sed -r 's/(set|table)/\n\1/g' to parse nft's output but I think it doesn't handle the case where a nftable's name ends with 'set' or 'table'. I've tried replacing them with sed -r 's/ (set|table)/\n\1/g' which seems to work.

@etomm
Copy link

etomm commented Jan 11, 2025

I think also that the monitoring part sometimes receives a line with two or more set, maybe when a DNS resolution return both ipv4 and ipv6 and instead of updating correctly both set it goes all in creating a new ipset with both names inside, even with a \n between them.

Example: I have hulu and hulu6 ipset for ipv4 and ipv6 after sometimes I finish with a set called:
hulu
hulu6

With the \n between the two names.

@posita
Copy link

posita commented Jan 17, 2025

Consider also adding the following signals to be trapped: QUIT, ABRT

@AcidSlide
Copy link

AcidSlide commented Feb 23, 2025

Hi all.. I had some issues with the script especially if you have other nft sets, like for example nft sets used by BanIP package.

The /tmp/nft2ipset generated script was eating a lot of unnecessary resources even triggering oom by nft. It also tries to add all the IP's from banip sets (during banip restarts or reloads) into mwan3 which should not be the case.

Sun Feb 23 20:08:09 2025 kern.warn kernel: [109990.841739] nft invoked oom-killer: gfp_mask=0xc2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_COMP|__GFP_NOMEMALLOC), order=1, oom_score_adj=0

Now, i've been tinkering with the generated script for almost a week now and I just want to share below a more optimized version of nft2ipset. Key point on the optimization, it only uses ipsets defined any mwan3 rules.

Edit/Update 2025-02-24: Re-adjusted the script as after a few reboots it started failing.. more of cleanup of the script with minor changes
Script Update 2025-02-26: It seems that the part "nft -nT monitor" is not killed when calling "restart" or "stop" for the nft2ipset service, so a quick fix for the service_stopped() funciton.

#!/bin/sh /etc/rc.common
# Start before firewall and mwan3 which are at Prio 19
START=18
APP=nft2ipset
USE_PROCD=1
SCRIPTPATH="/tmp/nft2ipset"

write_script() {
cat > "$1" <<'EOT'
#!/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 | grep -E "($(echo "$SET_NAMES" | sed 's/|/\\|/g'))" > "$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

I created a separate gist for easier installation into openwrt (see first comment on the gist): https://gist.github.com/AcidSlide/cd646a8cc81534ad4d3f48075310fe9b

@Kishi85
Copy link
Author

Kishi85 commented Jul 9, 2025

I'm so sorry not to have had time to update this earlier but I've now finally gotten around to to incorporate the following:

  • Fixed the sed syntax as suggested by @xurubin
  • Trapped QUIT and ABRT as suggested by @posita -> This should allow the script to always properly cleanup the nft monitor+fifo and also allow service_stopped to remove the temporary script file as it is no longer actively used by any process (or child thereof)
  • This script will now only sync nft to ipsets for sets that are in use by mwan3 as requested and implemented by @AcidSlide but I've had to move the uci call inside the processing loop to ensure mwan3 config changes to ipsets are always picked up without having the need to restart nft2ipset to repopulate SET_NAMES
  • Removed the hard requirement for nft sets to be in netfilter table fw4 it is not important for the scripts function.

I have not been able to reproduce the multiple lines issues people have mentioned as nft monitor works on a one line per operation basis so until I'm able to reproduce this there is no proper fix.

@AcidSlide
Copy link

Thanks for the update.. I'll do some test of the changes this weekend

@AcidSlide
Copy link

I have not been able to reproduce the multiple lines issues people have mentioned as nft monitor works on a one line per operation basis so until I'm able to reproduce this there is no proper fix.

Can confirm that it's spawning multiple instances of the nft2ipset:

# ps w | grep -i nft
16573 root      1440 S    {nft2ipset} /bin/sh /tmp/nft2ipset
16605 root     10864 S    {nft2ipset} /bin/sh /tmp/nft2ipset
16957 root     10864 S    {nft2ipset} /bin/sh /tmp/nft2ipset
18754 root      1796 R    grep --color=auto -i nft

And somehow my NFT Sets from banip are still being triggered by the /tmp/nft2ipset script

Sat Jul 12 13:17:59 2025 user.notice nft2ipset: Created new ipset etcompromised.v4 with timeout 0
Sat Jul 12 13:18:00 2025 user.notice nft2ipset: Created new ipset firehol1.v4 with timeout 0
Sat Jul 12 13:18:09 2025 user.notice nft2ipset: Created new ipset firehol2.v4 with timeout 0
Sat Jul 12 13:18:13 2025 user.notice nft2ipset: Created new ipset greensnow.v4 with timeout 0
Sat Jul 12 13:18:14 2025 user.notice nft2ipset: Created new ipset ipthreat.v4 with timeout 0

Note: posted also my latest version but not yet merged with your latest changes

@Kishi85
Copy link
Author

Kishi85 commented Jul 12, 2025

I have not been able to reproduce the multiple lines issues people have mentioned as nft monitor works on a one line per operation basis so until I'm able to reproduce this there is no proper fix.

Can confirm that it's spawning multiple instances of the nft2ipset:

# ps w | grep -i nft
16573 root      1440 S    {nft2ipset} /bin/sh /tmp/nft2ipset
16605 root     10864 S    {nft2ipset} /bin/sh /tmp/nft2ipset
16957 root     10864 S    {nft2ipset} /bin/sh /tmp/nft2ipset
18754 root      1796 R    grep --color=auto -i nft

That's not necessarily being the case it is likely that those are just the subshells here (one for nft monitor background process and the while read loop) and hence a display issue. Question is if there are multiple nft monitors up or if it is just one? if there are multiple then i'd have to look into procd's options some more if there is anything wrong there. A workaround would be to add a lock to the wrapper that allows the script to be only started once.
I'm currently running Openwrt 24.10.2 in a x64 VM on Proxmox. What version/architecture are you running this on?

And somehow my NFT Sets from banip are still being triggered by the /tmp/nft2ipset script

Sat Jul 12 13:17:59 2025 user.notice nft2ipset: Created new ipset etcompromised.v4 with timeout 0
Sat Jul 12 13:18:00 2025 user.notice nft2ipset: Created new ipset firehol1.v4 with timeout 0
Sat Jul 12 13:18:09 2025 user.notice nft2ipset: Created new ipset firehol2.v4 with timeout 0
Sat Jul 12 13:18:13 2025 user.notice nft2ipset: Created new ipset greensnow.v4 with timeout 0
Sat Jul 12 13:18:14 2025 user.notice nft2ipset: Created new ipset ipthreat.v4 with timeout 0

Note: posted also my latest version but not yet merged with your latest changes

That is weird as the script checks for non-mwan3 sets in line 87-92 now and skips processing that line from nft monitor:

NAME="$(echo "$LINE" | cut -d' ' -f 5)"
  if [ -z "$NAME" -o "$(uci show mwan3 | grep '\.ipset=' | awk -F '=' '{gsub(/[ "\047]/, "", $2); print $2}' | tr '\n' '|' | grep -w "$NAME" | wc -l)" -eq 0 ]; then
    # Empty or non-mwan3 ipset found
    continue
  fi

I'll add some debug logging on the next iteration so we might be able to find out what is going wrong here and also do some further testing myself to see if the check is working correctly. Never mind I've found the main problem for the filtering. The set will get created upon script start if it exists previously as the filtering happens at the wrong point.

@Kishi85
Copy link
Author

Kishi85 commented Jul 12, 2025

Updated once more with the following:

  • Simplified set name check to do a very simple grep on the mwan3 config file as it a lot less overhead than transforming the uci output and should do the same thing (if there are any problems with this assumptions please comment).
  • Added the missing set name check for the initial set creation that is outside of the monitor loop (this should finally cover all cases).
  • Undo previous refactoring that would add a lot of useless debug output

As for the multiple processes thing: I still cannot reproduce this on x64 (OpenWRT 24.10.2 with /bin/sh from BusyBox) so it might be something architecture dependent that i'm missing out on. From my knowledge nft2ipset showing multiple times could just be display issue due to subshells for the monitor background process and the while read loop. The real question is if there are multiple /tmp/nft2ipset-[0-9]+.monitorpid files and corresponding processes in the process list as that would indicate the script running simultaneously.

@AcidSlide
Copy link

AcidSlide commented Jul 12, 2025

As for the multiple processes thing: I still cannot reproduce this on x64

I'll do some re-test later and see if I can pinpoint where the multiple process is coming from and if there are multiple PID files also.

On my current version of the nft2ipset, the multi-process doesn't happen.

@AcidSlide
Copy link

The real question is if there are multiple /tmp/nft2ipset-[0-9]+.monitorpid files and corresponding processes in the process list as that would indicate the script running simultaneously

Latest version seems to work properly now

  • no multiple process seen via ps w | grep -i nft
  • none mwan3 ipset use, ignored properly now

You mentioned the monitorpid.. I saw multiple files before re-testing and not sure if it had to do with previous mentioned issue (if it was the cause or an output of it) of multiple process spawned but deleted all of them before testing

@Kishi85
Copy link
Author

Kishi85 commented Jul 13, 2025

The real question is if there are multiple /tmp/nft2ipset-[0-9]+.monitorpid files and corresponding processes in the process list as that would indicate the script running simultaneously

Latest version seems to work properly now

  • no multiple process seen via ps w | grep -i nft
  • none mwan3 ipset use, ignored properly now

You mentioned the monitorpid.. I saw multiple files before re-testing and not sure if it had to do with previous mentioned issue (if it was the cause or an output of it) of multiple process spawned but deleted all of them before testing

Glad to hear that it works now as intended on your end as well.

The multiple monitor files could have been remnants of crashes of the script as the cleanup trap would not have been executed in that case. Not a problem as long as the pid that is saved inside the file is no longer existing with the orphaned nft monitor process.

@AcidSlide
Copy link

AcidSlide commented Jul 19, 2025

@Kishi85 I updated my router today and somehow on my edge case, after a boot/reboot i've got multiple nft2ipset running

root@mt6000 /root [#]# ps w | grep -i nft
 1850 root      1452 S    {nft2ipset} /bin/sh /tmp/nft2ipset
 1959 root      6552 S    nft -nT monitor
20239 root      1980 S    grep --color=auto -iE post|oom|ntpd|TestNotify|nft2ipset
20328 root      1452 S    {nft2ipset} /bin/sh /tmp/nft2ipset
20329 root     46868 R    nft -nT list sets
20413 root      1812 S    grep --color=auto -i nft

root@mt6000 /root [#]# ls -laht /tmp/nft2ipset*
prw-r--r--    1 root     root           0 Jul 19 09:21 /tmp/nft2ipset-20512.nftmonitorfifo
-rwxr-xr-x    1 root     root        5.2K Jul 19 09:21 /tmp/nft2ipset
prw-r--r--    1 root     root           0 Jul 19 09:19 /tmp/nft2ipset-1850.nftmonitorfifo
-rw-r--r--    1 root     root           5 Jul 19 09:18 /tmp/nft2ipset-1850.nftmonitorpid

i'm still investigating how it's happening.. but so far it's only happening after a reboot/boot

Testing Update: ok found the issue.. doing a /etc/init.d/nft2ipset restart was the culprit. Doing a stop then start doesn't do this. It's only the restart is the issue.

Test Update #2: This is weird.. it's somehow a hit/miss thing. Mostly it happens after a reboot/boot. Or a timing issue?? When using restart.

@Kishi85
Copy link
Author

Kishi85 commented Jul 19, 2025

@Kishi85 I updated my router today and somehow on my edge case, after a boot/reboot i've got multiple nft2ipset running

root@mt6000 /root [#]# ps w | grep -i nft
 1850 root      1452 S    {nft2ipset} /bin/sh /tmp/nft2ipset
 1959 root      6552 S    nft -nT monitor
20239 root      1980 S    grep --color=auto -iE post|oom|ntpd|TestNotify|nft2ipset
20328 root      1452 S    {nft2ipset} /bin/sh /tmp/nft2ipset
20329 root     46868 R    nft -nT list sets
20413 root      1812 S    grep --color=auto -i nft

root@mt6000 /root [#]# ls -laht /tmp/nft2ipset*
prw-r--r--    1 root     root           0 Jul 19 09:21 /tmp/nft2ipset-20512.nftmonitorfifo
-rwxr-xr-x    1 root     root        5.2K Jul 19 09:21 /tmp/nft2ipset
prw-r--r--    1 root     root           0 Jul 19 09:19 /tmp/nft2ipset-1850.nftmonitorfifo
-rw-r--r--    1 root     root           5 Jul 19 09:18 /tmp/nft2ipset-1850.nftmonitorpid

i'm still investigating how it's happening.. but so far it's only happening after a reboot/boot

Testing Update: ok found the issue.. doing a /etc/init.d/nft2ipset restart was the culprit. Doing a stop then start doesn't do this. It's only the restart is the issue.

Test Update #2: This is weird.. it's somehow a hit/miss thing. Mostly it happens after a reboot/boot. Or a timing issue?? When using restart.

That is odd indeed and seems like something wrong with the procd parameters as the service and script should only be able to be started once. The script itself should be fine here but somehow the service starts twice? Would explain the restart behaviour as well. This could also be an odd racecondition in procd for the restart case?

Anyway, I've added an explicit pid file parameter to the procd configuration of the script so the real pid of the script gets stored somewhere for procd to reference. Maybe procd is just loosing track of the script pid it started without that for some odd reason? Then this should help, otherwise at least no further harm is done.

@antoniovalenzuela
Copy link

antoniovalenzuela commented Aug 13, 2025

Works OpenWrt 24.10.2

Replace dnsmaq to dnsmasq-full to suppor nftset

  1. /luci/admin/network/firewall/ipsets
imagen
  1. /luci/admin/network/dhcp
    Necessary Family IPv4+6 (does not work only with IPv4)
imagen
  1. /luci/admin/network/mwan3/rule
    handwritten ipset
imagen
  1. /etc/init.d/nft2ipset start

Test
nft list set inet fw4 filtrado
imagen

ipset list filtrado
imagen

Works perfectly with the mwan3 rule

@tkkost
Copy link

tkkost commented Dec 6, 2025

Hello.
Excuse me. Why did you use hash:ip? I created nft ipsets with an ipset-extras incuding different Geo-IP, different ASN and I guess the nft2ipset doesn't work well with it. All the IPs aren't included to the ipset, I guess. I changed hash:ip to hash:net in script, and it looks probably good, but maybe I don't know something?

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