diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java index 2de3333813..338eccfaa1 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbHelper.java @@ -7,7 +7,7 @@ public class NetDbHelper extends HelperBase { private String _routerPrefix; private String _version; private String _country; - private String _family, _caps, _ip; + private String _family, _caps, _ip, _sybil; private int _full; private boolean _lease; private boolean _debug; @@ -68,6 +68,12 @@ public class NetDbHelper extends HelperBase { _ip = DataHelper.stripHTML(c); // XSS } + /** @since 0.9.28 */ + public void setSybil(String c) { + if (c != null) + _sybil = DataHelper.stripHTML(c); // XSS + } + public void setFull(String f) { try { _full = Integer.parseInt(f); @@ -95,8 +101,9 @@ public class NetDbHelper extends HelperBase { try { renderNavBar(); if (_routerPrefix != null || _version != null || _country != null || - _family != null || _caps != null || _ip != null) - renderer.renderRouterInfoHTML(_out, _routerPrefix, _version, _country, _family, _caps, _ip); + _family != null || _caps != null || _ip != null || _sybil != null) + renderer.renderRouterInfoHTML(_out, _routerPrefix, _version, _country, + _family, _caps, _ip, _sybil); else if (_lease) renderer.renderLeaseSetHTML(_out, _debug); else if (_full == 3) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index 97acfe9c7b..68f6064b41 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -88,8 +88,10 @@ class NetDbRenderer { * @param family may be null */ public void renderRouterInfoHTML(Writer out, String routerPrefix, String version, - String country, String family, String caps, String ip) throws IOException { + String country, String family, String caps, + String ip, String sybil) throws IOException { StringBuilder buf = new StringBuilder(4*1024); + List sybils = sybil != null ? new ArrayList(128) : null; if (".".equals(routerPrefix)) { renderRouterInfo(buf, _context.router().getRouterInfo(), true, true); } else { @@ -116,14 +118,18 @@ class NetDbRenderer { (version != null && version.equals(ri.getVersion())) || (country != null && country.equals(_context.commSystem().getCountry(key))) || (family != null && family.equals(ri.getOption("family"))) || - (caps != null && caps.equals(ri.getCapabilities()))) { + (caps != null && ri.getCapabilities().contains(caps))) { renderRouterInfo(buf, ri, false, true); + if (sybil != null) + sybils.add(key); notFound = false; } else if (ip != null) { for (RouterAddress ra : ri.getAddresses()) { if (ipMode == 0) { if (ip.equals(ra.getHost())) { renderRouterInfo(buf, ri, false, true); + if (sybil != null) + sybils.add(key); notFound = false; break; } @@ -131,6 +137,8 @@ class NetDbRenderer { String host = ra.getHost(); if (host != null && host.startsWith(ip)) { renderRouterInfo(buf, ri, false, true); + if (sybil != null) + sybils.add(key); notFound = false; break; } @@ -153,6 +161,8 @@ class NetDbRenderer { } out.write(buf.toString()); out.flush(); + if (sybil != null) + SybilRenderer.renderSybilHTML(out, _context, sybils, sybil); } /** diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java index 73d59432f7..8293a4b07e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java @@ -16,6 +16,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import net.i2p.data.Base64; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; @@ -23,6 +24,7 @@ import net.i2p.data.LeaseSet; import net.i2p.data.router.RouterAddress; import net.i2p.data.router.RouterInfo; import net.i2p.data.router.RouterKeyGenerator; +import net.i2p.kademlia.XORComparator; import net.i2p.router.RouterContext; import net.i2p.router.TunnelPoolSettings; import net.i2p.router.crypto.FamilyKeyCrypto; @@ -450,7 +452,7 @@ class SybilRenderer { int i3 = i & 0xff; String sip = i0 + "." + i1 + '.' + i2 + '.' + i3; buf.append("

").append(count).append(" floodfills with IP ").append(sip) + .append(sip).append("&sybil\">").append(sip) .append(":

"); for (RouterInfo info : ris) { byte[] ip = getIP(info); @@ -503,7 +505,7 @@ class SybilRenderer { int i2 = i & 0xff; String sip = i0 + "." + i1 + '.' + i2 + ".0/24"; buf.append("

").append(count).append(" floodfills with IP ").append(sip) + .append(sip).append("&sybil\">").append(sip) .append(":

"); for (RouterInfo info : ris) { byte[] ip = getIP(info); @@ -553,7 +555,7 @@ class SybilRenderer { int i1 = i & 0xff; String sip = i0 + "." + i1 + ".0/16"; buf.append("

").append(count).append(" floodfills with IP ").append(sip) + .append(sip).append("&sybil\">").append(sip) .append("

"); for (RouterInfo info : ris) { byte[] ip = getIP(info); @@ -595,7 +597,7 @@ class SybilRenderer { int count = oc.count(s); String ss = DataHelper.escapeHTML(s); buf.append("

").append(count).append(" floodfills in declared family \"").append(ss).append("\"

"); + .append(ss).append("&sybil\">").append(ss).append("\"

"); for (RouterInfo info : ris) { String fam = info.getOption("family"); if (fam == null) @@ -901,6 +903,77 @@ class SybilRenderer { return distance; } + /** + * Called from NetDbRenderer + * + * @since 0.9.28 + */ + public static void renderSybilHTML(Writer out, RouterContext ctx, List sybils, String victim) throws IOException { + if (sybils.isEmpty()) + return; + final DecimalFormat fmt = new DecimalFormat("#0.00"); + XORComparator xor = new XORComparator(Hash.FAKE_HASH); + out.write("

Group Distances

\n"); + Collections.sort(sybils, xor); + Hash prev = null; + for (Hash h : sybils) { + out.write("\n"); + } + out.write("
HashDistance from previous
" + h.toBase64() + ""); + if (prev != null) { + BigInteger dist = HashDistance.getDistance(prev, h); + writeDistance(out, fmt, dist); + } + prev = h; + out.write("
\n"); + out.flush(); + + RouterKeyGenerator rkgen = ctx.routerKeyGenerator(); + long now = ctx.clock().now(); + final int days = 7; + Hash from = ctx.routerHash(); + if (victim != null) { + byte[] b = Base64.decode(victim); + if (b != null && b.length == Hash.HASH_LENGTH) + from = Hash.create(b); + } + out.write("

Distance to " + from.toBase64() + "

"); + prev = null; + for (int i = 0; i < days; i++) { + out.write("

Distance for " + new Date(now) + + "

\n"); + Hash rkey = rkgen.getRoutingKey(from, now); + xor = new XORComparator(rkey); + Collections.sort(sybils, xor); + for (Hash h : sybils) { + out.write("\n"); + } + out.write("
HashDistanceDistance from previous
" + h.toBase64() + ""); + BigInteger dist = HashDistance.getDistance(rkey, h); + writeDistance(out, fmt, dist); + out.write(""); + if (prev != null) { + dist = HashDistance.getDistance(prev, h); + writeDistance(out, fmt, dist); + } + prev = h; + out.write("
\n"); + out.flush(); + now += 24*60*60*1000; + prev = null; + } + } + + /** @since 0.9.28 */ + private static void writeDistance(Writer out, DecimalFormat fmt, BigInteger dist) throws IOException { + double distance = biLog2(dist); + if (distance < MIN_CLOSE) + out.write(""); + out.write(fmt.format(distance)); + if (distance < MIN_CLOSE) + out.write(""); + } + /** translate a string */ private String _t(String s) { return Messages.getString(s, _context); diff --git a/apps/routerconsole/jsp/netdb.jsp b/apps/routerconsole/jsp/netdb.jsp index f880bc3901..0a8f862c45 100644 --- a/apps/routerconsole/jsp/netdb.jsp +++ b/apps/routerconsole/jsp/netdb.jsp @@ -28,5 +28,6 @@ " /> " /> " /> + " /> diff --git a/router/java/src/net/i2p/data/router/RouterKeyGenerator.java b/router/java/src/net/i2p/data/router/RouterKeyGenerator.java index 02e49ede5a..0f71c1b513 100644 --- a/router/java/src/net/i2p/data/router/RouterKeyGenerator.java +++ b/router/java/src/net/i2p/data/router/RouterKeyGenerator.java @@ -186,6 +186,26 @@ public class RouterKeyGenerator extends RoutingKeyGenerator { return getKey(origKey, _nextModData); } + /** + * Get the routing key for the specified date, not today's + * + * @param time Java time + * @since 0.9.28 + */ + public Hash getRoutingKey(Hash origKey, long time) { + String modVal; + synchronized(this) { + modVal = _fmt.format(time); + } + if (modVal.length() != LENGTH) + throw new IllegalStateException(); + byte[] mod = new byte[LENGTH]; + for (int i = 0; i < LENGTH; i++) { + mod[i] = (byte)(modVal.charAt(i) & 0xFF); + } + return getKey(origKey, mod); + } + /** * Generate a modified (yet consistent) hash from the origKey by generating the * SHA256 of the targetKey with the specified modData appended to it