171 lines
6.1 KiB
Dart
171 lines
6.1 KiB
Dart
import 'dart:io';
|
|
import 'package:waylume_server/wireguard/peers.dart';
|
|
|
|
class VpnSessionService {
|
|
|
|
/// Gets all peers currently on the WireGuard interface
|
|
static Future<List<Map<String, dynamic>>> getAllLocalPeers() async {
|
|
try {
|
|
final peers = <Map<String, dynamic>>[];
|
|
|
|
// 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<List<Map<String, dynamic>>> getInactivePeers() async {
|
|
final allPeers = await getAllLocalPeers();
|
|
return allPeers.where((peer) => peer['status'] == 'DEAD').toList();
|
|
}
|
|
|
|
/// Parses keepalive info string into structured data
|
|
static Map<String, dynamic> _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<String> _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';
|
|
}
|
|
}
|
|
} |