mirror of
https://github.com/librespeed/speedtest.git
synced 2026-05-13 16:26:38 +00:00
fix: return client IPv6 address via cloudflared (#757)
* 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.
This commit is contained in:
parent
98f447c8db
commit
f1f48ae53e
1 changed files with 48 additions and 12 deletions
|
|
@ -1,19 +1,55 @@
|
|||
<?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() {
|
||||
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
|
||||
$ip = $_SERVER['HTTP_X_REAL_IP'];
|
||||
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
$ip = preg_replace('/,.*/', '', $ip); # hosts are comma-separated, client is first
|
||||
} else {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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'] ?? '';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue