|
@@ -136,6 +136,8 @@ public class PsiphonTunnel {
|
|
|
private AtomicReference<String> mClientPlatformSuffix;
|
|
private AtomicReference<String> mClientPlatformSuffix;
|
|
|
private final boolean mShouldRouteThroughTunnelAutomatically;
|
|
private final boolean mShouldRouteThroughTunnelAutomatically;
|
|
|
private final NetworkMonitor mNetworkMonitor;
|
|
private final NetworkMonitor mNetworkMonitor;
|
|
|
|
|
+ private AtomicReference<String> mActiveNetworkType;
|
|
|
|
|
+ private AtomicReference<String> mActiveNetworkPrimaryDNSServer;
|
|
|
|
|
|
|
|
// Only one PsiphonVpn instance may exist at a time, as the underlying
|
|
// Only one PsiphonVpn instance may exist at a time, as the underlying
|
|
|
// psi.Psi and tun2socks implementations each contain global state.
|
|
// psi.Psi and tun2socks implementations each contain global state.
|
|
@@ -187,6 +189,8 @@ public class PsiphonTunnel {
|
|
|
mClientPlatformPrefix = new AtomicReference<String>("");
|
|
mClientPlatformPrefix = new AtomicReference<String>("");
|
|
|
mClientPlatformSuffix = new AtomicReference<String>("");
|
|
mClientPlatformSuffix = new AtomicReference<String>("");
|
|
|
mShouldRouteThroughTunnelAutomatically = shouldRouteThroughTunnelAutomatically;
|
|
mShouldRouteThroughTunnelAutomatically = shouldRouteThroughTunnelAutomatically;
|
|
|
|
|
+ mActiveNetworkType = new AtomicReference<String>("");
|
|
|
|
|
+ mActiveNetworkPrimaryDNSServer = new AtomicReference<String>("");
|
|
|
mNetworkMonitor = new NetworkMonitor(new NetworkMonitor.NetworkChangeListener() {
|
|
mNetworkMonitor = new NetworkMonitor(new NetworkMonitor.NetworkChangeListener() {
|
|
|
@Override
|
|
@Override
|
|
|
public void onChanged() {
|
|
public void onChanged() {
|
|
@@ -196,7 +200,7 @@ public class PsiphonTunnel {
|
|
|
mHostService.onDiagnosticMessage("reconnect error: " + e);
|
|
mHostService.onDiagnosticMessage("reconnect error: " + e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ }, mActiveNetworkType, mActiveNetworkPrimaryDNSServer, mHostService);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Object clone() throws CloneNotSupportedException {
|
|
public Object clone() throws CloneNotSupportedException {
|
|
@@ -625,7 +629,7 @@ public class PsiphonTunnel {
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public String getPrimaryDnsServer() {
|
|
public String getPrimaryDnsServer() {
|
|
|
- return PsiphonTunnel.getPrimaryDnsServer(mHostService.getContext(), mHostService);
|
|
|
|
|
|
|
+ return mPsiphonTunnel.getPrimaryDnsServer(mHostService.getContext(), mHostService);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -671,10 +675,26 @@ public class PsiphonTunnel {
|
|
|
return hasConnectivity ? 1 : 0;
|
|
return hasConnectivity ? 1 : 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static String getPrimaryDnsServer(Context context, HostLogger logger) {
|
|
|
|
|
|
|
+ private String getPrimaryDnsServer(Context context, HostLogger logger) {
|
|
|
|
|
+
|
|
|
|
|
+ // Use the primary DNS server set by mNetworkMonitor,
|
|
|
|
|
+ // mActiveNetworkPrimaryDNSServer, when available. It's the most
|
|
|
|
|
+ // reliable mechanism. Otherwise fallback to
|
|
|
|
|
+ // getFirstActiveNetworkDnsResolver or DEFAULT_PRIMARY_DNS_SERVER.
|
|
|
|
|
+ //
|
|
|
|
|
+ // mActiveNetworkPrimaryDNSServer is not available on API < 21
|
|
|
|
|
+ // (LOLLIPOP). mActiveNetworkPrimaryDNSServer may also be temporarily
|
|
|
|
|
+ // unavailable if the last active network has been lost and no new
|
|
|
|
|
+ // one has yet replaced it.
|
|
|
|
|
+
|
|
|
|
|
+ String primaryDNSServer = mActiveNetworkPrimaryDNSServer.get();
|
|
|
|
|
+ if (primaryDNSServer != "") {
|
|
|
|
|
+ return primaryDNSServer;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
String dnsResolver = null;
|
|
String dnsResolver = null;
|
|
|
try {
|
|
try {
|
|
|
- dnsResolver = getFirstActiveNetworkDnsResolver(context);
|
|
|
|
|
|
|
+ dnsResolver = getFirstActiveNetworkDnsResolver(context, mVpnMode.get());
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
logger.onDiagnosticMessage("failed to get active network DNS resolver: " + e.getMessage());
|
|
logger.onDiagnosticMessage("failed to get active network DNS resolver: " + e.getMessage());
|
|
|
dnsResolver = DEFAULT_PRIMARY_DNS_SERVER;
|
|
dnsResolver = DEFAULT_PRIMARY_DNS_SERVER;
|
|
@@ -693,6 +713,10 @@ public class PsiphonTunnel {
|
|
|
|
|
|
|
|
private static String getNetworkID(Context context) {
|
|
private static String getNetworkID(Context context) {
|
|
|
|
|
|
|
|
|
|
+ // TODO: getActiveNetworkInfo is deprecated in API 29; once
|
|
|
|
|
+ // getActiveNetworkInfo is no longer available, use
|
|
|
|
|
+ // mActiveNetworkType which is updated by mNetworkMonitor.
|
|
|
|
|
+
|
|
|
// The network ID contains potential PII. In tunnel-core, the network ID
|
|
// The network ID contains potential PII. In tunnel-core, the network ID
|
|
|
// is used only locally in the client and not sent to the server.
|
|
// is used only locally in the client and not sent to the server.
|
|
|
//
|
|
//
|
|
@@ -1235,22 +1259,25 @@ public class PsiphonTunnel {
|
|
|
throw new Exception("no private address available");
|
|
throw new Exception("no private address available");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public static String getFirstActiveNetworkDnsResolver(Context context)
|
|
|
|
|
|
|
+ private static String getFirstActiveNetworkDnsResolver(Context context, boolean isVpnMode)
|
|
|
throws Exception {
|
|
throws Exception {
|
|
|
- Collection<InetAddress> dnsResolvers = getActiveNetworkDnsResolvers(context);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ Collection<InetAddress> dnsResolvers = getActiveNetworkDnsResolvers(context, isVpnMode);
|
|
|
if (!dnsResolvers.isEmpty()) {
|
|
if (!dnsResolvers.isEmpty()) {
|
|
|
- // strip the leading slash e.g., "/192.168.1.1"
|
|
|
|
|
String dnsResolver = dnsResolvers.iterator().next().toString();
|
|
String dnsResolver = dnsResolvers.iterator().next().toString();
|
|
|
|
|
+ // strip the leading slash e.g., "/192.168.1.1"
|
|
|
if (dnsResolver.startsWith("/")) {
|
|
if (dnsResolver.startsWith("/")) {
|
|
|
dnsResolver = dnsResolver.substring(1);
|
|
dnsResolver = dnsResolver.substring(1);
|
|
|
}
|
|
}
|
|
|
return dnsResolver;
|
|
return dnsResolver;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
throw new Exception("no active network DNS resolver");
|
|
throw new Exception("no active network DNS resolver");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static Collection<InetAddress> getActiveNetworkDnsResolvers(Context context)
|
|
|
|
|
|
|
+ private static Collection<InetAddress> getActiveNetworkDnsResolvers(Context context, boolean isVpnMode)
|
|
|
throws Exception {
|
|
throws Exception {
|
|
|
|
|
+
|
|
|
final String errorMessage = "getActiveNetworkDnsResolvers failed";
|
|
final String errorMessage = "getActiveNetworkDnsResolvers failed";
|
|
|
ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
|
|
ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
|
|
|
|
|
|
|
@@ -1259,36 +1286,17 @@ public class PsiphonTunnel {
|
|
|
if (connectivityManager == null) {
|
|
if (connectivityManager == null) {
|
|
|
throw new Exception(errorMessage, new Throwable("couldn't get ConnectivityManager system service"));
|
|
throw new Exception(errorMessage, new Throwable("couldn't get ConnectivityManager system service"));
|
|
|
}
|
|
}
|
|
|
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
|
- NetworkRequest networkRequest = new NetworkRequest.Builder().build();
|
|
|
|
|
- final CountDownLatch countDownLatch = new CountDownLatch(1);
|
|
|
|
|
- try {
|
|
|
|
|
- ConnectivityManager.NetworkCallback networkCallback =
|
|
|
|
|
- new ConnectivityManager.NetworkCallback() {
|
|
|
|
|
- @Override
|
|
|
|
|
- public void onLinkPropertiesChanged(Network network,
|
|
|
|
|
- LinkProperties linkProperties) {
|
|
|
|
|
- dnsAddresses.addAll(linkProperties.getDnsServers());
|
|
|
|
|
- countDownLatch.countDown();
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
|
|
|
|
|
- connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
|
|
|
|
|
- countDownLatch.await(1, TimeUnit.SECONDS);
|
|
|
|
|
- connectivityManager.unregisterNetworkCallback(networkCallback);
|
|
|
|
|
- } catch (RuntimeException ignored) {
|
|
|
|
|
- // Failed to register network callback
|
|
|
|
|
- } catch (InterruptedException e) {
|
|
|
|
|
- Thread.currentThread().interrupt();
|
|
|
|
|
- }
|
|
|
|
|
- if (!dnsAddresses.isEmpty()) {
|
|
|
|
|
- return dnsAddresses;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
try {
|
|
try {
|
|
|
- // Hidden API
|
|
|
|
|
- // - only available in Android 4.0+
|
|
|
|
|
- // - no guarantee will be available beyond 4.2, or on all vendor devices
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Hidden API:
|
|
|
|
|
+ //
|
|
|
|
|
+ // - Only available in Android 4.0+
|
|
|
|
|
+ // - No guarantee will be available beyond 4.2, or on all vendor
|
|
|
|
|
+ // devices
|
|
|
|
|
+ // - Field reports indicate this is no longer working on some --
|
|
|
|
|
+ // but not all -- Android 10+ devices
|
|
|
|
|
+
|
|
|
Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
|
|
Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
|
|
|
Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
|
|
Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
|
|
|
Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
|
|
Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
|
|
@@ -1320,6 +1328,59 @@ public class PsiphonTunnel {
|
|
|
throw new Exception(errorMessage, e);
|
|
throw new Exception(errorMessage, e);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (!dnsAddresses.isEmpty()) {
|
|
|
|
|
+ return dnsAddresses;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
|
+
|
|
|
|
|
+ // This case is attempted only when the hidden API fails:
|
|
|
|
|
+ //
|
|
|
|
|
+ // - Testing shows the hidden API still works more reliably on
|
|
|
|
|
+ // some Android 11+ devices
|
|
|
|
|
+ // - Testing indicates that the NetworkRequest can sometimes
|
|
|
|
|
+ // select the wrong network
|
|
|
|
|
+ // - e.g., mobile instead of WiFi, and return the wrong DNS
|
|
|
|
|
+ // servers
|
|
|
|
|
+ // - there's currently no way to filter for the "currently
|
|
|
|
|
+ // active default data network" returned by, e.g., the
|
|
|
|
|
+ // deprecated getActiveNetworkInfo
|
|
|
|
|
+ // - we cannot add the NET_CAPABILITY_FOREGROUND capability to
|
|
|
|
|
+ // the NetworkRequest at this time due to target SDK
|
|
|
|
|
+ // constraints
|
|
|
|
|
+
|
|
|
|
|
+ NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder()
|
|
|
|
|
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
|
|
|
|
+
|
|
|
|
|
+ if (isVpnMode) {
|
|
|
|
|
+ // In VPN mode, we want the DNS servers for the underlying physical network.
|
|
|
|
|
+ networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ NetworkRequest networkRequest = networkRequestBuilder.build();
|
|
|
|
|
+
|
|
|
|
|
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
|
|
|
|
|
+ try {
|
|
|
|
|
+ ConnectivityManager.NetworkCallback networkCallback =
|
|
|
|
|
+ new ConnectivityManager.NetworkCallback() {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onLinkPropertiesChanged(Network network,
|
|
|
|
|
+ LinkProperties linkProperties) {
|
|
|
|
|
+ dnsAddresses.addAll(linkProperties.getDnsServers());
|
|
|
|
|
+ countDownLatch.countDown();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
|
|
|
|
|
+ countDownLatch.await(1, TimeUnit.SECONDS);
|
|
|
|
|
+ connectivityManager.unregisterNetworkCallback(networkCallback);
|
|
|
|
|
+ } catch (RuntimeException ignored) {
|
|
|
|
|
+ // Failed to register network callback
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return dnsAddresses;
|
|
return dnsAddresses;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1344,9 +1405,20 @@ public class PsiphonTunnel {
|
|
|
private static class NetworkMonitor {
|
|
private static class NetworkMonitor {
|
|
|
private final NetworkChangeListener listener;
|
|
private final NetworkChangeListener listener;
|
|
|
private ConnectivityManager.NetworkCallback networkCallback;
|
|
private ConnectivityManager.NetworkCallback networkCallback;
|
|
|
|
|
+ private AtomicReference<String> activeNetworkType;
|
|
|
|
|
+ private AtomicReference<String> activeNetworkPrimaryDNSServer;
|
|
|
|
|
+ private HostLogger logger;
|
|
|
|
|
+
|
|
|
|
|
+ public NetworkMonitor(
|
|
|
|
|
+ NetworkChangeListener listener,
|
|
|
|
|
+ AtomicReference<String> activeNetworkType,
|
|
|
|
|
+ AtomicReference<String> activeNetworkPrimaryDNSServer,
|
|
|
|
|
+ HostLogger logger) {
|
|
|
|
|
|
|
|
- public NetworkMonitor(NetworkChangeListener listener) {
|
|
|
|
|
this.listener = listener;
|
|
this.listener = listener;
|
|
|
|
|
+ this.activeNetworkType = activeNetworkType;
|
|
|
|
|
+ this.activeNetworkPrimaryDNSServer = activeNetworkPrimaryDNSServer;
|
|
|
|
|
+ this.logger = logger;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void start(Context context) {
|
|
private void start(Context context) {
|
|
@@ -1360,18 +1432,18 @@ public class PsiphonTunnel {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
networkCallback = new ConnectivityManager.NetworkCallback() {
|
|
networkCallback = new ConnectivityManager.NetworkCallback() {
|
|
|
- boolean isInitialState = true;
|
|
|
|
|
|
|
+ private boolean isInitialState = true;
|
|
|
private Network currentActiveNetwork;
|
|
private Network currentActiveNetwork;
|
|
|
|
|
|
|
|
private void consumeActiveNetwork(Network network) {
|
|
private void consumeActiveNetwork(Network network) {
|
|
|
if (isInitialState) {
|
|
if (isInitialState) {
|
|
|
isInitialState = false;
|
|
isInitialState = false;
|
|
|
- currentActiveNetwork = network;
|
|
|
|
|
|
|
+ setCurrentActiveNetworkAndProperties(network);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!network.equals(currentActiveNetwork)) {
|
|
if (!network.equals(currentActiveNetwork)) {
|
|
|
- currentActiveNetwork = network;
|
|
|
|
|
|
|
+ setCurrentActiveNetworkAndProperties(network);
|
|
|
if (listener != null) {
|
|
if (listener != null) {
|
|
|
listener.onChanged();
|
|
listener.onChanged();
|
|
|
}
|
|
}
|
|
@@ -1380,13 +1452,65 @@ public class PsiphonTunnel {
|
|
|
|
|
|
|
|
private void consumeLostNetwork(Network network) {
|
|
private void consumeLostNetwork(Network network) {
|
|
|
if (network.equals(currentActiveNetwork)) {
|
|
if (network.equals(currentActiveNetwork)) {
|
|
|
- currentActiveNetwork = null;
|
|
|
|
|
|
|
+ setCurrentActiveNetworkAndProperties(null);
|
|
|
if (listener != null) {
|
|
if (listener != null) {
|
|
|
listener.onChanged();
|
|
listener.onChanged();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private void setCurrentActiveNetworkAndProperties(Network network) {
|
|
|
|
|
+
|
|
|
|
|
+ currentActiveNetwork = network;
|
|
|
|
|
+
|
|
|
|
|
+ if (network == null) {
|
|
|
|
|
+
|
|
|
|
|
+ activeNetworkType.set("NONE");
|
|
|
|
|
+ activeNetworkPrimaryDNSServer.set("");
|
|
|
|
|
+ logger.onDiagnosticMessage("NetworkMonitor: clear current active network");
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ String networkType = "UNKNOWN";
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Limitation: a network may have both CELLULAR
|
|
|
|
|
+ // and WIFI transports, or different network
|
|
|
|
|
+ // transport types entirely. This logic currently
|
|
|
|
|
+ // mimics the type determination logic in
|
|
|
|
|
+ // getNetworkID.
|
|
|
|
|
+ NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
|
|
|
|
|
+ if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
|
|
|
|
+ networkType = "MOBILE";
|
|
|
|
|
+ } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
|
|
|
|
+ networkType = "WIFI";
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (java.lang.Exception e) {
|
|
|
|
|
+ }
|
|
|
|
|
+ activeNetworkType.set(networkType);
|
|
|
|
|
+
|
|
|
|
|
+ String primaryDNSServer = "";
|
|
|
|
|
+ try {
|
|
|
|
|
+ LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
|
|
|
|
|
+ List<InetAddress> dnsServers = linkProperties.getDnsServers();
|
|
|
|
|
+ if (!dnsServers.isEmpty()) {
|
|
|
|
|
+ primaryDNSServer = dnsServers.iterator().next().toString();
|
|
|
|
|
+ if (primaryDNSServer.startsWith("/")) {
|
|
|
|
|
+ primaryDNSServer = primaryDNSServer.substring(1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (java.lang.Exception e) {
|
|
|
|
|
+ }
|
|
|
|
|
+ activeNetworkPrimaryDNSServer.set(primaryDNSServer);
|
|
|
|
|
+
|
|
|
|
|
+ String message = "NetworkMonitor: set current active network " + networkType;
|
|
|
|
|
+ if (primaryDNSServer != "") {
|
|
|
|
|
+ // The DNS server address is potential PII and not logged.
|
|
|
|
|
+ message += " with DNS";
|
|
|
|
|
+ }
|
|
|
|
|
+ logger.onDiagnosticMessage(message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
|
|
public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
|
|
|
super.onCapabilitiesChanged(network, capabilities);
|
|
super.onCapabilitiesChanged(network, capabilities);
|