"1.2.3.4" / "::1" */ public static function host(string $peer): string { if (preg_match('/^\[([^\]]+)\]:\d+$/', $peer, $m)) { return $m[1]; } return preg_match('/^([^:]+):\d+$/', $peer, $m) ? $m[1] : $peer; } /** @param array $headers lower-case names */ public static function resolve(string $peer, array $headers): string { $ip = self::host($peer); if (!self::isPrivate($ip)) { return $ip; } // Traefik replaces X-Forwarded-For with the address it saw (it trusts no incoming X-Forwarded-* by default) $forwarded = array_map('trim', explode(',', $headers['x-forwarded-for'] ?? '')); $seen = (string)end($forwarded); if ($seen === '' || filter_var($seen, FILTER_VALIDATE_IP) === false) { $seen = trim($headers['x-real-ip'] ?? ''); } if (filter_var($seen, FILTER_VALIDATE_IP) === false) { return $ip; } $cf = trim($headers['cf-connecting-ip'] ?? ''); if ($cf !== '' && filter_var($cf, FILTER_VALIDATE_IP) !== false && self::inAny($seen, self::CLOUDFLARE)) { return $cf; } return $seen; } public static function isPrivate(string $ip): bool { return filter_var($ip, FILTER_VALIDATE_IP) !== false && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false; } /** @param list $ranges */ public static function inAny(string $ip, array $ranges): bool { $bin = @inet_pton($ip); if ($bin === false) { return false; } foreach ($ranges as $range) { [$net, $bits] = array_pad(explode('/', $range, 2), 2, null); $netBin = @inet_pton((string)$net); if ($netBin === false || strlen($netBin) !== strlen($bin)) { continue; } $bits = $bits === null ? strlen($bin) * 8 : (int)$bits; $bytes = intdiv($bits, 8); if (substr($bin, 0, $bytes) !== substr($netBin, 0, $bytes)) { continue; } $rest = $bits % 8; if ($rest === 0 || ((ord($bin[$bytes]) ^ ord($netBin[$bytes])) & (0xff << (8 - $rest)) & 0xff) === 0) { return true; } } return false; } }