一键屏蔽某国IP访问实战

在网站运营、网络安全或流量管控场景中,按国家/地区限制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使用的,所以我们直接在结构上增加allowdeny前缀,方便在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

四、注意事项

在具体的使用过程中还有一些是需要特别注意的:

  1. geo和allow的配置文件格式是不一样的。
  2. 如果使用云服务做转发,来源ip是云服务的地址,需要额外增加IP透传的功能。
  3. 远程地址的更新没那么快,可以设置每隔一周来定时更新一次。持续访问可能会被官方封禁哦。
相关推荐
Nan_Shu_6142 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#10 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界26 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路34 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug38 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213840 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子2 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端