Files
waylume_server/lib/services/vpn_session_service.dart

140 lines
4.7 KiB
Dart

import 'dart:io';
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')) {
final regex = RegExp(r'DEAD - Last handshake: ([^(]+) \((\d+)m (\d+)s ago\)');
final match = regex.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;
final totalMinutes = minutes + (seconds / 60).round();
return {
'public_key': publicKey,
'ip_address': ipAddress,
'last_handshake': lastHandshake,
'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) {
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';
}
}
}