Dynamic blacklist blocking using EdgeRouter

Subscribing to public blacklists and then blocking those connections using an ipset directly on the EdgeRouter for added network security.

Here's the full script:

#!/bin/sh
set -eu

WORKDIR="/config/blacklist"
FINAL_SET="BLACKLIST"
TMP_SET="BLACKLIST_TMP"

ABUSEIPDB_URL="https://raw.githubusercontent.com/borestad/blocklist-abuseipdb/refs/heads/main/abuseipdb-s100-7d.ipv4"
STOPFORUMSPAM_URL="https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/stopforumspam_7d.ipset"
SPAMHAUS_URL="https://www.spamhaus.org/drop/drop.lasso"

RAW_ABUSEIPDB="$WORKDIR/abuseipdb-s100-7d.ipv4"
RAW_STOPFORUMSPAM="$WORKDIR/stopforumspam_7d.ipset"
RAW_SPAMHAUS="$WORKDIR/drop.lasso"

COMBINED="$WORKDIR/combined.txt"
SORTED="$WORKDIR/combined.sorted.txt"
FINAL_LIST="$WORKDIR/final.txt"
RESTORE_FILE="$WORKDIR/ipset.restore"
WHITELIST="$WORKDIR/LocalWhitelist.txt"
WHITELIST_SORTED="$WORKDIR/whitelist.sorted.txt"
LOGFILE="$WORKDIR/update-blacklist.log"

mkdir -p "$WORKDIR"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOGFILE"
}

backup_file() {
    FILE="$1"
    if [ -f "$FILE" ]; then
        mv "$FILE" "$FILE.bak"
    fi
}

is_valid_ipv4_or_cidr() {
    echo "$1" | awk -F'[./]' '
    NF==4 || NF==5 {
        for (i=1; i<=4; i++) {
            if ($i == "" || $i < 0 || $i > 255) exit 1
        }
        if (NF==5 && ($5 == "" || $5 < 0 || $5 > 32)) exit 1
        exit 0
    }
    { exit 1 }'
}

is_reserved() {
    case "$1" in
        0.*|10.*|127.*|169.254.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*|192.168.*|224.*|240.*)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

filter_safe() {
    while IFS= read -r ip; do
        is_valid_ipv4_or_cidr "$ip" || continue
        base_ip=$(echo "$ip" | cut -d/ -f1)
        is_reserved "$base_ip" && continue
        echo "$ip"
    done
}

log "Starting blacklist update"

log "Backing up previous generated files"
backup_file "$COMBINED"
backup_file "$SORTED"
backup_file "$FINAL_LIST"
backup_file "$RESTORE_FILE"
backup_file "$WHITELIST_SORTED"

log "Downloading feeds"
curl -fsSL "$ABUSEIPDB_URL" -o "$RAW_ABUSEIPDB"
curl -fsSL "$STOPFORUMSPAM_URL" -o "$RAW_STOPFORUMSPAM"
curl -fsSL "$SPAMHAUS_URL" -o "$RAW_SPAMHAUS"

: > "$COMBINED"

log "Parsing AbuseIPDB feed"
if [ -s "$RAW_ABUSEIPDB" ]; then
    grep -E '^[0-9./]+$' "$RAW_ABUSEIPDB" | filter_safe >> "$COMBINED" || [ $? -eq 1 ]
fi

log "Parsing stopforumspam ipset feed"
if [ -s "$RAW_STOPFORUMSPAM" ]; then
    awk '/^add[[:space:]]+/ {print $3}' "$RAW_STOPFORUMSPAM" \
        | grep -E '^[0-9./]+$' \
        | filter_safe >> "$COMBINED" || [ $? -eq 1 ]
fi

log "Parsing Spamhaus DROP feed"
if [ -s "$RAW_SPAMHAUS" ]; then
    awk -F';' '{print $1}' "$RAW_SPAMHAUS" \
        | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \
        | grep -E '^[0-9./]+$' \
        | filter_safe >> "$COMBINED" || [ $? -eq 1 ]
fi

log "Deduplicating entries"
LC_ALL=C sort -u "$COMBINED" > "$SORTED"

if [ -f "$WHITELIST" ]; then
    log "Applying whitelist from $WHITELIST"
    : > "$WHITELIST_SORTED"
    if [ -s "$WHITELIST" ]; then
        grep -E '^[0-9./]+$' "$WHITELIST" \
            | filter_safe \
            | LC_ALL=C sort -u > "$WHITELIST_SORTED" || [ $? -eq 1 ]
    fi
    LC_ALL=C comm -23 "$SORTED" "$WHITELIST_SORTED" > "$FINAL_LIST"
else
    cp "$SORTED" "$FINAL_LIST"
fi

COUNT=$(wc -l < "$FINAL_LIST" | tr -d ' ')
log "Final blacklist count: $COUNT"

if [ "$COUNT" -eq 0 ]; then
    log "ERROR: Final blacklist is empty, aborting"
    exit 1
fi

log "Ensuring final ipset exists"
ipset list "$FINAL_SET" >/dev/null 2>&1 || \
    ipset create "$FINAL_SET" hash:net family inet hashsize 4096 maxelem 262144

log "Cleaning up stale temp set if present"
ipset destroy "$TMP_SET" >/dev/null 2>&1 || true

log "Building restore file"
{
    echo "create $TMP_SET hash:net family inet hashsize 4096 maxelem 262144"
    while IFS= read -r entry; do
        [ -n "$entry" ] && echo "add $TMP_SET $entry"
    done < "$FINAL_LIST"
} > "$RESTORE_FILE"

log "Loading temp set via ipset restore"
ipset restore -f "$RESTORE_FILE"

log "Swapping temp set into place"
ipset swap "$TMP_SET" "$FINAL_SET"

log "Destroying old temp set"
ipset destroy "$TMP_SET"

log "Blacklist update complete"

Assuming you've got the default WAN_IN and WAN_LOCAL firewall rules setup on your Edgerouter, you can run the following commands to deploy the ipset. Once it's setup on iptables then you're good to go. You may need to re-run these on reboot or using a cronjob or boot script.

iptables -C WAN_IN -m set --match-set BLACKLIST src -j DROP 2>/dev/null || \
iptables -I WAN_IN 1 -m set --match-set BLACKLIST src -j DROP

iptables -C WAN_LOCAL -m set --match-set BLACKLIST src -j DROP 2>/dev/null || \
iptables -I WAN_LOCAL 1 -m set --match-set BLACKLIST src -j DROP

The above script could be modified to subscribe to more lists, but I'm just using the three lists that work for me. I attempted to also sanitize the incoming IP data from the list, once it was working I added it to a shell script and installed this into the cronjob. Depending on which EdgeRouter you have, you'll need to consider how often to run this so you don't wear out your local storage.

Obviously the better solution would be to have a dedicated firewall, but I wanted to see if this was even possible and turns out, it is.

Subscribe to XGhozt - Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe