This is an experimental network database tool for debugging and analysis. Do not panic even if you see warnings below. " +
+ "Possible \"threats\" are summarized at the bottom, however these are unlikely to be real threats. " +
+ "If you see anything you would like to discuss with the devs, contact us on IRC #i2p-dev.
");
+
+ double tot = 0;
+ int count = 200;
+ byte[] b = new byte[32];
+ for (int i = 0; i < count; i++) {
+ _context.random().nextBytes(b);
+ Hash h = new Hash(b);
+ double d = closestDistance(h, ris);
+ tot += d;
+ }
+ DecimalFormat fmt = new DecimalFormat("#0.00");
+ double avgMinDist = tot / count;
+ buf.append("
Average closest floodfill distance: " + fmt.format(avgMinDist) + "
");
+ renderRouterInfo(buf, p.r1, null, false, false);
+ renderRouterInfo(buf, p.r2, null, false, false);
+ double point = MIN_CLOSE - distance;
+ if (point > 0) {
+ addPoints(points, p.r1.getHash(), point, fmt.format(point) + ": Too close to other floodfill " + p.r2.getHash().toBase64());
+ addPoints(points, p.r2.getHash(), point, fmt.format(point) + ": Too close to other floodfill " + p.r1.getHash().toBase64());
+ }
+ }
+ out.write(buf.toString());
+ out.flush();
+ buf.setLength(0);
+ }
+
+ private double closestDistance(Hash h, List ris) throws IOException {
+ BigInteger min = (new BigInteger("2")).pow(256);
+ for (RouterInfo info : ris) {
+ BigInteger dist = HashDistance.getDistance(h, info.getHash());
+ if (dist.compareTo(min) < 0)
+ min = dist;
+ }
+ return biLog2(min);
+ }
+
+ /** v4 only */
+ private static byte[] getIP(RouterInfo ri) {
+ for (RouterAddress ra : ri.getAddresses()) {
+ byte[] rv = ra.getIP();
+ if (rv != null && rv.length == 4)
+ return rv;
+ }
+ return null;
+ }
+
+ private static class FooComparator implements Comparator, Serializable {
+ private final ObjectCounter _o;
+ public FooComparator(ObjectCounter o) { _o = o;}
+ public int compare(Integer l, Integer r) {
+ // reverse by count
+ int rv = _o.count(r) - _o.count(l);
+ if (rv != 0)
+ return rv;
+ // foward by IP
+ return l.intValue() - r.intValue();
+ }
+ }
+
+ private void renderIPGroups32(Writer out, StringBuilder buf, List ris, Map points) throws IOException {
+ buf.append("
Floodfills with the Same IP
");
+ int sz = ris.size();
+ ObjectCounter oc = new ObjectCounter();
+ for (RouterInfo info : ris) {
+ byte[] ip = getIP(info);
+ if (ip == null)
+ continue;
+ Integer x = Integer.valueOf((int) DataHelper.fromLong(ip, 0, 4));
+ oc.increment(x);
+ }
+ List foo = new ArrayList();
+ for (Integer ii : oc.objects()) {
+ int count = oc.count(ii);
+ if (count >= 2)
+ foo.add(ii);
+ }
+ Collections.sort(foo, new FooComparator(oc));
+ boolean found = false;
+ DecimalFormat fmt = new DecimalFormat("#0.00");
+ for (Integer ii : foo) {
+ int count = oc.count(ii);
+ int i = ii.intValue();
+ int i0 = (i >> 24) & 0xff;
+ int i1 = (i >> 16) & 0xff;
+ int i2 = (i >> 8) & 0xff;
+ int i3 = i & 0xff;
+ buf.append("
").append(count).append(" floodfills with IP ").append(i0).append('.')
+ .append(i1).append('.').append(i2).append('.').append(i3)
+ .append(":
");
+ for (RouterInfo info : ris) {
+ byte[] ip = getIP(info);
+ if (ip == null)
+ continue;
+ if ((ip[0] & 0xff) != i0)
+ continue;
+ if ((ip[1] & 0xff) != i1)
+ continue;
+ if ((ip[2] & 0xff) != i2)
+ continue;
+ if ((ip[3] & 0xff) != i3)
+ continue;
+ found = true;
+ renderRouterInfo(buf, info, null, false, false);
+ double point = POINTS32 * (count - 1);
+ addPoints(points, info.getHash(), point, fmt.format(point) + ": Same IP with " + (count - 1) + " other");
+ }
+ }
+ if (!found)
+ buf.append("
");
+ int sz = ris.size();
+ ObjectCounter oc = new ObjectCounter();
+ for (RouterInfo info : ris) {
+ byte[] ip = getIP(info);
+ if (ip == null)
+ continue;
+ Integer x = Integer.valueOf((int) DataHelper.fromLong(ip, 0, 3));
+ oc.increment(x);
+ }
+ List foo = new ArrayList();
+ for (Integer ii : oc.objects()) {
+ int count = oc.count(ii);
+ if (count >= 2)
+ foo.add(ii);
+ }
+ Collections.sort(foo, new FooComparator(oc));
+ DecimalFormat fmt = new DecimalFormat("#0.00");
+ for (Integer ii : foo) {
+ int count = oc.count(ii);
+ int i = ii.intValue();
+ int i0 = i >> 16;
+ int i1 = (i >> 8) & 0xff;
+ int i2 = i & 0xff;
+ buf.append("
").append(count).append(" floodfills in ").append(i0).append('.')
+ .append(i1).append('.').append(i2).append(".0/24:
");
+ for (RouterInfo info : ris) {
+ byte[] ip = getIP(info);
+ if (ip == null)
+ continue;
+ if ((ip[0] & 0xff) != i0)
+ continue;
+ if ((ip[1] & 0xff) != i1)
+ continue;
+ if ((ip[2] & 0xff) != i2)
+ continue;
+ renderRouterInfo(buf, info, null, false, false);
+ double point = POINTS24 * (count - 1);
+ addPoints(points, info.getHash(), point, fmt.format(point) + ": Same /24 IP with " + (count - 1) + " other");
+ }
+ }
+ out.write(buf.toString());
+ out.flush();
+ buf.setLength(0);
+ }
+
+ /** no points */
+ private void renderIPGroups16(Writer out, StringBuilder buf, List ris) throws IOException {
+ buf.append("
Floodfills in the Same /16 (4 minimum)
");
+ int sz = ris.size();
+ ObjectCounter oc = new ObjectCounter();
+ for (RouterInfo info : ris) {
+ byte[] ip = getIP(info);
+ if (ip == null)
+ continue;
+ Integer x = Integer.valueOf((int) DataHelper.fromLong(ip, 0, 2));
+ oc.increment(x);
+ }
+ List foo = new ArrayList();
+ for (Integer ii : oc.objects()) {
+ int count = oc.count(ii);
+ if (count >= 4)
+ foo.add(ii);
+ }
+ Collections.sort(foo, new FooComparator(oc));
+ for (Integer ii : foo) {
+ int count = oc.count(ii);
+ int i = ii.intValue();
+ int i0 = i >> 8;
+ int i1 = i & 0xff;
+ buf.append("
").append(count).append(" floodfills in ").append(i0).append('.')
+ .append(i1).append(".0.0/16:
");
+ for (RouterInfo info : ris) {
+ byte[] ip = getIP(info);
+ if (ip == null)
+ continue;
+ if ((ip[0] & 0xff) != i0)
+ continue;
+ if ((ip[1] & 0xff) != i1)
+ continue;
+ renderRouterInfo(buf, info, null, false, false);
+ }
+ }
+ out.write(buf.toString());
+ out.flush();
+ buf.setLength(0);
+ }
+
+ private void renderRouterInfoHTML(Writer out, StringBuilder buf, Hash us, double avgMinDist,
+ List ris, Map points) throws IOException {
+ Collections.sort(ris, new RouterInfoRoutingKeyComparator(us));
+ double min = 256;
+ double max = 0;
+ double tot = 0;
+ double median = 0;
+ int count = Math.min(MAX, ris.size());
+ boolean isEven = (count % 2) == 0;
+ int medIdx = isEven ? (count / 2) - 1 : (count / 2);
+ DecimalFormat fmt = new DecimalFormat("#0.00");
+ for (int i = 0; i < count; i++) {
+ RouterInfo ri = ris.get(i);
+ double dist = renderRouterInfo(buf, ri, us, false, false);
+ if (dist < avgMinDist) {
+ if (i == 0) {
+ //buf.append("
Not to worry, but above router is closer than average minimum distance " + fmt.format(avgMinDist) + "
");
+ } else if (i == 1) {
+ buf.append("
Not to worry, but above routers are closer than average minimum distance " + fmt.format(avgMinDist) + "
");
+ } else if (i == 2) {
+ buf.append("
Possible Sybil Warning - above routers are closer than average minimum distance " + fmt.format(avgMinDist) + "
");
+ } else {
+ buf.append("
Major Sybil Warning - above router is closer than average minimum distance " + fmt.format(avgMinDist) + "
");
+ }
+ }
+ // this is dumb because they are already sorted
+ if (dist < min)
+ min = dist;
+ if (dist > max)
+ max = dist;
+ tot += dist;
+ if (i == medIdx)
+ median = dist;
+ else if (i == medIdx + 1 && isEven)
+ median = (median + dist) / 2;
+ double point = MIN_CLOSE - dist;
+ if (point > 0) {
+ point *= 2.0;
+ addPoints(points, ri.getHash(), point, fmt.format(point) + ": Too close to our key " + us.toBase64());
+ }
+ if (i >= MAX - 1)
+ break;
+ }
+ double avg = tot / count;
+ buf.append("
\n");
+ return distance;
+ }
+
+ /** translate a string */
+ private String _t(String s) {
+ return Messages.getString(s, _context);
+ }
+
+ /** tag only */
+ private static final String _x(String s) {
+ return s;
+ }
+
+ /**
+ * translate a string with a parameter
+ * This is a lot more expensive than _t(s), so use sparingly.
+ *
+ * @param s string to be translated containing {0}
+ * The {0} will be replaced by the parameter.
+ * Single quotes must be doubled, i.e. ' -> '' in the string.
+ * @param o parameter, not translated.
+ * To translate parameter also, use _t("foo {0} bar", _t("baz"))
+ * Do not double the single quotes in the parameter.
+ * Use autoboxing to call with ints, longs, floats, etc.
+ */
+ private String _t(String s, Object o) {
+ return Messages.getString(s, o, _context);
+ }
+}
diff --git a/history.txt b/history.txt
index 31be99462d..7d8eaafac5 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,6 @@
+2015-12-03 zzz
+ * Console: Add experimental Sybil analysis tool
+
2015-12-01 zzz
* i2psnark:
- Consolidate default tunnel length definition
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index c10128fe62..fecba78d69 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
- public final static long BUILD = 6;
+ public final static long BUILD = 7;
/** for example "-test" */
public final static String EXTRA = "";