/* This file is part of the project TraceroutePing, which is an Android library implementing Traceroute with ping under GPL license v3. Copyright (C) 2013 Olivier Goutay TraceroutePing is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. TraceroutePing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TraceroutePing. If not, see <http://www.gnu.org/licenses/>. */ package cn.darkal.networkdiagnosis.Task; import android.annotation.SuppressLint; import android.os.AsyncTask; import android.os.Handler; import android.util.Log; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; /** * This class contain everything needed to launch a traceroute using the ping command * * @author Olivier Goutay */ public class TraceRouteWithPing { public static final String PING_CMD_FORMMAT = "ping -c 1 -t %d "; private static final String PING = "PING"; private static final String FROM_PING = "From"; private static final String SMALL_FROM_PING = "from"; private static final String PARENTHESE_OPEN_PING = "("; private static final String PARENTHESE_CLOSE_PING = ")"; private static final String TIME_PING = "time="; private static final String EXCEED_PING = "exceed"; private static final String UNREACHABLE_PING = "100%"; // timeout handling private static final int TIMEOUT = 30000; private static Runnable runnableTimeout; private List<TraceRouteContainer> traces; private int ttl; private int finishedTasks; private String urlToPing; private String ipToPing; private float elapsedTime; private Handler handlerTimeout; private TraceTask mTask; private StringBuilder mTraceRouteResult = new StringBuilder(); public TraceRouteWithPing(String host, TraceTask task) { urlToPing = host; mTask = task; } /** * Launches the TraceRoute */ public void executeTraceRoute() { this.ttl = 1; this.finishedTasks = 0; this.traces = new ArrayList<TraceRouteContainer>(); new ExecutePingAsyncTask(50).execute(); } /** * Gets the ip from the string returned by a ping * * @param ping The string returned by a ping command * @return The ip contained in the ping */ private String parseIpFromPing(String ping) { String ip = ""; if (ping.contains(FROM_PING) || ping.contains(SMALL_FROM_PING)) { // Get ip when ttl exceeded int index = ping.indexOf(FROM_PING); if(index==0){ index = ping.indexOf(SMALL_FROM_PING); } ip = ping.substring(index + 5); if (ip.contains(PARENTHESE_OPEN_PING)) { // Get ip when in parenthese int indexOpen = ip.indexOf(PARENTHESE_OPEN_PING); int indexClose = ip.indexOf(PARENTHESE_CLOSE_PING); ip = ip.substring(indexOpen + 1, indexClose); } else { // Get ip when after from ip = ip.substring(0, ip.indexOf("\n")); if (ip.contains(":")) { index = ip.indexOf(":"); } else { index = ip.indexOf(" "); } ip = ip.substring(0, index); } } else { // Get ip when ping succeeded int indexOpen = ping.indexOf(PARENTHESE_OPEN_PING); int indexClose = ping.indexOf(PARENTHESE_CLOSE_PING); ip = ping.substring(indexOpen + 1, indexClose); } return ip; } /** * Gets the final ip we want to ping (example: if user fullfilled google.fr, final ip could be 8.8.8.8) * * @param ping The string returned by a ping command * @return The ip contained in the ping */ private String parseIpToPingFromPing(String ping) { String ip = ""; if (ping.contains(PING)) { // Get ip when ping succeeded int indexOpen = ping.indexOf(PARENTHESE_OPEN_PING); int indexClose = ping.indexOf(PARENTHESE_CLOSE_PING); ip = ping.substring(indexOpen + 1, indexClose); } return ip; } /** * Gets the time from ping command (if there is) * * @param ping The string returned by a ping command * @return The time contained in the ping */ private String parseTimeFromPing(String ping) { String time = ""; if (ping.contains(TIME_PING)) { int index = ping.indexOf(TIME_PING); time = ping.substring(index + 5); index = time.indexOf(" "); time = time.substring(0, index); } return time; } /** * Allows to timeout the ping if TIMEOUT exceeds. (-w and -W are not always supported on Android) */ private class TimeOutAsyncTask extends AsyncTask<Void, Void, Void> { private ExecutePingAsyncTask task; private int ttlTask; public TimeOutAsyncTask(ExecutePingAsyncTask task, int ttlTask) { this.task = task; this.ttlTask = ttlTask; } @Override protected Void doInBackground(Void... arg0) { return null; } @Override protected void onPostExecute(Void result) { if (handlerTimeout == null) { handlerTimeout = new Handler(); } // stop old timeout if (runnableTimeout != null) { handlerTimeout.removeCallbacks(runnableTimeout); } // define timeout runnableTimeout = new Runnable() { @Override public void run() { if (task != null) { if (ttlTask == finishedTasks) { task.setCancelled(true); task.cancel(true); } } } }; // launch timeout after a delay handlerTimeout.postDelayed(runnableTimeout, TIMEOUT); super.onPostExecute(result); } } /** * The task that ping an ip, with increasing time to live (ttl) value */ private class ExecutePingAsyncTask extends AsyncTask<Void, String, String> { private boolean isCancelled; private int maxTtl; public ExecutePingAsyncTask(int maxTtl) { this.maxTtl = maxTtl; } @Override protected void onProgressUpdate(String... values) { mTask.setResult(values[0]); } /** * Launches the ping, launches InetAddress to retrieve url if there is one, store trace */ @Override protected String doInBackground(Void... params) { try { String res = launchPing(urlToPing); mTraceRouteResult.append(res); publishProgress(res); TraceRouteContainer trace; //ping 失败。 if (res.contains(UNREACHABLE_PING) && !res.contains(EXCEED_PING)) { // Create the TraceRouteContainer object when ping // failed trace = new TraceRouteContainer("", parseIpFromPing(res), elapsedTime, false); } else { // Create the TraceRouteContainer object when succeed trace = new TraceRouteContainer("", parseIpFromPing(res), ttl == maxTtl ? Float.parseFloat(parseTimeFromPing(res)) : elapsedTime, true); // Get the host name from ip (unix ping do not support // hostname resolving) InetAddress inetAddr = InetAddress.getByName(trace.getIp()); Log.e("TAG", "getIP is " + trace.getIp()); String hostname = inetAddr.getHostName(); String canonicalHostname = inetAddr.getCanonicalHostName(); trace.setHostname(hostname); traces.add(trace); } return res; } catch (final Exception e) { e.printStackTrace(); } return ""; } /** * Launches ping command * * @param url The url to ping * @return The ping string */ @SuppressLint("NewApi") private String launchPing(String url) throws Exception { // Build ping command with parameters Process p; String command = String.format(PING_CMD_FORMMAT, ttl); Log.e("TAG", "The command is : " + command + url); long startTime = System.nanoTime(); // timeout task new TimeOutAsyncTask(this, ttl).execute(); // Launch command p = Runtime.getRuntime().exec(command + url); BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream())); // Construct the response from ping String s; String res = ""; while ((s = stdInput.readLine()) != null) { res += s + "\n"; if (s.contains(FROM_PING) || s.contains(SMALL_FROM_PING)) { // We store the elapsedTime when the line from ping comes elapsedTime = (System.nanoTime() - startTime) / 1000000.0f; } } p.destroy(); if (res.equals("")) { throw new IllegalArgumentException(); } // Store the wanted ip adress to compare with ping result if (ttl == 1) { Log.e("TAG", "ipToPings is : " + ipToPing + "res is:" + res); ipToPing = parseIpToPingFromPing(res); } Log.e("TAG", "launch ping result is : " + res); return res; } /** * Treat the previous ping (launches a ttl+1 if it is not the final ip, refresh the list on view etc...) */ @Override protected void onPostExecute(String result) { if (!isCancelled) { try { if (!"".equals(result)) { if ("No connectivity".equals(result)) { Log.e("TAG", "No connection"); } else { if (traces.size() > 0 && traces.get(traces.size() - 1).getIp().equals(ipToPing)) { if (ttl < maxTtl) { ttl = maxTtl; traces.remove(traces.size() - 1); new ExecutePingAsyncTask(maxTtl).execute(); } } else { if (ttl < maxTtl) { ttl++; new ExecutePingAsyncTask(maxTtl).execute(); } } } } finishedTasks++; } catch (final Exception e) { e.printStackTrace(); } } super.onPostExecute(result); } /** * Handles exception on ping * * @param e The exception thrown */ private void onException(Exception e) { if (e instanceof IllegalArgumentException) { //no ping } else { // error } finishedTasks++; } public void setCancelled(boolean isCancelled) { this.isCancelled = isCancelled; } } }