You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何filter_var将所有IP判定为私有/保留地址?代码问题排查

PHP IP地址有效性及私有/保留IP判断函数修复

问题描述

我编写了一个函数,用于判断通过$_SERVER["REMOTE_ADDR"]获取的客户端IP是否有效,以及是否属于私有/保留IP(如127.0.0.1、192.168.1.100等)。但无论是否开启VPN,所有IP都被PHP判定为同时属于私有和保留地址。使用环境为PHP 8.1.8(Win10系统),请问代码存在什么问题?

原代码

function ip($an_ip_address) {
    $flags = [
        "validate" => FILTER_VALIDATE_IP,
        "ipv4"     => FILTER_FLAG_IPV4,
        "ipv6"     => FILTER_FLAG_IPV6,
        "reserved" => FILTER_FLAG_NO_RES_RANGE,
        "private"  => FILTER_FLAG_NO_PRIV_RANGE,
    ];
    $info = [
        "ip"       => $an_ip_address,
        "valid"    => false,
        "type"     => "IPv",
        "reserved" => false,
        "private"  => false,
    ];

    $info["valid"] = filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]|$flags["ipv6"]) !== false;
    if (!$info["valid"]) {
        $info["type"] = null;
        return $info;
    }
    // Otherwise
    $info["type"] .= strval(filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]) !== false ? 4 : 6);

    // Now let's check if is in private range
    if (filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]|$flags["private"]) !== false) {
        // An reserved IPv4 address
        $info["private"] = true;
    } else if (filter_var($an_ip_address, $flags["validate"], $flags["ipv6"]|$flags["private"]) !== false) {
        // An reserved IPv4 address
        $info["private"] = true;
    }

    // Or in reserved range
    if (filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]|$flags["reserved"]) !== false) {
        // An reserved IPv4 address
        $info["reserved"] = true;
    } else if (filter_var($an_ip_address, $flags["validate"], $flags["ipv6"]|$flags["reserved"]) !== false) {
        // An reserved IPv4 address
        $info["reserved"] = true;
    }

    return $info;
}

// Let's check it out
foreach (["87.249.132.40","108.36.9.244","122.20.178.25"] as $ip) {
    $ip = ip($ip);
    echo json_encode($ip, JSON_PRETTY_PRINT).PHP_EOL;
}

原运行结果

{
    "ip": "87.249.132.40",
    "valid": true,
    "type": "IPv4",
    "reserved": true,
    "private": true
}

{
    "ip": "108.36.9.244",
    "valid": true,
    "type": "IPv4",
    "reserved": true,
    "private": true
}

{
    "ip": "122.20.178.25",
    "valid": true,
    "type": "IPv4",
    "reserved": true,
    "private": true
}

错误原因分析

问题出在你搞反了两个过滤标志的作用逻辑:

  • FILTER_FLAG_NO_PRIV_RANGE:传递这个标志时,filter_var会拒绝私有IP——如果输入的是私有IP,返回false;如果是公网IP,返回非false。
  • FILTER_FLAG_NO_RES_RANGE:同理,这个标志会拒绝保留IP——输入保留IP返回false,非保留IP返回非false。

你原来的代码里,把“返回非false”判定为“属于私有/保留IP”,刚好和实际逻辑相反,所以所有公网IP都被错误标记成了私有+保留地址。

修正后的代码

function ip($an_ip_address) {
    $flags = [
        "validate" => FILTER_VALIDATE_IP,
        "ipv4"     => FILTER_FLAG_IPV4,
        "ipv6"     => FILTER_FLAG_IPV6,
        "reserved" => FILTER_FLAG_NO_RES_RANGE,
        "private"  => FILTER_FLAG_NO_PRIV_RANGE,
    ];
    $info = [
        "ip"       => $an_ip_address,
        "valid"    => false,
        "type"     => "IPv",
        "reserved" => false,
        "private"  => false,
    ];

    // 验证IP有效性(支持IPv4/IPv6)
    $info["valid"] = filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]|$flags["ipv6"]) !== false;
    if (!$info["valid"]) {
        $info["type"] = null;
        return $info;
    }

    // 判断IP类型
    $info["type"] .= filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]) !== false ? '4' : '6';

    // 判断是否为私有IP:加上NO_PRIV_RANGE后返回false,说明IP是私有地址
    if (filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]|$flags["private"]) === false) {
        $info["private"] = true;
    } else if (filter_var($an_ip_address, $flags["validate"], $flags["ipv6"]|$flags["private"]) === false) {
        $info["private"] = true;
    }

    // 判断是否为保留IP:加上NO_RES_RANGE后返回false,说明IP是保留地址
    if (filter_var($an_ip_address, $flags["validate"], $flags["ipv4"]|$flags["reserved"]) === false) {
        $info["reserved"] = true;
    } else if (filter_var($an_ip_address, $flags["validate"], $flags["ipv6"]|$flags["reserved"]) === false) {
        $info["reserved"] = true;
    }

    return $info;
}

// 测试公网IP和私有IP
foreach (["87.249.132.40", "192.168.1.100", "127.0.0.1"] as $ip) {
    $result = ip($ip);
    echo json_encode($result, JSON_PRETTY_PRINT) . PHP_EOL;
}

修正后的运行结果示例

{
    "ip": "87.249.132.40",
    "valid": true,
    "type": "IPv4",
    "reserved": false,
    "private": false
}

{
    "ip": "192.168.1.100",
    "valid": true,
    "type": "IPv4",
    "reserved": false,
    "private": true
}

{
    "ip": "127.0.0.1",
    "valid": true,
    "type": "IPv4",
    "reserved": true,
    "private": true
}

内容的提问来源于Stack Exchange,提问作者Nathan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 02:20:49