mirror of
https://github.com/librespeed/speedtest.git
synced 2026-05-13 08:16:36 +00:00
* fix: return client IPv6 address via cloudflared The cloudflared reverse proxy populates the X-Forwarded-For header for origin IPv4 addresses, however origin IPv6 addresses are added in a different header: Cf-Connecting-Ipv6. This updates the getIP.php mechanism to retrieve the value of this header and to prefer it over other client IP headers (in both cases only if the Cf-Connecting-Ipv6 header exists and is not empty). * fix: Validate and normalise IP addresses from request headers getClientIp() used HTTP_CF_CONNECTING_IPV6 and other headers verbatim, allowing malformed values to reach ISP lookups and the offline DB. Add normalizeCandidateIp() helper that trims whitespace, extracts the first comma-separated token, and validates via filter_var(). Require FILTER_FLAG_IPV6 for the CF header and fall through to the next source on failure. Written with assistance from OpenCode using Claude Opus 4.6.
55 lines
1.8 KiB
PHP
Executable file
55 lines
1.8 KiB
PHP
Executable file
<?php
|
|
|
|
/**
|
|
* Normalize and validate an IP address candidate from a request header.
|
|
*
|
|
* Trims whitespace, takes the first comma-separated token (for XFF-like
|
|
* headers that may contain a chain of addresses), and validates the result
|
|
* with filter_var().
|
|
*
|
|
* @param string $raw Raw header value.
|
|
* @param int $extraFlags Additional FILTER_FLAG_* flags (e.g. FILTER_FLAG_IPV6).
|
|
*
|
|
* @return string|false The validated IP string, or false on failure.
|
|
*/
|
|
function normalizeCandidateIp($raw, $extraFlags = 0)
|
|
{
|
|
$ip = trim($raw);
|
|
// For XFF-like values, take the first address before a comma.
|
|
if (($pos = strpos($ip, ',')) !== false) {
|
|
$ip = trim(substr($ip, 0, $pos));
|
|
}
|
|
if ($ip === '') {
|
|
return false;
|
|
}
|
|
return filter_var($ip, FILTER_VALIDATE_IP, $extraFlags);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
function getClientIp()
|
|
{
|
|
// Cloudflare IPv6 header — must be a valid IPv6 address.
|
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IPV6'])) {
|
|
$ip = normalizeCandidateIp($_SERVER['HTTP_CF_CONNECTING_IPV6'], FILTER_FLAG_IPV6);
|
|
if ($ip !== false) {
|
|
return preg_replace('/^::ffff:/', '', $ip);
|
|
}
|
|
}
|
|
// Other forwarding / proxy headers — accept any valid IP.
|
|
foreach (['HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
|
|
if (!empty($_SERVER[$header])) {
|
|
$ip = normalizeCandidateIp($_SERVER[$header]);
|
|
if ($ip !== false) {
|
|
return preg_replace('/^::ffff:/', '', $ip);
|
|
}
|
|
}
|
|
}
|
|
// Fallback: REMOTE_ADDR is set by the web server and is always a single IP.
|
|
$ip = normalizeCandidateIp($_SERVER['REMOTE_ADDR'] ?? '');
|
|
if ($ip !== false) {
|
|
return preg_replace('/^::ffff:/', '', $ip);
|
|
}
|
|
return $_SERVER['REMOTE_ADDR'] ?? '';
|
|
}
|