PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/lantern/proxy/pt/BasePluggableTransport.java

https://gitlab.com/kidaa/lantern
Java | 312 lines | 235 code | 30 blank | 47 comment | 13 complexity | 8eae8c53ae7ebbedeb6bc64b091b0b07 MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0
  1. package org.lantern.proxy.pt;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.net.InetSocketAddress;
  6. import java.net.UnknownHostException;
  7. import java.util.HashSet;
  8. import java.util.Set;
  9. import java.util.concurrent.Callable;
  10. import java.util.concurrent.ExecutionException;
  11. import java.util.concurrent.Future;
  12. import java.util.concurrent.TimeUnit;
  13. import java.util.concurrent.TimeoutException;
  14. import java.util.concurrent.atomic.AtomicBoolean;
  15. import java.util.concurrent.atomic.AtomicReference;
  16. import org.apache.commons.exec.CommandLine;
  17. import org.apache.commons.exec.DefaultExecutor;
  18. import org.apache.commons.exec.ExecuteWatchdog;
  19. import org.apache.commons.exec.Executor;
  20. import org.apache.commons.exec.ShutdownHookProcessDestroyer;
  21. import org.apache.commons.io.FileUtils;
  22. import org.apache.commons.lang3.SystemUtils;
  23. import org.lantern.LanternClientConstants;
  24. import org.lantern.LanternUtils;
  25. import org.lantern.util.Threads;
  26. import org.littleshoot.util.NetworkUtils;
  27. import org.slf4j.Logger;
  28. import org.slf4j.LoggerFactory;
  29. /**
  30. * Base class for PluggableTransports like Flashlight and FTEProxy that use
  31. * similar setups.
  32. */
  33. public abstract class BasePluggableTransport implements PluggableTransport {
  34. private static final Logger LOGGER = LoggerFactory
  35. .getLogger(BasePluggableTransport.class);
  36. private static final Set<Class> ALREADY_COPIED_TRANSPORTS = new HashSet<Class>();
  37. protected File exe;
  38. private CommandLine cmd;
  39. private Executor cmdExec;
  40. protected String ptBasePath;
  41. /**
  42. * Construct a new Flashlight pluggable transport.
  43. *
  44. * @param copyToConfigFolder
  45. * - if true, the pluggable transport will be copied to and run
  46. * from the .lantern folder (necessary if the pluggable transport
  47. * modifies files in its path)
  48. * @param relativePath
  49. * - relative path of pluggable transport from the root of the
  50. * jar
  51. * @param executableName
  52. * - the name of the executable (not including .exe)
  53. */
  54. protected BasePluggableTransport(boolean copyToConfigFolder,
  55. String relativePath,
  56. String executableName) {
  57. String path = relativePath + "/" + executableName;
  58. if (SystemUtils.IS_OS_WINDOWS) {
  59. path += ".exe";
  60. }
  61. try {
  62. this.exe = LanternUtils.extractExecutableFromJar(path,
  63. LanternClientConstants.DATA_DIR);
  64. } catch (final IOException e) {
  65. throw new Error(String.format("Could not extract jar file from '%s': %s", path, e.getMessage()), e);
  66. }
  67. if (!exe.exists()) {
  68. String message = String.format(
  69. "%1$s executable missing at %2$s", getLogName(), exe);
  70. LOGGER.error(message, exe);
  71. throw new Error(message);
  72. }
  73. if (!exe.canExecute()) {
  74. String message = String.format(
  75. "%1$s executable not executable at %2$s", getLogName(), exe);
  76. LOGGER.error(message, exe);
  77. throw new Error(message);
  78. }
  79. this.ptBasePath = exe.getParentFile().getAbsolutePath();
  80. if (copyToConfigFolder) {
  81. copyToConfigFolder(exe, relativePath);
  82. }
  83. }
  84. /**
  85. * Add the arguments for starting the client.
  86. *
  87. * @param cmd
  88. * @param listenAddress
  89. * address at which the pluggable transport should listen
  90. * @param getModeAddress
  91. * @param proxyAddress
  92. * @return
  93. */
  94. protected abstract void addClientArgs(CommandLine cmd,
  95. InetSocketAddress listenAddress,
  96. InetSocketAddress getModeAddress,
  97. InetSocketAddress proxyAddress);
  98. /**
  99. * Add the arguments for starting the server.
  100. *
  101. * @param cmd
  102. * @param listenIp
  103. * @param listenPort
  104. * @param giveModeAddress
  105. * @return
  106. */
  107. protected abstract void addServerArgs(CommandLine cmd,
  108. String listenIp,
  109. int listenPort,
  110. InetSocketAddress giveModeAddress);
  111. @Override
  112. public InetSocketAddress startClient(
  113. InetSocketAddress getModeAddress,
  114. InetSocketAddress proxyAddress) {
  115. LOGGER.info("Starting {} client", getLogName());
  116. InetSocketAddress listenAddress = new InetSocketAddress(
  117. getModeAddress.getAddress(),
  118. LanternUtils.findFreePort());
  119. cmd = new CommandLine(this.exe);
  120. addClientArgs(cmd, listenAddress, getModeAddress, proxyAddress);
  121. // Just wait for a moment for the PT process to either run as a long-
  122. // lived process (as it should) or to return with some unexpected error.
  123. final Future<Integer> fut = exec();
  124. try {
  125. final int val = fut.get(4, TimeUnit.SECONDS);
  126. if (val != 0) {
  127. LOGGER.error("Unexpected return value from PT: "+val);
  128. throw new RuntimeException("Unexpected return value from PT: "+val);
  129. }
  130. } catch (final InterruptedException e) {
  131. LOGGER.error("Unexpected interrupt?", e);
  132. throw new RuntimeException("Unexpected interrupt", e);
  133. } catch (final ExecutionException e) {
  134. // This indicates an actual error, likely with the return value of
  135. // the process.
  136. LOGGER.error("Error executing PT", e);
  137. throw new RuntimeException("Error executing PT", e);
  138. } catch (final TimeoutException e) {
  139. // Pluggable transport clients are generally expected to be
  140. // long-lived, so this is expected.
  141. LOGGER.debug("Timed out waiting for PT return value AS EXPECTED");
  142. }
  143. if (!LanternUtils.waitForServer(listenAddress, 60000)) {
  144. throw new RuntimeException(String.format("Unable to start %1$s",
  145. getLogName()));
  146. }
  147. return listenAddress;
  148. }
  149. @Override
  150. public void stopClient() {
  151. LOGGER.info("Stopping {} client", getLogName());
  152. cmdExec.getWatchdog().destroyProcess();
  153. }
  154. @Override
  155. public void startServer(final int listenPort,
  156. InetSocketAddress giveModeAddress) {
  157. LOGGER.info("Starting {} server", getLogName());
  158. try {
  159. final String listenIp = NetworkUtils.getLocalHost()
  160. .getHostAddress();
  161. cmd = new CommandLine(this.exe);
  162. addServerArgs(cmd, listenIp, listenPort, giveModeAddress);
  163. final Future<Integer> exitFuture = exec();
  164. // Record exception related to startup of server
  165. final AtomicReference<RuntimeException> exception = new AtomicReference<RuntimeException>();
  166. final AtomicBoolean exceptionSet = new AtomicBoolean();
  167. // Check for termination of process
  168. Thread terminationThread = new Thread() {
  169. public void run() {
  170. try {
  171. exitFuture.get();
  172. } catch (Exception e) {
  173. exception.set(new RuntimeException(
  174. String.format(
  175. "Unable to execute process: %1$s",
  176. e.getMessage()), e));
  177. } finally {
  178. synchronized (exception) {
  179. exceptionSet.set(true);
  180. exception.notifyAll();
  181. }
  182. }
  183. }
  184. };
  185. terminationThread.setDaemon(true);
  186. terminationThread.start();
  187. // Check for server listening
  188. Thread listenCheckThread = new Thread() {
  189. public void run() {
  190. if (!LanternUtils
  191. .waitForServer(listenIp, listenPort, 60000)) {
  192. synchronized (exception) {
  193. exception.set(new RuntimeException(String
  194. .format(
  195. "Unable to start %1$s server",
  196. getLogName())));
  197. exceptionSet.set(true);
  198. exception.notifyAll();
  199. }
  200. }
  201. }
  202. };
  203. listenCheckThread.setDaemon(true);
  204. listenCheckThread.start();
  205. // Take the first exception
  206. try {
  207. synchronized (exception) {
  208. if (!exceptionSet.get()) {
  209. exception.wait();
  210. }
  211. }
  212. } catch (InterruptedException ie) {
  213. throw new RuntimeException(
  214. "Unable to determine status of server");
  215. }
  216. RuntimeException e = exception.get();
  217. if (e != null) {
  218. throw e;
  219. }
  220. } catch (UnknownHostException uhe) {
  221. throw new RuntimeException("Unable to determine interface ip: "
  222. + uhe.getMessage(), uhe);
  223. }
  224. }
  225. @Override
  226. public void stopServer() {
  227. LOGGER.info("Stopping {} server", getLogName());
  228. cmdExec.getWatchdog().destroyProcess();
  229. }
  230. private void copyToConfigFolder(final File exe, final String relativePath) {
  231. final File from = exe.getParentFile();
  232. final File to = new File(LanternClientConstants.CONFIG_DIR, relativePath);
  233. synchronized (ALREADY_COPIED_TRANSPORTS) {
  234. if (!ALREADY_COPIED_TRANSPORTS.contains(getClass())) {
  235. LOGGER.info("Copying {} from {} to {}",
  236. getLogName(),
  237. from.getAbsolutePath(),
  238. to.getAbsolutePath());
  239. try {
  240. FileUtils.copyDirectory(from, to);
  241. } catch (Exception e) {
  242. throw new Error(String.format(
  243. "Unable to stage %1$s to .lantern: %2$s",
  244. getLogName(), e.getMessage()), e);
  245. }
  246. ALREADY_COPIED_TRANSPORTS.add(getClass());
  247. } else {
  248. LOGGER.info("Not copying {} because it's already been copied",
  249. getLogName());
  250. }
  251. }
  252. // Always update base path to point to copied location
  253. ptBasePath = to.getAbsolutePath();
  254. }
  255. private Future<Integer> exec() {
  256. cmdExec = new DefaultExecutor();
  257. cmdExec.setStreamHandler(buildLoggingStreamHandler(LOGGER, System.in));
  258. cmdExec.setProcessDestroyer(new ShutdownHookProcessDestroyer());
  259. cmdExec.setWatchdog(new ExecuteWatchdog(
  260. ExecuteWatchdog.INFINITE_TIMEOUT));
  261. LOGGER.info("About to run cmd: {}", cmd);
  262. return Threads.newSingleThreadExecutor("PluggableTransportRunner")
  263. .submit(
  264. new Callable<Integer>() {
  265. @Override
  266. public Integer call() throws Exception {
  267. return cmdExec.execute(cmd);
  268. }
  269. });
  270. }
  271. protected LoggingStreamHandler buildLoggingStreamHandler(
  272. Logger logger,
  273. InputStream is) {
  274. return new LoggingStreamHandler(logger, is);
  275. }
  276. private String getLogName() {
  277. return getClass().getSimpleName();
  278. }
  279. protected static String stringify(Object value) {
  280. return String.format("%1$s", value);
  281. }
  282. }