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
Hash | Distance from previous |
\n");
+ Collections.sort(sybils, xor);
+ Hash prev = null;
+ for (Hash h : sybils) {
+ out.write("" + h.toBase64() + " | ");
+ if (prev != null) {
+ BigInteger dist = HashDistance.getDistance(prev, h);
+ writeDistance(out, fmt, dist);
+ }
+ prev = h;
+ out.write(" |
\n");
+ }
+ 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) +
+ "
Hash | Distance | Distance from previous |
\n");
+ Hash rkey = rkgen.getRoutingKey(from, now);
+ xor = new XORComparator(rkey);
+ Collections.sort(sybils, xor);
+ for (Hash h : sybils) {
+ out.write("" + 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.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 @@
" />
" />
" />
+ " />