1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 04:18:09 +03:00

fix(dev): show WG VPN IP and RSSH bind in network self-test

Persist tunnel endpoint metadata from connect credentials so the VPN
section reports allocated addresses: WireGuard vpn-ip plus hub-only
egress hint, and RSSH bastion/remote-bind/local-forward with live
rssh-connected status.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-06-17 14:01:19 +02:00
parent 26ef8f5399
commit f1bf164a78
3 changed files with 127 additions and 1 deletions

View File

@@ -69,6 +69,10 @@ public final class AppPreferences {
private static final String KEY_DEV_REMOTE_ACCESS_RANDOM = "dev_remote_access_random"; private static final String KEY_DEV_REMOTE_ACCESS_RANDOM = "dev_remote_access_random";
private static final String KEY_DEV_REMOTE_ACCESS_SESSION_ID = "dev_remote_access_session_id"; private static final String KEY_DEV_REMOTE_ACCESS_SESSION_ID = "dev_remote_access_session_id";
private static final String KEY_DEV_REMOTE_ACCESS_EXPIRES_AT = "dev_remote_access_expires_at"; private static final String KEY_DEV_REMOTE_ACCESS_EXPIRES_AT = "dev_remote_access_expires_at";
private static final String KEY_DEV_REMOTE_ACCESS_VPN_ADDRESS = "dev_remote_access_vpn_address";
private static final String KEY_DEV_REMOTE_ACCESS_RSSH_BASTION = "dev_remote_access_rssh_bastion";
private static final String KEY_DEV_REMOTE_ACCESS_RSSH_REMOTE_BIND = "dev_remote_access_rssh_remote_bind";
private static final String KEY_DEV_REMOTE_ACCESS_RSSH_LOCAL_FORWARD = "dev_remote_access_rssh_local_forward";
private static final String KEY_DEV_REMOTE_ACCESS_VPN_ROUTE_SCOPE = "dev_remote_access_vpn_route_scope"; private static final String KEY_DEV_REMOTE_ACCESS_VPN_ROUTE_SCOPE = "dev_remote_access_vpn_route_scope";
private static final String KEY_DEV_REMOTE_ACCESS_VPN_APP_SCOPE = "dev_remote_access_vpn_app_scope"; private static final String KEY_DEV_REMOTE_ACCESS_VPN_APP_SCOPE = "dev_remote_access_vpn_app_scope";
private static final String KEY_DEV_ADB_WIFI_KEEPER = "dev_adb_wifi_keeper"; private static final String KEY_DEV_ADB_WIFI_KEEPER = "dev_adb_wifi_keeper";
@@ -519,11 +523,73 @@ public final class AppPreferences {
.apply(); .apply();
} }
/** WG interface address from last connect credentials (host part, no CIDR). */
public static String getDevRemoteAccessVpnAddress(Context context) {
return prefs(context).getString(KEY_DEV_REMOTE_ACCESS_VPN_ADDRESS, "");
}
public static void setDevRemoteAccessVpnAddress(Context context, String addressCidr) {
String host = addressCidr != null ? addressCidr.trim() : "";
int slash = host.indexOf('/');
if (slash > 0) {
host = host.substring(0, slash);
}
prefs(context).edit()
.putString(KEY_DEV_REMOTE_ACCESS_VPN_ADDRESS, host)
.apply();
}
public static String getDevRemoteAccessRsshBastionEndpoint(Context context) {
return prefs(context).getString(KEY_DEV_REMOTE_ACCESS_RSSH_BASTION, "");
}
/** Bastion-side operator bind (127.0.0.1:port) from last RSSH connect. */
public static String getDevRemoteAccessRsshRemoteBind(Context context) {
return prefs(context).getString(KEY_DEV_REMOTE_ACCESS_RSSH_REMOTE_BIND, "");
}
public static String getDevRemoteAccessRsshLocalForward(Context context) {
return prefs(context).getString(KEY_DEV_REMOTE_ACCESS_RSSH_LOCAL_FORWARD, "");
}
public static void setDevRemoteAccessRsshTunnelMeta(
Context context,
String bastionHost,
int bastionPort,
int remoteBindPort,
String localForwardHost,
int localForwardPort) {
String host = bastionHost != null ? bastionHost.trim() : "";
String bastion = host.isEmpty()
? ""
: host + ":" + Math.max(1, Math.min(65535, bastionPort));
String remoteBind = remoteBindPort > 0 ? "127.0.0.1:" + remoteBindPort : "";
String localHost = localForwardHost != null && !localForwardHost.isEmpty()
? localForwardHost.trim() : "127.0.0.1";
String localForward = localForwardPort > 0 ? localHost + ":" + localForwardPort : "";
prefs(context).edit()
.putString(KEY_DEV_REMOTE_ACCESS_RSSH_BASTION, bastion)
.putString(KEY_DEV_REMOTE_ACCESS_RSSH_REMOTE_BIND, remoteBind)
.putString(KEY_DEV_REMOTE_ACCESS_RSSH_LOCAL_FORWARD, localForward)
.apply();
}
/** Clears WG/RSSH tunnel endpoint fields from the last connect (not mode/route prefs). */
public static void clearDevRemoteAccessTunnelMeta(Context context) {
prefs(context).edit()
.remove(KEY_DEV_REMOTE_ACCESS_VPN_ADDRESS)
.remove(KEY_DEV_REMOTE_ACCESS_RSSH_BASTION)
.remove(KEY_DEV_REMOTE_ACCESS_RSSH_REMOTE_BIND)
.remove(KEY_DEV_REMOTE_ACCESS_RSSH_LOCAL_FORWARD)
.apply();
}
public static void clearDevRemoteAccessSession(Context context) { public static void clearDevRemoteAccessSession(Context context) {
prefs(context).edit() prefs(context).edit()
.remove(KEY_DEV_REMOTE_ACCESS_SESSION_ID) .remove(KEY_DEV_REMOTE_ACCESS_SESSION_ID)
.remove(KEY_DEV_REMOTE_ACCESS_EXPIRES_AT) .remove(KEY_DEV_REMOTE_ACCESS_EXPIRES_AT)
.apply(); .apply();
clearDevRemoteAccessTunnelMeta(context);
} }
public static long getDevRemoteAccessExpiresAt(Context context) { public static long getDevRemoteAccessExpiresAt(Context context) {

View File

@@ -43,7 +43,7 @@ public final class DevVpnStatusProbe {
String typeLabel = mode == RemoteAccessMode.DISABLED ? "off" : mode.name().toLowerCase(Locale.US); String typeLabel = mode == RemoteAccessMode.DISABLED ? "off" : mode.name().toLowerCase(Locale.US);
r.itemPass("type", typeLabel); r.itemPass("type", typeLabel);
if (mode != RemoteAccessMode.DISABLED) { if (mode == RemoteAccessMode.WIREGUARD) {
r.itemPass( r.itemPass(
"vpn-route-scope", "vpn-route-scope",
RemoteAccessVpnRouting.routeScopeLabel( RemoteAccessVpnRouting.routeScopeLabel(
@@ -78,6 +78,24 @@ public final class DevVpnStatusProbe {
String tunnelLabel = formatTunnelStateLabel(tunnelState, last); String tunnelLabel = formatTunnelStateLabel(tunnelState, last);
r.item(tunnelLevel, "tunnel-state", tunnelLabel); r.item(tunnelLevel, "tunnel-state", tunnelLabel);
if (mode == RemoteAccessMode.WIREGUARD) {
String vpnIp = AppPreferences.getDevRemoteAccessVpnAddress(context);
if (!vpnIp.isEmpty()) {
r.itemPass("vpn-ip", vpnIp);
} else if (RemoteAccessStatusStore.STATE_SUCCESS.equals(tunnelState)
|| RemoteAccessStatusStore.STATE_CONNECTING.equals(tunnelState)) {
r.item(DevNetworkSelfTestReport.Level.WARN, "vpn-ip", "not stored (reconnect session)");
}
if (AppPreferences.getDevRemoteAccessVpnRouteScope(context)
== RemoteAccessVpnRouting.RouteScope.HUB_ONLY) {
r.itemPass(
"wan-egress-via-vpn",
"no (hub-only — only " + RemoteAccessVpnRouting.DEFAULT_HUB_ALLOWED_IPS + ")");
}
} else if (mode == RemoteAccessMode.RSSH) {
appendRsshTunnelMeta(context, r, tunnelState);
}
if (mode != RemoteAccessMode.DISABLED if (mode != RemoteAccessMode.DISABLED
&& !RemoteAccessStatusStore.STATE_OFF.equals(controlPlane)) { && !RemoteAccessStatusStore.STATE_OFF.equals(controlPlane)) {
DevNetworkSelfTestReport.Level cpLevel; DevNetworkSelfTestReport.Level cpLevel;
@@ -98,6 +116,8 @@ public final class DevVpnStatusProbe {
if (mainProcess && mode == RemoteAccessMode.WIREGUARD) { if (mainProcess && mode == RemoteAccessMode.WIREGUARD) {
boolean bound = VpnServiceClient.get(context).isBound(); boolean bound = VpnServiceClient.get(context).isBound();
r.itemPass("vpn-process-bound", bound ? "yes (:vpn AIDL)" : "no (not bound — normal if idle)"); r.itemPass("vpn-process-bound", bound ? "yes (:vpn AIDL)" : "no (not bound — normal if idle)");
} else if (mainProcess && mode == RemoteAccessMode.RSSH) {
r.itemPass("rssh-connected", ReverseSshTunnelBridge.isConnected() ? "yes" : "no");
} }
if (!vpnDetail.isEmpty()) { if (!vpnDetail.isEmpty()) {
r.itemPass("detail", vpnDetail); r.itemPass("detail", vpnDetail);
@@ -304,6 +324,33 @@ public final class DevVpnStatusProbe {
return caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN); return caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
} }
private static void appendRsshTunnelMeta(
Context context,
DevNetworkSelfTestReport r,
String tunnelState) {
String bastion = AppPreferences.getDevRemoteAccessRsshBastionEndpoint(context);
if (!bastion.isEmpty()) {
r.itemPass("rssh-bastion", bastion);
} else if (RemoteAccessStatusStore.STATE_SUCCESS.equals(tunnelState)
|| RemoteAccessStatusStore.STATE_CONNECTING.equals(tunnelState)) {
r.item(DevNetworkSelfTestReport.Level.WARN, "rssh-bastion", "not stored (reconnect session)");
}
String remoteBind = AppPreferences.getDevRemoteAccessRsshRemoteBind(context);
if (!remoteBind.isEmpty()) {
r.itemPass("rssh-remote-bind", remoteBind);
} else if (RemoteAccessStatusStore.STATE_SUCCESS.equals(tunnelState)
|| RemoteAccessStatusStore.STATE_CONNECTING.equals(tunnelState)) {
r.item(DevNetworkSelfTestReport.Level.WARN, "rssh-remote-bind", "not stored (reconnect session)");
}
String localForward = AppPreferences.getDevRemoteAccessRsshLocalForward(context);
if (!localForward.isEmpty()) {
r.itemPass("rssh-local-forward", localForward);
}
r.itemPass(
"wan-egress-via-vpn",
"no (RSSH — reverse SSH only, no device routing)");
}
private static void appendTraffic(DevNetworkSelfTestReport r, Context context) { private static void appendTraffic(DevNetworkSelfTestReport r, Context context) {
int uid = Process.myUid(); int uid = Process.myUid();
long rx = TrafficStats.getUidRxBytes(uid); long rx = TrafficStats.getUidRxBytes(uid);

View File

@@ -219,6 +219,19 @@ public final class RemoteAccessService extends Service {
"connect without credentials", sessionId); "connect without credentials", sessionId);
return; return;
} }
AppPreferences.clearDevRemoteAccessTunnelMeta(this);
if ("wireguard".equals(tunnel)) {
AppPreferences.setDevRemoteAccessVpnAddress(
this, creds.optString("interface_address", ""));
} else if ("ssh_reverse".equals(tunnel)) {
AppPreferences.setDevRemoteAccessRsshTunnelMeta(
this,
creds.optString("bastion_host", ""),
creds.optInt("bastion_port", 443),
creds.optInt("remote_bind_port", 0),
creds.optString("local_forward_host", "127.0.0.1"),
creds.optInt("local_forward_port", 8022));
}
if ("wireguard".equals(tunnel)) { if ("wireguard".equals(tunnel)) {
String wgConfig = WireGuardConfigBuilder.fromConnectCredentials(this, creds); String wgConfig = WireGuardConfigBuilder.fromConnectCredentials(this, creds);
if (wgConfig.isEmpty()) { if (wgConfig.isEmpty()) {