diff --git a/app/Libraries/SystemOperations.php b/app/Libraries/SystemOperations.php index a606deb..dacbf38 100644 --- a/app/Libraries/SystemOperations.php +++ b/app/Libraries/SystemOperations.php @@ -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