host || ! $radius->radius_secret) { throw ValidationException::withMessages(['radius' => 'Host dan shared secret RADIUS wajib lengkap.']); } $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); if ($socket === false) { throw ValidationException::withMessages(['radius' => 'Socket UDP untuk RADIUS tidak dapat dibuat.']); } $timeout = max(1, min((int) ($radius->timeout ?: 5), 10)); socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => $timeout, 'usec' => 0]); $identifier = random_int(0, 255); $requestAuthenticator = random_bytes(16); $request = pack('CCn', 12, $identifier, 20).$requestAuthenticator; $startedAt = hrtime(true); try { $sent = @socket_sendto( $socket, $request, strlen($request), 0, $radius->host, (int) ($radius->radius_auth_port ?: 1812), ); if ($sent === false) { throw ValidationException::withMessages(['radius' => 'Status-Server RADIUS gagal dikirim.']); } $response = ''; $from = ''; $port = 0; $received = @socket_recvfrom($socket, $response, 4096, 0, $from, $port); if ($received === false || $received < 20) { throw ValidationException::withMessages([ 'radius' => 'RADIUS tidak merespons Status-Server. Pastikan Status-Server diizinkan oleh server.', ]); } ['code' => $code, 'identifier' => $responseIdentifier, 'length' => $length] = unpack('Ccode/Cidentifier/nlength', substr($response, 0, 4)); if ($responseIdentifier !== $identifier || $length > strlen($response)) { throw ValidationException::withMessages(['radius' => 'Respons RADIUS tidak valid.']); } $attributes = substr($response, 20, $length - 20); $expected = md5(substr($response, 0, 4).$requestAuthenticator.$attributes.$radius->radius_secret, true); if (! hash_equals($expected, substr($response, 4, 16))) { throw ValidationException::withMessages(['radius' => 'Authenticator respons RADIUS tidak valid.']); } return [ 'connected' => true, 'latency_ms' => (int) round((hrtime(true) - $startedAt) / 1_000_000), 'host' => $radius->host, 'auth_port' => (int) ($radius->radius_auth_port ?: 1812), 'accounting_port' => (int) ($radius->radius_accounting_port ?: 1813), 'response_code' => $code, 'probe' => 'radius_status_server', ]; } finally { socket_close($socket); } } }