@@ -813,6 +813,42 @@ async fn download_geoip_database(app_handle: tauri::AppHandle) -> Result<(), Str
813813}
814814
815815// VPN commands
816+ #[ derive( serde:: Serialize ) ]
817+ #[ serde( rename_all = "camelCase" ) ]
818+ struct VpnDependencyStatus {
819+ is_available : bool ,
820+ requires_external_install : bool ,
821+ missing_binary : bool ,
822+ missing_windows_adapter : bool ,
823+ dependency_check_failed : bool ,
824+ }
825+
826+ #[ tauri:: command]
827+ async fn get_vpn_dependency_status ( vpn_type : vpn:: VpnType ) -> Result < VpnDependencyStatus , String > {
828+ match vpn_type {
829+ vpn:: VpnType :: WireGuard => Ok ( VpnDependencyStatus {
830+ is_available : true ,
831+ requires_external_install : false ,
832+ missing_binary : false ,
833+ missing_windows_adapter : false ,
834+ dependency_check_failed : false ,
835+ } ) ,
836+ vpn:: VpnType :: OpenVPN => {
837+ let status = crate :: vpn:: openvpn_socks5:: OpenVpnSocks5Server :: dependency_status ( ) ;
838+ let is_available =
839+ status. binary_found && !status. missing_windows_adapter && !status. dependency_check_failed ;
840+
841+ Ok ( VpnDependencyStatus {
842+ is_available,
843+ requires_external_install : true ,
844+ missing_binary : !status. binary_found ,
845+ missing_windows_adapter : status. missing_windows_adapter ,
846+ dependency_check_failed : status. dependency_check_failed ,
847+ } )
848+ }
849+ }
850+ }
851+
816852#[ tauri:: command]
817853async fn import_vpn_config (
818854 content : String ,
@@ -986,45 +1022,81 @@ async fn check_vpn_validity(
9861022 . unwrap_or_default ( )
9871023 . as_secs ( ) ;
9881024
989- // Start a temporary VPN worker to send real traffic
1025+ let had_existing_worker = vpn_worker_storage:: find_vpn_worker_by_vpn_id ( & vpn_id) . is_some ( ) ;
1026+
9901027 let vpn_worker = vpn_worker_runner:: start_vpn_worker ( & vpn_id)
9911028 . await
9921029 . map_err ( |e| format ! ( "Failed to start VPN worker: {e}" ) ) ?;
9931030
994- let socks_url = format ! ( "socks5://127.0.0.1:{}" , vpn_worker. local_port. unwrap_or( 0 ) ) ;
995-
996- // Fetch public IP through the VPN SOCKS5 proxy
997- let result = match ip_utils:: fetch_public_ip ( Some ( & socks_url) ) . await {
998- Ok ( ip) => {
999- let ( city, country, country_code) =
1000- crate :: proxy_manager:: ProxyManager :: get_ip_geolocation ( & ip)
1001- . await
1002- . unwrap_or_default ( ) ;
1003-
1004- crate :: proxy_manager:: ProxyCheckResult {
1005- ip,
1006- city,
1007- country,
1008- country_code,
1009- timestamp : now,
1010- is_valid : true ,
1031+ let socks_url = format ! (
1032+ "socks5://127.0.0.1:{}" ,
1033+ vpn_worker. local_port. unwrap_or_default( )
1034+ ) ;
1035+
1036+ let local_proxy = crate :: proxy_runner:: start_proxy_process ( Some ( socks_url) , None )
1037+ . await
1038+ . map_err ( |error| error. to_string ( ) ) ;
1039+ let local_proxy = match local_proxy {
1040+ Ok ( proxy) => proxy,
1041+ Err ( error_message) => {
1042+ if !had_existing_worker {
1043+ let _ = vpn_worker_runner:: stop_vpn_worker ( & vpn_worker. id ) . await ;
10111044 }
1045+ return Err ( format ! ( "Failed to start validation proxy: {error_message}" ) ) ;
10121046 }
1013- Err ( e) => {
1014- log:: warn!( "VPN check failed to fetch public IP: {e}" ) ;
1015- crate :: proxy_manager:: ProxyCheckResult {
1016- ip : String :: new ( ) ,
1017- city : None ,
1018- country : None ,
1019- country_code : None ,
1020- timestamp : now,
1021- is_valid : false ,
1047+ } ;
1048+
1049+ let local_proxy_url = format ! (
1050+ "http://127.0.0.1:{}" ,
1051+ local_proxy. local_port. unwrap_or_default( )
1052+ ) ;
1053+
1054+ let mut result = None ;
1055+ for attempt in 0 ..3 {
1056+ if attempt > 0 {
1057+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) . await ;
1058+ }
1059+
1060+ match ip_utils:: fetch_public_ip ( Some ( & local_proxy_url) ) . await {
1061+ Ok ( ip) => {
1062+ let ( city, country, country_code) =
1063+ crate :: proxy_manager:: ProxyManager :: get_ip_geolocation ( & ip)
1064+ . await
1065+ . unwrap_or_default ( ) ;
1066+
1067+ result = Some ( crate :: proxy_manager:: ProxyCheckResult {
1068+ ip,
1069+ city,
1070+ country,
1071+ country_code,
1072+ timestamp : now,
1073+ is_valid : true ,
1074+ } ) ;
1075+ break ;
1076+ }
1077+ Err ( error) => {
1078+ log:: warn!(
1079+ "VPN validation attempt {} failed to fetch public IP through donut-proxy: {}" ,
1080+ attempt + 1 ,
1081+ error
1082+ ) ;
10221083 }
10231084 }
1024- } ;
1085+ }
10251086
1026- // Stop the temporary VPN worker
1027- let _ = vpn_worker_runner:: stop_vpn_worker ( & vpn_worker. id ) . await ;
1087+ let _ = crate :: proxy_runner:: stop_proxy_process ( & local_proxy. id ) . await ;
1088+ if !had_existing_worker {
1089+ let _ = vpn_worker_runner:: stop_vpn_worker ( & vpn_worker. id ) . await ;
1090+ }
1091+
1092+ let result = result. unwrap_or ( crate :: proxy_manager:: ProxyCheckResult {
1093+ ip : String :: new ( ) ,
1094+ city : None ,
1095+ country : None ,
1096+ country_code : None ,
1097+ timestamp : now,
1098+ is_valid : false ,
1099+ } ) ;
10281100
10291101 Ok ( result)
10301102}
@@ -1932,6 +2004,7 @@ pub fn run() {
19322004 add_mcp_to_claude_code,
19332005 remove_mcp_from_claude_code,
19342006 // VPN commands
2007+ get_vpn_dependency_status,
19352008 import_vpn_config,
19362009 list_vpn_configs,
19372010 get_vpn_config,
0 commit comments