2009-10-17 23:16:53 +00:00
|
|
|
package net.i2p.router.web;
|
|
|
|
/*
|
|
|
|
* free (adj.): unencumbered; not under the control of others
|
|
|
|
* Written by jrandom in 2003 and released into the public domain
|
|
|
|
* with no warranty of any kind, either expressed or implied.
|
|
|
|
* It probably won't make your computer catch on fire, or eat
|
|
|
|
* your children, but it might. Use at your own risk.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2014-06-15 16:14:13 +00:00
|
|
|
import java.io.Serializable;
|
2009-10-17 23:16:53 +00:00
|
|
|
import java.io.Writer;
|
2010-05-10 15:22:10 +00:00
|
|
|
import java.math.BigInteger; // debug
|
2010-06-16 13:23:21 +00:00
|
|
|
import java.text.Collator;
|
2010-05-10 15:22:10 +00:00
|
|
|
import java.text.DecimalFormat; // debug
|
2009-10-17 23:16:53 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Comparator;
|
2012-05-20 18:20:48 +00:00
|
|
|
import java.util.Date;
|
2009-10-17 23:16:53 +00:00
|
|
|
import java.util.List;
|
2010-06-16 13:23:21 +00:00
|
|
|
import java.util.Locale;
|
2010-10-02 15:51:48 +00:00
|
|
|
import java.util.Map;
|
2009-10-17 23:16:53 +00:00
|
|
|
import java.util.Set;
|
|
|
|
import java.util.TreeSet;
|
|
|
|
|
|
|
|
import net.i2p.data.DataHelper;
|
|
|
|
import net.i2p.data.Destination;
|
|
|
|
import net.i2p.data.Hash;
|
2013-06-09 14:42:51 +00:00
|
|
|
import net.i2p.data.Lease;
|
2009-10-17 23:16:53 +00:00
|
|
|
import net.i2p.data.LeaseSet;
|
2014-08-21 17:36:06 +00:00
|
|
|
import net.i2p.data.router.RouterAddress;
|
|
|
|
import net.i2p.data.router.RouterInfo;
|
2009-10-17 23:16:53 +00:00
|
|
|
import net.i2p.router.RouterContext;
|
|
|
|
import net.i2p.router.TunnelPoolSettings;
|
2012-11-19 16:22:09 +00:00
|
|
|
import net.i2p.router.util.HashDistance; // debug
|
2012-02-25 18:40:27 +00:00
|
|
|
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
2015-09-27 15:53:37 +00:00
|
|
|
import net.i2p.util.Log;
|
2009-10-17 23:16:53 +00:00
|
|
|
import net.i2p.util.ObjectCounter;
|
2013-10-14 15:29:12 +00:00
|
|
|
import net.i2p.util.Translate;
|
2010-01-10 16:42:21 +00:00
|
|
|
import net.i2p.util.VersionComparator;
|
2009-10-17 23:16:53 +00:00
|
|
|
|
2015-12-03 17:44:15 +00:00
|
|
|
class NetDbRenderer {
|
2012-02-17 23:08:03 +00:00
|
|
|
private final RouterContext _context;
|
2009-10-17 23:16:53 +00:00
|
|
|
|
|
|
|
public NetDbRenderer (RouterContext ctx) {
|
|
|
|
_context = ctx;
|
|
|
|
}
|
|
|
|
|
2014-06-15 16:14:13 +00:00
|
|
|
/**
|
|
|
|
* Inner class, can't be Serializable
|
|
|
|
*/
|
2010-05-10 15:22:10 +00:00
|
|
|
private class LeaseSetComparator implements Comparator<LeaseSet> {
|
|
|
|
public int compare(LeaseSet l, LeaseSet r) {
|
|
|
|
Destination dl = l.getDestination();
|
|
|
|
Destination dr = r.getDestination();
|
2009-10-17 23:16:53 +00:00
|
|
|
boolean locall = _context.clientManager().isLocal(dl);
|
|
|
|
boolean localr = _context.clientManager().isLocal(dr);
|
|
|
|
if (locall && !localr) return -1;
|
|
|
|
if (localr && !locall) return 1;
|
|
|
|
return dl.calculateHash().toBase64().compareTo(dr.calculateHash().toBase64());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-05-10 15:22:10 +00:00
|
|
|
/** for debugging @since 0.7.14 */
|
2014-06-15 16:14:13 +00:00
|
|
|
private static class LeaseSetRoutingKeyComparator implements Comparator<LeaseSet>, Serializable {
|
2010-05-10 15:22:10 +00:00
|
|
|
private final Hash _us;
|
|
|
|
public LeaseSetRoutingKeyComparator(Hash us) {
|
|
|
|
_us = us;
|
|
|
|
}
|
|
|
|
public int compare(LeaseSet l, LeaseSet r) {
|
2015-12-03 17:44:15 +00:00
|
|
|
return HashDistance.getDistance(_us, l.getRoutingKey()).compareTo(HashDistance.getDistance(_us, r.getRoutingKey()));
|
2010-05-10 15:22:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-15 16:14:13 +00:00
|
|
|
private static class RouterInfoComparator implements Comparator<RouterInfo>, Serializable {
|
2010-05-10 15:22:10 +00:00
|
|
|
public int compare(RouterInfo l, RouterInfo r) {
|
|
|
|
return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64());
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-03 16:55:01 +00:00
|
|
|
/**
|
|
|
|
* One String must be non-null
|
|
|
|
*
|
|
|
|
* @param routerPrefix may be null. "." for our router only
|
|
|
|
* @param version may be null
|
|
|
|
* @param country may be null
|
2016-11-17 14:33:33 +00:00
|
|
|
* @param family may be null
|
2015-06-03 16:55:01 +00:00
|
|
|
*/
|
2016-11-17 14:33:33 +00:00
|
|
|
public void renderRouterInfoHTML(Writer out, String routerPrefix, String version,
|
2016-11-24 18:04:40 +00:00
|
|
|
String country, String family, String caps,
|
|
|
|
String ip, String sybil) throws IOException {
|
2009-10-17 23:16:53 +00:00
|
|
|
StringBuilder buf = new StringBuilder(4*1024);
|
2016-11-24 18:04:40 +00:00
|
|
|
List<Hash> sybils = sybil != null ? new ArrayList<Hash>(128) : null;
|
2009-10-17 23:16:53 +00:00
|
|
|
if (".".equals(routerPrefix)) {
|
|
|
|
renderRouterInfo(buf, _context.router().getRouterInfo(), true, true);
|
|
|
|
} else {
|
|
|
|
boolean notFound = true;
|
2013-06-09 14:42:51 +00:00
|
|
|
Set<RouterInfo> routers = _context.netDb().getRouters();
|
2016-11-23 18:08:15 +00:00
|
|
|
int ipMode = 0;
|
|
|
|
if (ip != null) {
|
|
|
|
if (ip.endsWith("/24")) {
|
|
|
|
ipMode = 1;
|
|
|
|
} else if (ip.endsWith("/16")) {
|
|
|
|
ipMode = 2;
|
|
|
|
} else if (ip.endsWith("/8")) {
|
|
|
|
ipMode = 3;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < ipMode; i++) {
|
|
|
|
int last = ip.lastIndexOf('.');
|
|
|
|
if (last > 0)
|
|
|
|
ip = ip.substring(0, last + 1);
|
|
|
|
}
|
|
|
|
}
|
2013-06-09 14:42:51 +00:00
|
|
|
for (RouterInfo ri : routers) {
|
2009-10-17 23:16:53 +00:00
|
|
|
Hash key = ri.getIdentity().getHash();
|
2015-06-03 16:55:01 +00:00
|
|
|
if ((routerPrefix != null && key.toBase64().startsWith(routerPrefix)) ||
|
|
|
|
(version != null && version.equals(ri.getVersion())) ||
|
2016-11-17 14:33:33 +00:00
|
|
|
(country != null && country.equals(_context.commSystem().getCountry(key))) ||
|
2016-11-23 16:00:36 +00:00
|
|
|
(family != null && family.equals(ri.getOption("family"))) ||
|
2016-11-24 18:04:40 +00:00
|
|
|
(caps != null && ri.getCapabilities().contains(caps))) {
|
2009-10-17 23:16:53 +00:00
|
|
|
renderRouterInfo(buf, ri, false, true);
|
2016-11-24 18:04:40 +00:00
|
|
|
if (sybil != null)
|
|
|
|
sybils.add(key);
|
2009-10-17 23:16:53 +00:00
|
|
|
notFound = false;
|
2016-11-23 16:13:07 +00:00
|
|
|
} else if (ip != null) {
|
|
|
|
for (RouterAddress ra : ri.getAddresses()) {
|
2016-11-23 18:08:15 +00:00
|
|
|
if (ipMode == 0) {
|
|
|
|
if (ip.equals(ra.getHost())) {
|
|
|
|
renderRouterInfo(buf, ri, false, true);
|
2016-11-24 18:04:40 +00:00
|
|
|
if (sybil != null)
|
|
|
|
sybils.add(key);
|
2016-11-23 18:08:15 +00:00
|
|
|
notFound = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
String host = ra.getHost();
|
|
|
|
if (host != null && host.startsWith(ip)) {
|
|
|
|
renderRouterInfo(buf, ri, false, true);
|
2016-11-24 18:04:40 +00:00
|
|
|
if (sybil != null)
|
|
|
|
sybils.add(key);
|
2016-11-23 18:08:15 +00:00
|
|
|
notFound = false;
|
|
|
|
break;
|
|
|
|
}
|
2016-11-23 16:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-03 16:55:01 +00:00
|
|
|
if (notFound) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Router")).append(' ');
|
2015-06-03 16:55:01 +00:00
|
|
|
if (routerPrefix != null)
|
|
|
|
buf.append(routerPrefix);
|
|
|
|
else if (version != null)
|
|
|
|
buf.append(version);
|
|
|
|
else if (country != null)
|
|
|
|
buf.append(country);
|
2016-11-17 14:33:33 +00:00
|
|
|
else if (family != null)
|
|
|
|
buf.append(_t("Family")).append(' ').append(family);
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(' ').append(_t("not found in network database"));
|
2015-06-03 16:55:01 +00:00
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
out.write(buf.toString());
|
|
|
|
out.flush();
|
2016-11-24 18:04:40 +00:00
|
|
|
if (sybil != null)
|
|
|
|
SybilRenderer.renderSybilHTML(out, _context, sybils, sybil);
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
|
2010-05-10 15:22:10 +00:00
|
|
|
/**
|
2010-05-13 17:02:32 +00:00
|
|
|
* @param debug @since 0.7.14 sort by distance from us, display
|
2010-05-10 15:22:10 +00:00
|
|
|
* median distance, and other stuff, useful when floodfill
|
|
|
|
*/
|
|
|
|
public void renderLeaseSetHTML(Writer out, boolean debug) throws IOException {
|
2009-10-17 23:16:53 +00:00
|
|
|
StringBuilder buf = new StringBuilder(4*1024);
|
2012-02-26 21:15:31 +00:00
|
|
|
if (debug)
|
2012-06-01 13:30:38 +00:00
|
|
|
buf.append("<p>Debug mode - Sorted by hash distance, closest first</p>\n");
|
2010-05-10 15:22:10 +00:00
|
|
|
Hash ourRKey;
|
|
|
|
Set<LeaseSet> leases;
|
|
|
|
DecimalFormat fmt;
|
|
|
|
if (debug) {
|
2010-05-13 17:02:32 +00:00
|
|
|
ourRKey = _context.routerHash();
|
2013-11-21 11:31:50 +00:00
|
|
|
leases = new TreeSet<LeaseSet>(new LeaseSetRoutingKeyComparator(ourRKey));
|
2010-05-10 15:22:10 +00:00
|
|
|
fmt = new DecimalFormat("#0.00");
|
|
|
|
} else {
|
|
|
|
ourRKey = null;
|
2013-11-21 11:31:50 +00:00
|
|
|
leases = new TreeSet<LeaseSet>(new LeaseSetComparator());
|
2010-05-10 15:22:10 +00:00
|
|
|
fmt = null;
|
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
leases.addAll(_context.netDb().getLeases());
|
2012-02-17 23:08:03 +00:00
|
|
|
int medianCount = 0;
|
2012-02-25 18:40:27 +00:00
|
|
|
int rapCount = 0;
|
2010-05-10 15:22:10 +00:00
|
|
|
BigInteger median = null;
|
|
|
|
int c = 0;
|
2015-09-27 15:53:37 +00:00
|
|
|
if (leases.isEmpty()) {
|
|
|
|
if (!debug)
|
|
|
|
buf.append("<i>").append(_t("none")).append("</i>");
|
|
|
|
} else {
|
|
|
|
if (debug) {
|
2012-02-17 23:08:03 +00:00
|
|
|
// Find the center of the RAP leasesets
|
|
|
|
for (LeaseSet ls : leases) {
|
|
|
|
if (ls.getReceivedAsPublished())
|
2012-02-25 18:40:27 +00:00
|
|
|
rapCount++;
|
2012-02-17 23:08:03 +00:00
|
|
|
}
|
2012-02-25 18:40:27 +00:00
|
|
|
medianCount = rapCount / 2;
|
2015-09-27 15:53:37 +00:00
|
|
|
}
|
|
|
|
long now = _context.clock().now();
|
|
|
|
for (LeaseSet ls : leases) {
|
2009-10-17 23:16:53 +00:00
|
|
|
Destination dest = ls.getDestination();
|
|
|
|
Hash key = dest.calculateHash();
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<b>").append(_t("LeaseSet")).append(": ").append(key.toBase64()).append("</b>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
if (_context.clientManager().isLocal(dest)) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(" (<a href=\"tunnels#" + key.toBase64().substring(0,4) + "\">" + _t("Local") + "</a> ");
|
2009-10-17 23:16:53 +00:00
|
|
|
if (! _context.clientManager().shouldPublishLeaseSet(key))
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Unpublished") + ' ');
|
|
|
|
buf.append(_t("Destination") + ' ');
|
2009-10-17 23:16:53 +00:00
|
|
|
TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(key);
|
|
|
|
if (in != null && in.getDestinationNickname() != null)
|
|
|
|
buf.append(in.getDestinationNickname());
|
|
|
|
else
|
|
|
|
buf.append(dest.toBase64().substring(0, 6));
|
2014-10-03 17:44:24 +00:00
|
|
|
buf.append(")<br>\n");
|
2014-12-02 15:11:12 +00:00
|
|
|
String b32 = dest.toBase32();
|
|
|
|
buf.append("<a href=\"http://").append(b32).append("\">").append(b32).append("</a><br>\n");
|
|
|
|
String host = _context.namingService().reverseLookup(dest);
|
|
|
|
if (host == null) {
|
|
|
|
buf.append("<a href=\"/susidns/addressbook.jsp?book=private&destination=")
|
2015-09-25 19:55:36 +00:00
|
|
|
.append(dest.toBase64()).append("#add\">").append(_t("Add to local addressbook")).append("</a><br>\n");
|
2014-12-02 15:11:12 +00:00
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
} else {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(" (").append(_t("Destination")).append(' ');
|
2009-10-17 23:16:53 +00:00
|
|
|
String host = _context.namingService().reverseLookup(dest);
|
2014-10-03 17:44:24 +00:00
|
|
|
if (host != null) {
|
|
|
|
buf.append("<a href=\"http://").append(host).append("/\">").append(host).append("</a>)<br>\n");
|
|
|
|
} else {
|
|
|
|
String b32 = dest.toBase32();
|
|
|
|
buf.append(dest.toBase64().substring(0, 6)).append(")<br>\n" +
|
|
|
|
"<a href=\"http://").append(b32).append("\">").append(b32).append("</a><br>\n" +
|
|
|
|
"<a href=\"/susidns/addressbook.jsp?book=private&destination=")
|
2015-09-25 19:55:36 +00:00
|
|
|
.append(dest.toBase64()).append("#add\">").append(_t("Add to local addressbook")).append("</a><br>\n");
|
2014-10-03 17:44:24 +00:00
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
2013-06-09 14:42:51 +00:00
|
|
|
long exp = ls.getLatestLeaseDate()-now;
|
2009-10-17 23:16:53 +00:00
|
|
|
if (exp > 0)
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Expires in {0}", DataHelper.formatDuration2(exp)));
|
2009-10-17 23:16:53 +00:00
|
|
|
else
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Expired {0} ago", DataHelper.formatDuration2(0-exp)));
|
2013-06-09 14:42:51 +00:00
|
|
|
buf.append("<br>\n");
|
2010-05-10 15:22:10 +00:00
|
|
|
if (debug) {
|
2012-02-17 23:08:03 +00:00
|
|
|
buf.append("RAP? " + ls.getReceivedAsPublished());
|
|
|
|
buf.append(" RAR? " + ls.getReceivedAsReply());
|
2010-05-10 15:22:10 +00:00
|
|
|
BigInteger dist = HashDistance.getDistance(ourRKey, ls.getRoutingKey());
|
2012-02-17 23:08:03 +00:00
|
|
|
if (ls.getReceivedAsPublished()) {
|
|
|
|
if (c++ == medianCount)
|
|
|
|
median = dist;
|
|
|
|
}
|
2012-02-26 21:15:31 +00:00
|
|
|
buf.append(" Dist: <b>").append(fmt.format(biLog2(dist))).append("</b><br>");
|
2014-10-03 17:44:24 +00:00
|
|
|
//buf.append(dest.toBase32()).append("<br>");
|
2014-01-03 15:31:08 +00:00
|
|
|
buf.append("Sig type: ").append(dest.getSigningPublicKey().getType()).append("<br>");
|
2012-02-26 21:15:31 +00:00
|
|
|
buf.append("Routing Key: ").append(ls.getRoutingKey().toBase64());
|
2010-05-10 15:22:10 +00:00
|
|
|
buf.append("<br>");
|
2012-02-26 21:15:31 +00:00
|
|
|
buf.append("Encryption Key: ").append(ls.getEncryptionKey().toBase64().substring(0, 20)).append("...<br>");
|
2010-05-10 15:22:10 +00:00
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
for (int i = 0; i < ls.getLeaseCount(); i++) {
|
2013-06-09 14:42:51 +00:00
|
|
|
Lease lease = ls.getLease(i);
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Lease")).append(' ').append(i + 1).append(": ").append(_t("Gateway")).append(' ');
|
2013-06-09 14:42:51 +00:00
|
|
|
buf.append(_context.commSystem().renderPeerHTML(lease.getGateway()));
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(' ').append(_t("Tunnel")).append(' ').append(lease.getTunnelId().getTunnelId()).append(' ');
|
2013-06-09 14:42:51 +00:00
|
|
|
if (debug) {
|
|
|
|
long exl = lease.getEndDate().getTime() - now;
|
|
|
|
if (exl > 0)
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Expires in {0}", DataHelper.formatDuration2(exl)));
|
2013-06-09 14:42:51 +00:00
|
|
|
else
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append(_t("Expired {0} ago", DataHelper.formatDuration2(0-exl)));
|
2013-06-09 14:42:51 +00:00
|
|
|
}
|
|
|
|
buf.append("<br>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
buf.append("<hr>\n");
|
|
|
|
out.write(buf.toString());
|
|
|
|
buf.setLength(0);
|
2015-09-27 15:53:37 +00:00
|
|
|
} // for each
|
|
|
|
} // !empty
|
2010-05-10 15:22:10 +00:00
|
|
|
if (debug) {
|
2012-02-25 18:40:27 +00:00
|
|
|
FloodfillNetworkDatabaseFacade netdb = (FloodfillNetworkDatabaseFacade)_context.netDb();
|
|
|
|
buf.append("<p><b>Total Leasesets: ").append(leases.size());
|
|
|
|
buf.append("</b></p><p><b>Published (RAP) Leasesets: ").append(netdb.getKnownLeaseSets());
|
2014-09-15 18:23:01 +00:00
|
|
|
buf.append("</b></p><p><b>Mod Data: \"").append(DataHelper.getUTF8(_context.routerKeyGenerator().getModData()))
|
|
|
|
.append("\" Last Changed: ").append(new Date(_context.routerKeyGenerator().getLastChanged()));
|
|
|
|
buf.append("</b></p><p><b>Next Mod Data: \"").append(DataHelper.getUTF8(_context.routerKeyGenerator().getNextModData()))
|
|
|
|
.append("\" Change in: ").append(DataHelper.formatDuration(_context.routerKeyGenerator().getTimeTillMidnight()));
|
2012-02-25 18:40:27 +00:00
|
|
|
int ff = _context.peerManager().getPeersByCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL).size();
|
|
|
|
buf.append("</b></p><p><b>Known Floodfills: ").append(ff);
|
|
|
|
buf.append("</b></p><p><b>Currently Floodfill? ");
|
|
|
|
buf.append(netdb.floodfillEnabled() ? "yes" : "no");
|
2011-04-10 18:22:43 +00:00
|
|
|
buf.append("</b></p><p><b>Network data (only valid if floodfill):");
|
2012-02-17 23:08:03 +00:00
|
|
|
//buf.append("</b></p><p><b>Center of Key Space (router hash): " + ourRKey.toBase64());
|
2010-05-10 15:22:10 +00:00
|
|
|
if (median != null) {
|
|
|
|
double log2 = biLog2(median);
|
2012-02-25 18:40:27 +00:00
|
|
|
buf.append("</b></p><p><b>Median distance (bits): ").append(fmt.format(log2));
|
2014-03-28 14:01:39 +00:00
|
|
|
// 2 for 4 floodfills... -1 for median
|
2015-02-07 14:01:56 +00:00
|
|
|
// this can be way off for unknown reasons
|
2014-03-28 14:01:39 +00:00
|
|
|
int total = (int) Math.round(Math.pow(2, 2 + 256 - 1 - log2));
|
2012-02-25 18:40:27 +00:00
|
|
|
buf.append("</b></p><p><b>Estimated total floodfills: ").append(total);
|
2015-02-07 14:01:56 +00:00
|
|
|
buf.append("</b></p><p><b>Estimated total leasesets: ").append(total * rapCount / 4);
|
2012-05-20 18:20:48 +00:00
|
|
|
} else {
|
|
|
|
buf.append("</b></p><p><b>Not floodfill or no data");
|
2010-05-10 15:22:10 +00:00
|
|
|
}
|
2011-04-10 18:22:43 +00:00
|
|
|
buf.append("</b></p>");
|
2010-05-10 15:22:10 +00:00
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
out.write(buf.toString());
|
|
|
|
out.flush();
|
|
|
|
}
|
|
|
|
|
2010-05-10 15:22:10 +00:00
|
|
|
/**
|
|
|
|
* For debugging
|
|
|
|
* http://forums.sun.com/thread.jspa?threadID=597652
|
|
|
|
* @since 0.7.14
|
|
|
|
*/
|
2015-12-03 17:44:15 +00:00
|
|
|
public static double biLog2(BigInteger a) {
|
2010-05-10 15:22:10 +00:00
|
|
|
int b = a.bitLength() - 1;
|
|
|
|
double c = 0;
|
|
|
|
double d = 0.5;
|
|
|
|
for (int i = b; i >= 0; --i) {
|
|
|
|
if (a.testBit(i))
|
|
|
|
c += d;
|
|
|
|
d /= 2;
|
|
|
|
}
|
|
|
|
return b + c;
|
|
|
|
}
|
|
|
|
|
2009-11-07 19:32:00 +00:00
|
|
|
/**
|
2012-06-08 16:10:26 +00:00
|
|
|
* @param mode 0: charts only; 1: full routerinfos; 2: abbreviated routerinfos
|
2009-11-07 19:32:00 +00:00
|
|
|
*/
|
|
|
|
public void renderStatusHTML(Writer out, int mode) throws IOException {
|
2009-10-17 23:16:53 +00:00
|
|
|
if (!_context.netDb().isInitialized()) {
|
2015-09-25 19:55:36 +00:00
|
|
|
out.write(_t("Not initialized"));
|
2009-10-17 23:16:53 +00:00
|
|
|
out.flush();
|
|
|
|
return;
|
|
|
|
}
|
2015-09-27 15:53:37 +00:00
|
|
|
Log log = _context.logManager().getLog(NetDbRenderer.class);
|
|
|
|
long start = System.currentTimeMillis();
|
2009-10-17 23:16:53 +00:00
|
|
|
|
2009-11-07 19:32:00 +00:00
|
|
|
boolean full = mode == 1;
|
|
|
|
boolean shortStats = mode == 2;
|
2012-06-08 16:10:26 +00:00
|
|
|
boolean showStats = full || shortStats; // this means show the router infos
|
2009-10-17 23:16:53 +00:00
|
|
|
Hash us = _context.routerHash();
|
|
|
|
|
2009-10-28 18:26:50 +00:00
|
|
|
StringBuilder buf = new StringBuilder(8192);
|
2012-06-08 16:10:26 +00:00
|
|
|
if (showStats) {
|
|
|
|
RouterInfo ourInfo = _context.router().getRouterInfo();
|
|
|
|
renderRouterInfo(buf, ourInfo, true, true);
|
|
|
|
out.write(buf.toString());
|
|
|
|
buf.setLength(0);
|
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
|
2013-11-21 11:31:50 +00:00
|
|
|
ObjectCounter<String> versions = new ObjectCounter<String>();
|
|
|
|
ObjectCounter<String> countries = new ObjectCounter<String>();
|
2013-10-19 16:37:13 +00:00
|
|
|
int[] transportCount = new int[TNAMES.length];
|
2009-10-17 23:16:53 +00:00
|
|
|
|
2013-11-21 11:31:50 +00:00
|
|
|
Set<RouterInfo> routers = new TreeSet<RouterInfo>(new RouterInfoComparator());
|
2009-10-17 23:16:53 +00:00
|
|
|
routers.addAll(_context.netDb().getRouters());
|
2013-06-09 14:42:51 +00:00
|
|
|
for (RouterInfo ri : routers) {
|
2009-10-17 23:16:53 +00:00
|
|
|
Hash key = ri.getIdentity().getHash();
|
|
|
|
boolean isUs = key.equals(us);
|
|
|
|
if (!isUs) {
|
2009-11-07 19:32:00 +00:00
|
|
|
if (showStats) {
|
|
|
|
renderRouterInfo(buf, ri, false, full);
|
|
|
|
out.write(buf.toString());
|
|
|
|
buf.setLength(0);
|
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
String routerVersion = ri.getOption("router.version");
|
|
|
|
if (routerVersion != null)
|
|
|
|
versions.increment(routerVersion);
|
|
|
|
String country = _context.commSystem().getCountry(key);
|
|
|
|
if(country != null)
|
|
|
|
countries.increment(country);
|
2009-11-09 17:11:20 +00:00
|
|
|
transportCount[classifyTransports(ri)]++;
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-27 15:53:37 +00:00
|
|
|
long end = System.currentTimeMillis();
|
|
|
|
if (log.shouldWarn())
|
|
|
|
log.warn("part 1 took " + (end - start));
|
|
|
|
start = end;
|
2009-10-17 23:16:53 +00:00
|
|
|
|
2012-06-08 16:10:26 +00:00
|
|
|
//
|
|
|
|
// don't bother to reindent
|
|
|
|
//
|
|
|
|
if (!showStats) {
|
|
|
|
|
|
|
|
// the summary table
|
2011-03-09 15:55:52 +00:00
|
|
|
buf.append("<table border=\"0\" cellspacing=\"30\"><tr><th colspan=\"3\">")
|
2015-09-25 19:55:36 +00:00
|
|
|
.append(_t("Network Database Router Statistics"))
|
2011-03-12 16:11:10 +00:00
|
|
|
.append("</th></tr><tr><td style=\"vertical-align: top;\">");
|
2009-11-09 17:11:20 +00:00
|
|
|
// versions table
|
2013-11-21 11:31:50 +00:00
|
|
|
List<String> versionList = new ArrayList<String>(versions.objects());
|
2010-05-05 16:51:54 +00:00
|
|
|
if (!versionList.isEmpty()) {
|
2010-01-10 16:42:21 +00:00
|
|
|
Collections.sort(versionList, Collections.reverseOrder(new VersionComparator()));
|
2009-10-17 23:16:53 +00:00
|
|
|
buf.append("<table>\n");
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<tr><th>" + _t("Version") + "</th><th>" + _t("Count") + "</th></tr>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
for (String routerVersion : versionList) {
|
|
|
|
int num = versions.count(routerVersion);
|
2015-06-03 16:55:01 +00:00
|
|
|
String ver = DataHelper.stripHTML(routerVersion);
|
|
|
|
buf.append("<tr><td align=\"center\"><a href=\"/netdb?v=").append(ver).append("\">").append(ver);
|
|
|
|
buf.append("</a></td><td align=\"center\">").append(num).append("</td></tr>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
buf.append("</table>\n");
|
|
|
|
}
|
2011-03-09 15:55:52 +00:00
|
|
|
buf.append("</td><td style=\"vertical-align: top;\">");
|
2009-10-17 23:16:53 +00:00
|
|
|
out.write(buf.toString());
|
|
|
|
buf.setLength(0);
|
2015-09-27 15:53:37 +00:00
|
|
|
end = System.currentTimeMillis();
|
|
|
|
if (log.shouldWarn())
|
|
|
|
log.warn("part 2 took " + (end - start));
|
|
|
|
start = end;
|
2009-10-17 23:16:53 +00:00
|
|
|
|
2009-11-09 17:11:20 +00:00
|
|
|
// transports table
|
|
|
|
buf.append("<table>\n");
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<tr><th align=\"left\">" + _t("Transports") + "</th><th>" + _t("Count") + "</th></tr>\n");
|
2013-10-19 16:37:13 +00:00
|
|
|
for (int i = 0; i < TNAMES.length; i++) {
|
2009-11-09 17:11:20 +00:00
|
|
|
int num = transportCount[i];
|
|
|
|
if (num > 0) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<tr><td>").append(_t(TNAMES[i]));
|
2009-11-09 17:11:20 +00:00
|
|
|
buf.append("</td><td align=\"center\">").append(num).append("</td></tr>\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf.append("</table>\n");
|
2011-03-09 15:55:52 +00:00
|
|
|
buf.append("</td><td style=\"vertical-align: top;\">");
|
2009-11-09 17:11:20 +00:00
|
|
|
out.write(buf.toString());
|
|
|
|
buf.setLength(0);
|
2015-09-27 15:53:37 +00:00
|
|
|
end = System.currentTimeMillis();
|
|
|
|
if (log.shouldWarn())
|
|
|
|
log.warn("part 3 took " + (end - start));
|
|
|
|
start = end;
|
2009-11-09 17:11:20 +00:00
|
|
|
|
|
|
|
// country table
|
2013-11-21 11:31:50 +00:00
|
|
|
List<String> countryList = new ArrayList<String>(countries.objects());
|
2010-05-05 16:51:54 +00:00
|
|
|
if (!countryList.isEmpty()) {
|
2009-11-07 19:32:00 +00:00
|
|
|
Collections.sort(countryList, new CountryComparator());
|
2009-10-17 23:16:53 +00:00
|
|
|
buf.append("<table>\n");
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<tr><th align=\"left\">" + _t("Country") + "</th><th>" + _t("Count") + "</th></tr>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
for (String country : countryList) {
|
|
|
|
int num = countries.count(country);
|
2011-11-28 20:32:23 +00:00
|
|
|
buf.append("<tr><td><img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase(Locale.US)).append("\"");
|
2015-06-03 16:55:01 +00:00
|
|
|
buf.append(" src=\"/flags.jsp?c=").append(country).append("\"> <a href=\"/netdb?c=").append(country).append("\">");
|
2013-10-14 15:29:12 +00:00
|
|
|
buf.append(getTranslatedCountry(country));
|
2015-06-03 16:55:01 +00:00
|
|
|
buf.append("</a></td><td align=\"center\">").append(num).append("</td></tr>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
buf.append("</table>\n");
|
|
|
|
}
|
2009-11-09 17:11:20 +00:00
|
|
|
|
2009-10-17 23:16:53 +00:00
|
|
|
buf.append("</td></tr></table>");
|
2015-09-27 15:53:37 +00:00
|
|
|
end = System.currentTimeMillis();
|
|
|
|
if (log.shouldWarn())
|
|
|
|
log.warn("part 4 took " + (end - start));
|
|
|
|
start = end;
|
2012-06-08 16:10:26 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// don't bother to reindent
|
|
|
|
//
|
|
|
|
} // if !showStats
|
|
|
|
|
2009-10-17 23:16:53 +00:00
|
|
|
out.write(buf.toString());
|
|
|
|
out.flush();
|
|
|
|
}
|
|
|
|
|
2013-10-14 15:29:12 +00:00
|
|
|
/**
|
|
|
|
* Countries now in a separate bundle
|
|
|
|
* @param code two-letter country code
|
|
|
|
* @since 0.9.9
|
|
|
|
*/
|
|
|
|
private String getTranslatedCountry(String code) {
|
|
|
|
String name = _context.commSystem().getCountryName(code);
|
2013-11-18 23:18:46 +00:00
|
|
|
return Translate.getString(name, _context, Messages.COUNTRY_BUNDLE_NAME);
|
2013-10-14 15:29:12 +00:00
|
|
|
}
|
|
|
|
|
2014-06-15 16:14:13 +00:00
|
|
|
/**
|
|
|
|
* Sort by translated country name using rules for the current language setting
|
|
|
|
* Inner class, can't be Serializable
|
|
|
|
*/
|
2010-06-16 13:23:21 +00:00
|
|
|
private class CountryComparator implements Comparator<String> {
|
2014-06-15 16:14:13 +00:00
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
private final Collator coll;
|
2010-06-16 13:23:21 +00:00
|
|
|
|
|
|
|
public CountryComparator() {
|
|
|
|
super();
|
|
|
|
coll = Collator.getInstance(new Locale(Messages.getLanguage(_context)));
|
|
|
|
}
|
|
|
|
|
|
|
|
public int compare(String l, String r) {
|
2013-10-14 15:29:12 +00:00
|
|
|
return coll.compare(getTranslatedCountry(l),
|
|
|
|
getTranslatedCountry(r));
|
2009-11-07 19:32:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-17 23:16:53 +00:00
|
|
|
/**
|
|
|
|
* Be careful to use stripHTML for any displayed routerInfo data
|
|
|
|
* to prevent vulnerabilities
|
|
|
|
*/
|
|
|
|
private void renderRouterInfo(StringBuilder buf, RouterInfo info, boolean isUs, boolean full) {
|
|
|
|
String hash = info.getIdentity().getHash().toBase64();
|
|
|
|
buf.append("<table><tr><th><a name=\"").append(hash.substring(0, 6)).append("\" ></a>");
|
|
|
|
if (isUs) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<a name=\"our-info\" ></a><b>" + _t("Our info") + ": ").append(hash).append("</b></th></tr><tr><td>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
} else {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<b>" + _t("Peer info for") + ":</b> ").append(hash).append("\n");
|
2012-06-01 13:30:38 +00:00
|
|
|
if (!full) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("[<a href=\"netdb?r=").append(hash.substring(0, 6)).append("\" >").append(_t("Full entry")).append("</a>]");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
2012-06-01 13:30:38 +00:00
|
|
|
buf.append("</th></tr><tr><td>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
long age = _context.clock().now() - info.getPublished();
|
2009-11-21 13:32:32 +00:00
|
|
|
if (isUs && _context.router().isHidden()) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<b>").append(_t("Hidden")).append(", ").append(_t("Updated")).append(":</b> ")
|
|
|
|
.append(_t("{0} ago", DataHelper.formatDuration2(age))).append("<br>\n");
|
2009-11-21 13:32:32 +00:00
|
|
|
} else if (age > 0) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<b>").append(_t("Published")).append(":</b> ")
|
|
|
|
.append(_t("{0} ago", DataHelper.formatDuration2(age))).append("<br>\n");
|
2009-11-21 13:32:32 +00:00
|
|
|
} else {
|
|
|
|
// shouldnt happen
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<b>" + _t("Published") + ":</b> in ").append(DataHelper.formatDuration2(0-age)).append("???<br>\n");
|
2009-11-21 13:32:32 +00:00
|
|
|
}
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<b>").append(_t("Signing Key")).append(":</b> ")
|
2014-08-23 23:48:16 +00:00
|
|
|
.append(info.getIdentity().getSigningPublicKey().getType().toString());
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<br>\n<b>" + _t("Address(es)") + ":</b> ");
|
2009-10-17 23:16:53 +00:00
|
|
|
String country = _context.commSystem().getCountry(info.getIdentity().getHash());
|
|
|
|
if(country != null) {
|
2011-11-28 20:32:23 +00:00
|
|
|
buf.append("<img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase(Locale.US)).append('\"');
|
2013-10-14 15:29:12 +00:00
|
|
|
buf.append(" title=\"").append(getTranslatedCountry(country)).append('\"');
|
2009-10-17 23:16:53 +00:00
|
|
|
buf.append(" src=\"/flags.jsp?c=").append(country).append("\"> ");
|
|
|
|
}
|
2013-06-09 14:42:51 +00:00
|
|
|
for (RouterAddress addr : info.getAddresses()) {
|
2010-03-17 16:15:52 +00:00
|
|
|
String style = addr.getTransportStyle();
|
|
|
|
buf.append("<b>").append(DataHelper.stripHTML(style)).append(":</b> ");
|
|
|
|
int cost = addr.getCost();
|
|
|
|
if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append('[').append(_t("cost")).append('=').append("" + cost).append("] ");
|
2013-11-22 09:34:42 +00:00
|
|
|
Map<Object, Object> p = addr.getOptionsMap();
|
|
|
|
for (Map.Entry<Object, Object> e : p.entrySet()) {
|
2010-10-02 15:51:48 +00:00
|
|
|
String name = (String) e.getKey();
|
|
|
|
String val = (String) e.getValue();
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append('[').append(_t(DataHelper.stripHTML(name))).append('=').append(DataHelper.stripHTML(val)).append("] ");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
buf.append("</td></tr>\n");
|
|
|
|
if (full) {
|
2015-09-25 19:55:36 +00:00
|
|
|
buf.append("<tr><td>" + _t("Stats") + ": <br><code>");
|
2013-11-22 09:34:42 +00:00
|
|
|
Map<Object, Object> p = info.getOptionsMap();
|
|
|
|
for (Map.Entry<Object, Object> e : p.entrySet()) {
|
2011-12-23 21:41:58 +00:00
|
|
|
String key = (String) e.getKey();
|
|
|
|
String val = (String) e.getValue();
|
2009-10-17 23:16:53 +00:00
|
|
|
buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br>\n");
|
|
|
|
}
|
|
|
|
buf.append("</code></td></tr>\n");
|
|
|
|
}
|
2011-03-12 16:11:10 +00:00
|
|
|
buf.append("</table>\n");
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|
|
|
|
|
2009-11-09 17:11:20 +00:00
|
|
|
private static final int SSU = 1;
|
|
|
|
private static final int SSUI = 2;
|
|
|
|
private static final int NTCP = 4;
|
2013-10-19 16:37:13 +00:00
|
|
|
private static final int IPV6 = 8;
|
2009-11-09 17:11:20 +00:00
|
|
|
private static final String[] TNAMES = { _x("Hidden or starting up"), _x("SSU"), _x("SSU with introducers"), "",
|
2013-10-19 16:37:13 +00:00
|
|
|
_x("NTCP"), _x("NTCP and SSU"), _x("NTCP and SSU with introducers"), "",
|
2013-10-19 22:09:23 +00:00
|
|
|
"", _x("IPv6 SSU"), _x("IPv6 Only SSU, introducers"), _x("IPv6 SSU, introducers"),
|
|
|
|
_x("IPv6 NTCP"), _x("IPv6 NTCP, SSU"), _x("IPv6 Only NTCP, SSU, introducers"), _x("IPv6 NTCP, SSU, introducers") };
|
2009-11-09 17:11:20 +00:00
|
|
|
/**
|
|
|
|
* what transport types
|
|
|
|
*/
|
2011-04-10 18:22:43 +00:00
|
|
|
private static int classifyTransports(RouterInfo info) {
|
2009-11-09 17:11:20 +00:00
|
|
|
int rv = 0;
|
2011-04-10 18:22:43 +00:00
|
|
|
for (RouterAddress addr : info.getAddresses()) {
|
2009-11-09 17:11:20 +00:00
|
|
|
String style = addr.getTransportStyle();
|
|
|
|
if (style.equals("NTCP")) {
|
|
|
|
rv |= NTCP;
|
|
|
|
} else if (style.equals("SSU")) {
|
2011-12-23 21:41:58 +00:00
|
|
|
if (addr.getOption("iport0") != null)
|
2009-11-09 17:11:20 +00:00
|
|
|
rv |= SSUI;
|
|
|
|
else
|
|
|
|
rv |= SSU;
|
|
|
|
}
|
2013-10-19 16:37:13 +00:00
|
|
|
String host = addr.getHost();
|
|
|
|
if (host != null && host.contains(":"))
|
|
|
|
rv |= IPV6;
|
|
|
|
|
2009-11-09 17:11:20 +00:00
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2009-10-18 14:06:07 +00:00
|
|
|
/** translate a string */
|
2015-09-25 19:55:36 +00:00
|
|
|
private String _t(String s) {
|
2009-10-18 14:06:07 +00:00
|
|
|
return Messages.getString(s, _context);
|
|
|
|
}
|
2009-10-29 23:22:51 +00:00
|
|
|
|
2009-11-09 17:11:20 +00:00
|
|
|
/** tag only */
|
|
|
|
private static final String _x(String s) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2009-10-29 23:22:51 +00:00
|
|
|
/**
|
|
|
|
* translate a string with a parameter
|
2015-09-25 19:55:36 +00:00
|
|
|
* This is a lot more expensive than _t(s), so use sparingly.
|
2009-10-29 23:22:51 +00:00
|
|
|
*
|
|
|
|
* @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.
|
2015-11-16 19:32:00 +00:00
|
|
|
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
|
2009-10-29 23:22:51 +00:00
|
|
|
* Do not double the single quotes in the parameter.
|
|
|
|
* Use autoboxing to call with ints, longs, floats, etc.
|
|
|
|
*/
|
2015-09-25 19:55:36 +00:00
|
|
|
private String _t(String s, Object o) {
|
2009-10-29 23:22:51 +00:00
|
|
|
return Messages.getString(s, o, _context);
|
|
|
|
}
|
2009-10-17 23:16:53 +00:00
|
|
|
}
|