在网站运营、网络安全或流量管控场景中,按国家/地区限制IP访问(如仅允许国内IP访问、屏蔽特定国家IP)是常见需求。实现这一需求的核心步骤是:获取目标国家的IP段 → 将IP段配置到 Nginx 中生效。本文将详细讲解如何通过Node.js或Shell脚本获取国家IP段,并结合 Nginx 的geo模块完成配置。
一、选择 IP 数据源
获取国家 IP 段的前提是选择可靠的数据源,目前主流免费/商用数据源包括:
1. MaxMind GeoLite2
MaxMind 是全球知名的 IP 地理信息服务商,提供免费的 GeoLite2 数据库(需注册账号),包含 IPv4/IPv6 的国家 / 地区映射,数据精度高且更新及时(每月更新)。
2. apnic
apnic提供免费可直接获取的IP信息,直接下载就能获取最新的IP数据信息。
3. 其他
- ip2region:国内开源的 IP 定位库,支持国家 / 城市级映射,数据文件小(约 10MB),查询速度快。
- 17monip(纯真 IP):国内常用的 IP 库,需定期下载更新包。
- IP2Location LITE:类似 GeoLite2 的免费数据库,提供 CSV/MMDB 格式。
本文以apnic为例讲解(兼容性和易用性最优)。
二、获取国家 IP 段的方法
方法 1:使用bash脚本获取并处理
这种方式适合linux系统下的简单使用,兼容centos、ubuntu等系统。
步骤一:获取数据
可以使用wget或者curl这种命令直接从远程下载脚本,脚本地址取最新数据http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest,脚本内容类似:
bash
wget -c -O /tmp http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
curl -C - -o /tmp http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
步骤二:处理数据
官方提供的数据通常都是txt格式的,逐行进行排列的数据。我们要使用还需要对数据进行解析,把我们需要的数据单独获取出来。可以使用脚本的awk命令进行处理。
awk的命令格式:
bash
awk [参数] [处理内容] [操作对象]
完整脚本
我们假设要把数据处理成"起始IP|数量/前缀长度|注册机构|国家代码"的格式,按照这个逻辑,再加上错误处理,我们输出以下脚本:
bash
#!/bin/bash
set -eu # 移除 pipefail,兼容低版本 Bash,保留关键错误检查
# 脚本配置
URL="http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest"
DOWNLOAD_DIR="/root/apnic"
DOWNLOAD_FILENAME="delegated-apnic-latest"
IPV4_OUTPUT_FILENAME="apnic_ipv4.txt"
IPV6_OUTPUT_FILENAME="apnic_ipv6.txt"
# 帮助信息
show_help() {
echo "用法:$0 --download-dir <下载目录> [--temp-dir <临时目录>]"
echo "选项:"
echo " --download-dir 可选,文件下载保存目录"
echo " -h/--help 显示帮助信息"
}
# 解析命令行参数
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--download-dir)
DOWNLOAD_DIR="$2"
shift 2
;;
-h|--help)
show_help
exit 0
;;
*)
echo "错误:未知参数 $1"
show_help
exit 1
;;
esac
done
}
# 检查依赖工具(wget 或 curl)
check_dependencies() {
if command -v wget &> /dev/null; then
DOWNLOAD_TOOL="wget"
elif command -v curl &> /dev/null; then
DOWNLOAD_TOOL="curl"
else
echo "错误:未找到 wget 或 curl,请先安装其中一个工具"
exit 1
fi
}
# 下载文件 + 校验文件有效性
download_file() {
local download_path="${DOWNLOAD_DIR}/${DOWNLOAD_FILENAME}"
echo "=== 开始下载文件 ==="
echo "URL: $URL"
echo "保存路径: $download_path"
# 检查下载目录是否存在
if [[ ! -d "$DOWNLOAD_DIR" ]]; then
mkdir -p "$DOWNLOAD_DIR" || {
echo "错误:无法创建保持目录 $DOWNLOAD_DIR"
exit 1
}
fi
# 下载文件(支持断点续传)
if [[ "$DOWNLOAD_TOOL" == "wget" ]]; then
wget -c -O "$download_path" "$URL" || {
echo "错误:wget 下载失败(可能是网络问题或服务器异常)"
exit 1
}
else
curl -C - -o "$download_path" "$URL" || {
echo "错误:curl 下载失败(可能是网络问题或服务器异常)"
exit 1
}
fi
# 校验下载文件是否为空
if [[ ! -s "$download_path" ]]; then
echo "错误:下载的文件为空,请检查 URL 或网络连接"
rm -f "$download_path" # 删除空文件
exit 1
fi
echo "=== 文件下载完成(大小:$(du -sh "$download_path" | awk '{print $1}'))==="
echo "下载文件路径: $download_path"
echo
return 0
}
# 解析并筛选 IPv4/IPv6(容错增强)
parse_and_filter_ip() {
local download_path="${DOWNLOAD_DIR}/${DOWNLOAD_FILENAME}"
local ipv4_output="${DOWNLOAD_DIR}/${IPV4_OUTPUT_FILENAME}"
local ipv6_output="${DOWNLOAD_DIR}/${IPV6_OUTPUT_FILENAME}"
echo "=== 开始解析文件 ==="
echo "源文件: $download_path"
echo "解析目录: $DOWNLOAD_DIR"
# 清空输出文件(避免残留旧数据)
> "$ipv4_output"
> "$ipv6_output"
# 筛选规则:
# 1. 跳过注释行(以 # 开头)和空行
# 2. 第 3 列(type)为 ipv4/ipv6 的行
# 3. 输出格式:起始IP|数量/前缀长度|注册机构|国家代码(字段 4|5|1|2)
# 用 awk 处理,即使部分行格式异常也不中断
awk -F '|' '
!/^#/ && NF >= 6 { # 只处理非注释行且字段数≥6的行
if ($3 == "ipv4") {
print $4 "|" $5 "|" $1 "|" $2 >> "'"$ipv4_output"'"
} else if ($3 == "ipv6") {
print $4 "|" $5 "|" $1 "|" $2 >> "'"$ipv6_output"'"
}
}
' "$download_path" || {
echo "警告:部分行格式异常,已跳过"
}
# 检查筛选结果(允许其中一种IP类型为空,避免误判)
if [[ -s "$ipv4_output" ]]; then
echo "IPv4 筛选完成,记录数:$(wc -l < "$ipv4_output") 行"
else
echo "警告:未筛选到 IPv4 数据(可能文件格式变更或无相关记录)"
fi
if [[ -s "$ipv6_output" ]]; then
echo "IPv6 筛选完成,记录数:$(wc -l < "$ipv6_output") 行"
else
echo "警告:未筛选到 IPv6 数据(可能文件格式变更或无相关记录)"
fi
echo "=== 解析筛选完成 ==="
echo "IPv4 文件路径: $ipv4_output"
echo "IPv6 文件路径: $ipv6_output"
echo
return 0
}
# 主流程
main() {
parse_args "$@"
check_dependencies
download_file
parse_and_filter_ip
echo "=== 任务全部完成 ==="
}
# 启动脚本
main "$@"
脚本首先设置默认变量,也就是默认的存储位置是/root/apnic,然后再把最新数据下载到文件里,通过命令筛选我们需要的数据分别存到对应的文件中。
方法 2:Node.js处理数据
使用nodejs整体流程设计上就不需要临时存储文件了,我们直接每次获取之后在内存中就全部处理完成。整体处理如下:
js
const axios = require('axios');
// 要允许通过的国家
const ALLowList = ['CN', 'HK', 'MO', 'TW'];
// 获取远程数据
async function getApnicTxt() {
const res = await axios.get('http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest');
return res.data;
}
// apnic|BD|ipv4|103.132.248.0|1024|20190116|allocated
// apnic|ID|ipv6|2001:df0:f180::|48|20190718|assigned
function createIpRecord(line, dot = '24') {
const parts = line.split('|');
if (ALLowList.includes(parts[1])) {
return `allow ${parts[3]}/${dot}; #${parts[1]}`;
}
return `deny ${parts[3]}/${dot}; #${parts[1]}`;
}
async function main() {
const txts = await getApnicTxt();
const list = txts.split('\n');
const ipv4list = [];
const ipv6list = [];
for (let index = 0; index < list.length; index++) {
const txt = list[index];
if (txt.includes('|ipv4|')) {
ipv4list.push(createIpRecord(txt));
}
if (txt.includes('|ipv6|')) {
ipv6list.push(createIpRecord(txt, '32'));
}
}
console.log('ipv4list', ipv4list);
console.log('ipv4list', ipv6list);
}
main();
脚本中同样是先获取最新的数据,然后直接交给处理函数进行分批处理。这里我们假设处理的结果是给nginx使用的,所以我们直接在结构上增加allow和deny前缀,方便在nginx中直接使用。
三、配置nginx文件
方法1:使用allow进行管理
这种配置方式兼容新老版本的nginx,只需要在nginx中提前引入配置文件,每次更新配置文件内容之后重启nginx就能达到自动允许和拒绝来源国家的访问地址。
nginx
server {
listen 80;
listen [::]:80;
server_name your-domain.com;
# 引入允许的IP白名单
include /etc/nginx/conf.d/china-ipv4.conf;
include /etc/nginx/conf.d/china-ipv6.conf;
# 可选:放行本地回环(避免自己被拦)
allow 127.0.0.1;
allow ::1;
# 拒绝所有未匹配的请求
deny all;
location / {
root /var/www/html;
index index.html;
# 你的其他配置...
}
}
通过以上配置就可以达到只允许我们需要的访问,其他国家访问一律禁止。
方法2:使用geo模块
Nginx 通过geo模块(默认编译)实现 IP 段的快速匹配,该模块将 IP 段加载到内存中,查询效率极高(不受 IP 段数量影响)。
nginx
http {
# 定义IPv4的国家匹配变量($is_cn_v4:1=中国IP,0=非中国IP)
geo $is_cn_v4 {
default 0;
include /etc/nginx/conf.d/cn-ipv4.conf; # 引入中国IPv4段文件
}
# 定义IPv6的国家匹配变量(如需支持IPv6)
geo $is_cn_v6 {
default 0;
include /etc/nginx/conf.d/cn-ipv6.conf; # 引入中国IPv6段文件
}
# 合并IPv4/IPv6的匹配结果
map $scheme $is_cn {
http $is_cn_v4;
https $is_cn_v4;
httpv6 $is_cn_v6;
httpsv6 $is_cn_v6;
}
server {
listen 80;
listen [::]:80;
server_name yourdomain.com;
# 非中国IP返回403
if ($is_cn != 1) {
return 403;
}
# 正常业务配置...
location / {
root /var/www/html;
index index.html;
}
}
}
通过使用nginx的geo模块可以非常快速的识别到ip对应的国家,然后在具体的路由中通过自动一判断来返回自己想要返回的内容。同样的,配置文件更新之后要重新启动nginx服务。
其他命令
检查nginx配置文件是否正确。
nginx -t
重新启动nginx。
systemctl restart nginx
严重IP匹配的效果。
bash
curl -H "X-Forwarded-For: 8.8.8.8" http://yourdomain.com # 8.8.8.8为美国IP
四、注意事项
在具体的使用过程中还有一些是需要特别注意的:
- geo和allow的配置文件格式是不一样的。
- 如果使用云服务做转发,来源ip是云服务的地址,需要额外增加IP透传的功能。
- 远程地址的更新没那么快,可以设置每隔一周来定时更新一次。持续访问可能会被官方封禁哦。