Fix memory leaks, hang issues, gui helper token vulns, and log spam

This commit is contained in:
ospab 2026-06-16 14:11:37 +03:00
parent feaac0c713
commit 04761fb6a3
41 changed files with 454 additions and 37 deletions

View File

@ -8,7 +8,9 @@
![Crypto](https://img.shields.io/badge/Crypto-Noise__NNpsk0-blueviolet?style=for-the-badge) ![Crypto](https://img.shields.io/badge/Crypto-Noise__NNpsk0-blueviolet?style=for-the-badge)
![Transport](https://img.shields.io/badge/Transport-UDP%20ARQ-informational?style=for-the-badge) ![Transport](https://img.shields.io/badge/Transport-UDP%20ARQ-informational?style=for-the-badge)
**OSTP** (Ospab Stealth Transport Protocol) is a high-performance, censorship-resistant zero-signature transport protocol. It implements a custom, reliable ARQ transport over UDP, as well as a UoT (UDP-over-TCP) mode. Every byte on the wire — including packet headers — is cryptographically indistinguishable from random noise, making it highly resistant to Deep Packet Inspection (DPI), active probing, and statistical traffic analysis. > A fast, custom encrypted transport protocol written in Rust.
**OSTP** (Ospab Stealth Transport Protocol) is a high-performance transport protocol. It implements a custom ARQ transport over UDP, as well as a UoT (UDP-over-TCP) mode. Every byte on the wire — including packet headers — is cryptographically indistinguishable from random noise, making it highly resistant to Deep Packet Inspection (DPI).
--- ---

View File

@ -8,7 +8,9 @@
![Crypto](https://img.shields.io/badge/Crypto-Noise__NNpsk0-blueviolet?style=for-the-badge) ![Crypto](https://img.shields.io/badge/Crypto-Noise__NNpsk0-blueviolet?style=for-the-badge)
![Transport](https://img.shields.io/badge/Transport-UDP%20ARQ-informational?style=for-the-badge) ![Transport](https://img.shields.io/badge/Transport-UDP%20ARQ-informational?style=for-the-badge)
**OSTP** (Ospab Stealth Transport Protocol) — высокопроизводительный, устойчивый к цензуре zero-signature транспортный протокол. Реализует собственный надёжный ARQ-транспорт поверх UDP, а также режим UoT (UDP-over-TCP). Каждый байт, включая заголовки пакетов, криптографически неотличим от случайного шума. Полностью устойчив к Deep Packet Inspection (DPI), активному зондированию и статистическому анализу трафика. > Быстрый кастомный зашифрованный транспортный протокол на Rust.
**OSTP** (Ospab Stealth Transport Protocol) — кастомный транспортный протокол. Реализует собственный ARQ-транспорт поверх UDP, а также режим UoT (UDP-over-TCP). Каждый байт, включая заголовки пакетов, криптографически неотличим от случайного шума, что делает его устойчивым к системам глубокого анализа трафика (DPI).
--- ---

BIN
icons/circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
icons/sqare.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

15
icons/sqare.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="g2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#111827" />
<stop offset="100%" stop-color="#374151" />
</linearGradient>
<linearGradient id="g2_path" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#3B82F6" />
<stop offset="100%" stop-color="#14B8A6" />
</linearGradient>
</defs>
<rect width="512" height="512" rx="120" fill="url(#g2)" />
<path d="M144 256c0-61.9 50.1-112 112-112s112 50.1 112 112-50.1 112-112 112S144 317.9 144 256zm-48 0c0 88.4 71.6 160 160 160s160-71.6 160-160S344.4 96 256 96 96 167.6 96 256z" fill="url(#g2_path)"/>
<circle cx="256" cy="256" r="40" fill="#F59E0B" />
</svg>

After

Width:  |  Height:  |  Size: 779 B

View File

@ -34,9 +34,9 @@ use crate::{
Runner, Runner,
}; };
// NOTE: Default buffer could contain 20 AEAD packets // Reduced buffer sizes to 16KB to prevent excessive memory overhead (was 0x3FFF * 20 = 327KB per buffer)
const DEFAULT_TCP_SEND_BUFFER_SIZE: u32 = 0x3FFF * 20; const DEFAULT_TCP_SEND_BUFFER_SIZE: u32 = 16384;
const DEFAULT_TCP_RECV_BUFFER_SIZE: u32 = 0x3FFF * 20; const DEFAULT_TCP_RECV_BUFFER_SIZE: u32 = 16384;
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum TcpSocketState { enum TcpSocketState {
@ -542,7 +542,7 @@ impl AsyncWrite for TcpStream {
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
let mut control = self.control.lock(); let mut control = self.control.lock();
if matches!(control.send_state, TcpSocketState::Closed) { if matches!(control.send_state, TcpSocketState::Closed | TcpSocketState::Closing) {
return Ok(()).into(); return Ok(()).into();
} }

View File

@ -255,17 +255,17 @@ pub async fn run_native_tunnel(
if !should_bypass { if !should_bypass {
if let Some(proc_name) = crate::tunnel::process_lookup::get_process_name_from_port(local.port()) { if let Some(proc_name) = crate::tunnel::process_lookup::get_process_name_from_port(local.port()) {
if debug { if debug {
tracing::info!("TUN TCP lookup: port {} -> process {}", local.port(), proc_name); tracing::debug!("TUN TCP lookup: port {} -> process {}", local.port(), proc_name);
} }
if matcher.match_process(&proc_name) { if matcher.match_process(&proc_name) {
if debug { if debug {
tracing::info!("TUN TCP BYPASS (Process match): {} → {remote}", proc_name); tracing::debug!("TUN TCP BYPASS (Process match): {} → {remote}", proc_name);
} }
should_bypass = true; should_bypass = true;
} }
} else { } else {
if debug { if debug {
tracing::info!("TUN TCP lookup: port {} -> no process found", local.port()); tracing::debug!("TUN TCP lookup: port {} -> no process found", local.port());
} }
} }
} }

View File

@ -665,9 +665,7 @@ async fn handle_proxy_client(
).await; ).await;
} }
if true { tracing::debug!("proxy CONNECT stream_id={stream_id} target={target}");
tracing::info!("proxy CONNECT stream_id={stream_id} target={target}");
}
let target_host = if let Some((host, _)) = split_host_port(&target) { host } else { target.clone() }; let target_host = if let Some((host, _)) = split_host_port(&target) { host } else { target.clone() };
let target_port = match split_host_port(&target) { Some((_, p)) => p, None => 0 }; let target_port = match split_host_port(&target) { Some((_, p)) => p, None => 0 };
if matcher.should_bypass_target(&target_host, target_port, connect_timeout).await { if matcher.should_bypass_target(&target_host, target_port, connect_timeout).await {

View File

@ -50,17 +50,17 @@ pub async fn run_udp_nat(
if !should_bypass { if !should_bypass {
if let Some(proc_name) = crate::tunnel::process_lookup::get_process_name_from_port_udp(src.port()) { if let Some(proc_name) = crate::tunnel::process_lookup::get_process_name_from_port_udp(src.port()) {
if debug { if debug {
tracing::info!("TUN UDP lookup: port {} -> process {}", src.port(), proc_name); tracing::debug!("TUN UDP lookup: port {} -> process {}", src.port(), proc_name);
} }
if matcher_guard.match_process(&proc_name) { if matcher_guard.match_process(&proc_name) {
should_bypass = true; should_bypass = true;
if debug { if debug {
tracing::info!("TUN UDP BYPASS (Process match): {} ({} → {})", proc_name, src, dst); tracing::debug!("TUN UDP BYPASS (Process match): {} ({} → {})", proc_name, src, dst);
} }
} }
} else { } else {
if debug { if debug {
tracing::info!("TUN UDP lookup: port {} -> no process found", src.port()); tracing::debug!("TUN UDP lookup: port {} -> no process found", src.port());
} }
} }
} }

View File

@ -28,6 +28,10 @@ android {
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
}
} }
buildTypes { buildTypes {

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
@ -10,6 +11,7 @@
android:label="ostp_client" android:label="ostp_client"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon" android:icon="@mipmap/launcher_icon"
android:roundIcon="@mipmap/launcher_icon_round"
android:extractNativeLibs="true"> android:extractNativeLibs="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
@ -45,6 +47,7 @@
<service <service
android:name=".OstpVpnService" android:name=".OstpVpnService"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="connectedDevice"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.net.VpnService"/> <action android:name="android.net.VpnService"/>

View File

@ -133,6 +133,6 @@ class MainActivity : FlutterActivity() {
if (pendingConfigJson != null) { if (pendingConfigJson != null) {
intent.putExtra("configJson", pendingConfigJson) intent.putExtra("configJson", pendingConfigJson)
} }
startService(intent) androidx.core.content.ContextCompat.startForegroundService(this, intent)
} }
} }

View File

@ -16,6 +16,7 @@ import java.io.IOException
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.ServiceCompat
@Keep @Keep
class OstpVpnService : VpnService() { class OstpVpnService : VpnService() {
@ -56,7 +57,7 @@ class OstpVpnService : VpnService() {
if (action == "START") { if (action == "START") {
val configJson = intent.getStringExtra("configJson") ?: return START_NOT_STICKY val configJson = intent.getStringExtra("configJson") ?: return START_NOT_STICKY
// Launch foreground immediately so Android doesn't kill us // Launch foreground immediately so Android doesn't kill us
startForeground(NOTIF_ID, buildNotification(connecting = true)) ServiceCompat.startForeground(this, NOTIF_ID, buildNotification(connecting = true), ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
startVpn(configJson) startVpn(configJson)
} else if (action == "STOP") { } else if (action == "STOP") {
stopVpn() stopVpn()
@ -271,6 +272,9 @@ class OstpVpnService : VpnService() {
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e("OstpVpnService", "Error starting VPN", e) Log.e("OstpVpnService", "Error starting VPN", e)
android.os.Handler(android.os.Looper.getMainLooper()).post {
android.widget.Toast.makeText(applicationContext, "VPN Error: ${e.message}", android.widget.Toast.LENGTH_LONG).show()
}
stopVpn() stopVpn()
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#08080F</color>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>

View File

@ -1,2 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=1G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -9,6 +9,10 @@ import 'package:mobile_scanner/mobile_scanner.dart';
import 'app_routing_screen.dart'; import 'app_routing_screen.dart';
import 'logs_screen.dart'; import 'logs_screen.dart';
import 'qr_scanner_screen.dart'; import 'qr_scanner_screen.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
import 'package:package_info_plus/package_info_plus.dart';
class SettingsScreen extends StatefulWidget { class SettingsScreen extends StatefulWidget {
final SharedPreferences prefs; final SharedPreferences prefs;
@ -39,7 +43,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
String _tunStack = 'ostp'; // 'system' | 'ostp' String _tunStack = 'ostp'; // 'system' | 'ostp'
bool _muxEnabled = false; bool _muxEnabled = false;
late TextEditingController _muxSessionsCtrl; late TextEditingController _muxSessionsCtrl;
bool _isCheckingUpdates = false;
@override @override
void initState() { void initState() {
@ -174,6 +178,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
), ),
actions: [ actions: [
IconButton(
icon: const Icon(Icons.share_rounded),
tooltip: 'Share Config',
onPressed: _showShareModal,
),
IconButton( IconButton(
icon: const Icon(Icons.qr_code_scanner_rounded), icon: const Icon(Icons.qr_code_scanner_rounded),
onPressed: () async { onPressed: () async {
@ -487,10 +496,190 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
), ),
const SizedBox(height: 16),
InkWell(
onTap: _isCheckingUpdates ? null : _checkForUpdates,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.02),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: Row(
children: [
Icon(Icons.system_update_rounded, color: Colors.white70, size: 24),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Check for Updates',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white),
),
SizedBox(height: 4),
Text(
_isCheckingUpdates ? 'Checking...' : 'Check latest release on GitHub',
style: TextStyle(fontSize: 13, color: Colors.white54),
),
],
),
),
if (_isCheckingUpdates)
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white54),
)
else
const Icon(Icons.arrow_forward_ios_rounded, color: Colors.white54, size: 16),
],
),
),
),
const SizedBox(height: 40), const SizedBox(height: 40),
], ],
), ),
); );
} }
String _generateShareUrl() {
final host = _serverCtrl.text.trim();
final key = Uri.encodeComponent(_keyCtrl.text.trim());
if (host.isEmpty || key.isEmpty) return '';
final queryParams = <String>[];
if (_stealthSniCtrl.text.trim().isNotEmpty) {
queryParams.add('sni=${Uri.encodeComponent(_stealthSniCtrl.text.trim())}');
}
if (_pbkCtrl.text.trim().isNotEmpty) {
queryParams.add('pbk=${Uri.encodeComponent(_pbkCtrl.text.trim())}');
}
if (_sidCtrl.text.trim().isNotEmpty) {
queryParams.add('sid=${Uri.encodeComponent(_sidCtrl.text.trim())}');
}
if (_wss) {
queryParams.add('wss=true');
}
if (_transportMode != 'udp') {
queryParams.add('type=$_transportMode');
}
final queryString = queryParams.isEmpty ? '' : '?${queryParams.join('&')}';
return 'ostp://$key@$host$queryString';
}
void _showShareModal() {
final url = _generateShareUrl();
if (url.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Server Address and Access Key are required to share.')));
return;
}
showDialog(
context: context,
builder: (context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.surface,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
title: const Text('Share Config', textAlign: TextAlign.center),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: QrImageView(
data: url,
version: QrVersions.auto,
size: 200.0,
),
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: () {
Clipboard.setData(ClipboardData(text: url));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Copied to clipboard')));
Navigator.pop(context);
},
icon: const Icon(Icons.copy_rounded, color: Colors.white),
label: const Text('Copy Link', style: TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
)
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
)
],
);
}
);
}
Future<void> _checkForUpdates() async {
if (_isCheckingUpdates) return;
setState(() { _isCheckingUpdates = true; });
try {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.version;
final response = await http.get(Uri.parse('https://api.github.com/repos/ospab/ostp/releases/latest'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
final latestVersion = (data['tag_name'] as String).replaceAll('v', '');
final hasUpdate = latestVersion != currentVersion;
if (!mounted) return;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.surface,
title: Text(hasUpdate ? 'Update Available!' : 'Up to Date'),
content: Text(hasUpdate
? 'A new version ($latestVersion) is available on GitHub. You are currently running version $currentVersion.'
: 'You are running the latest version ($currentVersion).'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
if (hasUpdate)
TextButton(
onPressed: () {
Navigator.pop(context);
final url = Uri.parse(data['html_url'] ?? 'https://github.com/ospab/ostp/releases/latest');
launchUrl(url, mode: LaunchMode.externalApplication);
},
child: const Text('Download'),
)
],
);
}
);
} else {
throw Exception('HTTP ${response.statusCode}');
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error checking updates: $e')));
} finally {
if (mounted) setState(() { _isCheckingUpdates = false; });
}
}
} }

View File

@ -97,6 +97,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
ffi_leak_tracker:
dependency: transitive
description:
name: ffi_leak_tracker
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -136,6 +144,22 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
http:
dependency: "direct main"
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image: image:
dependency: transitive dependency: transitive
description: description:
@ -224,6 +248,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.3" version: "5.2.3"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852"
url: "https://pub.dev"
source: hosted
version: "10.1.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4
url: "https://pub.dev"
source: hosted
version: "4.1.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -288,6 +328,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
qr_flutter:
dependency: "direct main"
description:
name: qr_flutter
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
screen_retriever: screen_retriever:
dependency: transitive dependency: transitive
description: description:
@ -453,6 +509,78 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.2" version: "0.5.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
url: "https://pub.dev"
source: hosted
version: "6.3.30"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
url: "https://pub.dev"
source: hosted
version: "3.2.5"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -477,6 +605,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738
url: "https://pub.dev"
source: hosted
version: "6.3.0"
window_manager: window_manager:
dependency: "direct main" dependency: "direct main"
description: description:
@ -511,4 +647,4 @@ packages:
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.11.4 <4.0.0" dart: ">=3.11.4 <4.0.0"
flutter: ">=3.35.0" flutter: ">=3.38.1"

View File

@ -1,5 +1,5 @@
name: ostp_client name: ostp_client
description: "A new Flutter project." description: "OSTP VPN Client by Ospab Foundation."
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.2.96+11 version: 0.2.97+12
environment: environment:
sdk: ^3.11.4 sdk: ^3.11.4
@ -38,6 +38,10 @@ dependencies:
mobile_scanner: ^5.0.0 mobile_scanner: ^5.0.0
window_manager: ^0.5.1 window_manager: ^0.5.1
tray_manager: ^0.5.2 tray_manager: ^0.5.2
qr_flutter: ^4.1.0
http: ^1.6.0
url_launcher: ^6.3.2
package_info_plus: ^10.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -54,7 +58,7 @@ dev_dependencies:
flutter_launcher_icons: flutter_launcher_icons:
android: "launcher_icon" android: "launcher_icon"
ios: false ios: false
image_path: "android_icon.png" image_path: "../icons/sqare.png"
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@ -0,0 +1,44 @@
Add-Type -AssemblyName System.Drawing
$imgPath = "D:\ospab-projects\ostp\ostp-flutter\android_icon.png"
$backupPath = "D:\ospab-projects\ostp\ostp-flutter\android_icon_backup.png"
# Copy as backup
Copy-Item -Path $imgPath -Destination $backupPath -Force
# Load image
$srcImg = [System.Drawing.Image]::FromFile($imgPath)
$w = $srcImg.Width
$h = $srcImg.Height
# Create new transparent bitmap of the SAME size
$bmp = New-Object System.Drawing.Bitmap($w, $h)
$bmp.MakeTransparent()
$graphics = [System.Drawing.Graphics]::FromImage($bmp)
# Set high quality resizing
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
# Clear with transparent
$graphics.Clear([System.Drawing.Color]::Transparent)
# We want to scale to 50% and center
$newW = [math]::Round($w / 2)
$newH = [math]::Round($h / 2)
$x = [math]::Round(($w - $newW) / 2)
$y = [math]::Round(($h - $newH) / 2)
# Draw image scaled down
$rect = New-Object System.Drawing.Rectangle($x, $y, $newW, $newH)
$graphics.DrawImage($srcImg, $rect)
# Dispose graphics and src before saving over
$graphics.Dispose()
$srcImg.Dispose()
# Save over original
$bmp.Save($imgPath, [System.Drawing.Imaging.ImageFormat]::Png)
$bmp.Dispose()
Write-Output "Successfully resized image."

View File

@ -2665,7 +2665,7 @@ dependencies = [
[[package]] [[package]]
name = "ostp-client" name = "ostp-client"
version = "0.2.95" version = "0.2.97"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
@ -2699,7 +2699,7 @@ dependencies = [
[[package]] [[package]]
name = "ostp-core" name = "ostp-core"
version = "0.2.95" version = "0.2.97"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -2734,7 +2734,7 @@ dependencies = [
[[package]] [[package]]
name = "ostp-tun" name = "ostp-tun"
version = "0.2.95" version = "0.2.97"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"libc", "libc",

View File

@ -720,7 +720,13 @@ fn launch_as_admin(exe: &std::path::PathBuf, token: &str, port: u16) -> anyhow::
let exe_wstr: Vec<u16> = exe.as_os_str().encode_wide().chain(Some(0)).collect(); let exe_wstr: Vec<u16> = exe.as_os_str().encode_wide().chain(Some(0)).collect();
let verb_wstr: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect(); let verb_wstr: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect();
let params_str = format!("--port {} --token {}", port, token);
// Write token to temp file for security instead of passing via cmdline
let temp_dir = std::env::temp_dir();
let token_file = temp_dir.join(format!("ostp_auth_{}.tmp", rand::random::<u32>()));
std::fs::write(&token_file, token)?;
let params_str = format!("--port {} --token-file \"{}\"", port, token_file.display());
let params_wstr: Vec<u16> = OsStr::new(&params_str).encode_wide().chain(Some(0)).collect(); let params_wstr: Vec<u16> = OsStr::new(&params_str).encode_wide().chain(Some(0)).collect();
#[link(name = "shell32")] extern "system" { fn ShellExecuteW(h: *mut std::ffi::c_void, op: *const u16, f: *const u16, p: *const u16, d: *const u16, s: i32) -> isize; } #[link(name = "shell32")] extern "system" { fn ShellExecuteW(h: *mut std::ffi::c_void, op: *const u16, f: *const u16, p: *const u16, d: *const u16, s: i32) -> isize; }

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "ostp-gui", "productName": "ostp-gui",
"version": "0.2.96", "version": "0.2.97",
"identifier": "com.ospab.ostp", "identifier": "com.ospab.ostp",
"build": { "build": {
"frontendDist": "../src" "frontendDist": "../src"
@ -23,6 +23,7 @@
"bundle": { "bundle": {
"active": true, "active": true,
"targets": "all", "targets": "all",
"publisher": "Ospab Foundation",
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",

View File

@ -61,23 +61,27 @@ async fn main() -> Result<()> {
} }
} }
let mut expected_token = String::new(); let mut expected_token = std::env::var("OSTP_TUN_TOKEN").unwrap_or_default();
let mut port = 53211u16; let mut port = 53211u16;
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
for i in 1..args.len() { for i in 1..args.len() {
if args[i] == "--port" && i + 1 < args.len() { if args[i] == "--port" && i + 1 < args.len() {
port = args[i + 1].parse().unwrap_or(53211); port = args[i + 1].parse().unwrap_or(53211);
} }
if args[i] == "--token" && i + 1 < args.len() { if args[i] == "--token-file" && i + 1 < args.len() {
expected_token = args[i + 1].clone(); let path = &args[i + 1];
if let Ok(content) = std::fs::read_to_string(path) {
expected_token = content.trim().to_string();
let _ = std::fs::remove_file(path); // securely delete after reading
}
} }
} }
log_to_file("Helper started (TCP mode)"); log_to_file("Helper started (TCP mode)");
if expected_token.is_empty() { if expected_token.is_empty() {
log_to_file("FATAL: OSTP_TUN_TOKEN environment variable is required for security. Unauthorized access denied."); log_to_file("FATAL: Auth token is required for security (--token-file or OSTP_TUN_TOKEN).");
return Err(anyhow::anyhow!("OSTP_TUN_TOKEN environment variable is required")); return Err(anyhow::anyhow!("Auth token is required"));
} }
if let Err(e) = run_server(expected_token, port).await { if let Err(e) = run_server(expected_token, port).await {