improve: add 6-method fallback for server IP detection

- Method 1: \['SERVER_ADDR'] from Apache/Nginx (most reliable)
- Method 2: /sbin/ip (absolute path, works if ip not in PATH)
- Method 3: Parse /proc/net/route for default gateway interface + IP
- Method 4: hostname -I (space-separated IPs)
- Method 5: ifconfig (fallback for older systems)
- Method 6: Socket connection to 8.8.8.8 (pure PHP, no shell commands)

Each method validates IP and skips loopback (127.x, ::1, ::ffff:127.x)
Fixes 'Unavailable' when shell commands fail or aren't available
This commit is contained in:
DevSoftTech Server 2026-08-04 02:47:36 +05:30
parent 1ade027b00
commit 998eb7f2bf

View File

@ -430,38 +430,125 @@ class SystemOperations
private function readNetworkStatus(): string
{
// Primary: get first non-loopback IPv4 from Linux ip command (scope global)
$result = $this->safeCommand("ip -4 addr show scope global 2>/dev/null | grep -oP 'inet \K[0-9.]+' | head -1");
if ($result !== 'Unavailable' && trim($result) !== '' && filter_var(trim($result), FILTER_VALIDATE_IP)) {
return trim($result);
// Method 1: $_SERVER['SERVER_ADDR'] — set by Apache/Nginx when available
if (!empty($_SERVER['SERVER_ADDR'])) {
$ip = $_SERVER['SERVER_ADDR'];
if (filter_var($ip, FILTER_VALIDATE_IP) && !$this->isLoopbackIp($ip)) {
return $ip;
}
}
// Fallback: hostname -I and take first non-loopback IP
$result = $this->safeCommand('hostname -I 2>/dev/null');
// Method 2: Try /sbin/ip with absolute path (may not be in PATH)
$result = $this->safeCommand('/sbin/ip -4 addr show scope global 2>/dev/null | grep -oP "inet \K[0-9.]+"');
if ($result !== 'Unavailable' && trim($result) !== '') {
$ips = preg_split('/\s+/', trim($result));
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP) && !$this->isLoopbackIp($ip)) {
return $ip;
}
}
}
// Last resort: try gethostbyname but filter out loopback
$hostname = gethostname();
if ($hostname !== false) {
$ip = gethostbyname($hostname);
if ($ip !== $hostname && filter_var($ip, FILTER_VALIDATE_IP) && !$this->isLoopbackIp($ip)) {
return $ip;
// Method 3: Parse /proc/net/route to get gateway interface and its IP
if (file_exists('/proc/net/route')) {
$defaultIp = $this->getIpFromProcRoute();
if ($defaultIp && filter_var($defaultIp, FILTER_VALIDATE_IP) && !$this->isLoopbackIp($defaultIp)) {
return $defaultIp;
}
}
// Method 4: hostname -I (simple space-separated list)
$result = $this->safeCommand('hostname -I');
if ($result !== 'Unavailable' && trim($result) !== '') {
$ips = preg_split('/\s+/', trim($result));
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP) && !$this->isLoopbackIp($ip)) {
return $ip;
}
}
}
// Method 5: Try ifconfig output (older systems)
$result = $this->safeCommand('ifconfig 2>/dev/null | grep -oP "inet \K[0-9.]+" | grep -v "^127\."');
if ($result !== 'Unavailable' && trim($result) !== '') {
$ips = preg_split('/\s+/', trim($result));
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
// Method 6: Socket-based approach — connect to external host to get local IP
$localIp = $this->getLocalIpViaSocket();
if ($localIp && filter_var($localIp, FILTER_VALIDATE_IP) && !$this->isLoopbackIp($localIp)) {
return $localIp;
}
return 'Unavailable';
}
private function getIpFromProcRoute(): ?string
{
$lines = file('/proc/net/route', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($lines === false) {
return null;
}
// Find line with destination 00000000 (default route)
foreach ($lines as $line) {
$parts = preg_split('/\s+/', trim($line));
if (count($parts) >= 3 && $parts[1] === '00000000') {
// parts[0] is interface name
$ifname = $parts[0];
// Get IP for this interface from /proc/net/arp or /proc/net/dev_snmp6
$ip = $this->getIpForInterface($ifname);
if ($ip) {
return $ip;
}
}
}
return null;
}
private function getIpForInterface(string $ifname): ?string
{
// Try to get IP by parsing /proc/net/dev output combined with IP resolution
$result = $this->safeCommand("ip -4 addr show dev " . escapeshellarg($ifname) . " 2>/dev/null | grep -oP 'inet \K[0-9.]+'");
if ($result !== 'Unavailable' && trim($result) !== '') {
return trim(preg_split('/\s+/', trim($result))[0] ?? null);
}
return null;
}
private function getLocalIpViaSocket(): ?string
{
try {
// Create socket and connect to public DNS to determine local IP
$sock = @fsockopen('8.8.8.8', 53, $errno, $errstr, 2);
if (!$sock) {
return null;
}
$localName = @getsockname($sock);
@fclose($sock);
if ($localName) {
// getsockname returns array like ['127.0.0.1', 53]
return is_array($localName) ? ($localName[0] ?? null) : null;
}
} catch (\Throwable $e) {
// Socket operation failed, ignore
}
return null;
}
private function isLoopbackIp(string $ip): bool
{
return strpos($ip, '127.') === 0 || $ip === '::1';
return strpos($ip, '127.') === 0 || $ip === '::1' || strpos($ip, '::ffff:127.') === 0;
}
private function readInternetStatus(): string