Android Open Source - Speedometer Traceroute Task






From Project

Back to project page Speedometer.

License

The source code is released under:

Apache License

If you think the Android project Speedometer listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* Copyright 2012 Google Inc.
 */*from   w  w  w.  ja  v  a  2s .co  m*/
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.wireless.speed.speedometer.measurements;

import com.google.wireless.speed.speedometer.Config;
import com.google.wireless.speed.speedometer.Logger;
import com.google.wireless.speed.speedometer.MeasurementDesc;
import com.google.wireless.speed.speedometer.MeasurementError;
import com.google.wireless.speed.speedometer.MeasurementResult;
import com.google.wireless.speed.speedometer.MeasurementTask;
import com.google.wireless.speed.speedometer.R;
import com.google.wireless.speed.speedometer.SpeedometerApp;
import com.google.wireless.speed.speedometer.util.MeasurementJsonConvertor;
import com.google.wireless.speed.speedometer.util.PhoneUtils;
import com.google.wireless.speed.speedometer.util.Util;

import android.content.Context;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InvalidClassException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;

/**
 * A Callable task that handles Traceroute measurements
 * @author wenjiezeng@google.com (Steve Zeng)
 *
 */
public class TracerouteTask extends MeasurementTask {
  // Type name for internal use
  public static final String TYPE = "traceroute";
  // Human readable name for the task
  public static final String DESCRIPTOR = "traceroute";
  /* Default payload size of the ICMP packet, plus the 8-byte ICMP header resulting in a total of 
   * 64-byte ICMP packet */
  public static final int DEFAULT_PING_PACKET_SIZE = 56;
  public static final int DEFAULT_PING_TIMEOUT = 10;
  public static final int DEFAULT_MAX_HOP_CNT = 30;
  // Used to compute progress for user
  public static final int EXPECTED_HOP_CNT = 20;
  public static final int DEFAULT_PINGS_PER_HOP = 3;
  
  private Process pingProc = null;
  private boolean stopRequested = false;
  /**
   * The description of the Traceroute measurement 
   */
  public static class TracerouteDesc extends MeasurementDesc {
    // the host name or IP address to use as the target of the traceroute.
    public String target;
    // the packet per ICMP ping in the unit of bytes
    private int packetSizeByte;
    // the number of seconds we wait for a ping response.
    private int pingTimeoutSec;
    // the interval between successive pings in seconds
    private double pingIntervalSec; 
    // the number of pings we use for each ttl value
    private int pingsPerHop;        
    // the total number of pings will send before we declarethe traceroute fails
    private int maxHopCount;        
    // the location of the ping binary. Only used internally
    private String pingExe;         
    
    public TracerouteDesc(String key, Date startTime,
                          Date endTime, double intervalSec, long count, long priority, 
                          Map<String, String> params) throws InvalidParameterException {
      super(TracerouteTask.TYPE, key, startTime, endTime, intervalSec, count, 
          priority, params);
      initalizeParams(params);
      
      if (target == null || target.length() == 0) {
        throw new InvalidParameterException("Target of traceroute cannot be null");
      }
    }

    @Override
    public String getType() {
      return TracerouteTask.TYPE;
    }
    
    @Override
    protected void initalizeParams(Map<String, String> params) {
      
      if (params == null) {
        return;
      }
      
      // HTTP specific parameters according to the design document
      this.target = params.get("target");
      try {        
        String val;
        if ((val = params.get("packet_size_byte")) != null && val.length() > 0 &&
            Integer.parseInt(val) > 0) {
          this.packetSizeByte = Integer.parseInt(val);  
        } else {
          this.packetSizeByte = TracerouteTask.DEFAULT_PING_PACKET_SIZE;
        }
        if ((val = params.get("ping_timeout_sec")) != null && val.length() > 0 &&
            Integer.parseInt(val) > 0) {
          this.pingTimeoutSec = Integer.parseInt(val);
        } else {
          this.pingTimeoutSec = TracerouteTask.DEFAULT_PING_TIMEOUT;
        }
        if ((val = params.get("ping_interval_sec")) != null && val.length() > 0 &&
            Integer.parseInt(val) > 0) {
          this.pingIntervalSec = Integer.parseInt(val);
        } else {
          this.pingIntervalSec = Config.DEFAULT_INTERVAL_BETWEEN_ICMP_PACKET_SEC;
        }
        if ((val = params.get("pings_per_hop")) != null && val.length() > 0 &&
            Integer.parseInt(val) > 0) {
          this.pingsPerHop = Integer.parseInt(val);
        } else {
          this.pingsPerHop = TracerouteTask.DEFAULT_PINGS_PER_HOP;
        }
        if ((val = params.get("max_hop_count")) != null && val.length() > 0 && 
            Integer.parseInt(val) > 0) {
          this.maxHopCount = Integer.parseInt(val);  
        } else {
          this.maxHopCount = TracerouteTask.DEFAULT_MAX_HOP_CNT;
        }
      } catch (NumberFormatException e) {
        throw new InvalidParameterException("PingTask cannot be created due to invalid params");
      }
    }
    
  }
  
  public TracerouteTask(MeasurementDesc desc, Context parent) {
    super(new TracerouteDesc(desc.key, desc.startTime, desc.endTime, desc.intervalSec,
      desc.count, desc.priority, desc.parameters), parent);
  }
  
  /**
   * Returns a copy of the TracerouteTask
   */
  @Override
  public MeasurementTask clone() {
    MeasurementDesc desc = this.measurementDesc;
    TracerouteDesc newDesc = new TracerouteDesc(desc.key, desc.startTime, desc.endTime, 
      desc.intervalSec, desc.count, desc.priority, desc.parameters);
    return new TracerouteTask(newDesc, parent);
  }

  @Override
  public MeasurementResult call() throws MeasurementError {
    
    TracerouteDesc task = (TracerouteDesc) this.measurementDesc;
    int maxHopCount = task.maxHopCount;
    int ttl = 1;
    String hostIp = null;
    // TODO(Wenjie): Add a exhaustive list of ping locations for different Android phones
    task.pingExe = parent.getString(R.string.ping_executable);
    String target = task.target;
    boolean success = false;
    ArrayList<HopInfo> hopHosts = new ArrayList<HopInfo>();
    
    Logger.d("Starting traceroute on host " + task.target);
    
    
    try {
      hostIp = InetAddress.getByName(target).getHostAddress();
    } catch (UnknownHostException e) {
      Logger.e("Cannont resolve host " + target);
      throw new MeasurementError("target " + target + " cannot be resolved");
    }
    MeasurementResult result = null;
    
    while (maxHopCount-- >= 0 && !stopRequested) {
      /* Current traceroute implementation sends out three ICMP probes per TTL.
       * One ping every 0.2s is the lower bound before some platforms requires
       * root to run ping. We ping once every time to get a rough rtt as we cannot
       * get the exact rtt from the output of the ping command with ttl being set
       * */        
      String command = Util.constructCommand(task.pingExe, "-n", "-t", ttl,
        "-s", task.packetSizeByte, "-c 1 ", target);
      try {
        double rtt = 0;
        long t1;
        HashSet<String> hostsAtThisDistance = new HashSet<String>();
        for (int i = 0; i < task.pingsPerHop; i++) {
          t1 = System.currentTimeMillis();
          pingProc = Runtime.getRuntime().exec(command);
          rtt += System.currentTimeMillis() - t1;
          // Grab the output of the process that runs the ping command
          InputStream is = pingProc.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(is));

          /* Process each line of the ping output and extracts the intermediate hops into 
           * hostAtThisDistance */ 
          processPingOutput(br, hostsAtThisDistance, hostIp);
          cleanUp(pingProc);
          try {
            Thread.sleep((long) (task.pingIntervalSec * 1000));
          } catch (InterruptedException e) {
            Logger.i("Sleep interrupted between ping intervals");
          }
        }
        rtt = rtt / task.pingsPerHop;

        hopHosts.add(new HopInfo(hostsAtThisDistance, rtt));

        // Process the extracted IPs of intermediate hops
        StringBuffer progressStr = new StringBuffer(ttl + ": ");
        for (String ip : hostsAtThisDistance) {
          // If we have reached the final destination hostIp, print it out and clean up
          if (ip.compareTo(hostIp) == 0) {
            Logger.i(ttl + ": " + hostIp);
            Logger.i(" Finished! " + target + " reached in " + ttl + " hops");

            success = true;
            PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
            result = new MeasurementResult(phoneUtils.getDeviceInfo().deviceId, 
                phoneUtils.getDeviceProperty(), TracerouteTask.TYPE, 
                System.currentTimeMillis() * 1000, success, this.measurementDesc);
            result.addResult("num_hops", ttl);
            for (int i = 0; i < hopHosts.size(); i++) {
              HopInfo hopInfo = hopHosts.get(i);
              int hostIdx = 1;
              for (String host : hopInfo.hosts) {
                result.addResult("hop_" + i + "_addr_" + hostIdx++, host);
              }
              result.addResult("hop_" + i + "_rtt_ms", String.format("%.3f", hopInfo.rtt));
            }
            Logger.i(MeasurementJsonConvertor.toJsonString(result));
            return result;
          } else {
            // Otherwise, we aggregate various hosts at a given hop distance for printout
            progressStr.append(ip + " | ");
          }
        }
        // Remove the trailing separators
        progressStr.delete(progressStr.length() - 3, progressStr.length());
        Logger.i(progressStr.toString());

      } catch (SecurityException e) {
        Logger.e("Does not have the permission to run ping on this device");
      } catch (IOException e) {
        Logger.e("The ping program cannot be executed");
        Logger.e(e.getMessage());
      } finally {
        cleanUp(pingProc);
      }
      ttl++;
      this.progress = (int) (100 * ttl / (double) TracerouteTask.EXPECTED_HOP_CNT);
      this.progress = Math.min(Config.MAX_PROGRESS_BAR_VALUE, progress);
      broadcastProgressForUser(progress);
    }

    throw new MeasurementError("cannot perform traceroute to " + task.target);
  }

  @SuppressWarnings("rawtypes")
  public static Class getDescClass() throws InvalidClassException {
    return TracerouteDesc.class;
  }
  
  @Override
  public String getType() {
    return TracerouteTask.TYPE;
  }
  
  @Override
  public String getDescriptor() {
    return DESCRIPTOR;
  }
  
  private void cleanUp(Process proc) {
    if (proc != null) {
      // destroy() closes all open streams
      proc.destroy();
    }
  }

  private void processPingOutput(BufferedReader br, HashSet<String> hostsAtThisDistance,
      String hostIp) throws IOException {
    String line = null;
    while ((line = br.readLine()) != null) {
      Logger.d(line);
      if (line.startsWith("From")) {
        String ip = getHostIp(line);
        if (ip != null && ip.compareTo(hostIp) != 0) {
          hostsAtThisDistance.add(ip);
        }
      } else if (line.contains("time=")) {
        hostsAtThisDistance.add(hostIp);
      }
    }
  }

  /* TODO(Wenjie): The current search for valid IPs assumes the IP string is not a proper 
   * substring of the space-separated tokens. For more robust searching in case different 
   * outputs from ping due to its different versions, we need to refine the search 
   * by testing weather any substring of the tokens contains a valid IP */
  private String getHostIp(String line) {      
    String[] tokens = line.split(" ");
    // In most cases, the second element in the array is the IP
    String tempIp = tokens[1];
    if (isValidIpv4Addr(tempIp)) {
      return tempIp;
    } else {
      for (int i = 0; i < tokens.length; i++) {
        if (i == 1) {
          // Examined already
          continue;
        } else {
          if (isValidIpv4Addr(tokens[i])) {
            return tokens[i];
          }
        }
      }
    }
    
    return null;
  }
  
  // Tells whether the string is an valid IPv4 address
  private boolean isValidIpv4Addr(String ip) {
    String[] tokens = ip.split("\\.");
    if (tokens.length == 4) {
      for (int i = 0; i < 4; i++) {
        try {
          int val = Integer.parseInt(tokens[i]); 
          if (val < 0 || val > 255) {
            return false;
          }
        } catch (NumberFormatException e) {
          Logger.d(ip + " is not a valid IP address");
          return false;
        }
      }
      return true;
    }
    return false;
  }  
  
  private class HopInfo {
    // The hosts at a given hop distance
    public HashSet<String> hosts;
    // The average RRT for this hop distance
    public double rtt;
    
    protected HopInfo(HashSet<String> hosts, double rtt) {
      this.hosts = hosts;
      this.rtt = rtt;
    }
  }
  
  @Override
  public String toString() {
    TracerouteDesc desc = (TracerouteDesc) measurementDesc;
    return "[Traceroute]\n  Target: " + desc.target + "\n  Interval (sec): " + desc.intervalSec 
    + "\n  Next run: " + desc.startTime;
  }
  
  @Override
  public void stop() {
    stopRequested = true;
    cleanUp(pingProc);
  }
}




Java Source Code List

com.google.wireless.speed.speedometer.AboutActivity.java
com.google.wireless.speed.speedometer.AccountSelector.java
com.google.wireless.speed.speedometer.BatteryCapPowerManager.java
com.google.wireless.speed.speedometer.Checkin.java
com.google.wireless.speed.speedometer.Config.java
com.google.wireless.speed.speedometer.DeviceInfo.java
com.google.wireless.speed.speedometer.DeviceProperty.java
com.google.wireless.speed.speedometer.Logger.java
com.google.wireless.speed.speedometer.MeasurementCreationActivity.java
com.google.wireless.speed.speedometer.MeasurementDesc.java
com.google.wireless.speed.speedometer.MeasurementError.java
com.google.wireless.speed.speedometer.MeasurementResult.java
com.google.wireless.speed.speedometer.MeasurementScheduleConsoleActivity.java
com.google.wireless.speed.speedometer.MeasurementScheduler.java
com.google.wireless.speed.speedometer.MeasurementSkippedException.java
com.google.wireless.speed.speedometer.MeasurementTask.java
com.google.wireless.speed.speedometer.ResultsConsoleActivity.java
com.google.wireless.speed.speedometer.SpeedometerApp.java
com.google.wireless.speed.speedometer.SpeedometerPreferenceActivity.java
com.google.wireless.speed.speedometer.SplashScreenActivity.java
com.google.wireless.speed.speedometer.SystemConsoleActivity.java
com.google.wireless.speed.speedometer.UpdateIntent.java
com.google.wireless.speed.speedometer.WatchdogBootReceiver.java
com.google.wireless.speed.speedometer.measurements.DnsLookupTask.java
com.google.wireless.speed.speedometer.measurements.HttpTask.java
com.google.wireless.speed.speedometer.measurements.PingTask.java
com.google.wireless.speed.speedometer.measurements.TracerouteTask.java
com.google.wireless.speed.speedometer.measurements.UDPBurstTask.java
com.google.wireless.speed.speedometer.util.MeasurementJsonConvertor.java
com.google.wireless.speed.speedometer.util.PhoneUtils.java
com.google.wireless.speed.speedometer.util.Util.java