✧ 君と集まって星座になれたら ✦ ROKUTOSEI
1255 字
6 分钟
使用 iptables + ipset 屏蔽主流云厂商 ASN 与恶意扫描流量

本文记录利用 iptables 结合 ipset 屏蔽主流云服务商(AWS, DigitalOcean, Azure, Vultr, Oracle 等)扫描探针与探测流量的方案。

直接在 iptables 中处理大量 IP 网段会导致性能劣化。ipset 使用哈希结构维护 IP 集合,可在 O(1)O(1) 时间复杂度内完成检索,并通过 ipset swap 实现增量无缝更新。


1. 防火墙初始化部署脚本#

在部署前,禁用 ufw 以防规则冲突:

Terminal window
sudo ufw disable
sudo systemctl stop ufw
sudo systemctl disable ufw

创建脚本 /usr/local/bin/deploy_firewall.sh:

#!/bin/bash
# ============================================================================
# 防火墙初始化部署脚本
# ============================================================================
SSH_PORT=54321
WEB_PORTS=(80 443)
ENABLE_IPV6=1
NEIGHBOR_V4="-"
NEIGHBOR_V6="-"
export PATH="/usr/sbin:/sbin:$PATH"
check_v6() {
if [ "$ENABLE_IPV6" != "1" ]; then echo 0; return; fi
if [ -f /proc/net/if_inet6 ] && [ "$(wc -l < /proc/net/if_inet6)" -gt 0 ]; then
command -v ip6tables &>/dev/null && echo 1 || echo 0
else
echo 0
fi
}
check_deps() {
local missing=()
for cmd in ipset iptables; do
command -v "$cmd" &>/dev/null || missing+=("$cmd")
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "Missing dependencies: ${missing[*]}"
echo "Run: sudo apt install -y ipset iptables"
exit 1
fi
}
install_persistent() {
if dpkg -l iptables-persistent 2>/dev/null | grep -q '^ii' && dpkg -l ipset-persistent 2>/dev/null | grep -q '^ii'; then
return 0
fi
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections 2>/dev/null
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections 2>/dev/null
DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent ipset-persistent &>/dev/null
}
create_ipsets() {
ipset create bad_asn_v4 hash:net family inet hashsize 4096 maxelem 500000 -exist 2>/dev/null
if [ "$HAS_V6" = "1" ]; then
ipset create bad_asn_v6 hash:net family inet6 hashsize 4096 maxelem 500000 -exist 2>/dev/null
fi
}
deploy_v4() {
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -F
iptables -X
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
if [ "$NEIGHBOR_V4" != "-" ]; then
iptables -A INPUT -s "$NEIGHBOR_V4" -j DROP
fi
# 丢弃匹配 ipset 集合源的连接请求
iptables -A INPUT -m set --match-set bad_asn_v4 src -j DROP
iptables -A INPUT -p tcp --dport "$SSH_PORT" -j ACCEPT
for port in "${WEB_PORTS[@]}"; do
iptables -A INPUT -p tcp --dport "$port" -j ACCEPT
done
}
deploy_v6() {
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT ACCEPT
ip6tables -F
ip6tables -X
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
if [ "$NEIGHBOR_V6" != "-" ]; then
ip6tables -A INPUT -s "$NEIGHBOR_V6" -j DROP
fi
ip6tables -A INPUT -m set --match-set bad_asn_v6 src -j DROP
ip6tables -A INPUT -p tcp --dport "$SSH_PORT" -j ACCEPT
for port in "${WEB_PORTS[@]}"; do
ip6tables -A INPUT -p tcp --dport "$port" -j ACCEPT
done
}
persist() {
if command -v netfilter-persistent &>/dev/null; then
netfilter-persistent save 2>/dev/null
else
mkdir -p /etc/iptables
ipset save > /etc/iptables/ipsets 2>/dev/null
iptables-save > /etc/iptables/rules.v4
[ "$HAS_V6" = "1" ] && ip6tables-save > /etc/iptables/rules.v6
fi
}
main() {
if [ "$(id -u)" != "0" ]; then exit 1; fi
HAS_V6=$(check_v6)
check_deps
create_ipsets
deploy_v4
[ "$HAS_V6" = "1" ] && deploy_v6
install_persistent
persist
}
main "$@"

赋予权限并运行:

Terminal window
sudo chmod +x /usr/local/bin/deploy_firewall.sh
sudo /usr/local/bin/deploy_firewall.sh

2. 动态黑名单拉取更新脚本#

创建脚本 /usr/local/bin/update_blacklists.sh:

#!/bin/bash
# ============================================================================
# 自治系统 IP 段同步脚本
# ============================================================================
ASNS=(
"14061|DigitalOcean"
"16509|AWS"
"15169|Google_Cloud"
"8075|Azure"
"20473|Vultr"
"31898|Oracle"
)
CACHE_DIR="/var/lib/seedbox-firewall"
export PATH="/usr/sbin:/sbin:$PATH"
set -o pipefail
check_v6() {
if [ -f /proc/net/if_inet6 ] && [ "$(wc -l < /proc/net/if_inet6)" -gt 0 ]; then
command -v ip6tables &>/dev/null && echo 1 || echo 0
else
echo 0
fi
}
check_deps() {
local missing=()
for cmd in curl awk python3 ipset whois sha256sum; do
command -v "$cmd" &>/dev/null || missing+=("$cmd")
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "Missing: ${missing[*]}. Run: sudo apt install -y curl gawk python3 ipset iproute2 whois"
exit 1
fi
}
init_cache() {
mkdir -p "$CACHE_DIR" 2>/dev/null
}
get_cached_hash() {
[ -f "${CACHE_DIR}/${1}.sha256" ] && cat "${CACHE_DIR}/${1}.sha256" || echo ""
}
set_cached_hash() {
echo "$2" > "${CACHE_DIR}/${1}.sha256"
}
is_changed() {
if [ ! -s "$2" ]; then return 0; fi
local new_hash=$(sha256sum "$2" | awk '{print $1}')
local old_hash=$(get_cached_hash "$1")
[ "$new_hash" != "$old_hash" ]
}
update_cache() {
local new_hash=$(sha256sum "$2" | awk '{print $1}')
set_cached_hash "$1" "$new_hash"
}
update_asn() {
local work="/tmp/asn-update-$$"
mkdir -p "$work"
local v4_cidr="${CACHE_DIR}/asn_v4.cidr"
local v6_cidr="${CACHE_DIR}/asn_v6.cidr"
local v4_tmp="${work}/asn_v4.cidr"
local v6_tmp="${work}/asn_v6.cidr"
true > "$v4_tmp"
[ "$HAS_V6" = "1" ] && true > "$v6_tmp"
local v4_total=0 v6_total=0
for ITEM in "${ASNS[@]}"; do
ASN="${ITEM%%|*}"
local whois_out="${work}/whois_${ASN}.txt"
if ! timeout 20 whois -h whois.radb.net -- "-i origin AS${ASN}" > "$whois_out" 2>/dev/null; then
rm -f "$whois_out"
continue
fi
local v4_count=$(awk '/^route:/ {print $2}' "$whois_out" | grep -v '^$' | tee -a "$v4_tmp" | wc -l)
v4_total=$((v4_total + v4_count))
if [ "$HAS_V6" = "1" ]; then
local v6_count=$(awk '/^route6:/ {print $2}' "$whois_out" | grep -v '^$' | tee -a "$v6_tmp" | wc -l)
v6_total=$((v6_total + v6_count))
fi
rm -f "$whois_out"
done
# 增量比对,利用 ipset swap 无缝替换
if [ "$v4_total" -ge 50 ] && is_changed "asn_v4" "$v4_tmp"; then
local new_set="bad_asn_v4_new"
ipset create "$new_set" hash:net family inet hashsize 4096 maxelem 500000 -exist 2>/dev/null
{
echo "create $new_set hash:net family inet hashsize 4096 maxelem 500000 -exist"
awk '{print "add '"$new_set"' " $0}' "$v4_tmp"
} | ipset restore -exist 2>/dev/null
ipset swap "$new_set" bad_asn_v4
ipset destroy "$new_set" 2>/dev/null
cp "$v4_tmp" "$v4_cidr"
update_cache "asn_v4" "$v4_tmp"
fi
if [ "$HAS_V6" = "1" ] && [ "$v6_total" -ge 50 ] && is_changed "asn_v6" "$v6_tmp"; then
local new_set="bad_asn_v6_new"
ipset create "$new_set" hash:net family inet6 hashsize 4096 maxelem 500000 -exist 2>/dev/null
{
echo "create $new_set hash:net family inet6 hashsize 4096 maxelem 500000 -exist"
awk '{print "add '"$new_set"' " $0}' "$v6_tmp"
} | ipset restore -exist 2>/dev/null
ipset swap "$new_set" bad_asn_v6
ipset destroy "$new_set" 2>/dev/null
cp "$v6_tmp" "$v6_cidr"
update_cache "asn_v6" "$v6_tmp"
fi
rm -rf "$work"
}
main() {
if [ "$(id -u)" != "0" ]; then exit 1; fi
HAS_V6=$(check_v6)
check_deps
init_cache
update_asn
}
main "$@"

赋予权限:

Terminal window
sudo chmod +x /usr/local/bin/update_blacklists.sh

3. 首次加载与定时同步配置#

手动运行脚本拉取数据并初始化 ipset 集合:

Terminal window
sudo /usr/local/bin/update_blacklists.sh

添加 crontab 任务,设置每周日凌晨 4 点进行规则库同步:

Terminal window
sudo crontab -e
# 写入以下配置
0 4 * * 0 /usr/local/bin/update_blacklists.sh > /var/log/blacklist-update.log 2>&1

执行以下命令查看拦截链状态:

Terminal window
sudo iptables -L INPUT -v -n --line-numbers

在 INPUT 规则链中应存在 match-set bad_asn_v4 src DROP 相关的全局阻断项(位列 SSH 与 Web 端口放行规则之前)。

NOTE

iptables 按自上而下的顺序处理规则,由于黑名单 DROP 规则位于开放端口 ACCEPT 规则之前,来自黑名单 ASN 的流量会在前半部分被直接丢弃,非黑名单流量才能匹配到后面的端口放行规则。

Rokutosei
Rokutosei ✦ 六等星
“君と集まって星座になれたら”
使用 iptables + ipset 屏蔽主流云厂商 ASN 与恶意扫描流量
https://blog.rokutosei.com/posts/linux/block_asn_using_iptables_and_ipset/
作者
Rokutosei
发布于
2026-07-13
许可协议
CC BY-NC-SA 4.0
✧ 文章目录 ✦
1
1. 防火墙初始化部署脚本
2
2. 动态黑名单拉取更新脚本
3
3. 首次加载与定时同步配置