本文介绍如何在 Ubuntu 24 系统上配置 iptables 和 ipset,实现按国家限制特定端口的访问,主要用于保护服务器关键端口(如 SSH、RCON)免受国外 IP 扫描和攻击。

Ubuntu 24 配置限制端口按国家访问

在搭建完 Minecraft 服务器后,我发现 RCON 端口频繁遭受国外 IP 的扫描攻击,SSH 端口也可能面临类似问题。本文介绍如何针对特定端口设置国家访问限制。

获取 IP 地址列表

由于大多数攻击来自国外,我们将使用中国 IP 地址作为白名单。

下载 IP 地址列表

我们使用 Loyalsoldier/geoip 项目的数据,该项目对 MaxMind 官方 GeoIP 数据进行了优化:

  • 中国大陆 IPv4 数据融合了 IPIP.net 和 @gaoyifan/china-operator-ip
  • 中国大陆 IPv6 数据融合了 MaxMind GeoLite2 和 @gaoyifan/china-operator-ip

需要下载的文件:

1
2
3
mkdir -p /etc/firewall_persist/geoip
wget -O /etc/firewall_persist/geoip/cn.txt https://github.com/Loyalsoldier/geoip/raw/release/text/cn.txt
wget -O /etc/firewall_persist/geoip/private.txt https://github.com/Loyalsoldier/geoip/raw/release/text/private.txt

分离 IPv4 和 IPv6 地址

创建脚本 /usr/local/bin/split_ipv4_ipv6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash

# 日志文件路径
log_file="/var/log/split_ipv4_ipv6.log"

# 检查是否提供了输入文件
if [ -z "$1" ]; then
echo "$(date): Error: No input file provided." >> "$log_file"
exit 1
fi

input_file="$1"
base_name=$(basename "$input_file" .txt)
ipv4_file="${base_name}_ipv4.txt"
ipv6_file="${base_name}_ipv6.txt"

# 清空输出文件
> "$ipv4_file"
> "$ipv6_file"

# 逐行读取输入文件
while read -r line; do
if [[ $line =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$ ]]; then
echo "$line" >> "$ipv4_file"
elif [[ $line =~ ^[0-9a-fA-F:]+(/[0-9]+)?$ ]]; then
echo "$line" >> "$ipv6_file"
else
echo "$(date): Invalid line: $line" >> "$log_file"
fi
done < "$input_file"

echo "$(date): IPv4 addresses saved to $ipv4_file" >> "$log_file"
echo "$(date): IPv6 addresses saved to $ipv6_file" >> "$log_file"

设置执行权限并运行:

1
2
3
4
chmod +x /usr/local/bin/split_ipv4_ipv6
cd /etc/firewall_persist/geoip
split_ipv4_ipv6 /etc/firewall_persist/geoip/cn.txt
split_ipv4_ipv6 /etc/firewall_persist/geoip/private.txt

配置 ipset

安装 ipset:

1
apt install ipset

创建 IP 地址集合:

1
2
3
4
5
6
7
# 创建 IPv4 集合
ipset create cn_ip_v4 hash:net family inet
ipset create private_ip_v4 hash:net family inet

# 创建 IPv6 集合
ipset create cn_ip_v6 hash:net family inet6
ipset create private_ip_v6 hash:net family inet6

将 IP 地址添加到集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while read -r cidr; do
ipset add cn_ip_v4 "$cidr"
done < /etc/firewall_persist/geoip/cn_ipv4.txt

while read -r cidr; do
ipset add private_ip_v4 "$cidr"
done < /etc/firewall_persist/geoip/private_ipv4.txt

while read -r cidr; do
ipset add cn_ip_v6 "$cidr"
done < /etc/firewall_persist/geoip/cn_ipv6.txt

while read -r cidr; do
ipset add private_ip_v6 "$cidr"
done < /etc/firewall_persist/geoip/private_ipv6.txt

创建防火墙规则

以 SSH 端口(22)为例:

1
2
3
4
5
6
iptables -A INPUT -p tcp --dport 22 -m set --match-set private_ip_v4 src -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -m set --match-set private_ip_v6 src -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m set --match-set cn_ip_v4 src -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -m set --match-set cn_ip_v6 src -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
ip6tables -A INPUT -p tcp --dport 22 -j DROP

规则持久化

创建持久化脚本 /usr/local/sbin/firewall_persist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/bin/bash

# 定义保存文件路径
IPTABLES_SAVE_FILE="/etc/firewall_persist/iptables_rules.v4"
IP6TABLES_SAVE_FILE="/etc/firewall_persist/iptables_rules.v6"
IPSET_SAVE_FILE="/etc/firewall_persist/ipset.conf"

# 检查是否以root权限运行
if [ "$EUID" -ne 0 ]; then
echo "请以root权限运行此脚本"
exit 1
fi

# 创建保存目录(如果不存在)
mkdir -p /etc/firewall_persist

# 函数:保存规则
save_rules() {
echo "正在保存防火墙规则..."

# 保存iptables规则(IPv4)
iptables-save > "$IPTABLES_SAVE_FILE"
if [ $? -eq 0 ]; then
echo "IPv4规则已保存到 $IPTABLES_SAVE_FILE"
else
echo "保存IPv4规则失败"
fi

# 保存ip6tables规则(IPv6)
ip6tables-save > "$IP6TABLES_SAVE_FILE"
if [ $? -eq 0 ]; then
echo "IPv6规则已保存到 $IP6TABLES_SAVE_FILE"
else
echo "保存IPv6规则失败"
fi

# 保存ipset集合
ipset save > "$IPSET_SAVE_FILE"
if [ $? -eq 0 ]; then
echo "ipset规则已保存到 $IPSET_SAVE_FILE"
else
echo "保存ipset规则失败"
fi
}

# 函数:加载规则
load_rules() {
echo "正在加载防火墙规则..."

# 加载ipset集合
if [ -f "$IPSET_SAVE_FILE" ]; then
ipset restore < "$IPSET_SAVE_FILE"
echo "已加载ipset规则"
else
echo "ipset规则文件不存在"
fi

# 加载iptables规则(IPv4)
if [ -f "$IPTABLES_SAVE_FILE" ]; then
iptables-restore < "$IPTABLES_SAVE_FILE"
echo "已加载IPv4规则"
else
echo "IPv4规则文件不存在"
fi

# 加载ip6tables规则(IPv6)
if [ -f "$IP6TABLES_SAVE_FILE" ]; then
ip6tables-restore < "$IP6TABLES_SAVE_FILE"
echo "已加载IPv6规则"
else
echo "IPv6规则文件不存在"
fi
}

# 主逻辑
case "$1" in
"save")
save_rules
;;
"load")
load_rules
;;
*)
echo "用法: $0 {save|load}"
echo "save - 保存当前iptables和ipset规则"
echo "load - 加载保存的iptables和ipset规则"
exit 1
;;
esac

exit 0

设置权限并保存当前规则:

1
2
chmod +x /usr/local/sbin/firewall_persist
firewall_persist save

设置开机自启

创建服务文件:

1
nano /etc/systemd/system/firewall-restore.service

写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Restore iptables and ipset rules
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/firewall_persist load
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

启用服务:

1
systemctl enable firewall-restore.service