Fix memory leaks, hang issues, gui helper token vulns, and log spam
|
|
@ -8,7 +8,9 @@
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
**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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
**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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
|
@ -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 |
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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"/>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -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>
|
||||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#08080F</color>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#fff</color>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -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; });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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."
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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(¶ms_str).encode_wide().chain(Some(0)).collect();
|
let params_wstr: Vec<u16> = OsStr::new(¶ms_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; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||