PsiphonTunnel.java 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  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.NetworkInfo;
  25. import android.net.VpnService;
  26. import android.net.wifi.WifiInfo;
  27. import android.net.wifi.WifiManager;
  28. import android.os.Build;
  29. import android.os.ParcelFileDescriptor;
  30. import android.telephony.TelephonyManager;
  31. import android.util.Base64;
  32. import org.json.JSONArray;
  33. import org.json.JSONException;
  34. import org.json.JSONObject;
  35. import java.io.File;
  36. import java.io.FileInputStream;
  37. import java.io.FileOutputStream;
  38. import java.io.IOException;
  39. import java.io.PrintStream;
  40. import java.lang.reflect.InvocationTargetException;
  41. import java.lang.reflect.Method;
  42. import java.net.Inet4Address;
  43. import java.net.InetAddress;
  44. import java.net.NetworkInterface;
  45. import java.net.SocketException;
  46. import java.security.KeyStore;
  47. import java.security.KeyStoreException;
  48. import java.security.NoSuchAlgorithmException;
  49. import java.security.cert.CertificateException;
  50. import java.security.cert.X509Certificate;
  51. import java.util.ArrayList;
  52. import java.util.Collection;
  53. import java.util.Collections;
  54. import java.util.Enumeration;
  55. import java.util.HashMap;
  56. import java.util.List;
  57. import java.util.Locale;
  58. import java.util.Map;
  59. import java.util.concurrent.atomic.AtomicBoolean;
  60. import java.util.concurrent.atomic.AtomicInteger;
  61. import java.util.concurrent.atomic.AtomicReference;
  62. import psi.Psi;
  63. import psi.PsiphonProvider;
  64. import psi.PsiphonProviderNoticeHandler;
  65. public class PsiphonTunnel {
  66. public interface HostLogger {
  67. default public void onDiagnosticMessage(String message) {}
  68. }
  69. public interface HostService extends HostLogger {
  70. public String getAppName();
  71. public Context getContext();
  72. public String getPsiphonConfig();
  73. default public Object getVpnService() {return null;} // Object must be a VpnService (Android < 4 cannot reference this class name)
  74. default public Object newVpnServiceBuilder() {return null;} // Object must be a VpnService.Builder (Android < 4 cannot reference this class name)
  75. default public void onAvailableEgressRegions(List<String> regions) {}
  76. default public void onSocksProxyPortInUse(int port) {}
  77. default public void onHttpProxyPortInUse(int port) {}
  78. default public void onListeningSocksProxyPort(int port) {}
  79. default public void onListeningHttpProxyPort(int port) {}
  80. default public void onUpstreamProxyError(String message) {}
  81. default public void onConnecting() {}
  82. default public void onConnected() {}
  83. default public void onHomepage(String url) {}
  84. default public void onClientRegion(String region) {}
  85. default public void onClientUpgradeDownloaded(String filename) {}
  86. default public void onClientIsLatestVersion() {}
  87. default public void onSplitTunnelRegion(String region) {}
  88. default public void onUntunneledAddress(String address) {}
  89. default public void onBytesTransferred(long sent, long received) {}
  90. default public void onStartedWaitingForNetworkConnectivity() {}
  91. default public void onStoppedWaitingForNetworkConnectivity() {}
  92. default public void onActiveAuthorizationIDs(List<String> authorizations) {}
  93. default public void onTrafficRateLimits(long upstreamBytesPerSecond, long downstreamBytesPerSecond) {}
  94. default public void onApplicationParameter(String key, Object value) {}
  95. default public void onServerAlert(String reason, String subject) {}
  96. default public void onExiting() {}
  97. }
  98. private final HostService mHostService;
  99. private AtomicBoolean mVpnMode;
  100. private PrivateAddress mPrivateAddress;
  101. private AtomicReference<ParcelFileDescriptor> mTunFd;
  102. private AtomicInteger mLocalSocksProxyPort;
  103. private AtomicBoolean mRoutingThroughTunnel;
  104. private Thread mTun2SocksThread;
  105. private AtomicBoolean mIsWaitingForNetworkConnectivity;
  106. private AtomicReference<String> mClientPlatformPrefix;
  107. private AtomicReference<String> mClientPlatformSuffix;
  108. private final boolean mShouldRouteThroughTunnelAutomatically;
  109. // Only one PsiphonVpn instance may exist at a time, as the underlying
  110. // psi.Psi and tun2socks implementations each contain global state.
  111. private static PsiphonTunnel mPsiphonTunnel;
  112. public static synchronized PsiphonTunnel newPsiphonTunnel(HostService hostService) {
  113. return newPsiphonTunnelImpl(hostService, true);
  114. }
  115. // The two argument override in case the host app wants to take control over calling routeThroughTunnel()
  116. public static synchronized PsiphonTunnel newPsiphonTunnel(HostService hostService, boolean shouldRouteThroughTunnelAutomatically) {
  117. return newPsiphonTunnelImpl(hostService, shouldRouteThroughTunnelAutomatically);
  118. }
  119. private static PsiphonTunnel newPsiphonTunnelImpl(HostService hostService, boolean shouldRouteThroughTunnelAutomatically) {
  120. if (mPsiphonTunnel != null) {
  121. mPsiphonTunnel.stop();
  122. }
  123. // Load the native go code embedded in psi.aar
  124. System.loadLibrary("gojni");
  125. mPsiphonTunnel = new PsiphonTunnel(hostService, shouldRouteThroughTunnelAutomatically);
  126. return mPsiphonTunnel;
  127. }
  128. // Returns default path where upgrade downloads will be paved. Only applicable if
  129. // DataRootDirectory was not set in the outer config. If DataRootDirectory was set in the
  130. // outer config, use getUpgradeDownloadFilePath with its value instead.
  131. public static String getDefaultUpgradeDownloadFilePath(Context context) {
  132. return Psi.upgradeDownloadFilePath(defaultDataRootDirectory(context).getAbsolutePath());
  133. }
  134. // Returns the path where upgrade downloads will be paved relative to the configured
  135. // DataRootDirectory.
  136. public static String getUpgradeDownloadFilePath(String dataRootDirectoryPath) {
  137. return Psi.upgradeDownloadFilePath(dataRootDirectoryPath);
  138. }
  139. private static File defaultDataRootDirectory(Context context) {
  140. return context.getFileStreamPath("ca.psiphon.PsiphonTunnel.tunnel-core");
  141. }
  142. private PsiphonTunnel(HostService hostService, boolean shouldRouteThroughTunnelAutomatically) {
  143. mHostService = hostService;
  144. mVpnMode = new AtomicBoolean(false);
  145. mTunFd = new AtomicReference<ParcelFileDescriptor>();
  146. mLocalSocksProxyPort = new AtomicInteger(0);
  147. mRoutingThroughTunnel = new AtomicBoolean(false);
  148. mIsWaitingForNetworkConnectivity = new AtomicBoolean(false);
  149. mClientPlatformPrefix = new AtomicReference<String>("");
  150. mClientPlatformSuffix = new AtomicReference<String>("");
  151. mShouldRouteThroughTunnelAutomatically = shouldRouteThroughTunnelAutomatically;
  152. }
  153. public Object clone() throws CloneNotSupportedException {
  154. throw new CloneNotSupportedException();
  155. }
  156. //----------------------------------------------------------------------------------------------
  157. // Public API
  158. //----------------------------------------------------------------------------------------------
  159. // To start, call in sequence: startRouting(), then startTunneling(). After startRouting()
  160. // succeeds, the caller must call stop() to clean up. These functions should not be called
  161. // concurrently. Do not call stop() while startRouting() or startTunneling() is in progress.
  162. // In case the host application requests manual control of routing through tunnel by calling
  163. // PsiphonTunnel.newPsiphonTunnel(HostService hostservice, shouldRouteThroughTunnelAutomatically = false)
  164. // it should also call routeThroughTunnel() at some point, usually after receiving onConnected() callback,
  165. // otherwise it will be called automatically.
  166. // Returns true when the VPN routing is established; returns false if the VPN could not
  167. // be started due to lack of prepare or revoked permissions (called should re-prepare and
  168. // try again); throws exception for other error conditions.
  169. public synchronized boolean startRouting() throws Exception {
  170. // Load tun2socks library embedded in the aar
  171. // If this method is called more than once with the same library name, the second and subsequent calls are ignored.
  172. // http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#loadLibrary%28java.lang.String%29
  173. System.loadLibrary("tun2socks");
  174. return startVpn();
  175. }
  176. // Starts routing traffic via tunnel by starting tun2socks if it is not running already.
  177. // This will be called automatically right after tunnel gets connected in case the host application
  178. // did not request a manual control over this functionality, see PsiphonTunnel.newPsiphonTunnel
  179. public void routeThroughTunnel() {
  180. if (!mRoutingThroughTunnel.compareAndSet(false, true)) {
  181. return;
  182. }
  183. ParcelFileDescriptor tunFd = mTunFd.getAndSet(null);
  184. if (tunFd == null) {
  185. return;
  186. }
  187. String socksServerAddress = "127.0.0.1:" + Integer.toString(mLocalSocksProxyPort.get());
  188. String udpgwServerAddress = "127.0.0.1:" + Integer.toString(UDPGW_SERVER_PORT);
  189. startTun2Socks(
  190. tunFd,
  191. VPN_INTERFACE_MTU,
  192. mPrivateAddress.mRouter,
  193. VPN_INTERFACE_NETMASK,
  194. socksServerAddress,
  195. udpgwServerAddress,
  196. true);
  197. mHostService.onDiagnosticMessage("routing through tunnel");
  198. // TODO: should double-check tunnel routing; see:
  199. // https://bitbucket.org/psiphon/psiphon-circumvention-system/src/1dc5e4257dca99790109f3bf374e8ab3a0ead4d7/Android/PsiphonAndroidLibrary/src/com/psiphon3/psiphonlibrary/TunnelCore.java?at=default#cl-779
  200. }
  201. // Throws an exception in error conditions. In the case of an exception, the routing
  202. // started by startRouting() is not immediately torn down (this allows the caller to control
  203. // exactly when VPN routing is stopped); caller should call stop() to clean up.
  204. public synchronized void startTunneling(String embeddedServerEntries) throws Exception {
  205. startPsiphon(embeddedServerEntries);
  206. }
  207. // Note: to avoid deadlock, do not call directly from a HostService callback;
  208. // instead post to a Handler if necessary to trigger from a HostService callback.
  209. // For example, deadlock can occur when a Notice callback invokes stop() since stop() calls
  210. // Psi.stop() which will block waiting for tunnel-core Controller to shutdown which in turn
  211. // waits for Notice callback invoker to stop, meanwhile the callback thread has blocked waiting
  212. // for stop().
  213. public synchronized void stop() {
  214. stopVpn();
  215. stopPsiphon();
  216. mVpnMode.set(false);
  217. mLocalSocksProxyPort.set(0);
  218. }
  219. // Note: same deadlock note as stop().
  220. public synchronized void restartPsiphon() throws Exception {
  221. stopPsiphon();
  222. startPsiphon("");
  223. }
  224. // Creates a temporary dummy VPN interface in order to prevent traffic leaking while performing
  225. // complete VPN and tunnel restart, for example, caused by host app settings change.
  226. // Note: same deadlock note as stop().
  227. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  228. public synchronized void seamlessVpnRestart(VpnService.Builder vpnServiceBuilder) throws Exception {
  229. // Perform seamless VPN interface swap Psiphon VPN -> dummy VPN
  230. //
  231. // From https://developer.android.com/reference/android/net/VpnService.Builder.html#establish()
  232. // "However, it is rare but not impossible to have two interfaces while performing a seamless handover.
  233. // In this case, the old interface will be deactivated when the new one is created successfully. Both
  234. // file descriptors are valid but now outgoing packets will be routed to the new interface. Therefore,
  235. // after draining the old file descriptor, the application MUST close it and start using the new file
  236. // descriptor."
  237. ParcelFileDescriptor dummyVpnFd = startDummyVpn(vpnServiceBuilder);
  238. try {
  239. // Clean up and restart Psiphon VPN interface, which will also do the swap dummy VPN -> Psiphon VPN
  240. stopVpn();
  241. startVpn();
  242. } finally {
  243. // Close dummy VPN file descriptor as per documentation.
  244. if (dummyVpnFd != null) {
  245. try {
  246. dummyVpnFd.close();
  247. } catch (IOException e) {
  248. }
  249. }
  250. }
  251. // Restart the tunnel.
  252. restartPsiphon();
  253. }
  254. public void setClientPlatformAffixes(String prefix, String suffix) {
  255. mClientPlatformPrefix.set(prefix);
  256. mClientPlatformSuffix.set(suffix);
  257. }
  258. public String exportExchangePayload() {
  259. return Psi.exportExchangePayload();
  260. }
  261. public boolean importExchangePayload(String payload) {
  262. return Psi.importExchangePayload(payload);
  263. }
  264. // Upload a feedback package to Psiphon Inc. The app collects feedback and diagnostics
  265. // information in a particular format, then calls this function to upload it for later
  266. // investigation. The feedback compatible config and upload path must be provided by
  267. // Psiphon Inc.
  268. public static void sendFeedback(Context context, HostLogger logger, String feedbackConfigJson,
  269. String diagnosticsJson, String uploadPath,
  270. String clientPlatformPrefix, String clientPlatformSuffix) throws Exception {
  271. try {
  272. // Adds fields used in feedback upload, e.g. client platform.
  273. String psiphonConfig = buildPsiphonConfig(context, logger, feedbackConfigJson,
  274. clientPlatformPrefix, clientPlatformSuffix, false, 0);
  275. PsiphonProviderNoticeHandlerShim noticeHandler = new PsiphonProviderNoticeHandlerShim(logger);
  276. Psi.sendFeedback(psiphonConfig, diagnosticsJson, uploadPath, noticeHandler);
  277. } catch (java.lang.Exception e) {
  278. throw new Exception("Error sending feedback", e);
  279. }
  280. }
  281. // Writes Go runtime profile information to a set of files in the specifiec output directory.
  282. // cpuSampleDurationSeconds and blockSampleDurationSeconds determines how to long to wait and
  283. // sample profiles that require active sampling. When set to 0, these profiles are skipped.
  284. public void writeRuntimeProfiles(String outputDirectory, int cpuSampleDurationSeconnds, int blockSampleDurationSeconds) {
  285. Psi.writeRuntimeProfiles(outputDirectory, cpuSampleDurationSeconnds, blockSampleDurationSeconds);
  286. }
  287. //----------------------------------------------------------------------------------------------
  288. // VPN Routing
  289. //----------------------------------------------------------------------------------------------
  290. private final static String VPN_INTERFACE_NETMASK = "255.255.255.0";
  291. private final static int VPN_INTERFACE_MTU = 1500;
  292. private final static int UDPGW_SERVER_PORT = 7300;
  293. private final static String DEFAULT_PRIMARY_DNS_SERVER = "8.8.4.4";
  294. private final static String DEFAULT_SECONDARY_DNS_SERVER = "8.8.8.8";
  295. // Note: Atomic variables used for getting/setting local proxy port, routing flag, and
  296. // tun fd, as these functions may be called via PsiphonProvider callbacks. Do not use
  297. // synchronized functions as stop() is synchronized and a deadlock is possible as callbacks
  298. // can be called while stop holds the lock.
  299. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  300. private boolean startVpn() throws Exception {
  301. mVpnMode.set(true);
  302. mPrivateAddress = selectPrivateAddress();
  303. Locale previousLocale = Locale.getDefault();
  304. final String errorMessage = "startVpn failed";
  305. try {
  306. // Workaround for https://code.google.com/p/android/issues/detail?id=61096
  307. Locale.setDefault(new Locale("en"));
  308. int mtu = VPN_INTERFACE_MTU;
  309. String dnsResolver = mPrivateAddress.mRouter;
  310. ParcelFileDescriptor tunFd =
  311. ((VpnService.Builder) mHostService.newVpnServiceBuilder())
  312. .setSession(mHostService.getAppName())
  313. .setMtu(mtu)
  314. .addAddress(mPrivateAddress.mIpAddress, mPrivateAddress.mPrefixLength)
  315. .addRoute("0.0.0.0", 0)
  316. .addRoute(mPrivateAddress.mSubnet, mPrivateAddress.mPrefixLength)
  317. .addDnsServer(dnsResolver)
  318. .establish();
  319. if (tunFd == null) {
  320. // As per http://developer.android.com/reference/android/net/VpnService.Builder.html#establish%28%29,
  321. // this application is no longer prepared or was revoked.
  322. return false;
  323. }
  324. mTunFd.set(tunFd);
  325. mRoutingThroughTunnel.set(false);
  326. mHostService.onDiagnosticMessage("VPN established");
  327. } catch(IllegalArgumentException e) {
  328. throw new Exception(errorMessage, e);
  329. } catch(IllegalStateException e) {
  330. throw new Exception(errorMessage, e);
  331. } catch(SecurityException e) {
  332. throw new Exception(errorMessage, e);
  333. } finally {
  334. // Restore the original locale.
  335. Locale.setDefault(previousLocale);
  336. }
  337. return true;
  338. }
  339. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  340. private ParcelFileDescriptor startDummyVpn(VpnService.Builder vpnServiceBuilder) throws Exception {
  341. PrivateAddress privateAddress = selectPrivateAddress();
  342. Locale previousLocale = Locale.getDefault();
  343. final String errorMessage = "startDummyVpn failed";
  344. final ParcelFileDescriptor tunFd;
  345. try {
  346. // Workaround for https://code.google.com/p/android/issues/detail?id=61096
  347. Locale.setDefault(new Locale("en"));
  348. int mtu = VPN_INTERFACE_MTU;
  349. String dnsResolver = privateAddress.mRouter;
  350. tunFd = vpnServiceBuilder
  351. .setSession(mHostService.getAppName())
  352. .setMtu(mtu)
  353. .addAddress(privateAddress.mIpAddress, privateAddress.mPrefixLength)
  354. .addRoute("0.0.0.0", 0)
  355. .addRoute(privateAddress.mSubnet, privateAddress.mPrefixLength)
  356. .addDnsServer(dnsResolver)
  357. .establish();
  358. } catch(IllegalArgumentException e) {
  359. throw new Exception(errorMessage, e);
  360. } catch(IllegalStateException e) {
  361. throw new Exception(errorMessage, e);
  362. } catch(SecurityException e) {
  363. throw new Exception(errorMessage, e);
  364. } finally {
  365. // Restore the original locale.
  366. Locale.setDefault(previousLocale);
  367. }
  368. return tunFd;
  369. }
  370. private boolean isVpnMode() {
  371. return mVpnMode.get();
  372. }
  373. private void setLocalSocksProxyPort(int port) {
  374. mLocalSocksProxyPort.set(port);
  375. }
  376. private void stopVpn() {
  377. stopTun2Socks();
  378. ParcelFileDescriptor tunFd = mTunFd.getAndSet(null);
  379. if (tunFd != null) {
  380. try {
  381. tunFd.close();
  382. } catch (IOException e) {
  383. }
  384. }
  385. mRoutingThroughTunnel.set(false);
  386. }
  387. //----------------------------------------------------------------------------------------------
  388. // PsiphonProvider (Core support) interface implementation
  389. //----------------------------------------------------------------------------------------------
  390. // The PsiphonProvider functions are called from Go, and must be public to be accessible
  391. // via the gobind mechanim. To avoid making internal implementation functions public,
  392. // PsiphonProviderShim is used as a wrapper.
  393. private class PsiphonProviderShim implements PsiphonProvider {
  394. private PsiphonTunnel mPsiphonTunnel;
  395. public PsiphonProviderShim(PsiphonTunnel psiphonTunnel) {
  396. mPsiphonTunnel = psiphonTunnel;
  397. }
  398. @Override
  399. public void notice(String noticeJSON) {
  400. mPsiphonTunnel.notice(noticeJSON);
  401. }
  402. @Override
  403. public String bindToDevice(long fileDescriptor) throws Exception {
  404. return mPsiphonTunnel.bindToDevice(fileDescriptor);
  405. }
  406. @Override
  407. public long hasNetworkConnectivity() {
  408. return mPsiphonTunnel.hasNetworkConnectivity();
  409. }
  410. @Override
  411. public String getPrimaryDnsServer() {
  412. return mPsiphonTunnel.getPrimaryDnsServer();
  413. }
  414. @Override
  415. public String getSecondaryDnsServer() {
  416. return mPsiphonTunnel.getSecondaryDnsServer();
  417. }
  418. @Override
  419. public String iPv6Synthesize(String IPv4Addr) {
  420. return mPsiphonTunnel.iPv6Synthesize(IPv4Addr);
  421. }
  422. @Override
  423. public String getNetworkID() {
  424. return mPsiphonTunnel.getNetworkID();
  425. }
  426. }
  427. //----------------------------------------------------------------------------------------------
  428. // PsiphonProviderNoticeHandler (Core support) interface implementation
  429. //----------------------------------------------------------------------------------------------
  430. // The PsiphonProviderNoticeHandler function is called from Go, and must be public to be
  431. // accessible via the gobind mechanim. To avoid making internal implementation functions public,
  432. // PsiphonProviderNoticeHandlerShim is used as a wrapper.
  433. private static class PsiphonProviderNoticeHandlerShim implements PsiphonProviderNoticeHandler {
  434. private HostLogger mLogger;
  435. public PsiphonProviderNoticeHandlerShim(HostLogger logger) {
  436. mLogger = logger;
  437. }
  438. @Override
  439. public void notice(String noticeJSON) {
  440. try {
  441. JSONObject notice = new JSONObject(noticeJSON);
  442. String noticeType = notice.getString("noticeType");
  443. if (noticeType == null) {
  444. return;
  445. }
  446. JSONObject data = notice.getJSONObject("data");
  447. if (data == null) {
  448. return;
  449. }
  450. String diagnosticMessage = noticeType + ": " + data.toString();
  451. mLogger.onDiagnosticMessage(diagnosticMessage);
  452. } catch (java.lang.Exception e) {
  453. mLogger.onDiagnosticMessage("Error handling notice " + e.toString());
  454. }
  455. }
  456. }
  457. private void notice(String noticeJSON) {
  458. handlePsiphonNotice(noticeJSON);
  459. }
  460. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  461. private String bindToDevice(long fileDescriptor) throws Exception {
  462. if (!((VpnService)mHostService.getVpnService()).protect((int)fileDescriptor)) {
  463. throw new Exception("protect socket failed");
  464. }
  465. return "";
  466. }
  467. private long hasNetworkConnectivity() {
  468. boolean hasConnectivity = hasNetworkConnectivity(mHostService.getContext());
  469. boolean wasWaitingForNetworkConnectivity = mIsWaitingForNetworkConnectivity.getAndSet(!hasConnectivity);
  470. // HasNetworkConnectivity may be called many times, but only invoke
  471. // callbacks once per loss or resumption of connectivity, so, e.g.,
  472. // the HostService may log a single message.
  473. if (!hasConnectivity && !wasWaitingForNetworkConnectivity) {
  474. mHostService.onStartedWaitingForNetworkConnectivity();
  475. } else if (hasConnectivity && wasWaitingForNetworkConnectivity) {
  476. mHostService.onStoppedWaitingForNetworkConnectivity();
  477. }
  478. // TODO: change to bool return value once gobind supports that type
  479. return hasConnectivity ? 1 : 0;
  480. }
  481. private String getPrimaryDnsServer() {
  482. String dnsResolver = null;
  483. try {
  484. dnsResolver = getFirstActiveNetworkDnsResolver(mHostService.getContext());
  485. } catch (Exception e) {
  486. mHostService.onDiagnosticMessage("failed to get active network DNS resolver: " + e.getMessage());
  487. dnsResolver = DEFAULT_PRIMARY_DNS_SERVER;
  488. }
  489. return dnsResolver;
  490. }
  491. private String getSecondaryDnsServer() {
  492. return DEFAULT_SECONDARY_DNS_SERVER;
  493. }
  494. private String iPv6Synthesize(String IPv4Addr) {
  495. return IPv4Addr;
  496. }
  497. private String getNetworkID() {
  498. // The network ID contains potential PII. In tunnel-core, the network ID
  499. // is used only locally in the client and not sent to the server.
  500. //
  501. // See network ID requirements here:
  502. // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter
  503. String networkID = "UNKNOWN";
  504. Context context = mHostService.getContext();
  505. ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
  506. NetworkInfo activeNetworkInfo = null;
  507. try {
  508. activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
  509. } catch (java.lang.Exception e) {
  510. // May get exceptions due to missing permissions like android.permission.ACCESS_NETWORK_STATE.
  511. // Apps using the Psiphon Library and lacking android.permission.ACCESS_NETWORK_STATE will
  512. // proceed and use tactics, but with "UNKNOWN" as the sole network ID.
  513. }
  514. if (activeNetworkInfo != null && activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
  515. networkID = "WIFI";
  516. try {
  517. WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
  518. WifiInfo wifiInfo = wifiManager.getConnectionInfo();
  519. if (wifiInfo != null) {
  520. String wifiNetworkID = wifiInfo.getBSSID();
  521. if (wifiNetworkID.equals("02:00:00:00:00:00")) {
  522. // "02:00:00:00:00:00" is reported when the app does not have the ACCESS_COARSE_LOCATION permission:
  523. // https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-hardware-id
  524. // The Psiphon client should allow the user to opt-in to this permission. If they decline, fail over
  525. // to using the WiFi IP address.
  526. wifiNetworkID = String.valueOf(wifiInfo.getIpAddress());
  527. }
  528. networkID += "-" + wifiNetworkID;
  529. }
  530. } catch (java.lang.Exception e) {
  531. // May get exceptions due to missing permissions like android.permission.ACCESS_WIFI_STATE.
  532. // Fall through and use just "WIFI"
  533. }
  534. } else if (activeNetworkInfo != null && activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
  535. networkID = "MOBILE";
  536. try {
  537. TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
  538. if (telephonyManager != null) {
  539. networkID += "-" + telephonyManager.getNetworkOperator();
  540. }
  541. } catch (java.lang.Exception e) {
  542. // May get exceptions due to missing permissions.
  543. // Fall through and use just "MOBILE"
  544. }
  545. }
  546. return networkID;
  547. }
  548. //----------------------------------------------------------------------------------------------
  549. // Psiphon Tunnel Core
  550. //----------------------------------------------------------------------------------------------
  551. private void startPsiphon(String embeddedServerEntries) throws Exception {
  552. stopPsiphon();
  553. mIsWaitingForNetworkConnectivity.set(false);
  554. mHostService.onDiagnosticMessage("starting Psiphon library");
  555. try {
  556. Psi.start(
  557. loadPsiphonConfig(mHostService.getContext()),
  558. embeddedServerEntries,
  559. "",
  560. new PsiphonProviderShim(this),
  561. isVpnMode(),
  562. false // Do not use IPv6 synthesizer for android
  563. );
  564. } catch (java.lang.Exception e) {
  565. throw new Exception("failed to start Psiphon library", e);
  566. }
  567. mHostService.onDiagnosticMessage("Psiphon library started");
  568. }
  569. private void stopPsiphon() {
  570. mHostService.onDiagnosticMessage("stopping Psiphon library");
  571. Psi.stop();
  572. mHostService.onDiagnosticMessage("Psiphon library stopped");
  573. }
  574. private String loadPsiphonConfig(Context context)
  575. throws IOException, JSONException, Exception {
  576. return buildPsiphonConfig(context, mHostService, mHostService.getPsiphonConfig(),
  577. mClientPlatformPrefix.get(), mClientPlatformSuffix.get(), isVpnMode(),
  578. mLocalSocksProxyPort.get());
  579. }
  580. private static String buildPsiphonConfig(Context context, HostLogger logger, String psiphonConfig,
  581. String clientPlatformPrefix, String clientPlatformSuffix,
  582. boolean isVpnMode, Integer localSocksProxyPort)
  583. throws IOException, JSONException, Exception {
  584. // Load settings from the raw resource JSON config file and
  585. // update as necessary. Then write JSON to disk for the Go client.
  586. JSONObject json = new JSONObject(psiphonConfig);
  587. // On Android, this directory must be set to the app private storage area.
  588. // The Psiphon library won't be able to use its current working directory
  589. // and the standard temporary directories do not exist.
  590. if (!json.has("DataRootDirectory")) {
  591. File dataRootDirectory = defaultDataRootDirectory(context);
  592. if (!dataRootDirectory.exists()) {
  593. boolean created = dataRootDirectory.mkdir();
  594. if (!created) {
  595. throw new Exception("failed to create data root directory: " + dataRootDirectory.getPath());
  596. }
  597. }
  598. json.put("DataRootDirectory", defaultDataRootDirectory(context));
  599. }
  600. // Migrate datastore files from legacy directory.
  601. if (!json.has("DataStoreDirectory")) {
  602. json.put("MigrateDataStoreDirectory", context.getFilesDir());
  603. }
  604. // Migrate remote server list downloads from legacy location.
  605. if (!json.has("RemoteServerListDownloadFilename")) {
  606. File remoteServerListDownload = new File(context.getFilesDir(), "remote_server_list");
  607. json.put("MigrateRemoteServerListDownloadFilename", remoteServerListDownload.getAbsolutePath());
  608. }
  609. // Migrate obfuscated server list download files from legacy directory.
  610. File oslDownloadDir = new File(context.getFilesDir(), "osl");
  611. json.put("MigrateObfuscatedServerListDownloadDirectory", oslDownloadDir.getAbsolutePath());
  612. // Note: onConnecting/onConnected logic assumes 1 tunnel connection
  613. json.put("TunnelPoolSize", 1);
  614. // Continue to run indefinitely until connected
  615. if (!json.has("EstablishTunnelTimeoutSeconds")) {
  616. json.put("EstablishTunnelTimeoutSeconds", 0);
  617. }
  618. // This parameter is for stats reporting
  619. if (!json.has("TunnelWholeDevice")) {
  620. json.put("TunnelWholeDevice", isVpnMode ? 1 : 0);
  621. }
  622. json.put("EmitBytesTransferred", true);
  623. if (localSocksProxyPort != 0 && (!json.has("LocalSocksProxyPort") || json.getInt("LocalSocksProxyPort") == 0)) {
  624. // When mLocalSocksProxyPort is set, tun2socks is already configured
  625. // to use that port value. So we force use of the same port.
  626. // A side-effect of this is that changing the SOCKS port preference
  627. // has no effect with restartPsiphon(), a full stop() is necessary.
  628. json.put("LocalSocksProxyPort", localSocksProxyPort);
  629. }
  630. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  631. try {
  632. json.put(
  633. "TrustedCACertificatesFilename",
  634. setupTrustedCertificates(context, logger));
  635. } catch (Exception e) {
  636. logger.onDiagnosticMessage(e.getMessage());
  637. }
  638. }
  639. json.put("DeviceRegion", getDeviceRegion(context));
  640. StringBuilder clientPlatform = new StringBuilder();
  641. if (clientPlatformPrefix.length() > 0) {
  642. clientPlatform.append(clientPlatformPrefix);
  643. }
  644. clientPlatform.append("Android_");
  645. clientPlatform.append(Build.VERSION.RELEASE);
  646. clientPlatform.append("_");
  647. clientPlatform.append(context.getPackageName());
  648. if (clientPlatformSuffix.length() > 0) {
  649. clientPlatform.append(clientPlatformSuffix);
  650. }
  651. json.put("ClientPlatform", clientPlatform.toString().replaceAll("[^\\w\\-\\.]", "_"));
  652. return json.toString();
  653. }
  654. private void handlePsiphonNotice(String noticeJSON) {
  655. try {
  656. // All notices are sent on as diagnostic messages
  657. // except those that may contain private user data.
  658. boolean diagnostic = true;
  659. JSONObject notice = new JSONObject(noticeJSON);
  660. String noticeType = notice.getString("noticeType");
  661. if (noticeType.equals("Tunnels")) {
  662. int count = notice.getJSONObject("data").getInt("count");
  663. if (count > 0) {
  664. if (isVpnMode() && mShouldRouteThroughTunnelAutomatically) {
  665. routeThroughTunnel();
  666. }
  667. mHostService.onConnected();
  668. } else {
  669. mHostService.onConnecting();
  670. }
  671. } else if (noticeType.equals("AvailableEgressRegions")) {
  672. JSONArray egressRegions = notice.getJSONObject("data").getJSONArray("regions");
  673. ArrayList<String> regions = new ArrayList<String>();
  674. for (int i=0; i<egressRegions.length(); i++) {
  675. regions.add(egressRegions.getString(i));
  676. }
  677. mHostService.onAvailableEgressRegions(regions);
  678. } else if (noticeType.equals("SocksProxyPortInUse")) {
  679. mHostService.onSocksProxyPortInUse(notice.getJSONObject("data").getInt("port"));
  680. } else if (noticeType.equals("HttpProxyPortInUse")) {
  681. mHostService.onHttpProxyPortInUse(notice.getJSONObject("data").getInt("port"));
  682. } else if (noticeType.equals("ListeningSocksProxyPort")) {
  683. int port = notice.getJSONObject("data").getInt("port");
  684. setLocalSocksProxyPort(port);
  685. mHostService.onListeningSocksProxyPort(port);
  686. } else if (noticeType.equals("ListeningHttpProxyPort")) {
  687. int port = notice.getJSONObject("data").getInt("port");
  688. mHostService.onListeningHttpProxyPort(port);
  689. } else if (noticeType.equals("UpstreamProxyError")) {
  690. mHostService.onUpstreamProxyError(notice.getJSONObject("data").getString("message"));
  691. } else if (noticeType.equals("ClientUpgradeDownloaded")) {
  692. mHostService.onClientUpgradeDownloaded(notice.getJSONObject("data").getString("filename"));
  693. } else if (noticeType.equals("ClientIsLatestVersion")) {
  694. mHostService.onClientIsLatestVersion();
  695. } else if (noticeType.equals("Homepage")) {
  696. mHostService.onHomepage(notice.getJSONObject("data").getString("url"));
  697. } else if (noticeType.equals("ClientRegion")) {
  698. mHostService.onClientRegion(notice.getJSONObject("data").getString("region"));
  699. } else if (noticeType.equals("SplitTunnelRegion")) {
  700. mHostService.onSplitTunnelRegion(notice.getJSONObject("data").getString("region"));
  701. } else if (noticeType.equals("Untunneled")) {
  702. mHostService.onUntunneledAddress(notice.getJSONObject("data").getString("address"));
  703. } else if (noticeType.equals("BytesTransferred")) {
  704. diagnostic = false;
  705. JSONObject data = notice.getJSONObject("data");
  706. mHostService.onBytesTransferred(data.getLong("sent"), data.getLong("received"));
  707. } else if (noticeType.equals("ActiveAuthorizationIDs")) {
  708. JSONArray activeAuthorizationIDs = notice.getJSONObject("data").getJSONArray("IDs");
  709. ArrayList<String> authorizations = new ArrayList<String>();
  710. for (int i=0; i<activeAuthorizationIDs.length(); i++) {
  711. authorizations.add(activeAuthorizationIDs.getString(i));
  712. }
  713. mHostService.onActiveAuthorizationIDs(authorizations);
  714. } else if (noticeType.equals("TrafficRateLimits")) {
  715. JSONObject data = notice.getJSONObject("data");
  716. mHostService.onTrafficRateLimits(
  717. data.getLong("upstreamBytesPerSecond"), data.getLong("downstreamBytesPerSecond"));
  718. } else if (noticeType.equals("Exiting")) {
  719. mHostService.onExiting();
  720. } else if (noticeType.equals("ActiveTunnel")) {
  721. if (isVpnMode()) {
  722. if (notice.getJSONObject("data").getBoolean("isTCS")) {
  723. disableUdpGwKeepalive();
  724. } else {
  725. enableUdpGwKeepalive();
  726. }
  727. }
  728. } else if (noticeType.equals("ApplicationParameter")) {
  729. mHostService.onApplicationParameter(
  730. notice.getJSONObject("data").getString("key"),
  731. notice.getJSONObject("data").get("value"));
  732. } else if (noticeType.equals("ServerAlert")) {
  733. mHostService.onServerAlert(
  734. notice.getJSONObject("data").getString("reason"),
  735. notice.getJSONObject("data").getString("subject"));
  736. }
  737. if (diagnostic) {
  738. String diagnosticMessage = noticeType + ": " + notice.getJSONObject("data").toString();
  739. mHostService.onDiagnosticMessage(diagnosticMessage);
  740. }
  741. } catch (JSONException e) {
  742. // Ignore notice
  743. }
  744. }
  745. private static String setupTrustedCertificates(Context context, HostLogger logger) throws Exception {
  746. // Copy the Android system CA store to a local, private cert bundle file.
  747. //
  748. // This results in a file that can be passed to SSL_CTX_load_verify_locations
  749. // for use with OpenSSL modes in tunnel-core.
  750. // https://www.openssl.org/docs/manmaster/ssl/SSL_CTX_load_verify_locations.html
  751. //
  752. // TODO: to use the path mode of load_verify_locations would require emulating
  753. // the filename scheme used by c_rehash:
  754. // https://www.openssl.org/docs/manmaster/apps/c_rehash.html
  755. // http://stackoverflow.com/questions/19237167/the-new-subject-hash-openssl-algorithm-differs
  756. File directory = context.getDir("PsiphonCAStore", Context.MODE_PRIVATE);
  757. final String errorMessage = "copy AndroidCAStore failed";
  758. try {
  759. File file = new File(directory, "certs.dat");
  760. // Pave a fresh copy on every run, which ensures we're not using old certs.
  761. // Note: assumes KeyStore doesn't return revoked certs.
  762. //
  763. // TODO: this takes under 1 second, but should we avoid repaving every time?
  764. file.delete();
  765. PrintStream output = null;
  766. try {
  767. output = new PrintStream(new FileOutputStream(file));
  768. KeyStore keyStore;
  769. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
  770. keyStore = KeyStore.getInstance("AndroidCAStore");
  771. keyStore.load(null, null);
  772. } else {
  773. keyStore = KeyStore.getInstance("BKS");
  774. FileInputStream inputStream = new FileInputStream("/etc/security/cacerts.bks");
  775. try {
  776. keyStore.load(inputStream, "changeit".toCharArray());
  777. } finally {
  778. if (inputStream != null) {
  779. inputStream.close();
  780. }
  781. }
  782. }
  783. Enumeration<String> aliases = keyStore.aliases();
  784. while (aliases.hasMoreElements()) {
  785. String alias = aliases.nextElement();
  786. X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
  787. output.println("-----BEGIN CERTIFICATE-----");
  788. String pemCert = new String(Base64.encode(cert.getEncoded(), Base64.NO_WRAP), "UTF-8");
  789. // OpenSSL appears to reject the default linebreaking done by Base64.encode,
  790. // so we manually linebreak every 64 characters
  791. for (int i = 0; i < pemCert.length() ; i+= 64) {
  792. output.println(pemCert.substring(i, Math.min(i + 64, pemCert.length())));
  793. }
  794. output.println("-----END CERTIFICATE-----");
  795. }
  796. logger.onDiagnosticMessage("prepared PsiphonCAStore");
  797. return file.getAbsolutePath();
  798. } finally {
  799. if (output != null) {
  800. output.close();
  801. }
  802. }
  803. } catch (KeyStoreException e) {
  804. throw new Exception(errorMessage, e);
  805. } catch (NoSuchAlgorithmException e) {
  806. throw new Exception(errorMessage, e);
  807. } catch (CertificateException e) {
  808. throw new Exception(errorMessage, e);
  809. } catch (IOException e) {
  810. throw new Exception(errorMessage, e);
  811. }
  812. }
  813. private static String getDeviceRegion(Context context) {
  814. String region = "";
  815. TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
  816. if (telephonyManager != null) {
  817. region = telephonyManager.getSimCountryIso();
  818. if (region == null) {
  819. region = "";
  820. }
  821. if (region.length() == 0 && telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
  822. region = telephonyManager.getNetworkCountryIso();
  823. if (region == null) {
  824. region = "";
  825. }
  826. }
  827. }
  828. if (region.length() == 0) {
  829. Locale defaultLocale = Locale.getDefault();
  830. if (defaultLocale != null) {
  831. region = defaultLocale.getCountry();
  832. }
  833. }
  834. return region.toUpperCase(Locale.US);
  835. }
  836. //----------------------------------------------------------------------------------------------
  837. // Tun2Socks
  838. //----------------------------------------------------------------------------------------------
  839. @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
  840. private void startTun2Socks(
  841. final ParcelFileDescriptor vpnInterfaceFileDescriptor,
  842. final int vpnInterfaceMTU,
  843. final String vpnIpAddress,
  844. final String vpnNetMask,
  845. final String socksServerAddress,
  846. final String udpgwServerAddress,
  847. final boolean udpgwTransparentDNS) {
  848. if (mTun2SocksThread != null) {
  849. return;
  850. }
  851. mTun2SocksThread = new Thread(new Runnable() {
  852. @Override
  853. public void run() {
  854. runTun2Socks(
  855. vpnInterfaceFileDescriptor.detachFd(),
  856. vpnInterfaceMTU,
  857. vpnIpAddress,
  858. vpnNetMask,
  859. socksServerAddress,
  860. udpgwServerAddress,
  861. udpgwTransparentDNS ? 1 : 0);
  862. }
  863. });
  864. mTun2SocksThread.start();
  865. mHostService.onDiagnosticMessage("tun2socks started");
  866. }
  867. private void stopTun2Socks() {
  868. if (mTun2SocksThread != null) {
  869. try {
  870. terminateTun2Socks();
  871. mTun2SocksThread.join();
  872. } catch (InterruptedException e) {
  873. Thread.currentThread().interrupt();
  874. }
  875. mTun2SocksThread = null;
  876. mHostService.onDiagnosticMessage("tun2socks stopped");
  877. }
  878. }
  879. public static void logTun2Socks(String level, String channel, String msg) {
  880. String logMsg = "tun2socks: " + level + "(" + channel + "): " + msg;
  881. mPsiphonTunnel.mHostService.onDiagnosticMessage(logMsg);
  882. }
  883. private native static int runTun2Socks(
  884. int vpnInterfaceFileDescriptor,
  885. int vpnInterfaceMTU,
  886. String vpnIpAddress,
  887. String vpnNetMask,
  888. String socksServerAddress,
  889. String udpgwServerAddress,
  890. int udpgwTransparentDNS);
  891. private native static int terminateTun2Socks();
  892. private native static int enableUdpGwKeepalive();
  893. private native static int disableUdpGwKeepalive();
  894. //----------------------------------------------------------------------------------------------
  895. // Implementation: Network Utils
  896. //----------------------------------------------------------------------------------------------
  897. private static boolean hasNetworkConnectivity(Context context) {
  898. ConnectivityManager connectivityManager =
  899. (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
  900. if (connectivityManager == null) {
  901. return false;
  902. }
  903. NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
  904. return networkInfo != null && networkInfo.isConnected();
  905. }
  906. private static class PrivateAddress {
  907. final public String mIpAddress;
  908. final public String mSubnet;
  909. final public int mPrefixLength;
  910. final public String mRouter;
  911. public PrivateAddress(String ipAddress, String subnet, int prefixLength, String router) {
  912. mIpAddress = ipAddress;
  913. mSubnet = subnet;
  914. mPrefixLength = prefixLength;
  915. mRouter = router;
  916. }
  917. }
  918. private static PrivateAddress selectPrivateAddress() throws Exception {
  919. // Select one of 10.0.0.1, 172.16.0.1, or 192.168.0.1 depending on
  920. // which private address range isn't in use.
  921. Map<String, PrivateAddress> candidates = new HashMap<String, PrivateAddress>();
  922. candidates.put( "10", new PrivateAddress("10.0.0.1", "10.0.0.0", 8, "10.0.0.2"));
  923. candidates.put("172", new PrivateAddress("172.16.0.1", "172.16.0.0", 12, "172.16.0.2"));
  924. candidates.put("192", new PrivateAddress("192.168.0.1", "192.168.0.0", 16, "192.168.0.2"));
  925. candidates.put("169", new PrivateAddress("169.254.1.1", "169.254.1.0", 24, "169.254.1.2"));
  926. List<NetworkInterface> netInterfaces;
  927. try {
  928. netInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
  929. } catch (SocketException e) {
  930. throw new Exception("selectPrivateAddress failed", e);
  931. }
  932. for (NetworkInterface netInterface : netInterfaces) {
  933. for (InetAddress inetAddress : Collections.list(netInterface.getInetAddresses())) {
  934. if (inetAddress instanceof Inet4Address) {
  935. String ipAddress = inetAddress.getHostAddress();
  936. if (ipAddress.startsWith("10.")) {
  937. candidates.remove("10");
  938. }
  939. else if (
  940. ipAddress.length() >= 6 &&
  941. ipAddress.substring(0, 6).compareTo("172.16") >= 0 &&
  942. ipAddress.substring(0, 6).compareTo("172.31") <= 0) {
  943. candidates.remove("172");
  944. }
  945. else if (ipAddress.startsWith("192.168")) {
  946. candidates.remove("192");
  947. }
  948. }
  949. }
  950. }
  951. if (candidates.size() > 0) {
  952. return candidates.values().iterator().next();
  953. }
  954. throw new Exception("no private address available");
  955. }
  956. public static String getFirstActiveNetworkDnsResolver(Context context)
  957. throws Exception {
  958. Collection<InetAddress> dnsResolvers = getActiveNetworkDnsResolvers(context);
  959. if (!dnsResolvers.isEmpty()) {
  960. // strip the leading slash e.g., "/192.168.1.1"
  961. String dnsResolver = dnsResolvers.iterator().next().toString();
  962. if (dnsResolver.startsWith("/")) {
  963. dnsResolver = dnsResolver.substring(1);
  964. }
  965. return dnsResolver;
  966. }
  967. throw new Exception("no active network DNS resolver");
  968. }
  969. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  970. private static Collection<InetAddress> getActiveNetworkDnsResolvers(Context context)
  971. throws Exception {
  972. final String errorMessage = "getActiveNetworkDnsResolvers failed";
  973. ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
  974. try {
  975. // Hidden API
  976. // - only available in Android 4.0+
  977. // - no guarantee will be available beyond 4.2, or on all vendor devices
  978. ConnectivityManager connectivityManager =
  979. (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
  980. Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
  981. Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
  982. Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
  983. if (linkProperties != null) {
  984. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  985. Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses", new Class []{});
  986. Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
  987. for (Object dns : dnses) {
  988. dnsAddresses.add((InetAddress)dns);
  989. }
  990. } else {
  991. // LinkProperties is public in API 21 (and the DNS function signature has changed)
  992. for (InetAddress dns : ((LinkProperties)linkProperties).getDnsServers()) {
  993. dnsAddresses.add(dns);
  994. }
  995. }
  996. }
  997. } catch (ClassNotFoundException e) {
  998. throw new Exception(errorMessage, e);
  999. } catch (NoSuchMethodException e) {
  1000. throw new Exception(errorMessage, e);
  1001. } catch (IllegalArgumentException e) {
  1002. throw new Exception(errorMessage, e);
  1003. } catch (IllegalAccessException e) {
  1004. throw new Exception(errorMessage, e);
  1005. } catch (InvocationTargetException e) {
  1006. throw new Exception(errorMessage, e);
  1007. } catch (NullPointerException e) {
  1008. throw new Exception(errorMessage, e);
  1009. }
  1010. return dnsAddresses;
  1011. }
  1012. //----------------------------------------------------------------------------------------------
  1013. // Exception
  1014. //----------------------------------------------------------------------------------------------
  1015. public static class Exception extends java.lang.Exception {
  1016. private static final long serialVersionUID = 1L;
  1017. public Exception(String message) {
  1018. super(message);
  1019. }
  1020. public Exception(String message, Throwable cause) {
  1021. super(message + ": " + cause.getMessage());
  1022. }
  1023. }
  1024. }