From ae6d929f4a4459c8a67508b2bf2d5e2d3b167db4 Mon Sep 17 00:00:00 2001 From: Mg Pig Date: Mon, 1 Dec 2025 01:13:05 +0800 Subject: [PATCH] fix(mobile): Add DHCP polling to fix Android VPN startup failure (#1628) --- easytier-gui/src-tauri/src/lib.rs | 36 +++++++++++++++++++++- easytier-gui/src/composables/event.ts | 11 ++++++- easytier-gui/src/composables/mobile_vpn.ts | 18 +++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/easytier-gui/src-tauri/src/lib.rs b/easytier-gui/src-tauri/src/lib.rs index 924c144..1209462 100644 --- a/easytier-gui/src-tauri/src/lib.rs +++ b/easytier-gui/src-tauri/src/lib.rs @@ -113,6 +113,39 @@ async fn run_network_instance( .await .map_err(|e| e.to_string())?; + #[cfg(target_os = "android")] + if let Some(instance_manager) = INSTANCE_MANAGER.read().await.as_ref() { + let instance_uuid = instance_id + .parse::() + .map_err(|e| e.to_string())?; + if let Some(instance_ref) = instance_manager + .iter() + .find(|item| *item.key() == instance_uuid) + { + if let Some(mut event_receiver) = instance_ref.value().subscribe_event() { + let app_clone = app.clone(); + let instance_id_clone = instance_id.clone(); + tokio::spawn(async move { + loop { + match event_receiver.recv().await { + Ok(event) => { + if let easytier::common::global_ctx::GlobalCtxEvent::DhcpIpv4Changed(_, _) = event { + let _ = app_clone.emit("dhcp_ip_changed", instance_id_clone.clone()); + } + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => { + break; + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { + event_receiver = event_receiver.resubscribe(); + } + } + } + }); + } + } + } + app.emit("post_run_network_instance", instance_id) .map_err(|e| e.to_string())?; Ok(()) @@ -643,7 +676,8 @@ mod manager { app: &AppHandle, ) -> Result<(), easytier::rpc_service::remote_client::RemoteClientError> { - for inst_id in self.get_enabled_instances_with_tun_ids() { + let inst_ids: Vec = self.get_enabled_instances_with_tun_ids().collect(); + for inst_id in inst_ids { self.handle_update_network_state(app.clone(), inst_id, true) .await?; } diff --git a/easytier-gui/src/composables/event.ts b/easytier-gui/src/composables/event.ts index 9cf0ca4..00353f6 100644 --- a/easytier-gui/src/composables/event.ts +++ b/easytier-gui/src/composables/event.ts @@ -7,6 +7,7 @@ const EVENTS = Object.freeze({ PRE_RUN_NETWORK_INSTANCE: 'pre_run_network_instance', POST_RUN_NETWORK_INSTANCE: 'post_run_network_instance', VPN_SERVICE_STOP: 'vpn_service_stop', + DHCP_IP_CHANGED: 'dhcp_ip_changed', }); function onSaveConfigs(event: Event) { @@ -30,15 +31,23 @@ async function onVpnServiceStop(event: Event) { await onNetworkInstanceChange(event.payload); } +async function onDhcpIpChanged(event: Event) { + console.log(`Received event '${EVENTS.DHCP_IP_CHANGED}' for instance: ${event.payload}`); + if (type() === 'android') { + await onNetworkInstanceChange(event.payload); + } +} + export async function listenGlobalEvents() { const unlisteners = [ await listen(EVENTS.SAVE_CONFIGS, onSaveConfigs), await listen(EVENTS.PRE_RUN_NETWORK_INSTANCE, onPreRunNetworkInstance), await listen(EVENTS.POST_RUN_NETWORK_INSTANCE, onPostRunNetworkInstance), await listen(EVENTS.VPN_SERVICE_STOP, onVpnServiceStop), + await listen(EVENTS.DHCP_IP_CHANGED, onDhcpIpChanged), ]; return () => { unlisteners.forEach(unlisten => unlisten()); }; -} \ No newline at end of file +} diff --git a/easytier-gui/src/composables/mobile_vpn.ts b/easytier-gui/src/composables/mobile_vpn.ts index 8114150..3aef5fe 100644 --- a/easytier-gui/src/composables/mobile_vpn.ts +++ b/easytier-gui/src/composables/mobile_vpn.ts @@ -13,6 +13,9 @@ interface vpnStatus { dns: string | null | undefined } +let dhcpPollingTimer: NodeJS.Timeout | null = null +const DHCP_POLLING_INTERVAL = 2000 // 2秒后重试 + const curVpnStatus: vpnStatus = { running: false, ipv4Addr: undefined, @@ -125,6 +128,12 @@ function getRoutesForVpn(routes: Route[], node_config: NetworkTypes.NetworkConfi export async function onNetworkInstanceChange(instanceId: string) { console.error('vpn service network instance change id', instanceId) + + if (dhcpPollingTimer) { + clearTimeout(dhcpPollingTimer) + dhcpPollingTimer = null + } + if (!instanceId) { await doStopVpn() return @@ -140,6 +149,15 @@ export async function onNetworkInstanceChange(instanceId: string) { } const virtual_ip = Utils.ipv4ToString(curNetworkInfo?.my_node_info?.virtual_ipv4.address) + + if (config.dhcp && (!virtual_ip || !virtual_ip.length)) { + console.log('DHCP enabled but no IP yet, will retry in', DHCP_POLLING_INTERVAL, 'ms') + dhcpPollingTimer = setTimeout(() => { + onNetworkInstanceChange(instanceId) + }, DHCP_POLLING_INTERVAL) + return + } + if (!virtual_ip || !virtual_ip.length) { await doStopVpn() return