应使用ip2long转整型后按rfc标准位运算校验:先filter_var验证ipv4格式,再排除127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10及0.0.0.0和255.255.255.255。
PHP 获取本机 IP 时返回 127.0.0.1 或私有段,怎么判断它不合法?
PHP 中用 $_SERVER['SERVER_ADDR'] 或 gethostbyname(gethostname()) 拿到的“本机 IP”,很可能只是回环地址或内网地址(如 127.0.0.1、192.168.x.x、10.x.x.x、172.16.x.x–172.31.x.x),不能直接用于外网通信或日志标记。关键不是“获取失败”,而是“获取到了但不可用”。
校验逻辑必须分两步:先确认是否为 IPv4 地址格式,再排除所有 RFC 1918 / RFC 5735 定义的保留段。
-
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)是基础门槛,过滤掉空值、IPv6、格式错误 - 接着用
ip2long($ip)转整型后做位运算比字符串匹配更可靠(避免strpos误判192.168.10.1匹配到192.168就截断) - 特别注意
0.0.0.0和255.255.255.255也属于非法主机地址,需单独排除
用 ip2long 做私有网段精确拦截(推荐写法)
字符串前缀匹配易出错,比如 stripos($ip, '10.') === 0 会漏掉 10.0.0.1 以外的变体,也挡不住 100.64.0.0/10(CGNAT 段)。用整型比较最稳:
function isValidPublicIp($ip) {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return false;
}
$ipLong = ip2long($ip);
if ($ipLong === false || $ipLong === -1) return false;
// 127.0.0.0/8
if (($ipLong & 0xFF000000) === 0x7F000000) return false;
// 10.0.0.0/8
if (($ipLong & 0xFF000000) === 0x0A000000) return false;
// 172.16.0.0/12
if (($ipLong & 0xFFF00000) === 0xAC100000) return false;
// 192.168.0.0/16
if (($ipLong & 0xFFFF0000) === 0xC0A80000) return false;
// 100.64.0.0/10(CGNAT)
if (($ipLong & 0xFFC00000) === 0x64400000) return false;
// 0.0.0.0/8 和 255.255.255.255
if ($ipLong === 0 || $ipLong === 0xFFFFFFFF) return false;
return true;
}
$_SERVER[‘SERVER_ADDR’] 为什么经常不准?
这个值取决于 Web 服务器(Apache/Nginx)绑定的监听地址,不是系统真实网卡 IP。常见陷阱: