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; import java.io.Writer; import java.math.BigInteger; // debug import java.text.DecimalFormat; // debug import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import net.i2p.crypto.TrustedUpdate; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.LeaseSet; import net.i2p.data.RouterAddress; import net.i2p.data.RouterInfo; import net.i2p.router.RouterContext; import net.i2p.router.TunnelPoolSettings; import net.i2p.router.networkdb.kademlia.HashDistance; // debug import net.i2p.util.HexDump; // debug import net.i2p.util.ObjectCounter; import net.i2p.util.VersionComparator; public class NetDbRenderer { private RouterContext _context; public NetDbRenderer (RouterContext ctx) { _context = ctx; } private class LeaseSetComparator implements Comparator { public int compare(LeaseSet l, LeaseSet r) { Destination dl = l.getDestination(); Destination dr = r.getDestination(); 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()); } } /** for debugging @since 0.7.14 */ private static class LeaseSetRoutingKeyComparator implements Comparator { private final Hash _us; public LeaseSetRoutingKeyComparator(Hash us) { _us = us; } public int compare(LeaseSet l, LeaseSet r) { return HashDistance.getDistance(_us, l.getRoutingKey()).subtract(HashDistance.getDistance(_us, r.getRoutingKey())).signum(); } } private static class RouterInfoComparator implements Comparator { public int compare(RouterInfo l, RouterInfo r) { return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64()); } } public void renderRouterInfoHTML(Writer out, String routerPrefix) throws IOException { StringBuilder buf = new StringBuilder(4*1024); buf.append("

" + _("Network Database RouterInfo Lookup") + "

\n"); if (".".equals(routerPrefix)) { renderRouterInfo(buf, _context.router().getRouterInfo(), true, true); } else { boolean notFound = true; Set routers = _context.netDb().getRouters(); for (Iterator iter = routers.iterator(); iter.hasNext(); ) { RouterInfo ri = (RouterInfo)iter.next(); Hash key = ri.getIdentity().getHash(); if (key.toBase64().startsWith(routerPrefix)) { renderRouterInfo(buf, ri, false, true); notFound = false; } } if (notFound) buf.append(_("Router") + ' ').append(routerPrefix).append(' ' + _("not found in network database") ); } out.write(buf.toString()); out.flush(); } /** * @param debug @since 0.7.14 sort by routing key, display distance from our routing key * median distance, and other stuff, useful when floodfill */ public void renderLeaseSetHTML(Writer out, boolean debug) throws IOException { StringBuilder buf = new StringBuilder(4*1024); buf.append("

" + _("Network Database Contents") + "

\n"); buf.append("" + _("View RouterInfo") + ""); buf.append("

").append(_("LeaseSets")).append("

\n"); Hash ourRKey; Set leases; DecimalFormat fmt; if (debug) { ourRKey = _context.routingKeyGenerator().getRoutingKey(_context.routerHash()); leases = new TreeSet(new LeaseSetRoutingKeyComparator(ourRKey)); fmt = new DecimalFormat("#0.00"); } else { ourRKey = null; leases = new TreeSet(new LeaseSetComparator()); fmt = null; } leases.addAll(_context.netDb().getLeases()); int medianCount = leases.size() / 2; BigInteger median = null; int c = 0; long now = _context.clock().now(); for (Iterator iter = leases.iterator(); iter.hasNext(); ) { LeaseSet ls = (LeaseSet)iter.next(); Destination dest = ls.getDestination(); Hash key = dest.calculateHash(); buf.append("").append(_("LeaseSet")).append(": ").append(key.toBase64()); if (_context.clientManager().isLocal(dest)) { buf.append(" (" + _("Local") + " "); if (! _context.clientManager().shouldPublishLeaseSet(key)) buf.append(_("Unpublished") + ' '); buf.append(_("Destination") + ' '); TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(key); if (in != null && in.getDestinationNickname() != null) buf.append(in.getDestinationNickname()); else buf.append(dest.toBase64().substring(0, 6)); } else { buf.append(" (" + _("Destination") + ' '); String host = _context.namingService().reverseLookup(dest); if (host != null) buf.append(host); else buf.append(dest.toBase64().substring(0, 6)); } buf.append(")
\n"); long exp = ls.getEarliestLeaseDate()-now; if (exp > 0) buf.append(_("Expires in {0}", DataHelper.formatDuration(exp))).append("
\n"); else buf.append(_("Expired {0} ago", DataHelper.formatDuration(0-exp))).append("
\n"); if (debug) { buf.append("RAP? " + ls.getReceivedAsPublished() + ' '); buf.append("RAR? " + ls.getReceivedAsReply() + ' '); BigInteger dist = HashDistance.getDistance(ourRKey, ls.getRoutingKey()); if (c++ == medianCount) median = dist; buf.append("Dist: " + fmt.format(biLog2(dist)) + " "); buf.append("RKey: " + ls.getRoutingKey().toBase64() + ' '); buf.append("
"); } for (int i = 0; i < ls.getLeaseCount(); i++) { buf.append(_("Lease")).append(' ').append(i + 1).append(": " + _("Gateway") + ' '); buf.append(_context.commSystem().renderPeerHTML(ls.getLease(i).getGateway())); buf.append(' ' + _("Tunnel") + ' ').append(ls.getLease(i).getTunnelId().getTunnelId()).append("
\n"); } buf.append("
\n"); out.write(buf.toString()); buf.setLength(0); } if (debug) { buf.append("

Total Leasesets: " + leases.size()); buf.append("

Our RKey: " + ourRKey.toBase64() + "

"); buf.append("

Mod Data: " + HexDump.dump(_context.routingKeyGenerator().getModData()) + "

"); if (median != null) { double log2 = biLog2(median); buf.append("

Median distance (bits): " + fmt.format(log2)); // 1 for median, 3 for 8 floodfills... is this right? buf.append("

Estimated total floodfills: " + Math.round(Math.pow(2, 1 + 3 + 255 - log2))); } } out.write(buf.toString()); out.flush(); } /** * For debugging * http://forums.sun.com/thread.jspa?threadID=597652 * @since 0.7.14 */ private static double biLog2(BigInteger a) { 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; } /** * @param mode 0: our info and charts only; 1: full routerinfos and charts; 2: abbreviated routerinfos and charts */ public void renderStatusHTML(Writer out, int mode) throws IOException { out.write("

" + _("Network Database Contents") + " (" + _("View LeaseSets") + ")

\n"); if (!_context.netDb().isInitialized()) { out.write(_("Not initialized")); out.flush(); return; } boolean full = mode == 1; boolean shortStats = mode == 2; boolean showStats = full || shortStats; Hash us = _context.routerHash(); out.write("

" + _("Routers") + " (" + _("Show all routers")); else out.write("?f=1#routers\" >" + _("Show all routers with full stats")); out.write(")

\n"); StringBuilder buf = new StringBuilder(8192); RouterInfo ourInfo = _context.router().getRouterInfo(); renderRouterInfo(buf, ourInfo, true, true); out.write(buf.toString()); buf.setLength(0); ObjectCounter versions = new ObjectCounter(); ObjectCounter countries = new ObjectCounter(); int[] transportCount = new int[8]; Set routers = new TreeSet(new RouterInfoComparator()); routers.addAll(_context.netDb().getRouters()); for (Iterator iter = routers.iterator(); iter.hasNext(); ) { RouterInfo ri = (RouterInfo)iter.next(); Hash key = ri.getIdentity().getHash(); boolean isUs = key.equals(us); if (!isUs) { if (showStats) { renderRouterInfo(buf, ri, false, full); out.write(buf.toString()); buf.setLength(0); } String routerVersion = ri.getOption("router.version"); if (routerVersion != null) versions.increment(routerVersion); String country = _context.commSystem().getCountry(key); if(country != null) countries.increment(country); transportCount[classifyTransports(ri)]++; } } buf.append("
").append(_("Network Database Router Statistics")).append("
"); // versions table List versionList = new ArrayList(versions.objects()); if (!versionList.isEmpty()) { Collections.sort(versionList, Collections.reverseOrder(new VersionComparator())); buf.append("\n"); buf.append("\n"); for (String routerVersion : versionList) { int num = versions.count(routerVersion); buf.append("\n"); } buf.append("
" + _("Version") + "" + _("Count") + "
").append(DataHelper.stripHTML(routerVersion)); buf.append("").append(num).append("
\n"); } buf.append("
"); out.write(buf.toString()); buf.setLength(0); // transports table buf.append("\n"); buf.append("\n"); for (int i = 0; i < 8; i++) { int num = transportCount[i]; if (num > 0) { buf.append("\n"); } } buf.append("
" + _("Transports") + "" + _("Count") + "
").append(_(TNAMES[i])); buf.append("").append(num).append("
\n"); buf.append("
"); out.write(buf.toString()); buf.setLength(0); // country table List countryList = new ArrayList(countries.objects()); if (!countryList.isEmpty()) { Collections.sort(countryList, new CountryComparator()); buf.append("\n"); buf.append("\n"); for (String country : countryList) { int num = countries.count(country); buf.append("\n"); } buf.append("
" + _("Country") + "" + _("Count") + "
\"").append(country.toUpperCase()).append("\""); "); buf.append(_(_context.commSystem().getCountryName(country))); buf.append("").append(num).append("
\n"); } buf.append("
"); out.write(buf.toString()); out.flush(); } /** sort by translated country name */ private class CountryComparator implements Comparator { public int compare(Object l, Object r) { return _(_context.commSystem().getCountryName((String)l)) .compareTo(_(_context.commSystem().getCountryName((String)r))); } } /** * 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("\n"); if (full) { buf.append("\n"); } else { } buf.append("\n"); } private static final int SSU = 1; private static final int SSUI = 2; private static final int NTCP = 4; private static final String[] TNAMES = { _x("Hidden or starting up"), _x("SSU"), _x("SSU with introducers"), "", _x("NTCP"), _x("NTCP and SSU"), _x("NTCP and SSU with introducers"), "" }; /** * what transport types */ private int classifyTransports(RouterInfo info) { int rv = 0; String hash = info.getIdentity().getHash().toBase64(); for (Iterator iter = info.getAddresses().iterator(); iter.hasNext(); ) { RouterAddress addr = (RouterAddress)iter.next(); String style = addr.getTransportStyle(); if (style.equals("NTCP")) { rv |= NTCP; } else if (style.equals("SSU")) { if (addr.getOptions().getProperty("iport0") != null) rv |= SSUI; else rv |= SSU; } } return rv; } /** translate a string */ private String _(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 _(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 tranlslate parameter also, use _("foo {0} bar", _("baz")) * Do not double the single quotes in the parameter. * Use autoboxing to call with ints, longs, floats, etc. */ private String _(String s, Object o) { return Messages.getString(s, o, _context); } }
"); if (isUs) { buf.append("" + _("Our info") + ": ").append(hash).append("
\n"); } else { buf.append("" + _("Peer info for") + ": ").append(hash).append("\n"); if (full) { buf.append("[Back]
\n"); } else { buf.append("[").append(_("Full entry")).append("]
\n"); } } long age = _context.clock().now() - info.getPublished(); if (isUs && _context.router().isHidden()) { buf.append("").append(_("Hidden")).append(", ").append(_("Updated")).append(": ") .append(_("{0} ago", DataHelper.formatDuration(age))).append("
\n"); } else if (age > 0) { buf.append("").append(_("Published")).append(": ") .append(_("{0} ago", DataHelper.formatDuration(age))).append("
\n"); } else { // shouldnt happen buf.append("" + _("Published") + ": in ").append(DataHelper.formatDuration(0-age)).append("???
\n"); } buf.append("" + _("Address(es)") + ": "); String country = _context.commSystem().getCountry(info.getIdentity().getHash()); if(country != null) { buf.append("\"").append(country.toUpperCase()).append('\"'); "); } for (Iterator iter = info.getAddresses().iterator(); iter.hasNext(); ) { RouterAddress addr = (RouterAddress)iter.next(); String style = addr.getTransportStyle(); buf.append("").append(DataHelper.stripHTML(style)).append(": "); int cost = addr.getCost(); if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10))) buf.append('[').append(_("cost")).append('=').append("" + cost).append("] "); for (Iterator optIter = addr.getOptions().keySet().iterator(); optIter.hasNext(); ) { String name = (String)optIter.next(); String val = addr.getOptions().getProperty(name); buf.append('[').append(_(DataHelper.stripHTML(name))).append('=').append(DataHelper.stripHTML(val)).append("] "); } } buf.append("
" + _("Stats") + ":
\n"); for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) { String key = (String)iter.next(); String val = info.getOption(key); buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("
\n"); } buf.append("