diff --git a/lib/services/vpn_session_service.dart b/lib/services/vpn_session_service.dart index 2677010..1f6db51 100644 --- a/lib/services/vpn_session_service.dart +++ b/lib/services/vpn_session_service.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:waylume_server/wireguard/peers.dart'; class VpnSessionService { @@ -47,13 +48,14 @@ class VpnSessionService { /// Parses keepalive info string into structured data static Map _parseKeepaliveInfo(String keepaliveInfo, String publicKey, String ipAddress) { if (keepaliveInfo.startsWith('DEAD')) { - final regex = RegExp(r'DEAD - Last handshake: ([^(]+) \((\d+)m (\d+)s ago\)'); - final match = regex.firstMatch(keepaliveInfo); + // Handle regular DEAD peers (with handshake) + final handshakeRegex = RegExp(r'DEAD - Last handshake: ([^(]+) \((\d+)m (\d+)s ago\)'); + final handshakeMatch = handshakeRegex.firstMatch(keepaliveInfo); - if (match != null) { - final lastHandshake = match.group(1)?.trim(); - final minutes = int.tryParse(match.group(2) ?? '0') ?? 0; - final seconds = int.tryParse(match.group(3) ?? '0') ?? 0; + 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 { @@ -64,6 +66,24 @@ class VpnSessionService { '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); @@ -115,6 +135,17 @@ class VpnSessionService { 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); diff --git a/lib/wireguard/peers.dart b/lib/wireguard/peers.dart index 18ed22a..d40f987 100644 --- a/lib/wireguard/peers.dart +++ b/lib/wireguard/peers.dart @@ -69,6 +69,9 @@ Future createPeer() async { // Add peer to WireGuard await Process.run('wg', ['set', 'wg0', 'peer', publicKey.trim(), 'allowed-ips', '$ip/32']); + // Track peer creation time + await _trackPeerCreation(publicKey.trim()); + return WireGuardPeer( privateKey: privateKey, publicKey: publicKey.trim(), @@ -83,6 +86,10 @@ Future deletePeer(String publicKey) async { print('Error removing peer: ${result.stderr}'); return false; } + + // Clean up peer creation tracking + await _cleanupPeerCreation(publicKey); + return true; } @@ -134,3 +141,56 @@ Future _getNextIP() async { } throw Exception('No available IPs'); } + +/// Tracks when a peer was created +Future _trackPeerCreation(String publicKey) async { + try { + final file = File('/tmp/peer_creation_times.json'); + Map creationTimes = {}; + + if (await file.exists()) { + final content = await file.readAsString(); + creationTimes = jsonDecode(content); + } + + creationTimes[publicKey] = DateTime.now().millisecondsSinceEpoch; + await file.writeAsString(jsonEncode(creationTimes)); + } catch (e) { + print('Error tracking peer creation: $e'); + } +} + +/// Gets the creation time for a peer +Future getPeerCreationTime(String publicKey) async { + try { + final file = File('/tmp/peer_creation_times.json'); + if (!await file.exists()) return null; + + final content = await file.readAsString(); + final creationTimes = jsonDecode(content) as Map; + + final timestamp = creationTimes[publicKey]; + if (timestamp == null) return null; + + return DateTime.fromMillisecondsSinceEpoch(timestamp); + } catch (e) { + print('Error getting peer creation time: $e'); + return null; + } +} + +/// Cleans up peer creation tracking when peer is deleted +Future _cleanupPeerCreation(String publicKey) async { + try { + final file = File('/tmp/peer_creation_times.json'); + if (!await file.exists()) return; + + final content = await file.readAsString(); + final creationTimes = jsonDecode(content) as Map; + + creationTimes.remove(publicKey); + await file.writeAsString(jsonEncode(creationTimes)); + } catch (e) { + print('Error cleaning up peer creation tracking: $e'); + } +}