import 'dart:io'; import 'package:waylume_server/wireguard/peers.dart'; class VpnSessionService { /// Gets all peers currently on the WireGuard interface static Future>> getAllLocalPeers() async { try { final peers = >[]; // Get all peer IPs and public keys final allowedIpsResult = await Process.run('wg', ['show', 'wg0', 'allowed-ips']); if (allowedIpsResult.exitCode != 0) { return peers; } final lines = allowedIpsResult.stdout.toString().trim().split('\n'); for (final line in lines) { if (line.trim().isEmpty) continue; final parts = line.trim().split('\t'); if (parts.length >= 2) { final publicKey = parts[0]; final ipWithMask = parts[1]; final ipAddress = ipWithMask.split('/')[0]; final keepaliveInfo = await _getKeepaliveForPeer(publicKey); final peerData = _parseKeepaliveInfo(keepaliveInfo, publicKey, ipAddress); peers.add(peerData); } } return peers; } catch (e) { print('Error getting local peers: $e'); return []; } } /// Gets only inactive peers (no handshake in >150 seconds) static Future>> getInactivePeers() async { final allPeers = await getAllLocalPeers(); return allPeers.where((peer) => peer['status'] == 'DEAD').toList(); } /// Parses keepalive info string into structured data static Map _parseKeepaliveInfo(String keepaliveInfo, String publicKey, String ipAddress) { if (keepaliveInfo.startsWith('DEAD')) { // Handle regular DEAD peers (with handshake) final handshakeRegex = RegExp(r'DEAD - Last handshake: ([^(]+) \((\d+)m (\d+)s ago\)'); final handshakeMatch = handshakeRegex.firstMatch(keepaliveInfo); if (handshakeMatch != null) { final lastHandshake = handshakeMatch.group(1)?.trim(); final minutes = int.tryParse(handshakeMatch.group(2) ?? '0') ?? 0; final seconds = int.tryParse(handshakeMatch.group(3) ?? '0') ?? 0; final totalMinutes = minutes + (seconds / 60).round(); return { 'public_key': publicKey, 'ip_address': ipAddress, 'last_handshake': lastHandshake, 'minutes_since_handshake': totalMinutes, 'status': 'DEAD' }; } // Handle DEAD peers without handshake (never connected) final creationRegex = RegExp(r'DEAD - Last handshake: Never \((\d+)m (\d+)s since creation\)'); final creationMatch = creationRegex.firstMatch(keepaliveInfo); if (creationMatch != null) { final minutes = int.tryParse(creationMatch.group(1) ?? '0') ?? 0; final seconds = int.tryParse(creationMatch.group(2) ?? '0') ?? 0; final totalMinutes = minutes + (seconds / 60).round(); return { 'public_key': publicKey, 'ip_address': ipAddress, 'last_handshake': 'Never', 'minutes_since_handshake': totalMinutes, 'status': 'DEAD' }; } } else if (keepaliveInfo.startsWith('ALIVE')) { final regex = RegExp(r'ALIVE - Last handshake: ([^(]+) \((\d+)s ago\)'); final match = regex.firstMatch(keepaliveInfo); if (match != null) { final lastHandshake = match.group(1)?.trim(); final seconds = int.tryParse(match.group(2) ?? '0') ?? 0; final minutes = (seconds / 60).round(); return { 'public_key': publicKey, 'ip_address': ipAddress, 'last_handshake': lastHandshake, 'minutes_since_handshake': minutes, 'status': 'ALIVE' }; } } // Fallback for other statuses return { 'public_key': publicKey, 'ip_address': ipAddress, 'last_handshake': null, 'minutes_since_handshake': null, 'status': keepaliveInfo }; } /// Gets keepalive info for a specific peer static Future _getKeepaliveForPeer(String publicKey) async { try { final result = await Process.run('wg', ['show', 'wg0', 'dump']); if (result.exitCode != 0) { return 'Failed to get WireGuard info'; } final lines = result.stdout.toString().trim().split('\n'); // Skip first line (server info) and look for this peer for (int i = 1; i < lines.length; i++) { final line = lines[i].trim(); if (line.isEmpty) continue; final parts = line.split('\t'); if (parts.length >= 5 && parts[0] == publicKey) { final latestHandshake = parts[4]; final handshakeTime = int.tryParse(latestHandshake) ?? 0; if (handshakeTime == 0) { // Check peer creation time to see if it's stale final creationTime = await getPeerCreationTime(publicKey); if (creationTime != null) { final now = DateTime.now(); final duration = now.difference(creationTime); // Mark as DEAD if created more than 2 minutes 30 seconds ago (same threshold as handshake) if (duration.inSeconds > 150) { return 'DEAD - Last handshake: Never (${duration.inMinutes}m ${duration.inSeconds % 60}s since creation)'; } } return 'No handshake yet'; } else { final handshakeDateTime = DateTime.fromMillisecondsSinceEpoch(handshakeTime * 1000); final now = DateTime.now(); final duration = now.difference(handshakeDateTime); // Check if connection is dead (more than 2 minutes 30 seconds) if (duration.inSeconds > 150) { return 'DEAD - Last handshake: ${handshakeDateTime.toLocal()} (${duration.inMinutes}m ${duration.inSeconds % 60}s ago)'; } else { return 'ALIVE - Last handshake: ${handshakeDateTime.toLocal()} (${duration.inSeconds}s ago)'; } } } } return 'Peer not found in WireGuard'; } catch (e) { return 'Error checking keepalive: $e'; } } }