PsiphonTunnel.java 85 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813
  1. /*
  2. * Copyright (c) 2015, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package ca.psiphon;
  20. import android.annotation.TargetApi;
  21. import android.content.Context;
  22. import android.net.ConnectivityManager;
  23. import android.net.LinkProperties;
  24. import android.net.Network;
  25. import android.net.NetworkCapabilities;
  26. import android.net.NetworkInfo;
  27. import android.net.NetworkRequest;
  28. import android.net.VpnService;
  29. import android.net.wifi.WifiInfo;
  30. import android.net.wifi.WifiManager;
  31. import android.os.Build;
  32. import android.os.ParcelFileDescriptor;
  33. import android.telephony.TelephonyManager;
  34. import android.text.TextUtils;
  35. import android.util.Base64;
  36. import org.json.JSONArray;
  37. import org.json.JSONException;
  38. import org.json.JSONObject;
  39. import java.io.File;
  40. import java.io.FileInputStream;
  41. import java.io.FileOutputStream;
  42. import java.io.IOException;
  43. import java.io.PrintStream;
  44. import java.lang.reflect.InvocationTargetException;
  45. import java.lang.reflect.Method;
  46. import java.net.Inet4Address;
  47. import java.net.Inet6Address;
  48. import java.net.InetAddress;
  49. import java.net.NetworkInterface;
  50. import java.net.SocketException;
  51. import java.security.KeyStore;
  52. import java.security.KeyStoreException;
  53. import java.security.NoSuchAlgorithmException;
  54. import java.security.cert.CertificateException;
  55. import java.security.cert.X509Certificate;
  56. import java.util.ArrayList;
  57. import java.util.Collection;
  58. import java.util.Collections;
  59. import java.util.Enumeration;
  60. import java.util.HashMap;
  61. import java.util.List;
  62. import java.util.Locale;
  63. import java.util.Map;
  64. import java.util.concurrent.CountDownLatch;
  65. import java.util.concurrent.RejectedExecutionException;
  66. import java.util.concurrent.atomic.AtomicBoolean;
  67. import java.util.concurrent.atomic.AtomicInteger;
  68. import java.util.concurrent.atomic.AtomicReference;
  69. import java.util.concurrent.Executors;
  70. import java.util.concurrent.ExecutorService;
  71. import java.util.concurrent.Future;
  72. import java.util.concurrent.TimeUnit;
  73. import psi.Psi;
  74. import psi.PsiphonProvider;
  75. import psi.PsiphonProviderNetwork;
  76. import psi.PsiphonProviderNoticeHandler;
  77. import psi.PsiphonProviderFeedbackHandler;
  78. public class PsiphonTunnel {
  79. public interface HostLogger {
  80. default public void onDiagnosticMessage(String message) {}
  81. }
  82. // Protocol used to communicate the outcome of feedback upload operations to the application
  83. // using PsiphonTunnelFeedback.
  84. public interface HostFeedbackHandler {
  85. // Callback which is invoked once the feedback upload has completed.
  86. // If the exception is non-null, then the upload failed.
  87. default public void sendFeedbackCompleted(java.lang.Exception e) {}
  88. }
  89. public interface HostLibraryLoader {
  90. default public void loadLibrary(String library) {
  91. System.loadLibrary(library);
  92. }
  93. }
  94. public interface HostService extends HostLogger, HostLibraryLoader {
  95. public String getAppName();
  96. public Context getContext();
  97. public String getPsiphonConfig();
  98. default public Object getVpnService() {return null;} // Object must be a VpnService (Android < 4 cannot reference this class name)
  99. default public Object newVpnServiceBuilder() {return null;} // Object must be a VpnService.Builder (Android < 4 cannot reference this class name)
  100. default public void onAvailableEgressRegions(List<String> regions) {}
  101. default public void onSocksProxyPortInUse(int port) {}
  102. default public void onHttpProxyPortInUse(int port) {}
  103. default public void onListeningSocksProxyPort(int port) {}
  104. default public void onListeningHttpProxyPort(int port) {}
  105. default public void onUpstreamProxyError(String message) {}
  106. default public void onConnecting() {}
  107. default public void onConnected() {}
  108. default public void onHomepage(String url) {}
  109. default public void onClientRegion(String region) {}
  110. default public void onClientAddress(String address) {}
  111. default public void onClientUpgradeDownloaded(String filename) {}
  112. default public void onClientIsLatestVersion() {}
  113. default public void onSplitTunnelRegions(List<String> regions) {}
  114. default public void onUntunneledAddress(String address) {}
  115. default public void onBytesTransferred(long sent, long received) {}
  116. default public void onStartedWaitingForNetworkConnectivity() {}
  117. default public void onStoppedWaitingForNetworkConnectivity() {}
  118. default public void onActiveAuthorizationIDs(List<String> authorizations) {}
  119. default public void onTrafficRateLimits(long upstreamBytesPerSecond, long downstreamBytesPerSecond) {}
  120. default public void onApplicationParameters(Object parameters) {}
  121. default public void onServerAlert(String reason, String subject, List<String> actionURLs) {}
  122. /**
  123. * Called when tunnel-core emits a message to be displayed to the in-proxy operator.
  124. * @param message The operator message received.
  125. */
  126. default void onInproxyOperatorMessage(String message) {}
  127. /**
  128. * Called when tunnel-core reports proxy usage statistics.
  129. * By default onInproxyProxyActivity is disabled. Enable it by setting
  130. * EmitInproxyProxyActivity to true in the Psiphon config.
  131. * @param connectingClients Number of clients connecting to the proxy.
  132. * @param connectedClients Number of clients currently connected to the proxy.
  133. * @param bytesUp Bytes uploaded through the proxy since the last report.
  134. * @param bytesDown Bytes downloaded through the proxy since the last report.
  135. */
  136. default void onInproxyProxyActivity(int connectingClients, int connectedClients,long bytesUp, long bytesDown) {}
  137. default public void onExiting() {}
  138. }
  139. private final HostService mHostService;
  140. private AtomicBoolean mVpnMode;
  141. private PrivateAddress mPrivateAddress;
  142. private AtomicReference<ParcelFileDescriptor> mTunFd;
  143. private AtomicInteger mLocalSocksProxyPort;
  144. private AtomicBoolean mRoutingThroughTunnel;
  145. private Thread mTun2SocksThread;
  146. private AtomicBoolean mIsWaitingForNetworkConnectivity;
  147. private AtomicReference<String> mClientPlatformPrefix;
  148. private AtomicReference<String> mClientPlatformSuffix;
  149. private final boolean mShouldRouteThroughTunnelAutomatically;
  150. private final NetworkMonitor mNetworkMonitor;
  151. private AtomicReference<String> mActiveNetworkType;
  152. private AtomicReference<String> mActiveNetworkDNSServers;
  153. // Only one PsiphonVpn instance may exist at a time, as the underlying
  154. // psi.Psi and tun2socks implementations each contain global state.
  155. private static PsiphonTunnel mPsiphonTunnel;
  156. public static synchronized PsiphonTunnel newPsiphonTunnel(HostService hostService) {
  157. return newPsiphonTunnelImpl(hostService, true);
  158. }
  159. // The two argument override in case the host app wants to take control over calling routeThroughTunnel()
  160. public static synchronized PsiphonTunnel newPsiphonTunnel(HostService hostService, boolean shouldRouteThroughTunnelAutomatically) {
  161. return newPsiphonTunnelImpl(hostService, shouldRouteThroughTunnelAutomatically);
  162. }
  163. private static PsiphonTunnel newPsiphonTunnelImpl(HostService hostService, boolean shouldRouteThroughTunnelAutomatically) {
  164. if (mPsiphonTunnel != null) {
  165. mPsiphonTunnel.stop();
  166. }
  167. // Load the native go code embedded in psi.aar
  168. hostService.loadLibrary("gojni");
  169. mPsiphonTunnel = new PsiphonTunnel(hostService, shouldRouteThroughTunnelAutomatically);
  170. return mPsiphonTunnel;
  171. }
  172. // Returns default path where upgrade downloads will be paved. Only applicable if
  173. // DataRootDirectory was not set in the outer config. If DataRootDirectory was set in the
  174. // outer config, use getUpgradeDownloadFilePath with its value instead.
  175. public static String getDefaultUpgradeDownloadFilePath(Context context) {
  176. return Psi.upgradeDownloadFilePath(defaultDataRootDirectory(context).getAbsolutePath());
  177. }
  178. // Returns the path where upgrade downloads will be paved relative to the configured
  179. // DataRootDirectory.
  180. public static String getUpgradeDownloadFilePath(String dataRootDirectoryPath) {
  181. return Psi.upgradeDownloadFilePath(dataRootDirectoryPath);
  182. }
  183. private static File defaultDataRootDirectory(Context context) {
  184. return context.getFileStreamPath("ca.psiphon.PsiphonTunnel.tunnel-core");
  185. }
  186. private PsiphonTunnel(HostService hostService, boolean shouldRouteThroughTunnelAutomatically) {
  187. mHostService = hostService;
  188. mVpnMode = new AtomicBoolean(false);
  189. mTunFd = new AtomicReference<ParcelFileDescriptor>();
  190. mLocalSocksProxyPort = new AtomicInteger(0);
  191. mRoutingThroughTunnel = new AtomicBoolean(false);
  192. mIsWaitingForNetworkConnectivity = new AtomicBoolean(false);
  193. mClientPlatformPrefix = new AtomicReference<String>("");
  194. mClientPlatformSuffix = new AtomicReference<String>("");
  195. mShouldRouteThroughTunnelAutomatically = shouldRouteThroughTunnelAutomatically;
  196. mActiveNetworkType = new AtomicReference<String>("");
  197. mActiveNetworkDNSServers = new AtomicReference<String>("");
  198. mNetworkMonitor = new NetworkMonitor(new NetworkMonitor.NetworkChangeListener() {
  199. @Override
  200. public void onChanged() {
  201. try {
  202. reconnectPsiphon();
  203. } catch (Exception e) {
  204. mHostService.onDiagnosticMessage("reconnect error: " + e);
  205. }
  206. }
  207. });
  208. }
  209. public Object clone() throws CloneNotSupportedException {
  210. throw new CloneNotSupportedException();
  211. }
  212. //----------------------------------------------------------------------------------------------
  213. // Public API
  214. //----------------------------------------------------------------------------------------------
  215. // To start, call in sequence: startRouting(), then startTunneling(). After startRouting()
  216. // succeeds, the caller must call stop() to clean up. These functions should not be called
  217. // concurrently. Do not call stop() while startRouting() or startTunneling() is in progress.
  218. // In case the host application requests manual control of routing through tunnel by calling
  219. // PsiphonTunnel.newPsiphonTunnel(HostService hostservice, shouldRouteThroughTunnelAutomatically = false)
  220. // it should also call routeThroughTunnel() at some point, usually after receiving onConnected() callback,
  221. // otherwise it will be called automatically.
  222. // Returns true when the VPN routing is established; returns false if the VPN could not
  223. // be started due to lack of prepare or revoked permissions (called should re-prepare and
  224. // try again); throws exception for other error conditions.
  225. public synchronized boolean startRouting() throws Exception {
  226. // Load tun2socks library embedded in the aar
  227. // If this method is called more than once with the same library name, the second and subsequent calls are ignored.
  228. // http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#loadLibrary%28java.lang.String%29
  229. mHostService.loadLibrary("tun2socks");
  230. return startVpn();
  231. }
  232. // Starts routing traffic via tunnel by starting tun2socks if it is not running already.
  233. // This will be called automatically right after tunnel gets connected in case the host application
  234. // did not request a manual control over this functionality, see PsiphonTunnel.newPsiphonTunnel
  235. public void routeThroughTunnel() {
  236. if (!mRoutingThroughTunnel.compareAndSet(false, true)) {
  237. return;
  238. }
  239. ParcelFileDescriptor tunFd = mTunFd.get();
  240. if (tunFd == null) {
  241. return;
  242. }
  243. String socksServerAddress = "127.0.0.1:" + Integer.toString(mLocalSocksProxyPort.get());
  244. String udpgwServerAddress = "127.0.0.1:" + Integer.toString(UDPGW_SERVER_PORT);
  245. // We may call routeThroughTunnel and stopRouteThroughTunnel more than once within the same
  246. // VPN session. Since stopTun2Socks() closes the FD passed to startTun2Socks() we will use a
  247. // dup of the original tun FD and close the original only when we call stopVpn().
  248. //
  249. // Note that ParcelFileDescriptor.dup() may throw an IOException.
  250. try {
  251. startTun2Socks(
  252. tunFd.dup(),
  253. VPN_INTERFACE_MTU,
  254. mPrivateAddress.mRouter,
  255. VPN_INTERFACE_NETMASK,
  256. socksServerAddress,
  257. udpgwServerAddress,
  258. true);
  259. mHostService.onDiagnosticMessage("routing through tunnel");
  260. // TODO: should double-check tunnel routing; see:
  261. // https://bitbucket.org/psiphon/psiphon-circumvention-system/src/1dc5e4257dca99790109f3bf374e8ab3a0ead4d7/Android/PsiphonAndroidLibrary/src/com/psiphon3/psiphonlibrary/TunnelCore.java?at=default#cl-779
  262. } catch (IOException e) {
  263. mHostService.onDiagnosticMessage("routing through tunnel error: " + e);
  264. }
  265. }
  266. public void stopRouteThroughTunnel() {
  267. if (mRoutingThroughTunnel.compareAndSet(true, false)) {
  268. stopTun2Socks();
  269. }
  270. }
  271. // Throws an exception in error conditions. In the case of an exception, the routing
  272. // started by startRouting() is not immediately torn down (this allows the caller to control
  273. // exactly when VPN routing is stopped); caller should call stop() to clean up.
  274. public synchronized void startTunneling(String embeddedServerEntries) throws Exception {
  275. startPsiphon(embeddedServerEntries);
  276. }
  277. // Note: to avoid deadlock, do not call directly from a HostService callback;
  278. // instead post to a Handler if necessary to trigger from a HostService callback.
  279. // For example, deadlock can occur when a Notice callback invokes stop() since stop() calls
  280. // Psi.stop() which will block waiting for tunnel-core Controller to shutdown which in turn
  281. // waits for Notice callback invoker to stop, meanwhile the callback thread has blocked waiting
  282. // for stop().
  283. public synchronized void stop() {
  284. stopVpn();
  285. stopPsiphon();
  286. mVpnMode.set(false);
  287. mLocalSocksProxyPort.set(0);
  288. }
  289. // Note: same deadlock note as stop().
  290. public synchronized void restartPsiphon() throws Exception {
  291. stopPsiphon();
  292. startPsiphon("");
  293. }
  294. public synchronized void reconnectPsiphon() throws Exception {
  295. Psi.reconnectTunnel();
  296. }
  297. public void setClientPlatformAffixes(String prefix, String suffix) {
  298. mClientPlatformPrefix.set(prefix);
  299. mClientPlatformSuffix.set(suffix);
  300. }
  301. public String exportExchangePayload() {
  302. return Psi.exportExchangePayload();
  303. }
  304. public boolean importExchangePayload(String payload) {
  305. return Psi.importExchangePayload(payload);
  306. }
  307. // Writes Go runtime profile information to a set of files in the specifiec output directory.
  308. // cpuSampleDurationSeconds and blockSampleDurationSeconds determines how to long to wait and
  309. // sample profiles that require active sampling. When set to 0, these profiles are skipped.
  310. public void writeRuntimeProfiles(String outputDirectory, int cpuSampleDurationSeconnds, int blockSampleDurationSeconds) {
  311. Psi.writeRuntimeProfiles(outputDirectory, cpuSampleDurationSeconnds, blockSampleDurationSeconds);
  312. }
  313. // The interface for managing the Psiphon feedback upload operations.
  314. // Warnings:
  315. // - Should not be used in the same process as PsiphonTunnel.
  316. // - Only a single instance of PsiphonTunnelFeedback should be used at a time. Using multiple
  317. // instances in parallel, or concurrently, will result in undefined behavior.
  318. public static class PsiphonTunnelFeedback {
  319. private final ExecutorService workQueue = Executors.newSingleThreadExecutor();
  320. private final ExecutorService callbackQueue = Executors.newSingleThreadExecutor();
  321. void shutdownAndAwaitTermination(ExecutorService pool) {
  322. try {
  323. // Wait a while for existing tasks to terminate
  324. if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
  325. pool.shutdownNow(); // Cancel currently executing tasks
  326. // Wait a while for tasks to respond to being cancelled
  327. if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
  328. System.err.println("PsiphonTunnelFeedback: pool did not terminate");
  329. return;
  330. }
  331. }
  332. } catch (InterruptedException ie) {
  333. // (Re-)Cancel if current thread also interrupted
  334. pool.shutdownNow();
  335. // Preserve interrupt status
  336. Thread.currentThread().interrupt();
  337. }
  338. }
  339. // Upload a feedback package to Psiphon Inc. The app collects feedback and diagnostics
  340. // information in a particular format, then calls this function to upload it for later
  341. // investigation. The feedback compatible config and upload path must be provided by
  342. // Psiphon Inc. This call is asynchronous and returns before the upload completes. The
  343. // operation has completed when sendFeedbackCompleted() is called on the provided
  344. // HostFeedbackHandler. The provided HostLogger will be called to log informational notices,
  345. // including warnings.
  346. //
  347. // Warnings:
  348. // - Only one active upload is supported at a time. An ongoing upload will be cancelled if
  349. // this function is called again before it completes.
  350. // - An ongoing feedback upload started with startSendFeedback() should be stopped with
  351. // stopSendFeedback() before the process exits. This ensures that any underlying resources
  352. // are cleaned up; failing to do so may result in data store corruption or other undefined
  353. // behavior.
  354. // - PsiphonTunnel.startTunneling and startSendFeedback both make an attempt to migrate
  355. // persistent files from legacy locations in a one-time operation. If these functions are
  356. // called in parallel, then there is a chance that the migration attempts could execute at
  357. // the same time and result in non-fatal errors in one, or both, of the migration
  358. // operations.
  359. public void startSendFeedback(Context context, HostFeedbackHandler feedbackHandler, HostLogger logger,
  360. String feedbackConfigJson, String diagnosticsJson, String uploadPath,
  361. String clientPlatformPrefix, String clientPlatformSuffix) {
  362. workQueue.execute(new Runnable() {
  363. @Override
  364. public void run() {
  365. try {
  366. // Adds fields used in feedback upload, e.g. client platform.
  367. String psiphonConfig = buildPsiphonConfig(context, logger, feedbackConfigJson,
  368. clientPlatformPrefix, clientPlatformSuffix, false, 0);
  369. Psi.startSendFeedback(psiphonConfig, diagnosticsJson, uploadPath,
  370. new PsiphonProviderFeedbackHandler() {
  371. @Override
  372. public void sendFeedbackCompleted(java.lang.Exception e) {
  373. try {
  374. callbackQueue.execute(new Runnable() {
  375. @Override
  376. public void run() {
  377. feedbackHandler.sendFeedbackCompleted(e);
  378. }
  379. });
  380. } catch (RejectedExecutionException ignored) {
  381. }
  382. }
  383. },
  384. new PsiphonProviderNetwork() {
  385. @Override
  386. public long hasNetworkConnectivity() {
  387. boolean hasConnectivity = PsiphonTunnel.hasNetworkConnectivity(context);
  388. // TODO: change to bool return value once gobind supports that type
  389. return hasConnectivity ? 1 : 0;
  390. }
  391. @Override
  392. public String getNetworkID() {
  393. // startSendFeedback is invoked from the Psiphon UI process, not the Psiphon
  394. // VPN process.
  395. //
  396. // Case 1: no VPN is running
  397. //
  398. // isVpnMode = true/false doesn't change the network ID; the network ID will
  399. // be the physical network ID, and feedback may load existing tactics or may
  400. // fetch tactics.
  401. //
  402. // Case 2: Psiphon VPN is running
  403. //
  404. // In principle, we might want to set isVpnMode = true so that we obtain the
  405. // physical network ID and load any existing tactics. However, as the VPN
  406. // holds a lock on the data store, the load will fail; also no tactics request
  407. // is attempted.
  408. //
  409. // Hypothetically, if a tactics request did proceed, the tunneled client GeoIP
  410. // would not reflect the actual client location, and so it's safer to set
  411. // isVpnMode = false to ensure fetched tactics are stored under a distinct
  412. // Network ID ("VPN").
  413. //
  414. // Case 3: another VPN is running
  415. //
  416. // Unlike case 2, there's no Psiphon VPN process holding the data store lock.
  417. // As with case 2, there's some merit to setting isVpnMode = true in order to
  418. // load existing tactics, but since a tactics request may proceed, it's safer
  419. // to set isVpnMode = false and store fetched tactics under a distinct
  420. // Network ID ("VPN").
  421. return PsiphonTunnel.getNetworkID(context, false);
  422. }
  423. @Override
  424. public String iPv6Synthesize(String IPv4Addr) {
  425. // Unused on Android.
  426. return PsiphonTunnel.iPv6Synthesize(IPv4Addr);
  427. }
  428. @Override
  429. public long hasIPv6Route() {
  430. return PsiphonTunnel.hasIPv6Route(context, logger);
  431. }
  432. },
  433. new PsiphonProviderNoticeHandler() {
  434. @Override
  435. public void notice(String noticeJSON) {
  436. try {
  437. JSONObject notice = new JSONObject(noticeJSON);
  438. String noticeType = notice.getString("noticeType");
  439. if (noticeType == null) {
  440. return;
  441. }
  442. JSONObject data = notice.getJSONObject("data");
  443. if (data == null) {
  444. return;
  445. }
  446. String diagnosticMessage = noticeType + ": " + data.toString();
  447. try {
  448. callbackQueue.execute(new Runnable() {
  449. @Override
  450. public void run() {
  451. logger.onDiagnosticMessage(diagnosticMessage);
  452. }
  453. });
  454. } catch (RejectedExecutionException ignored) {
  455. }
  456. } catch (java.lang.Exception e) {
  457. try {
  458. callbackQueue.execute(new Runnable() {
  459. @Override
  460. public void run() {
  461. logger.onDiagnosticMessage("Error handling notice " + e.toString());
  462. }
  463. });
  464. } catch (RejectedExecutionException ignored) {
  465. }
  466. }
  467. }
  468. },
  469. false, // Do not use IPv6 synthesizer for Android
  470. true // Use hasIPv6Route on Android
  471. );
  472. } catch (java.lang.Exception e) {
  473. try {
  474. callbackQueue.execute(new Runnable() {
  475. @Override
  476. public void run() {
  477. feedbackHandler.sendFeedbackCompleted(new Exception("Error sending feedback", e));
  478. }
  479. });
  480. } catch (RejectedExecutionException ignored) {
  481. }
  482. }
  483. }
  484. });
  485. }
  486. // Interrupt an in-progress feedback upload operation started with startSendFeedback() and shutdown
  487. // executor queues.
  488. // NOTE: this instance cannot be reused after shutdown() has been called.
  489. public void shutdown() {
  490. workQueue.execute(new Runnable() {
  491. @Override
  492. public void run() {
  493. Psi.stopSendFeedback();
  494. }
  495. });
  496. shutdownAndAwaitTermination(workQueue);
  497. shutdownAndAwaitTermination(callbackQueue);
  498. }
  499. }
  500. //----------------------------------------------------------------------------------------------
  501. // VPN Routing
  502. //----------------------------------------------------------------------------------------------
  503. private final static String VPN_INTERFACE_NETMASK = "255.255.255.0";
  504. private final static int VPN_INTERFACE_MTU = 1500;
  505. private final static int UDPGW_SERVER_PORT = 7300;
  506. // Note: Atomic variables used for getting/setting local proxy port, routing flag, and
  507. // tun fd, as these functions may be called via PsiphonProvider callbacks. Do not use
  508. // synchronized functions as stop() is synchronized and a deadlock is possible as callbacks
  509. // can be called while stop holds the lock.
  510. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  511. private boolean startVpn() throws Exception {
  512. mVpnMode.set(true);
  513. mPrivateAddress = selectPrivateAddress();
  514. Locale previousLocale = Locale.getDefault();
  515. final String errorMessage = "startVpn failed";
  516. try {
  517. // Workaround for https://code.google.com/p/android/issues/detail?id=61096
  518. Locale.setDefault(new Locale("en"));
  519. int mtu = VPN_INTERFACE_MTU;
  520. String dnsResolver = mPrivateAddress.mRouter;
  521. ParcelFileDescriptor tunFd =
  522. ((VpnService.Builder) mHostService.newVpnServiceBuilder())
  523. .setSession(mHostService.getAppName())
  524. .setMtu(mtu)
  525. .addAddress(mPrivateAddress.mIpAddress, mPrivateAddress.mPrefixLength)
  526. .addRoute("0.0.0.0", 0)
  527. .addRoute(mPrivateAddress.mSubnet, mPrivateAddress.mPrefixLength)
  528. .addDnsServer(dnsResolver)
  529. .establish();
  530. if (tunFd == null) {
  531. // As per http://developer.android.com/reference/android/net/VpnService.Builder.html#establish%28%29,
  532. // this application is no longer prepared or was revoked.
  533. return false;
  534. }
  535. mTunFd.set(tunFd);
  536. mRoutingThroughTunnel.set(false);
  537. mHostService.onDiagnosticMessage("VPN established");
  538. } catch(IllegalArgumentException e) {
  539. throw new Exception(errorMessage, e);
  540. } catch(IllegalStateException e) {
  541. throw new Exception(errorMessage, e);
  542. } catch(SecurityException e) {
  543. throw new Exception(errorMessage, e);
  544. } finally {
  545. // Restore the original locale.
  546. Locale.setDefault(previousLocale);
  547. }
  548. return true;
  549. }
  550. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  551. private ParcelFileDescriptor startDummyVpn(VpnService.Builder vpnServiceBuilder) throws Exception {
  552. PrivateAddress privateAddress = selectPrivateAddress();
  553. Locale previousLocale = Locale.getDefault();
  554. final String errorMessage = "startDummyVpn failed";
  555. final ParcelFileDescriptor tunFd;
  556. try {
  557. // Workaround for https://code.google.com/p/android/issues/detail?id=61096
  558. Locale.setDefault(new Locale("en"));
  559. int mtu = VPN_INTERFACE_MTU;
  560. String dnsResolver = privateAddress.mRouter;
  561. tunFd = vpnServiceBuilder
  562. .setSession(mHostService.getAppName())
  563. .setMtu(mtu)
  564. .addAddress(privateAddress.mIpAddress, privateAddress.mPrefixLength)
  565. .addRoute("0.0.0.0", 0)
  566. .addRoute(privateAddress.mSubnet, privateAddress.mPrefixLength)
  567. .addDnsServer(dnsResolver)
  568. .establish();
  569. } catch(IllegalArgumentException e) {
  570. throw new Exception(errorMessage, e);
  571. } catch(IllegalStateException e) {
  572. throw new Exception(errorMessage, e);
  573. } catch(SecurityException e) {
  574. throw new Exception(errorMessage, e);
  575. } finally {
  576. // Restore the original locale.
  577. Locale.setDefault(previousLocale);
  578. }
  579. return tunFd;
  580. }
  581. private boolean isVpnMode() {
  582. return mVpnMode.get();
  583. }
  584. private void setLocalSocksProxyPort(int port) {
  585. mLocalSocksProxyPort.set(port);
  586. }
  587. private void stopVpn() {
  588. stopTun2Socks();
  589. ParcelFileDescriptor tunFd = mTunFd.getAndSet(null);
  590. if (tunFd != null) {
  591. try {
  592. tunFd.close();
  593. } catch (IOException e) {
  594. }
  595. }
  596. mRoutingThroughTunnel.set(false);
  597. }
  598. //----------------------------------------------------------------------------------------------
  599. // PsiphonProvider (Core support) interface implementation
  600. //----------------------------------------------------------------------------------------------
  601. // The PsiphonProvider functions are called from Go, and must be public to be accessible
  602. // via the gobind mechanim. To avoid making internal implementation functions public,
  603. // PsiphonProviderShim is used as a wrapper.
  604. private class PsiphonProviderShim implements PsiphonProvider {
  605. private PsiphonTunnel mPsiphonTunnel;
  606. public PsiphonProviderShim(PsiphonTunnel psiphonTunnel) {
  607. mPsiphonTunnel = psiphonTunnel;
  608. }
  609. @Override
  610. public void notice(String noticeJSON) {
  611. mPsiphonTunnel.notice(noticeJSON);
  612. }
  613. @Override
  614. public String bindToDevice(long fileDescriptor) throws Exception {
  615. return mPsiphonTunnel.bindToDevice(fileDescriptor);
  616. }
  617. @Override
  618. public long hasNetworkConnectivity() {
  619. return mPsiphonTunnel.hasNetworkConnectivity();
  620. }
  621. @Override
  622. public String getDNSServersAsString() {
  623. return mPsiphonTunnel.getDNSServers(mHostService.getContext(), mHostService);
  624. }
  625. @Override
  626. public String iPv6Synthesize(String IPv4Addr) {
  627. return PsiphonTunnel.iPv6Synthesize(IPv4Addr);
  628. }
  629. @Override
  630. public long hasIPv6Route() {
  631. return PsiphonTunnel.hasIPv6Route(mHostService.getContext(), mHostService);
  632. }
  633. @Override
  634. public String getNetworkID() {
  635. return PsiphonTunnel.getNetworkID(mHostService.getContext(), mPsiphonTunnel.isVpnMode());
  636. }
  637. }
  638. private void notice(String noticeJSON) {
  639. handlePsiphonNotice(noticeJSON);
  640. }
  641. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  642. private String bindToDevice(long fileDescriptor) throws Exception {
  643. if (!((VpnService)mHostService.getVpnService()).protect((int)fileDescriptor)) {
  644. throw new Exception("protect socket failed");
  645. }
  646. return "";
  647. }
  648. private long hasNetworkConnectivity() {
  649. boolean hasConnectivity = hasNetworkConnectivity(mHostService.getContext());
  650. boolean wasWaitingForNetworkConnectivity = mIsWaitingForNetworkConnectivity.getAndSet(!hasConnectivity);
  651. // HasNetworkConnectivity may be called many times, but only invoke
  652. // callbacks once per loss or resumption of connectivity, so, e.g.,
  653. // the HostService may log a single message.
  654. if (!hasConnectivity && !wasWaitingForNetworkConnectivity) {
  655. mHostService.onStartedWaitingForNetworkConnectivity();
  656. } else if (hasConnectivity && wasWaitingForNetworkConnectivity) {
  657. mHostService.onStoppedWaitingForNetworkConnectivity();
  658. }
  659. // TODO: change to bool return value once gobind supports that type
  660. return hasConnectivity ? 1 : 0;
  661. }
  662. private String getDNSServers(Context context, HostLogger logger) {
  663. // Use the DNS servers set by mNetworkMonitor,
  664. // mActiveNetworkDNSServers, when available. It's the most reliable
  665. // mechanism. Otherwise fallback to getActiveNetworkDNSServers.
  666. //
  667. // mActiveNetworkDNSServers is not available on API < 21
  668. // (LOLLIPOP). mActiveNetworkDNSServers may also be temporarily
  669. // unavailable if the last active network has been lost and no new
  670. // one has yet replaced it.
  671. String servers = mActiveNetworkDNSServers.get();
  672. if (servers != "") {
  673. return servers;
  674. }
  675. try {
  676. // Use the workaround, comma-delimited format required for gobind.
  677. servers = TextUtils.join(",", getActiveNetworkDNSServers(context, mVpnMode.get()));
  678. } catch (Exception e) {
  679. logger.onDiagnosticMessage("failed to get active network DNS resolver: " + e.getMessage());
  680. // Alternate DNS servers will be provided by psiphon-tunnel-core
  681. // config or tactics.
  682. }
  683. return servers;
  684. }
  685. private static String iPv6Synthesize(String IPv4Addr) {
  686. // Unused on Android.
  687. return IPv4Addr;
  688. }
  689. private static long hasIPv6Route(Context context, HostLogger logger) {
  690. boolean hasRoute = false;
  691. try {
  692. hasRoute = hasIPv6Route(context);
  693. } catch (Exception e) {
  694. logger.onDiagnosticMessage("failed to check IPv6 route: " + e.getMessage());
  695. }
  696. // TODO: change to bool return value once gobind supports that type
  697. return hasRoute ? 1 : 0;
  698. }
  699. private static String getNetworkID(Context context, boolean isVpnMode) {
  700. // TODO: getActiveNetworkInfo is deprecated in API 29; once
  701. // getActiveNetworkInfo is no longer available, use
  702. // mActiveNetworkType which is updated by mNetworkMonitor.
  703. // The network ID contains potential PII. In tunnel-core, the network ID
  704. // is used only locally in the client and not sent to the server.
  705. //
  706. // See network ID requirements here:
  707. // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter
  708. String networkID = "UNKNOWN";
  709. ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
  710. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  711. if (!isVpnMode) {
  712. NetworkCapabilities capabilities = null;
  713. try {
  714. Network nw = connectivityManager.getActiveNetwork();
  715. capabilities = connectivityManager.getNetworkCapabilities(nw);
  716. } catch (java.lang.Exception e) {
  717. // May get exceptions due to missing permissions like android.permission.ACCESS_NETWORK_STATE.
  718. // Apps using the Psiphon Library and lacking android.permission.ACCESS_NETWORK_STATE will
  719. // proceed and use tactics, but with "UNKNOWN" as the sole network ID.
  720. }
  721. if (capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
  722. return "VPN";
  723. }
  724. }
  725. }
  726. NetworkInfo activeNetworkInfo = null;
  727. try {
  728. activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
  729. } catch (java.lang.Exception e) {
  730. // May get exceptions due to missing permissions like android.permission.ACCESS_NETWORK_STATE.
  731. // Apps using the Psiphon Library and lacking android.permission.ACCESS_NETWORK_STATE will
  732. // proceed and use tactics, but with "UNKNOWN" as the sole network ID.
  733. }
  734. if (activeNetworkInfo != null && activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
  735. networkID = "WIFI";
  736. try {
  737. WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
  738. WifiInfo wifiInfo = wifiManager.getConnectionInfo();
  739. if (wifiInfo != null) {
  740. String wifiNetworkID = wifiInfo.getBSSID();
  741. if (wifiNetworkID.equals("02:00:00:00:00:00")) {
  742. // "02:00:00:00:00:00" is reported when the app does not have the ACCESS_COARSE_LOCATION permission:
  743. // https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-hardware-id
  744. // The Psiphon client should allow the user to opt-in to this permission. If they decline, fail over
  745. // to using the WiFi IP address.
  746. wifiNetworkID = String.valueOf(wifiInfo.getIpAddress());
  747. }
  748. networkID += "-" + wifiNetworkID;
  749. }
  750. } catch (java.lang.Exception e) {
  751. // May get exceptions due to missing permissions like android.permission.ACCESS_WIFI_STATE.
  752. // Fall through and use just "WIFI"
  753. }
  754. } else if (activeNetworkInfo != null && activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
  755. networkID = "MOBILE";
  756. try {
  757. TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
  758. if (telephonyManager != null) {
  759. networkID += "-" + telephonyManager.getNetworkOperator();
  760. }
  761. } catch (java.lang.Exception e) {
  762. // May get exceptions due to missing permissions.
  763. // Fall through and use just "MOBILE"
  764. }
  765. }
  766. return networkID;
  767. }
  768. //----------------------------------------------------------------------------------------------
  769. // Psiphon Tunnel Core
  770. //----------------------------------------------------------------------------------------------
  771. private void startPsiphon(String embeddedServerEntries) throws Exception {
  772. stopPsiphon();
  773. mIsWaitingForNetworkConnectivity.set(false);
  774. mHostService.onDiagnosticMessage("starting Psiphon library");
  775. try {
  776. // mNetworkMonitor.start() will wait up to 1 second before returning to give the network
  777. // callback a chance to populate active network properties before we start the tunnel.
  778. mNetworkMonitor.start(mHostService.getContext());
  779. Psi.start(
  780. loadPsiphonConfig(mHostService.getContext()),
  781. embeddedServerEntries,
  782. "",
  783. new PsiphonProviderShim(this),
  784. isVpnMode(),
  785. false, // Do not use IPv6 synthesizer for Android
  786. true // Use hasIPv6Route on Android
  787. );
  788. } catch (java.lang.Exception e) {
  789. throw new Exception("failed to start Psiphon library", e);
  790. }
  791. mHostService.onDiagnosticMessage("Psiphon library started");
  792. }
  793. private void stopPsiphon() {
  794. mHostService.onDiagnosticMessage("stopping Psiphon library");
  795. mNetworkMonitor.stop(mHostService.getContext());
  796. Psi.stop();
  797. mHostService.onDiagnosticMessage("Psiphon library stopped");
  798. }
  799. private String loadPsiphonConfig(Context context)
  800. throws IOException, JSONException, Exception {
  801. return buildPsiphonConfig(context, mHostService, mHostService.getPsiphonConfig(),
  802. mClientPlatformPrefix.get(), mClientPlatformSuffix.get(), isVpnMode(),
  803. mLocalSocksProxyPort.get());
  804. }
  805. private static String buildPsiphonConfig(Context context, HostLogger logger, String psiphonConfig,
  806. String clientPlatformPrefix, String clientPlatformSuffix,
  807. boolean isVpnMode, Integer localSocksProxyPort)
  808. throws IOException, JSONException, Exception {
  809. // Load settings from the raw resource JSON config file and
  810. // update as necessary. Then write JSON to disk for the Go client.
  811. JSONObject json = new JSONObject(psiphonConfig);
  812. // On Android, this directory must be set to the app private storage area.
  813. // The Psiphon library won't be able to use its current working directory
  814. // and the standard temporary directories do not exist.
  815. if (!json.has("DataRootDirectory")) {
  816. File dataRootDirectory = defaultDataRootDirectory(context);
  817. if (!dataRootDirectory.exists()) {
  818. boolean created = dataRootDirectory.mkdir();
  819. if (!created) {
  820. throw new Exception("failed to create data root directory: " + dataRootDirectory.getPath());
  821. }
  822. }
  823. json.put("DataRootDirectory", defaultDataRootDirectory(context));
  824. }
  825. // Migrate datastore files from legacy directory.
  826. if (!json.has("DataStoreDirectory")) {
  827. json.put("MigrateDataStoreDirectory", context.getFilesDir());
  828. }
  829. // Migrate remote server list downloads from legacy location.
  830. if (!json.has("RemoteServerListDownloadFilename")) {
  831. File remoteServerListDownload = new File(context.getFilesDir(), "remote_server_list");
  832. json.put("MigrateRemoteServerListDownloadFilename", remoteServerListDownload.getAbsolutePath());
  833. }
  834. // Migrate obfuscated server list download files from legacy directory.
  835. File oslDownloadDir = new File(context.getFilesDir(), "osl");
  836. json.put("MigrateObfuscatedServerListDownloadDirectory", oslDownloadDir.getAbsolutePath());
  837. // Continue to run indefinitely until connected
  838. if (!json.has("EstablishTunnelTimeoutSeconds")) {
  839. json.put("EstablishTunnelTimeoutSeconds", 0);
  840. }
  841. json.put("EmitBytesTransferred", true);
  842. if (localSocksProxyPort != 0 && (!json.has("LocalSocksProxyPort") || json.getInt("LocalSocksProxyPort") == 0)) {
  843. // When mLocalSocksProxyPort is set, tun2socks is already configured
  844. // to use that port value. So we force use of the same port.
  845. // A side-effect of this is that changing the SOCKS port preference
  846. // has no effect with restartPsiphon(), a full stop() is necessary.
  847. json.put("LocalSocksProxyPort", localSocksProxyPort);
  848. }
  849. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  850. try {
  851. json.put(
  852. "TrustedCACertificatesFilename",
  853. setupTrustedCertificates(context, logger));
  854. } catch (Exception e) {
  855. logger.onDiagnosticMessage(e.getMessage());
  856. }
  857. }
  858. json.put("DeviceRegion", getDeviceRegion(context));
  859. StringBuilder clientPlatform = new StringBuilder();
  860. if (clientPlatformPrefix.length() > 0) {
  861. clientPlatform.append(clientPlatformPrefix);
  862. }
  863. clientPlatform.append("Android_");
  864. clientPlatform.append(Build.VERSION.RELEASE);
  865. clientPlatform.append("_");
  866. clientPlatform.append(context.getPackageName());
  867. if (clientPlatformSuffix.length() > 0) {
  868. clientPlatform.append(clientPlatformSuffix);
  869. }
  870. json.put("ClientPlatform", clientPlatform.toString().replaceAll("[^\\w\\-\\.]", "_"));
  871. return json.toString();
  872. }
  873. private void handlePsiphonNotice(String noticeJSON) {
  874. try {
  875. // All notices are sent on as diagnostic messages
  876. // except those that may contain private user data.
  877. boolean diagnostic = true;
  878. JSONObject notice = new JSONObject(noticeJSON);
  879. String noticeType = notice.getString("noticeType");
  880. if (noticeType.equals("Tunnels")) {
  881. int count = notice.getJSONObject("data").getInt("count");
  882. if (count == 0) {
  883. mHostService.onConnecting();
  884. } else if (count == 1) {
  885. if (isVpnMode() && mShouldRouteThroughTunnelAutomatically) {
  886. routeThroughTunnel();
  887. }
  888. mHostService.onConnected();
  889. }
  890. // count > 1 is an additional multi-tunnel establishment, and not reported.
  891. } else if (noticeType.equals("AvailableEgressRegions")) {
  892. JSONArray egressRegions = notice.getJSONObject("data").getJSONArray("regions");
  893. ArrayList<String> regions = new ArrayList<String>();
  894. for (int i=0; i<egressRegions.length(); i++) {
  895. regions.add(egressRegions.getString(i));
  896. }
  897. mHostService.onAvailableEgressRegions(regions);
  898. } else if (noticeType.equals("SocksProxyPortInUse")) {
  899. mHostService.onSocksProxyPortInUse(notice.getJSONObject("data").getInt("port"));
  900. } else if (noticeType.equals("HttpProxyPortInUse")) {
  901. mHostService.onHttpProxyPortInUse(notice.getJSONObject("data").getInt("port"));
  902. } else if (noticeType.equals("ListeningSocksProxyPort")) {
  903. int port = notice.getJSONObject("data").getInt("port");
  904. setLocalSocksProxyPort(port);
  905. mHostService.onListeningSocksProxyPort(port);
  906. } else if (noticeType.equals("ListeningHttpProxyPort")) {
  907. int port = notice.getJSONObject("data").getInt("port");
  908. mHostService.onListeningHttpProxyPort(port);
  909. } else if (noticeType.equals("UpstreamProxyError")) {
  910. diagnostic = false;
  911. mHostService.onUpstreamProxyError(notice.getJSONObject("data").getString("message"));
  912. } else if (noticeType.equals("ClientUpgradeDownloaded")) {
  913. mHostService.onClientUpgradeDownloaded(notice.getJSONObject("data").getString("filename"));
  914. } else if (noticeType.equals("ClientIsLatestVersion")) {
  915. mHostService.onClientIsLatestVersion();
  916. } else if (noticeType.equals("Homepage")) {
  917. mHostService.onHomepage(notice.getJSONObject("data").getString("url"));
  918. } else if (noticeType.equals("ClientRegion")) {
  919. mHostService.onClientRegion(notice.getJSONObject("data").getString("region"));
  920. } else if (noticeType.equals("ClientAddress")) {
  921. diagnostic = false;
  922. mHostService.onClientAddress(notice.getJSONObject("data").getString("address"));
  923. } else if (noticeType.equals("SplitTunnelRegions")) {
  924. JSONArray splitTunnelRegions = notice.getJSONObject("data").getJSONArray("regions");
  925. ArrayList<String> regions = new ArrayList<String>();
  926. for (int i=0; i<splitTunnelRegions.length(); i++) {
  927. regions.add(splitTunnelRegions.getString(i));
  928. }
  929. mHostService.onSplitTunnelRegions(regions);
  930. } else if (noticeType.equals("Untunneled")) {
  931. diagnostic = false;
  932. mHostService.onUntunneledAddress(notice.getJSONObject("data").getString("address"));
  933. } else if (noticeType.equals("BytesTransferred")) {
  934. diagnostic = false;
  935. JSONObject data = notice.getJSONObject("data");
  936. mHostService.onBytesTransferred(data.getLong("sent"), data.getLong("received"));
  937. } else if (noticeType.equals("ActiveAuthorizationIDs")) {
  938. JSONArray activeAuthorizationIDs = notice.getJSONObject("data").getJSONArray("IDs");
  939. ArrayList<String> authorizations = new ArrayList<String>();
  940. for (int i=0; i<activeAuthorizationIDs.length(); i++) {
  941. authorizations.add(activeAuthorizationIDs.getString(i));
  942. }
  943. mHostService.onActiveAuthorizationIDs(authorizations);
  944. } else if (noticeType.equals("TrafficRateLimits")) {
  945. JSONObject data = notice.getJSONObject("data");
  946. mHostService.onTrafficRateLimits(
  947. data.getLong("upstreamBytesPerSecond"), data.getLong("downstreamBytesPerSecond"));
  948. } else if (noticeType.equals("Exiting")) {
  949. mHostService.onExiting();
  950. } else if (noticeType.equals("ActiveTunnel")) {
  951. if (isVpnMode()) {
  952. if (notice.getJSONObject("data").getBoolean("isTCS")) {
  953. disableUdpGwKeepalive();
  954. } else {
  955. enableUdpGwKeepalive();
  956. }
  957. }
  958. } else if (noticeType.equals("ApplicationParameters")) {
  959. mHostService.onApplicationParameters(
  960. notice.getJSONObject("data").get("parameters"));
  961. } else if (noticeType.equals("ServerAlert")) {
  962. JSONArray actionURLs = notice.getJSONObject("data").getJSONArray("actionURLs");
  963. ArrayList<String> actionURLsList = new ArrayList<String>();
  964. for (int i=0; i<actionURLs.length(); i++) {
  965. actionURLsList.add(actionURLs.getString(i));
  966. }
  967. mHostService.onServerAlert(
  968. notice.getJSONObject("data").getString("reason"),
  969. notice.getJSONObject("data").getString("subject"),
  970. actionURLsList);
  971. } else if (noticeType.equals("InproxyOperatorMessage")) {
  972. mHostService.onInproxyOperatorMessage( notice.getJSONObject("data").getString("message"));
  973. } else if (noticeType.equals("InproxyProxyActivity")) {
  974. JSONObject data = notice.getJSONObject("data");
  975. mHostService.onInproxyProxyActivity(
  976. data.getInt("connectingClients"),
  977. data.getInt("connectedClients"),
  978. data.getLong("bytesUp"),
  979. data.getLong("bytesDown"));
  980. }
  981. if (diagnostic) {
  982. String diagnosticMessage = noticeType + ": " + notice.getJSONObject("data").toString();
  983. mHostService.onDiagnosticMessage(diagnosticMessage);
  984. }
  985. } catch (JSONException e) {
  986. // Ignore notice
  987. }
  988. }
  989. private static String setupTrustedCertificates(Context context, HostLogger logger) throws Exception {
  990. // Copy the Android system CA store to a local, private cert bundle file.
  991. //
  992. // This results in a file that can be passed to SSL_CTX_load_verify_locations
  993. // for use with OpenSSL modes in tunnel-core.
  994. // https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_load_verify_locations.html
  995. //
  996. // TODO: to use the path mode of load_verify_locations would require emulating
  997. // the filename scheme used by c_rehash:
  998. // https://www.openssl.org/docs/manmaster/apps/c_rehash.html
  999. // http://stackoverflow.com/questions/19237167/the-new-subject-hash-openssl-algorithm-differs
  1000. File directory = context.getDir("PsiphonCAStore", Context.MODE_PRIVATE);
  1001. final String errorMessage = "copy AndroidCAStore failed";
  1002. try {
  1003. File file = new File(directory, "certs.dat");
  1004. // Pave a fresh copy on every run, which ensures we're not using old certs.
  1005. // Note: assumes KeyStore doesn't return revoked certs.
  1006. //
  1007. // TODO: this takes under 1 second, but should we avoid repaving every time?
  1008. file.delete();
  1009. PrintStream output = null;
  1010. try {
  1011. output = new PrintStream(new FileOutputStream(file));
  1012. KeyStore keyStore;
  1013. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  1014. keyStore = KeyStore.getInstance("AndroidCAStore");
  1015. keyStore.load(null, null);
  1016. } else {
  1017. keyStore = KeyStore.getInstance("BKS");
  1018. FileInputStream inputStream = new FileInputStream("/etc/security/cacerts.bks");
  1019. try {
  1020. keyStore.load(inputStream, "changeit".toCharArray());
  1021. } finally {
  1022. if (inputStream != null) {
  1023. inputStream.close();
  1024. }
  1025. }
  1026. }
  1027. Enumeration<String> aliases = keyStore.aliases();
  1028. while (aliases.hasMoreElements()) {
  1029. String alias = aliases.nextElement();
  1030. X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
  1031. output.println("-----BEGIN CERTIFICATE-----");
  1032. String pemCert = new String(Base64.encode(cert.getEncoded(), Base64.NO_WRAP), "UTF-8");
  1033. // OpenSSL appears to reject the default linebreaking done by Base64.encode,
  1034. // so we manually linebreak every 64 characters
  1035. for (int i = 0; i < pemCert.length() ; i+= 64) {
  1036. output.println(pemCert.substring(i, Math.min(i + 64, pemCert.length())));
  1037. }
  1038. output.println("-----END CERTIFICATE-----");
  1039. }
  1040. logger.onDiagnosticMessage("prepared PsiphonCAStore");
  1041. return file.getAbsolutePath();
  1042. } finally {
  1043. if (output != null) {
  1044. output.close();
  1045. }
  1046. }
  1047. } catch (KeyStoreException e) {
  1048. throw new Exception(errorMessage, e);
  1049. } catch (NoSuchAlgorithmException e) {
  1050. throw new Exception(errorMessage, e);
  1051. } catch (CertificateException e) {
  1052. throw new Exception(errorMessage, e);
  1053. } catch (IOException e) {
  1054. throw new Exception(errorMessage, e);
  1055. }
  1056. }
  1057. private static String getDeviceRegion(Context context) {
  1058. String region = "";
  1059. TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
  1060. if (telephonyManager != null) {
  1061. // getNetworkCountryIso, when present, is preferred over
  1062. // getSimCountryIso, since getNetworkCountryIso is the network
  1063. // the device is currently on, while getSimCountryIso is the home
  1064. // region of the SIM. While roaming, only getNetworkCountryIso
  1065. // may more accurately represent the actual device region.
  1066. if (telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
  1067. region = telephonyManager.getNetworkCountryIso();
  1068. if (region == null) {
  1069. region = "";
  1070. }
  1071. }
  1072. if (region.length() == 0) {
  1073. region = telephonyManager.getSimCountryIso();
  1074. if (region == null) {
  1075. region = "";
  1076. }
  1077. }
  1078. }
  1079. if (region.length() == 0) {
  1080. Locale defaultLocale = Locale.getDefault();
  1081. if (defaultLocale != null) {
  1082. region = defaultLocale.getCountry();
  1083. }
  1084. }
  1085. return region.toUpperCase(Locale.US);
  1086. }
  1087. //----------------------------------------------------------------------------------------------
  1088. // Tun2Socks
  1089. //----------------------------------------------------------------------------------------------
  1090. @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
  1091. private void startTun2Socks(
  1092. final ParcelFileDescriptor vpnInterfaceFileDescriptor,
  1093. final int vpnInterfaceMTU,
  1094. final String vpnIpAddress,
  1095. final String vpnNetMask,
  1096. final String socksServerAddress,
  1097. final String udpgwServerAddress,
  1098. final boolean udpgwTransparentDNS) {
  1099. if (mTun2SocksThread != null) {
  1100. return;
  1101. }
  1102. mTun2SocksThread = new Thread(new Runnable() {
  1103. @Override
  1104. public void run() {
  1105. runTun2Socks(
  1106. vpnInterfaceFileDescriptor.detachFd(),
  1107. vpnInterfaceMTU,
  1108. vpnIpAddress,
  1109. vpnNetMask,
  1110. socksServerAddress,
  1111. udpgwServerAddress,
  1112. udpgwTransparentDNS ? 1 : 0);
  1113. }
  1114. });
  1115. mTun2SocksThread.start();
  1116. mHostService.onDiagnosticMessage("tun2socks started");
  1117. }
  1118. private void stopTun2Socks() {
  1119. if (mTun2SocksThread != null) {
  1120. try {
  1121. terminateTun2Socks();
  1122. mTun2SocksThread.join();
  1123. } catch (InterruptedException e) {
  1124. Thread.currentThread().interrupt();
  1125. }
  1126. mTun2SocksThread = null;
  1127. mHostService.onDiagnosticMessage("tun2socks stopped");
  1128. }
  1129. }
  1130. public static void logTun2Socks(String level, String channel, String msg) {
  1131. String logMsg = "tun2socks: " + level + "(" + channel + "): " + msg;
  1132. mPsiphonTunnel.mHostService.onDiagnosticMessage(logMsg);
  1133. }
  1134. private native static int runTun2Socks(
  1135. int vpnInterfaceFileDescriptor,
  1136. int vpnInterfaceMTU,
  1137. String vpnIpAddress,
  1138. String vpnNetMask,
  1139. String socksServerAddress,
  1140. String udpgwServerAddress,
  1141. int udpgwTransparentDNS);
  1142. private native static int terminateTun2Socks();
  1143. private native static int enableUdpGwKeepalive();
  1144. private native static int disableUdpGwKeepalive();
  1145. //----------------------------------------------------------------------------------------------
  1146. // Implementation: Network Utils
  1147. //----------------------------------------------------------------------------------------------
  1148. private static boolean hasNetworkConnectivity(Context context) {
  1149. ConnectivityManager connectivityManager =
  1150. (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
  1151. if (connectivityManager == null) {
  1152. return false;
  1153. }
  1154. NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
  1155. return networkInfo != null && networkInfo.isConnected();
  1156. }
  1157. private static class PrivateAddress {
  1158. final public String mIpAddress;
  1159. final public String mSubnet;
  1160. final public int mPrefixLength;
  1161. final public String mRouter;
  1162. public PrivateAddress(String ipAddress, String subnet, int prefixLength, String router) {
  1163. mIpAddress = ipAddress;
  1164. mSubnet = subnet;
  1165. mPrefixLength = prefixLength;
  1166. mRouter = router;
  1167. }
  1168. }
  1169. private static PrivateAddress selectPrivateAddress() throws Exception {
  1170. // Select one of 10.0.0.1, 172.16.0.1, or 192.168.0.1 depending on
  1171. // which private address range isn't in use.
  1172. Map<String, PrivateAddress> candidates = new HashMap<String, PrivateAddress>();
  1173. candidates.put( "10", new PrivateAddress("10.0.0.1", "10.0.0.0", 8, "10.0.0.2"));
  1174. candidates.put("172", new PrivateAddress("172.16.0.1", "172.16.0.0", 12, "172.16.0.2"));
  1175. candidates.put("192", new PrivateAddress("192.168.0.1", "192.168.0.0", 16, "192.168.0.2"));
  1176. candidates.put("169", new PrivateAddress("169.254.1.1", "169.254.1.0", 24, "169.254.1.2"));
  1177. Enumeration<NetworkInterface> netInterfaces;
  1178. try {
  1179. netInterfaces = NetworkInterface.getNetworkInterfaces();
  1180. } catch (SocketException e) {
  1181. throw new Exception("selectPrivateAddress failed", e);
  1182. }
  1183. if (netInterfaces == null) {
  1184. throw new Exception("no network interfaces found");
  1185. }
  1186. for (NetworkInterface netInterface : Collections.list(netInterfaces)) {
  1187. for (InetAddress inetAddress : Collections.list(netInterface.getInetAddresses())) {
  1188. if (inetAddress instanceof Inet4Address) {
  1189. String ipAddress = inetAddress.getHostAddress();
  1190. if (ipAddress.startsWith("10.")) {
  1191. candidates.remove("10");
  1192. }
  1193. else if (
  1194. ipAddress.length() >= 6 &&
  1195. ipAddress.substring(0, 6).compareTo("172.16") >= 0 &&
  1196. ipAddress.substring(0, 6).compareTo("172.31") <= 0) {
  1197. candidates.remove("172");
  1198. }
  1199. else if (ipAddress.startsWith("192.168")) {
  1200. candidates.remove("192");
  1201. }
  1202. }
  1203. }
  1204. }
  1205. if (candidates.size() > 0) {
  1206. return candidates.values().iterator().next();
  1207. }
  1208. throw new Exception("no private address available");
  1209. }
  1210. private static Collection<String> getActiveNetworkDNSServers(Context context, boolean isVpnMode)
  1211. throws Exception {
  1212. ArrayList<String> servers = new ArrayList<String>();
  1213. for (InetAddress serverAddress : getActiveNetworkDNSServerAddresses(context, isVpnMode)) {
  1214. String server = serverAddress.toString();
  1215. // strip the leading slash e.g., "/192.168.1.1"
  1216. if (server.startsWith("/")) {
  1217. server = server.substring(1);
  1218. }
  1219. servers.add(server);
  1220. }
  1221. if (servers.isEmpty()) {
  1222. throw new Exception("no active network DNS resolver");
  1223. }
  1224. return servers;
  1225. }
  1226. private static Collection<InetAddress> getActiveNetworkDNSServerAddresses(Context context, boolean isVpnMode)
  1227. throws Exception {
  1228. final String errorMessage = "getActiveNetworkDNSServerAddresses failed";
  1229. ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
  1230. ConnectivityManager connectivityManager =
  1231. (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  1232. if (connectivityManager == null) {
  1233. throw new Exception(errorMessage, new Throwable("couldn't get ConnectivityManager system service"));
  1234. }
  1235. try {
  1236. // Hidden API:
  1237. //
  1238. // - Only available in Android 4.0+
  1239. // - No guarantee will be available beyond 4.2, or on all vendor
  1240. // devices
  1241. // - Field reports indicate this is no longer working on some --
  1242. // but not all -- Android 10+ devices
  1243. Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
  1244. Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
  1245. Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
  1246. if (linkProperties != null) {
  1247. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  1248. Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses", new Class []{});
  1249. Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
  1250. for (Object dns : dnses) {
  1251. dnsAddresses.add((InetAddress)dns);
  1252. }
  1253. } else {
  1254. // LinkProperties is public in API 21 (and the DNS function signature has changed)
  1255. for (InetAddress dns : ((LinkProperties)linkProperties).getDnsServers()) {
  1256. dnsAddresses.add(dns);
  1257. }
  1258. }
  1259. }
  1260. } catch (ClassNotFoundException e) {
  1261. } catch (NoSuchMethodException e) {
  1262. } catch (IllegalArgumentException e) {
  1263. } catch (IllegalAccessException e) {
  1264. } catch (InvocationTargetException e) {
  1265. } catch (NullPointerException e) {
  1266. }
  1267. if (!dnsAddresses.isEmpty()) {
  1268. return dnsAddresses;
  1269. }
  1270. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  1271. // This case is attempted only when the hidden API fails:
  1272. //
  1273. // - Testing shows the hidden API still works more reliably on
  1274. // some Android 11+ devices
  1275. // - Testing indicates that the NetworkRequest can sometimes
  1276. // select the wrong network
  1277. // - e.g., mobile instead of WiFi, and return the wrong DNS
  1278. // servers
  1279. // - there's currently no way to filter for the "currently
  1280. // active default data network" returned by, e.g., the
  1281. // deprecated getActiveNetworkInfo
  1282. // - we cannot add the NET_CAPABILITY_FOREGROUND capability to
  1283. // the NetworkRequest at this time due to target SDK
  1284. // constraints
  1285. NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder()
  1286. .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
  1287. if (isVpnMode) {
  1288. // In VPN mode, we want the DNS servers for the underlying physical network.
  1289. networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
  1290. }
  1291. NetworkRequest networkRequest = networkRequestBuilder.build();
  1292. // There is a potential race condition in which the following
  1293. // network callback may be invoked, by a worker thread, after
  1294. // unregisterNetworkCallback. Synchronized access to a local
  1295. // ArrayList copy avoids the
  1296. // java.util.ConcurrentModificationException crash we previously
  1297. // observed when getActiveNetworkDNSServers iterated over the
  1298. // same ArrayList object value that was modified by the
  1299. // callback.
  1300. //
  1301. // The late invocation of the callback still results in an empty
  1302. // list of DNS servers, but this behavior has been observed only
  1303. // in artificial conditions while rapidly starting and stopping
  1304. // PsiphonTunnel.
  1305. ArrayList<InetAddress> callbackDnsAddresses = new ArrayList<InetAddress>();
  1306. final CountDownLatch countDownLatch = new CountDownLatch(1);
  1307. try {
  1308. ConnectivityManager.NetworkCallback networkCallback =
  1309. new ConnectivityManager.NetworkCallback() {
  1310. @Override
  1311. public void onLinkPropertiesChanged(Network network,
  1312. LinkProperties linkProperties) {
  1313. synchronized (callbackDnsAddresses) {
  1314. callbackDnsAddresses.addAll(linkProperties.getDnsServers());
  1315. }
  1316. countDownLatch.countDown();
  1317. }
  1318. };
  1319. connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
  1320. countDownLatch.await(1, TimeUnit.SECONDS);
  1321. connectivityManager.unregisterNetworkCallback(networkCallback);
  1322. } catch (RuntimeException ignored) {
  1323. // Failed to register network callback
  1324. } catch (InterruptedException e) {
  1325. Thread.currentThread().interrupt();
  1326. }
  1327. synchronized (callbackDnsAddresses) {
  1328. dnsAddresses.addAll(callbackDnsAddresses);
  1329. }
  1330. }
  1331. return dnsAddresses;
  1332. }
  1333. private static boolean hasIPv6Route(Context context) throws Exception {
  1334. try {
  1335. // This logic mirrors the logic in
  1336. // psiphon/common/resolver.hasRoutableIPv6Interface. That
  1337. // function currently doesn't work on Android due to Go's
  1338. // net.InterfaceAddrs failing on Android SDK 30+ (see Go issue
  1339. // 40569). hasIPv6Route provides the same functionality via a
  1340. // callback into Java code.
  1341. // Note: don't exclude interfaces with the isPointToPoint
  1342. // property, which is true for certain mobile networks.
  1343. for (NetworkInterface netInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
  1344. if (netInterface.isUp() &&
  1345. !netInterface.isLoopback()) {
  1346. for (InetAddress address : Collections.list(netInterface.getInetAddresses())) {
  1347. // Per https://developer.android.com/reference/java/net/Inet6Address#textual-representation-of-ip-addresses,
  1348. // "Java will never return an IPv4-mapped address.
  1349. // These classes can take an IPv4-mapped address as
  1350. // input, both in byte array and text
  1351. // representation. However, it will be converted
  1352. // into an IPv4 address." As such, when the type of
  1353. // the IP address is Inet6Address, this should be
  1354. // an actual IPv6 address.
  1355. if (address instanceof Inet6Address &&
  1356. !address.isLinkLocalAddress() &&
  1357. !address.isSiteLocalAddress() &&
  1358. !address.isMulticastAddress ()) {
  1359. return true;
  1360. }
  1361. }
  1362. }
  1363. }
  1364. } catch (SocketException e) {
  1365. throw new Exception("hasIPv6Route failed", e);
  1366. }
  1367. return false;
  1368. }
  1369. //----------------------------------------------------------------------------------------------
  1370. // Exception
  1371. //----------------------------------------------------------------------------------------------
  1372. public static class Exception extends java.lang.Exception {
  1373. private static final long serialVersionUID = 1L;
  1374. public Exception(String message) {
  1375. super(message);
  1376. }
  1377. public Exception(String message, Throwable cause) {
  1378. super(message + ": " + cause.getMessage());
  1379. }
  1380. }
  1381. //----------------------------------------------------------------------------------------------
  1382. // Network connectivity monitor
  1383. //----------------------------------------------------------------------------------------------
  1384. private static class NetworkMonitor {
  1385. private final NetworkChangeListener listener;
  1386. private ConnectivityManager.NetworkCallback networkCallback;
  1387. public NetworkMonitor(
  1388. NetworkChangeListener listener) {
  1389. this.listener = listener;
  1390. }
  1391. private void start(Context context) throws InterruptedException {
  1392. final CountDownLatch setNetworkPropertiesCountDownLatch = new CountDownLatch(1);
  1393. // Need API 21(LOLLIPOP)+ for ConnectivityManager.NetworkCallback
  1394. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  1395. return;
  1396. }
  1397. ConnectivityManager connectivityManager =
  1398. (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  1399. if (connectivityManager == null) {
  1400. return;
  1401. }
  1402. networkCallback = new ConnectivityManager.NetworkCallback() {
  1403. private boolean isInitialState = true;
  1404. private Network currentActiveNetwork;
  1405. private void consumeActiveNetwork(Network network) {
  1406. if (isInitialState) {
  1407. isInitialState = false;
  1408. setCurrentActiveNetworkAndProperties(network);
  1409. return;
  1410. }
  1411. if (!network.equals(currentActiveNetwork)) {
  1412. setCurrentActiveNetworkAndProperties(network);
  1413. if (listener != null) {
  1414. listener.onChanged();
  1415. }
  1416. }
  1417. }
  1418. private void consumeLostNetwork(Network network) {
  1419. if (network.equals(currentActiveNetwork)) {
  1420. setCurrentActiveNetworkAndProperties(null);
  1421. if (listener != null) {
  1422. listener.onChanged();
  1423. }
  1424. }
  1425. }
  1426. private void setCurrentActiveNetworkAndProperties(Network network) {
  1427. currentActiveNetwork = network;
  1428. if (network == null) {
  1429. mPsiphonTunnel.mActiveNetworkType.set("NONE");
  1430. mPsiphonTunnel.mActiveNetworkDNSServers.set("");
  1431. mPsiphonTunnel.mHostService.onDiagnosticMessage("NetworkMonitor: clear current active network");
  1432. } else {
  1433. String networkType = "UNKNOWN";
  1434. try {
  1435. // Limitation: a network may have both CELLULAR
  1436. // and WIFI transports, or different network
  1437. // transport types entirely. This logic currently
  1438. // mimics the type determination logic in
  1439. // getNetworkID.
  1440. NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
  1441. if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
  1442. networkType = "VPN";
  1443. } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
  1444. networkType = "MOBILE";
  1445. } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
  1446. networkType = "WIFI";
  1447. }
  1448. } catch (java.lang.Exception e) {
  1449. }
  1450. mPsiphonTunnel.mActiveNetworkType.set(networkType);
  1451. ArrayList<String> servers = new ArrayList<String>();
  1452. try {
  1453. LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
  1454. List<InetAddress> serverAddresses = linkProperties.getDnsServers();
  1455. for (InetAddress serverAddress : serverAddresses) {
  1456. String server = serverAddress.toString();
  1457. if (server.startsWith("/")) {
  1458. server = server.substring(1);
  1459. }
  1460. servers.add(server);
  1461. }
  1462. } catch (java.lang.Exception e) {
  1463. }
  1464. // Use the workaround, comma-delimited format required for gobind.
  1465. mPsiphonTunnel.mActiveNetworkDNSServers.set(TextUtils.join(",", servers));
  1466. String message = "NetworkMonitor: set current active network " + networkType;
  1467. if (!servers.isEmpty()) {
  1468. // The DNS server address is potential PII and not logged.
  1469. message += " with DNS";
  1470. }
  1471. mPsiphonTunnel.mHostService.onDiagnosticMessage(message);
  1472. }
  1473. setNetworkPropertiesCountDownLatch.countDown();
  1474. }
  1475. @Override
  1476. public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
  1477. super.onCapabilitiesChanged(network, capabilities);
  1478. // Need API 23(M)+ for NET_CAPABILITY_VALIDATED
  1479. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
  1480. return;
  1481. }
  1482. // https://developer.android.com/reference/android/net/NetworkCapabilities#NET_CAPABILITY_VALIDATED
  1483. // Indicates that connectivity on this network was successfully validated.
  1484. // For example, for a network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was
  1485. // successfully detected.
  1486. if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
  1487. consumeActiveNetwork(network);
  1488. }
  1489. }
  1490. @Override
  1491. public void onAvailable(Network network) {
  1492. super.onAvailable(network);
  1493. // Skip on API 26(O)+ because onAvailable is guaranteed to be followed by
  1494. // onCapabilitiesChanged
  1495. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  1496. return;
  1497. }
  1498. consumeActiveNetwork(network);
  1499. }
  1500. @Override
  1501. public void onLost(Network network) {
  1502. super.onLost(network);
  1503. consumeLostNetwork(network);
  1504. }
  1505. };
  1506. try {
  1507. // When searching for a network to satisfy a request, all capabilities requested must be satisfied.
  1508. NetworkRequest.Builder builder = new NetworkRequest.Builder()
  1509. // Indicates that this network should be able to reach the internet.
  1510. .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
  1511. if (mPsiphonTunnel.mVpnMode.get()) {
  1512. // If we are in the VPN mode then ensure we monitor only the VPN's underlying
  1513. // active networks and not self.
  1514. builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
  1515. } else {
  1516. // If we are NOT in the VPN mode then monitor default active networks with the
  1517. // Internet capability, including VPN, to ensure we won't trigger a reconnect in
  1518. // case the VPN is up while the system switches the underlying network.
  1519. // Limitation: for Psiphon Library apps running over Psiphon VPN, or other VPNs
  1520. // with a similar architecture, it may be better to trigger a reconnect when
  1521. // the underlying physical network changes. When the underlying network
  1522. // changes, Psiphon VPN will remain up and reconnect its own tunnel. For the
  1523. // Psiphon app, this monitoring will detect no change. However, the Psiphon
  1524. // app's tunnel may be lost, and, without network change detection, initiating
  1525. // a reconnect will be delayed. For example, if the Psiphon app's tunnel is
  1526. // using QUIC, the Psiphon VPN will tunnel that traffic over udpgw. When
  1527. // Psiphon VPN reconnects, the egress source address of that UDP flow will
  1528. // change -- getting either a different source IP if the Psiphon server
  1529. // changes, or a different source port even if the same server -- and the QUIC
  1530. // server will drop the packets. The Psiphon app will initiate a reconnect only
  1531. // after a SSH keep alive probes timeout or a QUIC timeout.
  1532. //
  1533. // TODO: Add a second ConnectivityManager/NetworkRequest instance to monitor
  1534. // for underlying physical network changes while any VPN remains up.
  1535. builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
  1536. }
  1537. NetworkRequest networkRequest = builder.build();
  1538. // We are using requestNetwork and not registerNetworkCallback here because we found
  1539. // that the callbacks from requestNetwork are more accurate in terms of tracking
  1540. // currently active network. Another alternative to use for tracking active network
  1541. // would be registerDefaultNetworkCallback but a) it needs API >= 24 and b) doesn't
  1542. // provide a way to set up monitoring of underlying networks only when VPN transport
  1543. // is also active.
  1544. connectivityManager.requestNetwork(networkRequest, networkCallback);
  1545. } catch (RuntimeException ignored) {
  1546. // Could be a security exception or any other runtime exception on customized firmwares.
  1547. networkCallback = null;
  1548. }
  1549. // We are going to wait up to one second for the network callback to populate
  1550. // active network properties before returning.
  1551. setNetworkPropertiesCountDownLatch.await(1, TimeUnit.SECONDS);
  1552. }
  1553. private void stop(Context context) {
  1554. if (networkCallback == null) {
  1555. return;
  1556. }
  1557. // Need API 21(LOLLIPOP)+ for ConnectivityManager.NetworkCallback
  1558. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  1559. return;
  1560. }
  1561. ConnectivityManager connectivityManager =
  1562. (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  1563. if (connectivityManager == null) {
  1564. return;
  1565. }
  1566. // Note: ConnectivityManager.unregisterNetworkCallback() may throw
  1567. // "java.lang.IllegalArgumentException: NetworkCallback was not registered".
  1568. // This scenario should be handled in the start() above but we'll add a try/catch
  1569. // anyway to match the start's call to ConnectivityManager.registerNetworkCallback()
  1570. try {
  1571. connectivityManager.unregisterNetworkCallback(networkCallback);
  1572. } catch (RuntimeException ignored) {
  1573. }
  1574. networkCallback = null;
  1575. }
  1576. public interface NetworkChangeListener {
  1577. void onChanged();
  1578. }
  1579. }
  1580. }