package net.i2p.router.web; import java.io.IOException; import java.io.Serializable; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.data.router.RouterInfo; import net.i2p.data.TunnelId; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; import net.i2p.router.tunnel.HopConfig; import net.i2p.router.tunnel.pool.TunnelPool; import net.i2p.stat.RateStat; /** * For /tunnels.jsp, used by TunnelHelper. */ class TunnelRenderer { private RouterContext _context; private static final int DISPLAY_LIMIT = 200; public TunnelRenderer(RouterContext ctx) { _context = ctx; } public void renderStatusHTML(Writer out) throws IOException { out.write("<div class=\"wideload\"><h2><a name=\"exploratory\" ></a>" + _t("Exploratory tunnels") + " (<a href=\"/configtunnels#exploratory\">" + _t("configure") + "</a>)</h2>\n"); renderPool(out, _context.tunnelManager().getInboundExploratoryPool(), _context.tunnelManager().getOutboundExploratoryPool()); List<Hash> destinations = null; Map<Hash, TunnelPool> clientInboundPools = _context.tunnelManager().getInboundClientPools(); Map<Hash, TunnelPool> clientOutboundPools = _context.tunnelManager().getOutboundClientPools(); destinations = new ArrayList<Hash>(clientInboundPools.keySet()); boolean debug = _context.getBooleanProperty(HelperBase.PROP_ADVANCED); for (int i = 0; i < destinations.size(); i++) { Hash client = destinations.get(i); boolean isLocal = _context.clientManager().isLocal(client); if ((!isLocal) && (!debug)) continue; TunnelPool in = clientInboundPools.get(client); TunnelPool outPool = clientOutboundPools.get(client); // TODO the following code is duplicated in SummaryHelper String name = (in != null) ? in.getSettings().getDestinationNickname() : null; if ( (name == null) && (outPool != null) ) name = outPool.getSettings().getDestinationNickname(); if (name == null) name = client.toBase64().substring(0,4); out.write("<h2><a name=\"" + client.toBase64().substring(0,4) + "\" ></a>" + _t("Client tunnels for") + ' ' + DataHelper.escapeHTML(_t(name))); if (isLocal) out.write(" (<a href=\"/configtunnels#" + client.toBase64().substring(0,4) +"\">" + _t("configure") + "</a>)</h2>\n"); else out.write(" (" + _t("dead") + ")</h2>\n"); renderPool(out, in, outPool); } List<HopConfig> participating = _context.tunnelDispatcher().listParticipatingTunnels(); out.write("<h2><a name=\"participating\"></a>" + _t("Participating tunnels") + "</h2>\n"); if (!participating.isEmpty()) { Collections.sort(participating, new TunnelComparator()); out.write("<table><tr><th>" + _t("Receive on") + "</th><th>" + _t("From") + "</th><th>" + _t("Send on") + "</th><th>" + _t("To") + "</th><th>" + _t("Expiration") + "</th>" + "<th>" + _t("Usage") + "</th><th>" + _t("Rate") + "</th><th>" + _t("Role") + "</th></tr>\n"); } long processed = 0; RateStat rs = _context.statManager().getRate("tunnel.participatingMessageCount"); if (rs != null) processed = (long)rs.getRate(10*60*1000).getLifetimeTotalValue(); int inactive = 0; int displayed = 0; for (int i = 0; i < participating.size(); i++) { HopConfig cfg = participating.get(i); int count = cfg.getProcessedMessagesCount(); if (count <= 0) { inactive++; continue; } // everything that isn't 'recent' is already in the tunnel.participatingMessageCount stat processed += cfg.getRecentMessagesCount(); if (++displayed > DISPLAY_LIMIT) continue; out.write("<tr>"); if (cfg.getReceiveTunnel() != null) out.write("<td class=\"cells\" align=\"center\">" + cfg.getReceiveTunnel().getTunnelId() +"</td>"); else out.write("<td class=\"cells\" align=\"center\">n/a</td>"); if (cfg.getReceiveFrom() != null) out.write("<td class=\"cells\" align=\"center\">" + netDbLink(cfg.getReceiveFrom()) +"</td>"); else out.write("<td class=\"cells\"> </td>"); if (cfg.getSendTunnel() != null) out.write("<td class=\"cells\" align=\"center\">" + cfg.getSendTunnel().getTunnelId() +"</td>"); else out.write("<td class=\"cells\"> </td>"); if (cfg.getSendTo() != null) out.write("<td class=\"cells\" align=\"center\">" + netDbLink(cfg.getSendTo()) +"</td>"); else out.write("<td class=\"cells\"> </td>"); long timeLeft = cfg.getExpiration()-_context.clock().now(); if (timeLeft > 0) out.write("<td class=\"cells\" align=\"center\">" + DataHelper.formatDuration2(timeLeft) + "</td>"); else out.write("<td class=\"cells\" align=\"center\">(" + _t("grace period") + ")</td>"); out.write("<td class=\"cells\" align=\"center\">" + count + " KB</td>"); int lifetime = (int) ((_context.clock().now() - cfg.getCreation()) / 1000); if (lifetime <= 0) lifetime = 1; if (lifetime > 10*60) lifetime = 10*60; int bps = 1024 * count / lifetime; out.write("<td class=\"cells\" align=\"center\">" + bps + " Bps</td>"); if (cfg.getSendTo() == null) out.write("<td class=\"cells\" align=\"center\">" + _t("Outbound Endpoint") + "</td>"); else if (cfg.getReceiveFrom() == null) out.write("<td class=\"cells\" align=\"center\">" + _t("Inbound Gateway") + "</td>"); else out.write("<td class=\"cells\" align=\"center\">" + _t("Participant") + "</td>"); out.write("</tr>\n"); } if (!participating.isEmpty()) out.write("</table>\n"); if (displayed > DISPLAY_LIMIT) out.write("<div class=\"statusnotes\"><b>" + _t("Limited display to the {0} tunnels with the highest usage", DISPLAY_LIMIT) + "</b></div>\n"); if (inactive > 0) out.write("<div class=\"statusnotes\"><b>" + _t("Inactive participating tunnels") + ": " + inactive + "</b></div>\n"); else if (displayed <= 0) out.write("<div class=\"statusnotes\"><b>" + _t("none") + "</b></div>\n"); out.write("<div class=\"statusnotes\"><b>" + _t("Lifetime bandwidth usage") + ": " + DataHelper.formatSize2(processed*1024) + "B</b></div>\n"); //renderPeers(out); out.write("</div>"); } private static class TunnelComparator implements Comparator<HopConfig>, Serializable { public int compare(HopConfig l, HopConfig r) { return (r.getProcessedMessagesCount() - l.getProcessedMessagesCount()); } } private void renderPool(Writer out, TunnelPool in, TunnelPool outPool) throws IOException { List<TunnelInfo> tunnels = null; if (in == null) tunnels = new ArrayList<TunnelInfo>(); else tunnels = in.listTunnels(); if (outPool != null) tunnels.addAll(outPool.listTunnels()); long processedIn = (in != null ? in.getLifetimeProcessed() : 0); long processedOut = (outPool != null ? outPool.getLifetimeProcessed() : 0); int live = 0; int maxLength = 1; for (int i = 0; i < tunnels.size(); i++) { TunnelInfo info = tunnels.get(i); if (info.getLength() > maxLength) maxLength = info.getLength(); } out.write("<table><tr><th>" + _t("In/Out") + "</th><th>" + _t("Expiry") + "</th><th>" + _t("Usage") + "</th><th>" + _t("Gateway") + "</th>"); if (maxLength > 3) { out.write("<th align=\"center\" colspan=\"" + (maxLength - 2)); out.write("\">" + _t("Participants") + "</th>"); } else if (maxLength == 3) { out.write("<th>" + _t("Participant") + "</th>"); } if (maxLength > 1) { out.write("<th>" + _t("Endpoint") + "</th>"); } out.write("</tr>\n"); for (int i = 0; i < tunnels.size(); i++) { TunnelInfo info = tunnels.get(i); long timeLeft = info.getExpiration()-_context.clock().now(); if (timeLeft <= 0) continue; // don't display tunnels in their grace period live++; if (info.isInbound()) out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/inbound.png\" alt=\"Inbound\" title=\"Inbound\"></td>"); else out.write("<tr> <td class=\"cells\" align=\"center\"><img src=\"/themes/console/images/outbound.png\" alt=\"Outbound\" title=\"Outbound\"></td>"); out.write(" <td class=\"cells\" align=\"center\">" + DataHelper.formatDuration2(timeLeft) + "</td>\n"); int count = info.getProcessedMessagesCount(); out.write(" <td class=\"cells\" align=\"center\">" + count + " KB</td>\n"); for (int j = 0; j < info.getLength(); j++) { Hash peer = info.getPeer(j); TunnelId id = (info.isInbound() ? info.getReceiveTunnelId(j) : info.getSendTunnelId(j)); if (_context.routerHash().equals(peer)) { out.write(" <td class=\"cells\" align=\"center\">" + (id == null ? "" : "" + id) + "</td>"); } else { String cap = getCapacity(peer); out.write(" <td class=\"cells\" align=\"center\">" + netDbLink(peer) + (id == null ? "" : " " + id) + cap + "</td>"); } if (info.getLength() < maxLength && (info.getLength() == 1 || j == info.getLength() - 2)) { for (int k = info.getLength(); k < maxLength; k++) out.write(" <td class=\"cells\" align=\"center\"> </td>"); } } out.write("</tr>\n"); if (info.isInbound()) processedIn += count; else processedOut += count; } out.write("</table>\n"); if (in != null) { // PooledTunnelCreatorConfig List<?> pending = in.listPending(); if (!pending.isEmpty()) { out.write("<div class=\"statusnotes\"><center><b>" + _t("Build in progress") + ": " + pending.size() + " " + _t("inbound") + "</b></center></div>\n"); live += pending.size(); } } if (outPool != null) { // PooledTunnelCreatorConfig List<?> pending = outPool.listPending(); if (!pending.isEmpty()) { out.write("<div class=\"statusnotes\"><center><b>" + _t("Build in progress") + ": " + pending.size() + " " + _t("outbound") + "</b></center></div>\n"); live += pending.size(); } } if (live <= 0) out.write("<div class=\"statusnotes\"><center><b>" + _t("No tunnels; waiting for the grace period to end.") + "</b></center></div>\n"); out.write("<div class=\"statusnotes\"><center><b>" + _t("Lifetime bandwidth usage") + ": " + DataHelper.formatSize2(processedIn*1024) + "B " + _t("in") + ", " + DataHelper.formatSize2(processedOut*1024) + "B " + _t("out") + "</b></center></div>"); } /**** private void renderPeers(Writer out) throws IOException { // count up the peers in the local pools ObjectCounter<Hash> lc = new ObjectCounter(); int tunnelCount = countTunnelsPerPeer(lc); // count up the peers in the participating tunnels ObjectCounter<Hash> pc = new ObjectCounter(); int partCount = countParticipatingPerPeer(pc); Set<Hash> peers = new HashSet(lc.objects()); peers.addAll(pc.objects()); List<Hash> peerList = new ArrayList(peers); Collections.sort(peerList, new CountryComparator(this._context.commSystem())); out.write("<h2><a name=\"peers\"></a>" + _t("Tunnel Counts By Peer") + "</h2>\n"); out.write("<table><tr><th>" + _t("Peer") + "</th><th>" + _t("Our Tunnels") + "</th><th>" + _t("% of total") + "</th><th>" + _t("Participating Tunnels") + "</th><th>" + _t("% of total") + "</th></tr>\n"); for (Hash h : peerList) { out.write("<tr> <td class=\"cells\" align=\"center\">"); out.write(netDbLink(h)); out.write(" <td class=\"cells\" align=\"center\">" + lc.count(h)); out.write(" <td class=\"cells\" align=\"center\">"); if (tunnelCount > 0) out.write("" + (lc.count(h) * 100 / tunnelCount)); else out.write('0'); out.write(" <td class=\"cells\" align=\"center\">" + pc.count(h)); out.write(" <td class=\"cells\" align=\"center\">"); if (partCount > 0) out.write("" + (pc.count(h) * 100 / partCount)); else out.write('0'); out.write('\n'); } out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>" + _t("Totals") + "</b> <td align=\"center\"><b>" + tunnelCount); out.write("</b> <td> </td> <td align=\"center\"><b>" + partCount); out.write("</b> <td> </td></tr></table></div>\n"); } ****/ /* duplicate of that in tunnelPoolManager for now */ /** @return total number of non-fallback expl. + client tunnels */ /**** private int countTunnelsPerPeer(ObjectCounter<Hash> lc) { List<TunnelPool> pools = new ArrayList(); _context.tunnelManager().listPools(pools); int tunnelCount = 0; for (TunnelPool tp : pools) { for (TunnelInfo info : tp.listTunnels()) { if (info.getLength() > 1) { tunnelCount++; for (int j = 0; j < info.getLength(); j++) { Hash peer = info.getPeer(j); if (!_context.routerHash().equals(peer)) lc.increment(peer); } } } } return tunnelCount; } ****/ /** @return total number of part. tunnels */ /**** private int countParticipatingPerPeer(ObjectCounter<Hash> pc) { List<HopConfig> participating = _context.tunnelDispatcher().listParticipatingTunnels(); for (HopConfig cfg : participating) { Hash from = cfg.getReceiveFrom(); if (from != null) pc.increment(from); Hash to = cfg.getSendTo(); if (to != null) pc.increment(to); } return participating.size(); } private static class CountryComparator implements Comparator<Hash> { public CountryComparator(CommSystemFacade comm) { this.comm = comm; } public int compare(Hash l, Hash r) { // get both countries String lc = this.comm.getCountry(l); String rc = this.comm.getCountry(r); // make them non-null lc = (lc == null) ? "zzzz" : lc; rc = (rc == null) ? "zzzz" : rc; // let String handle the rest return lc.compareTo(rc); } private CommSystemFacade comm; } ****/ /** cap string */ private String getCapacity(Hash peer) { RouterInfo info = _context.netDb().lookupRouterInfoLocally(peer); if (info != null) { String caps = info.getCapabilities(); for (char c = Router.CAPABILITY_BW12; c <= Router.CAPABILITY_BW256; c++) { if (caps.indexOf(c) >= 0) return " " + c; } } return ""; } private String netDbLink(Hash peer) { return _context.commSystem().renderPeerHTML(peer); } /** translate a string */ private String _t(String s) { return Messages.getString(s, _context); } /** translate a string */ public String _t(String s, Object o) { return Messages.getString(s, o, _context); } }