Last active
December 6, 2025 15:45
-
-
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
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
| #!/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 |
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
Works OpenWrt 24.10.2
Replace dnsmaq to dnsmasq-full to suppor nftset
Necessary Family IPv4+6 (does not work only with IPv4)
handwritten ipset
Test

nft list set inet fw4 filtrado
ipset list filtrado

Works perfectly with the mwan3 rule