一键屏蔽某国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. 远程地址的更新没那么快,可以设置每隔一周来定时更新一次。持续访问可能会被官方封禁哦。
相关推荐
weixin79893765432...30 分钟前
使用 node.js 的心得
node.js
fruge36 分钟前
前端自动化脚本:用 Node.js 写批量处理工具(图片压缩、文件重命名)
前端·node.js·自动化
Jolyne_1 小时前
antd Image base64缓存 + loading 态优化方案
前端
BINGCHN1 小时前
NSSCTF每日一练 SWPUCTF2021 include--web
android·前端·android studio
O***p6041 小时前
JavaScript在Node.js中的集群负载均衡
javascript·node.js·负载均衡
Z***u6591 小时前
前端性能测试实践
前端
xhxxx2 小时前
prototype 是遗产,proto 是族谱:一文吃透 JS 原型链
前端·javascript
倾墨2 小时前
Bytebot源码学习
前端
用户93816912553602 小时前
VUE3项目--集成Sass
前端