From 2d692e0bc0a0b2c4cce7bba3c67deda950d4138f Mon Sep 17 00:00:00 2001 From: ImBenji Date: Tue, 5 Aug 2025 13:46:13 +0100 Subject: [PATCH] Rename speedKbps and dataCapMB parameters to bytesPerSecond and quotaBytes, respectively, in API and traffic control logic --- README.md | 73 +++++++++++++++++++++++++++--- lib/test_scripts/test_api.dart | 8 ++-- lib/web/peer_routes.dart | 16 +++---- lib/wireguard/traffic_control.dart | 57 ++++++++++++++++++----- 4 files changed, 123 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7e9bd1f..db209aa 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,70 @@ Client applications communicate through Supabase to discover available Waylume s - **Health Monitoring**: Continuous heartbeat system for server availability ### API Endpoints -- `POST /api/peers` - Create new VPN peer with generated keys and configuration -- `POST /api/peers/delete` - Remove VPN peer (publicKey in request body) -- `POST /api/peers/speed-limit` - Set bandwidth limits for peer (publicKey + speedKbps in request body) -- `POST /api/peers/data-cap` - Set data usage limits for peer (publicKey + dataCapMB in request body) -- `POST /api/peers/config` - Retrieve peer configuration (publicKey in request body, not implemented) + +#### `POST /api/peers` +Create a new VPN peer with automatically generated WireGuard keys and IP assignment. + +**Request:** No body required +**Response:** +```json +{ + "success": true, + "peer": { + "privateKey": "base64_private_key", + "publicKey": "base64_public_key", + "ip": "10.0.0.x" + }, + "config": "complete_wireguard_client_config" +} +``` + +#### `POST /api/peers/delete` +Remove a VPN peer and clean up associated WireGuard configuration. + +**Request Body:** +```json +{ + "publicKey": "peer_public_key_here" +} +``` + +#### `POST /api/peers/speed-limit` +Set bidirectional bandwidth limits for a peer (controls both upload and download speeds). + +**Request Body:** +```json +{ + "publicKey": "peer_public_key_here", + "bytesPerSecond": 125000 +} +``` +*Note: 125000 bytes/s = 1000 kbps = 1 Mbps* + +**Common Speed Conversions:** +- 125 KB/s = 1 Mbps +- 1,250 KB/s = 10 Mbps +- 12,500 KB/s = 100 Mbps + +#### `POST /api/peers/data-cap` +Set total data usage limits for a peer using iptables quota rules. + +**Request Body:** +```json +{ + "publicKey": "peer_public_key_here", + "quotaBytes": 1073741824 +} +``` +*Note: 1073741824 bytes = 1 GB* + +**Common Data Conversions:** +- 1 GB = 1,073,741,824 bytes +- 10 GB = 10,737,418,240 bytes +- 100 GB = 107,374,182,400 bytes + +#### `POST /api/peers/config` +Retrieve peer configuration (currently not implemented - returns 404). ### Security Features - Peer isolation using iptables rules (prevents peer-to-peer communication) @@ -130,14 +189,14 @@ curl -X POST http://localhost:3000/api/peers/delete \ ```bash curl -X POST http://localhost:3000/api/peers/speed-limit \ -H "Content-Type: application/json" \ - -d '{"publicKey": "PEER_PUBLIC_KEY_HERE", "speedKbps": 1000}' + -d '{"publicKey": "PEER_PUBLIC_KEY_HERE", "bytesPerSecond": 125000}' # 125000 bytes/s = 1000 kbps ``` ### Set data cap for peer ```bash curl -X POST http://localhost:3000/api/peers/data-cap \ -H "Content-Type: application/json" \ - -d '{"publicKey": "PEER_PUBLIC_KEY_HERE", "dataCapMB": 1024}' + -d '{"publicKey": "PEER_PUBLIC_KEY_HERE", "quotaBytes": 1073741824}' # 1073741824 bytes = 1024 MB ``` ### Get peer config diff --git a/lib/test_scripts/test_api.dart b/lib/test_scripts/test_api.dart index 3f02e2e..3438ff0 100644 --- a/lib/test_scripts/test_api.dart +++ b/lib/test_scripts/test_api.dart @@ -79,10 +79,10 @@ class ApiTester { print(' IP: $peerIP'); // Test 2: Set speed limit - print('\nšŸ“‹ TEST 2: Set speed limit (500 kbps)'); +e print('\nšŸ“‹ TEST 2: Set speed limit (62500 bytes/s = 500 kbps)'); final speedResponse = await makeRequest('POST', '/api/peers/speed-limit', { 'publicKey': publicKey, - 'speedKbps': 500, + 'bytesPerSecond': 62500, // 500 kbps = 500000 bits/s = 62500 bytes/s }); if (speedResponse['statusCode'] == 200 && speedResponse['body']['success'] == true) { @@ -92,10 +92,10 @@ class ApiTester { } // Test 3: Set data cap - print('\nšŸ“‹ TEST 3: Set data cap (1024 MB)'); + print('\nšŸ“‹ TEST 3: Set data cap (1073741824 bytes = 1024 MB)'); final dataCapResponse = await makeRequest('POST', '/api/peers/data-cap', { 'publicKey': publicKey, - 'dataCapMB': 1024, + 'quotaBytes': 1073741824, // 1024 * 1024 * 1024 bytes }); if (dataCapResponse['statusCode'] == 200 && dataCapResponse['body']['success'] == true) { diff --git a/lib/web/peer_routes.dart b/lib/web/peer_routes.dart index 06cd75d..9958cd0 100644 --- a/lib/web/peer_routes.dart +++ b/lib/web/peer_routes.dart @@ -116,7 +116,7 @@ class PeerRoutes { final body = await request.readAsString(); final data = jsonDecode(body) as Map; final publicKey = data['publicKey'] as String?; - final speedKbps = data['speedKbps'] as int?; + final bytesPerSecond = data['bytesPerSecond'] as int?; if (publicKey == null) { return Response.badRequest( @@ -128,17 +128,17 @@ class PeerRoutes { ); } - if (speedKbps == null) { + if (bytesPerSecond == null) { return Response.badRequest( body: jsonEncode({ 'success': false, - 'error': 'speedKbps parameter is required', + 'error': 'bytesPerSecond parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } - await setSpeedLimit(publicKey, speedKbps); + await setSpeedLimit(publicKey, bytesPerSecond); return Response.ok( jsonEncode({ @@ -163,7 +163,7 @@ class PeerRoutes { final body = await request.readAsString(); final data = jsonDecode(body) as Map; final publicKey = data['publicKey'] as String?; - final dataCapMB = data['dataCapMB'] as int?; + final quotaBytes = data['quotaBytes'] as int?; if (publicKey == null) { return Response.badRequest( @@ -175,17 +175,17 @@ class PeerRoutes { ); } - if (dataCapMB == null) { + if (quotaBytes == null) { return Response.badRequest( body: jsonEncode({ 'success': false, - 'error': 'dataCapMB parameter is required', + 'error': 'quotaBytes parameter is required', }), headers: {'Content-Type': 'application/json'}, ); } - await setDataCap(publicKey, dataCapMB); + await setDataCap(publicKey, quotaBytes); return Response.ok( jsonEncode({ diff --git a/lib/wireguard/traffic_control.dart b/lib/wireguard/traffic_control.dart index 9e76b36..11d595b 100644 --- a/lib/wireguard/traffic_control.dart +++ b/lib/wireguard/traffic_control.dart @@ -1,9 +1,10 @@ import 'dart:io'; class TrafficControlService { - static Future setSpeedLimit(String peerIP, int speedKbps) async { + static Future setSpeedLimit(String peerIP, int bytesPerSecond) async { final mark = _getMarkForIP(peerIP); - print('Setting speed limit for peer $peerIP to ${speedKbps}kbps (mark: $mark)'); + final kbps = (bytesPerSecond * 8 / 1000).round(); // Convert bytes/s to kbps + print('Setting speed limit for peer $peerIP to ${bytesPerSecond} bytes/s (${kbps}kbps, mark: $mark)'); try { // Ensure HTB qdisc exists on wg0 @@ -29,23 +30,55 @@ class TrafficControlService { } print('Running iptables MARK commands for $peerIP...'); - await _runIptablesCommand(['-I', 'FORWARD', '-s', peerIP, '-j', 'MARK', '--set-mark', mark.toString()]); - await _runIptablesCommand(['-I', 'FORWARD', '-d', peerIP, '-j', 'MARK', '--set-mark', mark.toString()]); + // Mark upload traffic (FROM peer) with uploadMark + await _runIptablesCommand(['-I', 'FORWARD', '-s', peerIP, '-j', 'MARK', '--set-mark', uploadMark.toString()]); + // Mark download traffic (TO peer) with downloadMark + await _runIptablesCommand(['-I', 'FORWARD', '-d', peerIP, '-j', 'MARK', '--set-mark', downloadMark.toString()]); - print('Running tc class add/change command...'); + // Create separate classes for upload and download + final uploadMark = mark; + final downloadMark = mark + 1000; // Offset to avoid conflicts + + print('Running tc class add/change commands for upload and download...'); + + // Upload class (traffic FROM peer) try { - await _runTcCommand(['class', 'add', 'dev', 'wg0', 'parent', '1:1', 'classid', '1:$mark', 'htb', 'rate', '${speedKbps}kbit', 'ceil', '${speedKbps}kbit']); + await _runTcCommand(['class', 'add', 'dev', 'wg0', 'parent', '1:1', 'classid', '1:$uploadMark', 'htb', 'rate', '${kbps}kbit', 'ceil', '${kbps}kbit']); } catch (e) { if (e.toString().contains('File exists')) { - print('Class exists, updating with change command...'); - await _runTcCommand(['class', 'change', 'dev', 'wg0', 'classid', '1:$mark', 'htb', 'rate', '${speedKbps}kbit', 'ceil', '${speedKbps}kbit']); + print('Upload class exists, updating...'); + await _runTcCommand(['class', 'change', 'dev', 'wg0', 'classid', '1:$uploadMark', 'htb', 'rate', '${kbps}kbit', 'ceil', '${kbps}kbit']); } else { rethrow; } } - print('Running tc filter add command...'); - await _runTcCommand(['filter', 'add', 'dev', 'wg0', 'protocol', 'ip', 'parent', '1:', 'prio', '1', 'handle', mark.toString(), 'fw', 'flowid', '1:$mark']); + // Download class (traffic TO peer) + try { + await _runTcCommand(['class', 'add', 'dev', 'wg0', 'parent', '1:1', 'classid', '1:$downloadMark', 'htb', 'rate', '${kbps}kbit', 'ceil', '${kbps}kbit']); + } catch (e) { + if (e.toString().contains('File exists')) { + print('Download class exists, updating...'); + await _runTcCommand(['class', 'change', 'dev', 'wg0', 'classid', '1:$downloadMark', 'htb', 'rate', '${kbps}kbit', 'ceil', '${kbps}kbit']); + } else { + rethrow; + } + } + + print('Running tc filter add commands...'); + // Upload filter + try { + await _runTcCommand(['filter', 'add', 'dev', 'wg0', 'protocol', 'ip', 'parent', '1:', 'prio', '1', 'handle', uploadMark.toString(), 'fw', 'flowid', '1:$uploadMark']); + } catch (e) { + if (!e.toString().contains('File exists')) rethrow; + } + + // Download filter + try { + await _runTcCommand(['filter', 'add', 'dev', 'wg0', 'protocol', 'ip', 'parent', '1:', 'prio', '1', 'handle', downloadMark.toString(), 'fw', 'flowid', '1:$downloadMark']); + } catch (e) { + if (!e.toString().contains('File exists')) rethrow; + } print('Speed limit set successfully for $peerIP'); } catch (e) { @@ -57,8 +90,8 @@ class TrafficControlService { } } - static Future setDataCap(String peerIP, int dataCapMB) async { - final quotaBytes = dataCapMB * 1024 * 1024; + static Future setDataCap(String peerIP, int quotaBytes) async { + print('Setting data cap for peer $peerIP to $quotaBytes bytes'); await _runIptablesCommand(['-I', 'FORWARD', '-s', peerIP, '-m', 'quota', '--quota', quotaBytes.toString(), '-j', 'ACCEPT']); await _runIptablesCommand(['-I', 'FORWARD', '-d', peerIP, '-m', 'quota', '--quota', quotaBytes.toString(), '-j', 'ACCEPT']);