diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index db209aa..d3f0221 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,13 @@ Set bidirectional bandwidth limits for a peer (controls both upload and download } ``` *Note: 125000 bytes/s = 1000 kbps = 1 Mbps* +*Use -1 for unlimited speed* **Common Speed Conversions:** - 125 KB/s = 1 Mbps - 1,250 KB/s = 10 Mbps - 12,500 KB/s = 100 Mbps +- -1 = Unlimited #### `POST /api/peers/data-cap` Set total data usage limits for a peer using iptables quota rules. @@ -87,11 +89,13 @@ Set total data usage limits for a peer using iptables quota rules. } ``` *Note: 1073741824 bytes = 1 GB* +*Use -1 for unlimited data* **Common Data Conversions:** - 1 GB = 1,073,741,824 bytes - 10 GB = 10,737,418,240 bytes - 100 GB = 107,374,182,400 bytes +- -1 = Unlimited #### `POST /api/peers/config` Retrieve peer configuration (currently not implemented - returns 404). diff --git a/lib/services/server_service.dart b/lib/services/server_service.dart index 0d09e32..5c38ede 100644 --- a/lib/services/server_service.dart +++ b/lib/services/server_service.dart @@ -4,7 +4,7 @@ import 'package:waylume_server/core/utils.dart'; class ServerService { static Future registerServer() async { - String ip = await getWanIp(); + String ip = "${await getWanIp()}:${fromEnivronment("EXTERNAL_PORT") ?? "3000"}"; var existsCheck = await SUPABASE_CLIENT .from("waylume_servers") diff --git a/lib/wireguard/traffic_control.dart b/lib/wireguard/traffic_control.dart index 13d3780..f39b171 100644 --- a/lib/wireguard/traffic_control.dart +++ b/lib/wireguard/traffic_control.dart @@ -3,6 +3,14 @@ import 'dart:io'; class TrafficControlService { static Future setSpeedLimit(String peerIP, int bytesPerSecond) async { final mark = _getMarkForIP(peerIP); + + // Handle unlimited speed (-1) + if (bytesPerSecond == -1) { + print('Removing speed limit for peer $peerIP (unlimited)'); + await _removeSpeedLimit(peerIP, mark); + return; + } + 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)'); @@ -125,8 +133,18 @@ class TrafficControlService { } static Future setDataCap(String peerIP, int quotaBytes) async { + // Handle unlimited data cap (-1) + if (quotaBytes == -1) { + print('Removing data cap for peer $peerIP (unlimited)'); + await _removeDataCap(peerIP); + return; + } + print('Setting data cap for peer $peerIP to $quotaBytes bytes'); + // Remove existing data cap rules first + await _removeDataCap(peerIP); + 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']); await _runIptablesCommand(['-A', 'FORWARD', '-s', peerIP, '-j', 'DROP']); @@ -157,6 +175,74 @@ class TrafficControlService { return 'eth0'; } + static Future _removeSpeedLimit(String peerIP, int mark) async { + final uploadMark = mark; + final downloadMark = mark + 1000; + + print('Removing speed limit rules for $peerIP...'); + + // Remove iptables mangle rules + try { + await _runIptablesCommand(['-t', 'mangle', '-D', 'POSTROUTING', '-s', peerIP, '-j', 'MARK', '--set-mark', uploadMark.toString()]); + } catch (e) { /* Rule doesn't exist, ignore */ } + try { + await _runIptablesCommand(['-t', 'mangle', '-D', 'FORWARD', '-d', peerIP, '-j', 'MARK', '--set-mark', downloadMark.toString()]); + } catch (e) { /* Rule doesn't exist, ignore */ } + + // Remove tc classes and filters + final outgoingInterface = await _detectOutgoingInterface(); + + try { + await _runTcCommand(['filter', 'del', 'dev', outgoingInterface, 'protocol', 'ip', 'parent', '2:', 'prio', '1', 'handle', uploadMark.toString(), 'fw']); + } catch (e) { /* Filter doesn't exist, ignore */ } + try { + await _runTcCommand(['filter', 'del', 'dev', 'wg0', 'protocol', 'ip', 'parent', '1:', 'prio', '1', 'handle', downloadMark.toString(), 'fw']); + } catch (e) { /* Filter doesn't exist, ignore */ } + + try { + await _runTcCommand(['class', 'del', 'dev', outgoingInterface, 'classid', '2:$uploadMark']); + } catch (e) { /* Class doesn't exist, ignore */ } + try { + await _runTcCommand(['class', 'del', 'dev', 'wg0', 'classid', '1:$downloadMark']); + } catch (e) { /* Class doesn't exist, ignore */ } + + print('Speed limit removed for $peerIP'); + } + + static Future _removeDataCap(String peerIP) async { + print('Removing data cap rules for $peerIP...'); + + // Remove quota rules (these are harder to target specifically, so we try common patterns) + try { + await _runIptablesCommand(['-D', 'FORWARD', '-s', peerIP, '-j', 'DROP']); + } catch (e) { /* Rule doesn't exist, ignore */ } + try { + await _runIptablesCommand(['-D', 'FORWARD', '-d', peerIP, '-j', 'DROP']); + } catch (e) { /* Rule doesn't exist, ignore */ } + + // Remove quota rules by flushing and re-adding basic rules + // This is a bit aggressive but necessary since quota rules are hard to target specifically + try { + final result = await Process.run('iptables', ['-S', 'FORWARD']); + if (result.exitCode == 0) { + final lines = result.stdout.toString().split('\n'); + for (final line in lines) { + if (line.contains(peerIP) && line.contains('quota')) { + // Extract the rule and convert -A to -D to delete it + final deleteRule = line.replaceFirst('-A', '-D'); + try { + await Process.run('iptables', deleteRule.split(' ').skip(1).toList()); + } catch (e) { /* Ignore individual failures */ } + } + } + } + } catch (e) { + print('Failed to remove specific quota rules: $e'); + } + + print('Data cap removed for $peerIP'); + } + static Future _runIptablesCommand(List args) async { final command = 'iptables ${args.join(' ')}'; print('Executing: $command');