");
+ buf.append(_context.commSystem().renderPeerHTML(peer));
+ buf.append(" | ");
switch (tier) {
case 1: buf.append("Fast, High Capacity"); break;
@@ -159,10 +149,7 @@ class ProfileOrganizerRenderer {
buf.append(' ').append(fails).append('/').append(total).append(" Test Fails");
}
buf.append("  | ");
- //buf.append("profile.txt ");
- //buf.append(" netDb | ");
- buf.append("netDb");
- buf.append("/profile");
+ buf.append(" | profile");
buf.append("/+- | \n");
buf.append("
");
}
@@ -192,17 +179,9 @@ class ProfileOrganizerRenderer {
PeerProfile prof = (PeerProfile)iter.next();
Hash peer = prof.getPeer();
- buf.append("");
- buf.append("");
- if (prof.getIsFailing()) {
- buf.append("-- ").append(peer.toBase64().substring(0,6)).append("");
- } else {
- if (prof.getIsActive()) {
- buf.append("++ ").append(peer.toBase64().substring(0,6)).append("");
- } else {
- buf.append(" ").append(peer.toBase64().substring(0,6));
- }
- }
+ buf.append("");
+ buf.append(_context.commSystem().renderPeerHTML(peer));
+ buf.append(" | ");
RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer);
if (info != null)
buf.append("" + info.getCapabilities() + " | ");
diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
index 871c9eb4c..5bc8bf137 100644
--- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
+++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java
@@ -29,16 +29,20 @@ import net.i2p.router.transport.ntcp.NTCPTransport;
import net.i2p.router.transport.udp.UDPAddress;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
+import net.i2p.util.SimpleTimer;
public class CommSystemFacadeImpl extends CommSystemFacade {
private Log _log;
private RouterContext _context;
private TransportManager _manager;
+ private GeoIP _geoIP;
public CommSystemFacadeImpl(RouterContext context) {
_context = context;
_log = _context.logManager().getLog(CommSystemFacadeImpl.class);
_manager = null;
+ startGeoIP();
}
public void startup() {
@@ -348,4 +352,56 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
return;
}
+ private static final int START_DELAY = 5*60*1000;
+ private static final int LOOKUP_TIME = 30*60*1000;
+ private void startGeoIP() {
+ _geoIP = new GeoIP(_context);
+ SimpleScheduler.getInstance().addPeriodicEvent(new Lookup(), START_DELAY, LOOKUP_TIME);
+ }
+
+ private class Lookup implements SimpleTimer.TimedEvent {
+ public void timeReached() {
+ _geoIP.blockingLookup();
+ }
+ }
+
+ public void queueLookup(byte[] ip) {
+ _geoIP.add(ip);
+ }
+
+ /**
+ * Right now this only uses the transport IP,
+ * which is present only after you've connected to the peer.
+ * We could also check the IP in the netDb entry...
+ */
+ public String getCountry(Hash peer) {
+ byte[] ip = TransportImpl.getIP(peer);
+ if (ip == null)
+ return null;
+ return _geoIP.get(ip);
+ }
+
+ public String renderPeerHTML(Hash peer) {
+ String h = peer.toBase64().substring(0, 4);
+ StringBuffer buf = new StringBuffer(128);
+ buf.append("");
+ boolean found = _context.netDb().lookupRouterInfoLocally(peer) != null;
+ if (found)
+ buf.append("");
+ buf.append(h);
+ if (found)
+ buf.append("");
+ buf.append("");
+ String c = getCountry(peer);
+ if (c != null) {
+ buf.append(" ");
+ }
+ return buf.toString();
+ }
}
diff --git a/router/java/src/net/i2p/router/transport/GeoIP.java b/router/java/src/net/i2p/router/transport/GeoIP.java
new file mode 100644
index 000000000..e737e02f4
--- /dev/null
+++ b/router/java/src/net/i2p/router/transport/GeoIP.java
@@ -0,0 +1,311 @@
+package net.i2p.router.transport;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Use at your own risk.
+ */
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+import net.i2p.util.ConcurrentHashSet;
+import net.i2p.util.Log;
+
+/**
+ * Manage geoip lookup in a file with the Tor geoip format.
+ *
+ * The lookup is expensive, so a lookup is queued with add().
+ * The actual lookup of multiple IPs is fired with lookup().
+ * To get a country for an IP, use get() which returns a lower-case,
+ * generally two-letter country code or null.
+ *
+ * Everything here uses longs, since Java is signed-only, the file is
+ * sorted by unsigned, and we don't store the table in memory
+ * (unlike in Blocklist.java, where it's in-memory so we want to be
+ * space-efficient)
+ *
+ * @author zzz
+ */
+public class GeoIP {
+ private Log _log;
+ private I2PAppContext _context;
+ private final Map _codeToName;
+ private final Map _IPToCountry;
+ private final Set _pendingSearch;
+ private final Set _notFound;
+ private final AtomicBoolean _lock;
+
+ public GeoIP(I2PAppContext context) {
+ _context = context;
+ _log = context.logManager().getLog(GeoIP.class);
+ _codeToName = new ConcurrentHashMap();
+ _IPToCountry = new ConcurrentHashMap();
+ _pendingSearch = new ConcurrentHashSet();
+ _notFound = new ConcurrentHashSet();
+ _lock = new AtomicBoolean();
+ readCountryFile();
+ }
+
+ static final String PROP_GEOIP_ENABLED = "routerconsole.geoip.enable";
+ static final String GEOIP_DIR_DEFAULT = "geoip";
+ static final String GEOIP_FILE_DEFAULT = "geoip.txt";
+ static final String COUNTRY_FILE_DEFAULT = "countries.txt";
+
+ /**
+ * Fire off a thread to lookup all pending IPs.
+ * There is no indication of completion.
+ * Results will be added to the table and available via get() after completion.
+ */
+/******
+ public void lookup() {
+ if (! Boolean.valueOf(_context.getProperty(PROP_GEOIP_ENABLED, "true")).booleanValue()) {
+ _pendingSearch.clear();
+ return;
+ }
+ Thread t = new Thread(new LookupJob());
+ t.start();
+ }
+******/
+
+ /**
+ * Blocking lookup of all pending IPs.
+ * Results will be added to the table and available via get() after completion.
+ */
+ public void blockingLookup() {
+ if (! Boolean.valueOf(_context.getProperty(PROP_GEOIP_ENABLED, "true")).booleanValue()) {
+ _pendingSearch.clear();
+ return;
+ }
+ LookupJob j = new LookupJob();
+ j.run();
+ }
+
+ private class LookupJob implements Runnable {
+ public void run() {
+ if (_lock.getAndSet(true))
+ return;
+ Long[] search = _pendingSearch.toArray(new Long[_pendingSearch.size()]);
+ if (search.length <= 0)
+ return;
+ _pendingSearch.clear();
+ Arrays.sort(search);
+ String[] countries = readGeoIPFile(search);
+
+ for (int i = 0; i < countries.length; i++) {
+ if (countries[i] != null)
+ _IPToCountry.put(search[i], countries[i]);
+ else
+ _notFound.add(search[i]);
+ }
+ _lock.set(false);
+ }
+ }
+
+ /**
+ * Read in and parse the country file.
+ * The geoip file need not be sorted.
+ *
+ * Acceptable formats (IPV4 only):
+ * #comment (# must be in column 1)
+ * code,full name
+ *
+ * Example:
+ * US,UNITED STATES
+ *
+ * To create:
+ * wget http://ip-to-country.webhosting.info/downloads/ip-to-country.csv.zip
+ * unzip ip-to-country.csv.zip
+ * cut -d, -f3,5 < ip-to-country.csv|sed 's/"//g' | sort | uniq > countries.txt
+ *
+ */
+ private void readCountryFile() {
+ File GeoFile = new File(GEOIP_DIR_DEFAULT, COUNTRY_FILE_DEFAULT);
+ if (GeoFile == null || (!GeoFile.exists())) {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("Country file not found: " + GeoFile.getAbsolutePath());
+ return;
+ }
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(GeoFile);
+ StringBuffer buf = new StringBuffer(128);
+ while (DataHelper.readLine(in, buf)) {
+ try {
+ if (buf.charAt(0) == '#') {
+ buf.setLength(0);
+ continue;
+ }
+ String[] s = buf.toString().split(",");
+ // todo convert name to mixed upper/lower case
+ _codeToName.put(s[0].toLowerCase(), s[1]);
+ } catch (IndexOutOfBoundsException ioobe) {
+ }
+ buf.setLength(0);
+ }
+ } catch (IOException ioe) {
+ if (_log.shouldLog(Log.ERROR))
+ _log.error("Error reading the Country File", ioe);
+ } finally {
+ if (in != null) try { in.close(); } catch (IOException ioe) {}
+ }
+ }
+
+ /**
+ * Read in and parse the geoip file.
+ * The geoip file must be sorted, and may not contain overlapping entries.
+ *
+ * Acceptable formats (IPV4 only):
+ * #comment (# must be in column 1)
+ * integer IP,integer IP, country code
+ *
+ * Example:
+ * 121195296,121195327,IT
+ *
+ * This is identical to the Tor geoip file, which can be found in
+ * src/config/geoip in their distribution, or /usr/local/lib/share/tor/geoip
+ * in their installation.
+ * Thanks to Tor for finding a source for the data, and the format script.
+ *
+ * To create:
+ * wget http://ip-to-country.webhosting.info/downloads/ip-to-country.csv.zip
+ * unzip ip-to-country.csv.zip
+ * cut -d, -f0-3 < ip-to-country.csv|sed 's/"//g' > geoip.txt
+ *
+ * @param search a sorted map of IPs to search, with null values
+ * returns the map with values entered
+ *
+ */
+ private String[] readGeoIPFile(Long[] search) {
+ File GeoFile = new File(GEOIP_DIR_DEFAULT, GEOIP_FILE_DEFAULT);
+ if (GeoFile == null || (!GeoFile.exists())) {
+ if (_log.shouldLog(Log.WARN))
+ _log.warn("GeoIP file not found: " + GeoFile.getAbsolutePath());
+ return new String[0];
+ }
+ String[] rv = new String[search.length];
+ int idx = 0;
+ long start = _context.clock().now();
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(GeoFile);
+ StringBuffer buf = new StringBuffer(128);
+ while (DataHelper.readLine(in, buf) && idx < search.length) {
+ try {
+ if (buf.charAt(0) == '#') {
+ buf.setLength(0);
+ continue;
+ }
+ String[] s = buf.toString().split(",");
+ long ip1 = Long.parseLong(s[0]);
+ long ip2 = Long.parseLong(s[1]);
+ while (search[idx].longValue() < ip1 && idx < search.length) {
+ idx++;
+ }
+ while (search[idx].longValue() >= ip1 && search[idx].longValue() <= ip2 && idx < search.length) {
+ rv[idx++] = s[2].toLowerCase();
+ }
+ } catch (IndexOutOfBoundsException ioobe) {
+ } catch (NumberFormatException nfe) {
+ }
+ buf.setLength(0);
+ }
+ } catch (IOException ioe) {
+ if (_log.shouldLog(Log.ERROR))
+ _log.error("Error reading the GeoFile", ioe);
+ } finally {
+ if (in != null) try { in.close(); } catch (IOException ioe) {}
+ }
+
+ if (_log.shouldLog(Log.WARN)) {
+ _log.warn("GeoIP processing finished, time: " + (_context.clock().now() - start));
+ }
+ return rv;
+ }
+
+ /**
+ * Add to the list needing lookup
+ */
+ public void add(String ip) {
+ InetAddress pi;
+ try {
+ pi = InetAddress.getByName(ip);
+ } catch (UnknownHostException uhe) {
+ return;
+ }
+ if (pi == null) return;
+ byte[] pib = pi.getAddress();
+ add(pib);
+ }
+
+ public void add(byte ip[]) {
+ if (ip.length != 4)
+ return;
+ add(toLong(ip));
+ }
+
+ private void add(long ip) {
+ Long li = Long.valueOf(ip);
+ if (!(_IPToCountry.containsKey(li) || _notFound.contains(li)))
+ _pendingSearch.add(li);
+ }
+
+ /**
+ * Get the country for an IP
+ * @return lower-case code, generally two letters.
+ */
+ public String get(String ip) {
+ InetAddress pi;
+ try {
+ pi = InetAddress.getByName(ip);
+ } catch (UnknownHostException uhe) {
+ return null;
+ }
+ if (pi == null) return null;
+ byte[] pib = pi.getAddress();
+ return get(pib);
+ }
+
+ public String get(byte ip[]) {
+ if (ip.length != 4)
+ return null;
+ return get(toLong(ip));
+ }
+
+ private String get(long ip) {
+ return _IPToCountry.get(Long.valueOf(ip));
+ }
+
+ private static long toLong(byte ip[]) {
+ int rv = 0;
+ for (int i = 0; i < 4; i++)
+ rv |= (ip[i] & 0xff) << ((3-i)*8);
+ return ((long) rv) & 0xffffffffl;
+ }
+
+ public String fullName(String code) {
+ return _codeToName.get(code);
+ }
+
+ public static void main(String args[]) {
+ GeoIP g = new GeoIP(new I2PAppContext());
+ String tests[] = {"0.0.0.0", "0.0.0.1", "0.0.0.2", "0.0.0.255", "1.0.0.0",
+ "94.3.3.3", "77.1.2.3", "127.0.0.0", "127.127.127.127", "128.0.0.0",
+ "89.8.9.3", "72.5.6.8", "217.4.9.7", "175.107.027.107", "135.6.5.2",
+ "129.1.2.3", "255.255.255.254", "255.255.255.255"};
+ for (int i = 0; i < tests.length; i++)
+ g.add(tests[i]);
+ g.blockingLookup();
+ for (int i = 0; i < tests.length; i++)
+ System.out.println(tests[i] + " : " + g.get(tests[i]));
+
+ }
+}
diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java
index be31d35f8..77a9bf323 100644
--- a/router/java/src/net/i2p/router/transport/TransportImpl.java
+++ b/router/java/src/net/i2p/router/transport/TransportImpl.java
@@ -538,8 +538,9 @@ public abstract class TransportImpl implements Transport {
_log.warn(this.getStyle() + " setting wasUnreachable to " + yes + " for " + peer);
}
- public /* static */ void setIP(Hash peer, byte[] ip) {
+ public void setIP(Hash peer, byte[] ip) {
_IPMap.put(peer, ip);
+ _context.commSystem().queueLookup(ip);
}
public static byte[] getIP(Hash peer) {
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
index 852ee28ff..cc982e26b 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
@@ -734,8 +734,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
}
}
- private static String netDbLink(Hash peer) {
- String h = peer.toBase64().substring(0, 4);
- return "" + h + "";
+ private String netDbLink(Hash peer) {
+ return _context.commSystem().renderPeerHTML(peer);
}
}
|