tor-android/orbotservice/src/main/java/org/torproject/android/service/TorEventHandler.java

326 lines
9.1 KiB
Java

package org.torproject.android.service;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import org.torproject.android.control.EventHandler;
import org.torproject.android.service.util.Prefs;
/**
* Created by n8fr8 on 9/25/16.
*/
public class TorEventHandler implements EventHandler, TorServiceConstants {
private TorService mService;
private long lastRead = -1;
private long lastWritten = -1;
private long mTotalTrafficWritten = 0;
private long mTotalTrafficRead = 0;
private NumberFormat mNumberFormat = null;
private HashMap<String,Node> hmBuiltNodes = new HashMap<String,Node>();
public class Node
{
String status;
String id;
String name;
String ipAddress;
String country;
String organization;
}
public HashMap<String,Node> getNodes ()
{
return hmBuiltNodes;
}
public TorEventHandler (TorService service)
{
mService = service;
mNumberFormat = NumberFormat.getInstance(Locale.getDefault()); //localized numbers!
}
@Override
public void message(String severity, String msg) {
mService.logNotice(severity + ": " + msg);
}
@Override
public void newDescriptors(List<String> orList) {
}
@Override
public void orConnStatus(String status, String orName) {
StringBuilder sb = new StringBuilder();
sb.append("orConnStatus (");
sb.append(parseNodeName(orName) );
sb.append("): ");
sb.append(status);
mService.debug(sb.toString());
}
@Override
public void streamStatus(String status, String streamID, String target) {
StringBuilder sb = new StringBuilder();
sb.append("StreamStatus (");
sb.append((streamID));
sb.append("): ");
sb.append(status);
mService.logNotice(sb.toString());
}
@Override
public void unrecognized(String type, String msg) {
StringBuilder sb = new StringBuilder();
sb.append("Message (");
sb.append(type);
sb.append("): ");
sb.append(msg);
mService.logNotice(sb.toString());
}
@Override
public void bandwidthUsed(long read, long written) {
if (read != lastRead || written != lastWritten)
{
StringBuilder sb = new StringBuilder();
sb.append(formatCount(read));
sb.append(" \u2193");
sb.append(" / ");
sb.append(formatCount(written));
sb.append(" \u2191");
int iconId = R.drawable.ic_stat_tor;
if (read > 0 || written > 0)
iconId = R.drawable.ic_stat_tor_xfer;
if (mService.hasConnectivity() && Prefs.expandedNotifications())
mService.showToolbarNotification(sb.toString(), mService.getNotifyId(), iconId);
mTotalTrafficWritten += written;
mTotalTrafficRead += read;
}
lastWritten = written;
lastRead = read;
mService.sendCallbackBandwidth(lastWritten, lastRead, mTotalTrafficWritten, mTotalTrafficRead);
}
private String formatCount(long count) {
// Converts the supplied argument into a string.
// Under 2Mb, returns "xxx.xKb"
// Over 2Mb, returns "xxx.xxMb"
if (mNumberFormat != null)
if (count < 1e6)
return mNumberFormat.format(Math.round((float)((int)(count*10/1024))/10)) + "kbps";
else
return mNumberFormat.format(Math.round((float)((int)(count*100/1024/1024))/100)) + "mbps";
else
return "";
//return count+" kB";
}
public void circuitStatus(String status, String circID, String path) {
/* once the first circuit is complete, then announce that Orbot is on*/
if (mService.getCurrentStatus() == STATUS_STARTING && TextUtils.equals(status, "BUILT"))
mService.sendCallbackStatus(STATUS_ON);
StringBuilder sb = new StringBuilder();
sb.append("Circuit (");
sb.append((circID));
sb.append(") ");
sb.append(status);
sb.append(": ");
StringTokenizer st = new StringTokenizer(path,",");
Node node = null;
while (st.hasMoreTokens())
{
String nodePath = st.nextToken();
node = new Node();
String[] nodeParts;
if (nodePath.contains("="))
nodeParts = nodePath.split("=");
else
nodeParts = nodePath.split("~");
if (nodeParts.length == 1)
{
node.id = nodeParts[0].substring(1);
node.name = node.id;
}
else if (nodeParts.length == 2)
{
node.id = nodeParts[0].substring(1);
node.name = nodeParts[1];
}
node.status = status;
sb.append(node.name);
if (st.hasMoreTokens())
sb.append (" > ");
}
if (Prefs.useDebugLogging())
mService.debug(sb.toString());
else if(status.equals("BUILT"))
mService.logNotice(sb.toString());
else if (status.equals("CLOSED"))
mService.logNotice(sb.toString());
if (Prefs.expandedNotifications())
{
//get IP from last nodename
if(status.equals("BUILT")){
if (node.ipAddress == null)
mService.exec(new ExternalIPFetcher(node));
hmBuiltNodes.put(circID, node);
}
if (status.equals("CLOSED"))
{
hmBuiltNodes.remove(circID);
}
}
}
private class ExternalIPFetcher implements Runnable {
private Node mNode;
private int MAX_ATTEMPTS = 3;
private final static String ONIONOO_BASE_URL = "https://onionoo.torproject.org/details?fields=country_name,as_name,or_addresses&lookup=";
public ExternalIPFetcher (Node node)
{
mNode = node;
}
public void run ()
{
for (int i = 0; i < MAX_ATTEMPTS; i++)
{
if (mService.getControlConnection() != null)
{
try {
URLConnection conn = null;
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8118));
conn = new URL(ONIONOO_BASE_URL + mNode.id).openConnection(proxy);
conn.setRequestProperty("Connection","Close");
conn.setConnectTimeout(60000);
conn.setReadTimeout(60000);
InputStream is = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
// getting JSON string from URL
StringBuffer json = new StringBuffer();
String line = null;
while ((line = reader.readLine())!=null)
json.append(line);
JSONObject jsonNodeInfo = new org.json.JSONObject(json.toString());
JSONArray jsonRelays = jsonNodeInfo.getJSONArray("relays");
if (jsonRelays.length() > 0)
{
mNode.ipAddress = jsonRelays.getJSONObject(0).getJSONArray("or_addresses").getString(0).split(":")[0];
mNode.country = jsonRelays.getJSONObject(0).getString("country_name");
mNode.organization = jsonRelays.getJSONObject(0).getString("as_name");
StringBuffer sbInfo = new StringBuffer();
sbInfo.append(mNode.ipAddress);
if (mNode.country != null)
sbInfo.append(' ').append(mNode.country);
if (mNode.organization != null)
sbInfo.append(" (").append(mNode.organization).append(')');
mService.logNotice(sbInfo.toString());
}
reader.close();
is.close();
break;
} catch (Exception e) {
mService.debug ("Error getting node details from onionoo: " + e.getMessage());
}
}
}
}
}
private String parseNodeName(String node)
{
if (node.indexOf('=')!=-1)
{
return (node.substring(node.indexOf("=")+1));
}
else if (node.indexOf('~')!=-1)
{
return (node.substring(node.indexOf("~")+1));
}
else
return node;
}
}