diff --git a/.editorconfig b/.editorconfig index e2ebbb4dfc..c3e9eeeced 100644 --- a/.editorconfig +++ b/.editorconfig @@ -26,6 +26,12 @@ indent_style = tab [apps/BOB/**/*.java] indent_style = tab +[apps/routerconsole/java/src/com/vuze/plugins/mlab/tools/ndt/swingemu/*.java] +indent_style = tab + +[apps/routerconsole/java/src/edu/internet2/ndt/*.java] +indent_style = tab + [apps/routerconsole/jsp/{createreseed.jsp,exportfamily.jsp,flags.jsp,index.jsp,viewhistory.jsp,viewrouterlog.jsp,viewstat.jsp,viewtheme.jsp,viewwrapperlog.jsp}] insert_final_newline = false @@ -44,7 +50,11 @@ indent_size = 2 [core/java/src/gnu/getopt/*.properties] charset = iso-8859-1 -[core/java/src/net/minidev/**/*.java] +[core/java/src/org/json/simple/**/*.java] +end_of_line = crlf +indent_style = tab + +[core/java/src/com/southernstorm/**/*.java] indent_style = tab [router/java/src/org/cybergarage/**/*.java] diff --git a/LICENSE.txt b/LICENSE.txt index 0c7ba87ade..89561a7cf6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -90,6 +90,10 @@ Public domain except as listed below: (not included in most distribution packages) See licenses/LICENSE-Apache2.0.txt + Noise library: + Copyright (C) 2016 Southern Storm Software, Pty Ltd. + See licenses/LICENSE-Noise.txt + Router (router.jar): Public domain except as listed below: diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java index 487e05e9f1..a0cc6224fb 100644 --- a/apps/BOB/src/net/i2p/BOB/BOB.java +++ b/apps/BOB/src/net/i2p/BOB/BOB.java @@ -242,7 +242,7 @@ public class BOB implements Runnable, ClientApp { save = true; } if (!props.containsKey(I2PClient.PROP_TCP_PORT)) { - props.setProperty(I2PClient.PROP_TCP_PORT, "7654"); + props.setProperty(I2PClient.PROP_TCP_PORT, Integer.toString(I2PClient.DEFAULT_LISTEN_PORT)); save = true; } if (!props.containsKey(PROP_BOB_PORT)) { diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java index 7dee1eeb33..b34947cbc2 100644 --- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java +++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java @@ -96,12 +96,14 @@ public class MUXlisten implements Runnable { } String i2cpHost = Q.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); - int i2cpPort = 7654; - String i2cpPortStr = Q.getProperty(I2PClient.PROP_TCP_PORT, "7654"); - try { - i2cpPort = Integer.parseInt(i2cpPortStr); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("Invalid I2CP port specified [" + i2cpPortStr + "]"); + int i2cpPort = I2PClient.DEFAULT_LISTEN_PORT; + String i2cpPortStr = Q.getProperty(I2PClient.PROP_TCP_PORT); + if (i2cpPortStr != null) { + try { + i2cpPort = Integer.parseInt(i2cpPortStr); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("Invalid I2CP port specified [" + i2cpPortStr + "]"); + } } if (this.come_in) { diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml index 5a18942ac5..02196c4162 100644 --- a/apps/desktopgui/build.xml +++ b/apps/desktopgui/build.xml @@ -34,6 +34,7 @@ + diff --git a/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java b/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java index 3b7984abeb..eeb9c958d3 100644 --- a/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java +++ b/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java @@ -6,16 +6,15 @@ import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; - +import java.io.IOException; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.SwingWorker; +import net.i2p.apps.systray.UrlLauncher; import net.i2p.data.DataHelper; import net.i2p.desktopgui.router.RouterManager; -import net.i2p.desktopgui.util.BrowseException; -import net.i2p.desktopgui.util.I2PDesktop; import net.i2p.router.RouterContext; import net.i2p.util.Log; import net.i2p.util.PortMapper; @@ -433,15 +432,16 @@ class InternalTrayManager extends TrayManager { * and launch the browser at it. * * Modified from I2PTunnelHTTPClientBase. - * TODO perhaps move this to a new PortMapper method. * * @since 0.9.26 */ private void launchBrowser() { + // null args ok + UrlLauncher launcher = new UrlLauncher(_context, null, null); String url = _context.portMapper().getConsoleURL(); try { - I2PDesktop.browse(url); - } catch (BrowseException e1) { + launcher.openUrl(url); + } catch (IOException e1) { log.log(Log.WARN, "Failed to open browser!", e1); } } diff --git a/apps/desktopgui/src/net/i2p/desktopgui/Main.java b/apps/desktopgui/src/net/i2p/desktopgui/Main.java index 754723b2b3..70b96d7a43 100644 --- a/apps/desktopgui/src/net/i2p/desktopgui/Main.java +++ b/apps/desktopgui/src/net/i2p/desktopgui/Main.java @@ -17,7 +17,6 @@ import net.i2p.app.ClientAppManager; import net.i2p.app.ClientAppState; import static net.i2p.app.ClientAppState.*; import net.i2p.desktopgui.router.RouterManager; -import net.i2p.desktopgui.util.*; import net.i2p.router.RouterContext; import net.i2p.router.app.RouterApp; import net.i2p.util.Log; diff --git a/apps/desktopgui/src/net/i2p/desktopgui/util/BrowseException.java b/apps/desktopgui/src/net/i2p/desktopgui/util/BrowseException.java deleted file mode 100644 index a5e4f4350c..0000000000 --- a/apps/desktopgui/src/net/i2p/desktopgui/util/BrowseException.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.i2p.desktopgui.util; - -public class BrowseException extends Exception { - - private static final long serialVersionUID = 1L; - - public BrowseException() { - super(); - } - - public BrowseException(String s) { - super(s); - } - - public BrowseException(String s, Throwable t) { - super(s, t); - } - - public BrowseException(Throwable t) { - super(t); - } - -} \ No newline at end of file diff --git a/apps/desktopgui/src/net/i2p/desktopgui/util/I2PDesktop.java b/apps/desktopgui/src/net/i2p/desktopgui/util/I2PDesktop.java deleted file mode 100644 index 696088cfb6..0000000000 --- a/apps/desktopgui/src/net/i2p/desktopgui/util/I2PDesktop.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.i2p.desktopgui.util; - -import java.awt.Desktop; -import java.awt.Desktop.Action; -import java.net.URI; - -public class I2PDesktop { - - public static void browse(String url) throws BrowseException { - if(Desktop.isDesktopSupported()) { - Desktop desktop = Desktop.getDesktop(); - if(desktop.isSupported(Action.BROWSE)) { - try { - desktop.browse(new URI(url)); - } catch (Exception e) { - throw new BrowseException(); - } - } - else { - throw new BrowseException(); - } - } - else { - throw new BrowseException(); - } - } -} diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 77bad1339b..bed0df7b92 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -99,7 +99,7 @@ public class I2PSnarkUtil { _baseName = baseName; _opts = new HashMap(); //setProxy("127.0.0.1", 4444); - setI2CPConfig("127.0.0.1", 7654, null); + setI2CPConfig("127.0.0.1", I2PClient.DEFAULT_LISTEN_PORT, null); _banlist = new ConcurrentHashSet(); _maxUploaders = Snark.MAX_TOTAL_UPLOADERS; _maxUpBW = SnarkManager.DEFAULT_MAX_UP_BW; diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 0817db4320..7af0e5c7b6 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -28,6 +28,7 @@ import net.i2p.I2PAppContext; import net.i2p.app.ClientApp; import net.i2p.app.ClientAppManager; import net.i2p.app.ClientAppState; +import net.i2p.client.I2PClient; import net.i2p.crypto.SHA1Hash; import net.i2p.crypto.SigType; import net.i2p.data.Base64; @@ -785,7 +786,7 @@ public class SnarkManager implements CompleteListener, ClientApp { if (!_config.containsKey(PROP_I2CP_HOST)) _config.setProperty(PROP_I2CP_HOST, "127.0.0.1"); if (!_config.containsKey(PROP_I2CP_PORT)) - _config.setProperty(PROP_I2CP_PORT, "7654"); + _config.setProperty(PROP_I2CP_PORT, Integer.toString(I2PClient.DEFAULT_LISTEN_PORT)); if (!_config.containsKey(PROP_I2CP_OPTS)) _config.setProperty(PROP_I2CP_OPTS, "inbound.length=3 outbound.length=3" + " inbound.quantity=" + DEFAULT_TUNNEL_QUANTITY + @@ -911,7 +912,7 @@ public class SnarkManager implements CompleteListener, ClientApp { private void updateConfig() { String i2cpHost = _config.getProperty(PROP_I2CP_HOST); - int i2cpPort = getInt(PROP_I2CP_PORT, 7654); + int i2cpPort = getInt(PROP_I2CP_PORT, I2PClient.DEFAULT_LISTEN_PORT); String opts = _config.getProperty(PROP_I2CP_OPTS); Map i2cpOpts = new HashMap(); if (opts != null) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index 5eadd9d8c8..9e22d9b3c6 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -97,7 +97,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging { public boolean ownDest = false; /** the I2CP port, non-null */ - public String port = System.getProperty(I2PClient.PROP_TCP_PORT, "7654"); + public String port = System.getProperty(I2PClient.PROP_TCP_PORT, Integer.toString(I2PClient.DEFAULT_LISTEN_PORT)); /** the I2CP host, non-null */ public String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1"); /** the listen-on host. Sadly the listen-on port does not have a field. */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index fa61d40427..102c37cd53 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -409,7 +409,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna Log _log = tunnel.getContext().logManager().getLog(I2PTunnelClientBase.class); Properties props = new Properties(); props.putAll(tunnel.getClientOptions()); - int portNum = 7654; + int portNum = I2PClient.DEFAULT_LISTEN_PORT; if (tunnel.port != null) { try { portNum = Integer.parseInt(tunnel.port); @@ -471,7 +471,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna // try to make this error sensible as it will happen... String portNum = getTunnel().port; if (portNum == null) - portNum = "7654"; + portNum = Integer.toString(I2PClient.DEFAULT_LISTEN_PORT); String msg; if (getTunnel().getContext().isRouterContext()) msg = "Unable to build tunnels for the client"; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index 6f881bafef..c3e325c251 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -210,7 +210,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { private I2PSocketManager createManager(InputStream privData) { Properties props = new Properties(); props.putAll(getTunnel().getClientOptions()); - int portNum = 7654; + int portNum = I2PClient.DEFAULT_LISTEN_PORT; if (getTunnel().port != null) { try { portNum = Integer.parseInt(getTunnel().port); @@ -305,7 +305,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { // try to make this error sensible as it will happen... String portNum = getTunnel().port; if (portNum == null) - portNum = "7654"; + portNum = Integer.toString(I2PClient.DEFAULT_LISTEN_PORT); String msg; if (getTunnel().getContext().isRouterContext()) msg = "Unable to build tunnels for the server at " + remoteHost.getHostAddress() + ':' + remotePort; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 84a3858576..ef436d2908 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -733,10 +733,10 @@ public class TunnelController implements Logging { int portNum = Integer.parseInt(port); _tunnel.port = String.valueOf(portNum); } catch (NumberFormatException nfe) { - _tunnel.port = "7654"; + _tunnel.port = Integer.toString(I2PClient.DEFAULT_LISTEN_PORT); } } else { - _tunnel.port = "7654"; + _tunnel.port = Integer.toString(I2PClient.DEFAULT_LISTEN_PORT); } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java index 13062b2b33..f0a154e512 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/ui/TunnelConfig.java @@ -836,7 +836,7 @@ public class TunnelConfig { if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) { config.setProperty(TunnelController.PROP_I2CP_PORT, _i2cpPort); } else { - config.setProperty(TunnelController.PROP_I2CP_PORT, "7654"); + config.setProperty(TunnelController.PROP_I2CP_PORT, Integer.toString(I2PClient.DEFAULT_LISTEN_PORT)); } } if (_privKeyFile != null) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index d89f05b379..ab8ccc06ff 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Set; import net.i2p.I2PException; +import net.i2p.client.I2PClient; import net.i2p.crypto.SigType; import net.i2p.data.Base64; import net.i2p.data.DataHelper; @@ -446,7 +447,7 @@ public class EditBean extends IndexBean { if (tun != null) return tun.getI2CPPort(); else - return "7654"; + return Integer.toString(I2PClient.DEFAULT_LISTEN_PORT); } public String getCustomOptions(int tunnel) { diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java index 9893f4cfa7..2f619e9390 100644 --- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java +++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerFactory.java @@ -275,7 +275,7 @@ public class I2PSocketManagerFactory { } private static int getPort() { - int i2cpPort = 7654; + int i2cpPort = I2PClient.DEFAULT_LISTEN_PORT; String i2cpPortStr = System.getProperty(I2PClient.PROP_TCP_PORT); if (i2cpPortStr != null) { try { diff --git a/apps/routerconsole/java/bundle-messages.sh b/apps/routerconsole/java/bundle-messages.sh index 57652909f2..cdeaedf978 100755 --- a/apps/routerconsole/java/bundle-messages.sh +++ b/apps/routerconsole/java/bundle-messages.sh @@ -54,7 +54,8 @@ ROUTERFILES="\ " # add ../java/ so the refs will work in the po file -JPATHS="../java/src ../jsp/WEB-INF ../java/strings $ROUTERFILES" +# do not scan 3rd-party code in java/src/com or java/src/edu +JPATHS="../java/src/net ../jsp/WEB-INF ../java/strings $ROUTERFILES" for i in ../locale/messages_*.po do # get language diff --git a/apps/routerconsole/java/src/edu/internet2/ndt/OsfwWorker.java b/apps/routerconsole/java/src/edu/internet2/ndt/OsfwWorker.java index c9d1f11bf0..2eb4bbf98a 100644 --- a/apps/routerconsole/java/src/edu/internet2/ndt/OsfwWorker.java +++ b/apps/routerconsole/java/src/edu/internet2/ndt/OsfwWorker.java @@ -7,7 +7,7 @@ import java.net.Socket; /** * OsfwWorker creates a thread that listens for a message from the server. It * functions to check if the server has sent a message that is valid and - * sufficient to determine if the server->client direction has a fire-wall. + * sufficient to determine if the server->client direction has a fire-wall. * *

* As part of the simple firewall test, the Server must try to connect to the @@ -49,7 +49,7 @@ public class OsfwWorker implements Runnable { * @param iParamTestTime * Test time duration to wait for message from server * @param _localParam - * Applet object used to set the result of the S->C firewall test + * Applet object used to set the result of the S->C firewall test */ OsfwWorker(ServerSocket srvSocketParam, int iParamTestTime, Tcpbw100 _localParam) { @@ -76,7 +76,7 @@ public class OsfwWorker implements Runnable { /** * run() method of this SFW Worker thread. This thread listens on the socket * from the server for a given time period, and checks to see if the server - * has sent a message that is valid and sufficient to determine if the S->C + * has sent a message that is valid and sufficient to determine if the S->C * direction has a fire-wall. * */ public void run() { diff --git a/apps/routerconsole/java/src/edu/internet2/ndt/Tcpbw100.java b/apps/routerconsole/java/src/edu/internet2/ndt/Tcpbw100.java index 6812fa6e2c..d8ba60dc7b 100644 --- a/apps/routerconsole/java/src/edu/internet2/ndt/Tcpbw100.java +++ b/apps/routerconsole/java/src/edu/internet2/ndt/Tcpbw100.java @@ -563,38 +563,38 @@ public class Tcpbw100 extends JApplet implements ActionListener { } /** - * Get Client->Server fire-wall test results. + * Get Client->Server fire-wall test results. * - * @return integer indicating C->S test results + * @return integer indicating C->S test results * */ public int getC2sSFWTestResults() { return this._iC2sSFWResult; } /** - * Set Client->Server fire-wall test results. + * Set Client->Server fire-wall test results. * * @param iParamC2SRes - * integer indicating C->S test results + * integer indicating C->S test results * */ public void setC2sSFWTestResults(int iParamC2SRes) { this._iC2sSFWResult = iParamC2SRes; } /** - * Get Server->Client fire-wall test results. + * Get Server->Client fire-wall test results. * - * @return integer indicating C->S test results + * @return integer indicating C->S test results * */ public int getS2cSFWTestResults() { return this._iS2cSFWResult; } /** - * Set server->Client fire-wall test results. + * Set server->Client fire-wall test results. * * @param iParamS2CRes - * integer indicating C->S test results + * integer indicating C->S test results * */ public void setS2cSFWTestResults(int iParamS2CRes) { this._iS2cSFWResult = iParamS2CRes; @@ -958,8 +958,8 @@ public class Tcpbw100 extends JApplet implements ActionListener { _chkboxDefaultTest.setSelected(true); // 3. configure number of tests SpinnerNumberModel model = new SpinnerNumberModel(); - model.setMinimum(new Integer(0)); - model.setValue(new Integer(1)); + model.setMinimum(Integer.valueOf(0)); + model.setValue(Integer.valueOf(1)); _spinnerTestCount.setModel(model); _spinnerTestCount.setPreferredSize(new Dimension(60, 20)); _cmboboxDelay = new JComboBox(); @@ -4548,7 +4548,7 @@ public class Tcpbw100 extends JApplet implements ActionListener { try { t.interrupt(); } catch (RuntimeException re) { - _log.warn("TG", re); + _log.debug("TG", re); } try { Thread.sleep(20); @@ -4559,7 +4559,7 @@ public class Tcpbw100 extends JApplet implements ActionListener { try { t.stop(); } catch (RuntimeException re) { - _log.warn("TG", re); + _log.debug("TG", re); } } } @@ -4571,7 +4571,7 @@ public class Tcpbw100 extends JApplet implements ActionListener { thread_group.destroy(); break; }catch( Throwable e ){ - _log.warn("TG", e); + _log.debug("TG", e); } } diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java b/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java new file mode 100644 index 0000000000..31ad8bf90b --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/Analysis.java @@ -0,0 +1,757 @@ +package net.i2p.router.sybil; + +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.i2p.app.ClientAppManager; +import net.i2p.app.ClientAppState; +import static net.i2p.app.ClientAppState.*; +import net.i2p.data.DataHelper; +import net.i2p.data.Destination; +import net.i2p.data.Hash; +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.router.JobImpl; +import net.i2p.router.RouterContext; +import net.i2p.router.TunnelPoolSettings; +import net.i2p.router.app.RouterApp; +import net.i2p.router.crypto.FamilyKeyCrypto; +import net.i2p.router.peermanager.DBHistory; +import net.i2p.router.peermanager.PeerProfile; +import net.i2p.router.tunnel.pool.TunnelPool; +import net.i2p.router.util.HashDistance; +import net.i2p.router.web.Messages; +import net.i2p.stat.Rate; +import net.i2p.stat.RateAverages; +import net.i2p.stat.RateStat; +import net.i2p.util.Addresses; +import net.i2p.util.Log; +import net.i2p.util.ObjectCounter; + +/** + * + * @since 0.9.38 split out from SybilRenderer + * + */ +public class Analysis extends JobImpl implements RouterApp { + + private final RouterContext _context; + private final Log _log; + private final ClientAppManager _cmgr; + private final PersistSybil _persister; + private volatile ClientAppState _state = UNINITIALIZED; + private final DecimalFormat fmt = new DecimalFormat("#0.00"); + private boolean _wasRun; + + /** + * The name we register with the ClientAppManager + */ + public static final String APP_NAME = "sybil"; + public static final String PROP_FREQUENCY = "router.sybilFrequency"; + private static final long MIN_FREQUENCY = 60*60*1000L; + private static final long MIN_UPTIME = 75*60*1000L; + + public static final int PAIRMAX = 20; + public static final int MAX = 10; + // multiplied by size - 1, will also get POINTS24 added + private static final double POINTS32 = 5.0; + // multiplied by size - 1, will also get POINTS16 added + private static final double POINTS24 = 4.0; + // multiplied by size - 1 + private static final double POINTS16 = 0.25; + private static final double POINTS_US32 = 25.0; + private static final double POINTS_US24 = 20.0; + private static final double POINTS_US16 = 10.0; + private static final double POINTS_FAMILY = -10.0; + private static final double POINTS_BAD_OUR_FAMILY = 100.0; + private static final double POINTS_OUR_FAMILY = -100.0; + public static final double MIN_CLOSE = 242.0; + private static final double PAIR_DISTANCE_FACTOR = 2.0; + private static final double OUR_KEY_FACTOR = 4.0; + private static final double VERSION_FACTOR = 1.0; + private static final double POINTS_BAD_VERSION = 50.0; + private static final double POINTS_UNREACHABLE = 4.0; + private static final double POINTS_NEW = 4.0; + private static final double POINTS_BANLIST = 25.0; + + /** Get via getInstance() */ + private Analysis(RouterContext ctx, ClientAppManager mgr, String[] args) { + super(ctx); + _context = ctx; + _log = ctx.logManager().getLog(Analysis.class); + _cmgr = mgr; + _persister = new PersistSybil(ctx); + } + + /** + * @return non-null, creates new if not already registered + */ + public synchronized static Analysis getInstance(RouterContext ctx) { + ClientAppManager cmgr = ctx.clientAppManager(); + if (cmgr == null) + return null; + Analysis rv = (Analysis) cmgr.getRegisteredApp(APP_NAME); + if (rv == null) { + rv = new Analysis(ctx, cmgr, null); + rv.startup(); + } + return rv; + } + + public PersistSybil getPersister() { return _persister; } + + /////// begin Job methods + + public void runJob() { + long now = _context.clock().now(); + _log.info("Running analysis"); + Map points = backgroundAnalysis(); + if (!points.isEmpty()) { + try { + _log.info("Storing analysis"); + _persister.store(now, points); + _log.info("Store complete"); + } catch (IOException ioe) { + _log.error("Failed to store analysis", ioe); + } + } + schedule(); + } + + /////// end Job methods + /////// begin ClientApp methods + + /** + * ClientApp interface + */ + public synchronized void startup() { + changeState(STARTING); + changeState(RUNNING); + _cmgr.register(this); + schedule(); + } + + /** + * ClientApp interface + * @param args ignored + */ + public synchronized void shutdown(String[] args) { + if (_state == STOPPED) + return; + changeState(STOPPING); + changeState(STOPPED); + } + + public ClientAppState getState() { + return _state; + } + + public String getName() { + return APP_NAME; + } + + public String getDisplayName() { + return "Sybil Analyzer"; + } + + /////// end ClientApp methods + + private synchronized void changeState(ClientAppState state) { + _state = state; + if (_cmgr != null) + _cmgr.notify(this, state, null, null); + } + + public synchronized void schedule() { + long freq = _context.getProperty(PROP_FREQUENCY, 0L); + if (freq > 0) { + List previous = _persister.load(); + long now = _context.clock().now() + 15*1000; + if (freq < MIN_FREQUENCY) + freq = MIN_FREQUENCY; + long when; + if (_wasRun) { + when = now + freq; + } else if (!previous.isEmpty()) { + when = Math.max(previous.get(0).longValue() + freq, now); + } else { + when = now; + } + long up = _context.router().getUptime(); + when = Math.max(when, now + MIN_UPTIME - up); + getTiming().setStartAfter(when); + _context.jobQueue().addJob(this); + } else { + _context.jobQueue().removeJob(this); + } + } + + private static class RouterInfoRoutingKeyComparator implements Comparator, Serializable { + private final Hash _us; + /** @param us ROUTING KEY */ + public RouterInfoRoutingKeyComparator(Hash us) { + _us = us; + } + public int compare(RouterInfo l, RouterInfo r) { + return HashDistance.getDistance(_us, l.getHash()).compareTo(HashDistance.getDistance(_us, r.getHash())); + } + } + + /** + * Merge points1 into points2. + * points1 is unmodified. + */ +/**** + private void mergePoints(Map points1, Map points2) { + for (Map.Entry e : points1.entrySet()) { + Hash h = e.getKey(); + Points p1 = e.getValue(); + Points p2 = points2.get(h); + if (p2 != null) { + p2.points += p1.points; + p2.reasons.addAll(p1.reasons); + } else { + points2.put(h, p1); + } + } + } +****/ + + /** */ + private void addPoints(Map points, Hash h, double d, String reason) { + Points dd = points.get(h); + if (dd != null) { + dd.addPoints(d, reason); + } else { + points.put(h, new Points(d, reason)); + } + } + + /** + * All the floodfills, not including us + * @since 0.9.38 split out from renderRouterInfoHTML + */ + public List getFloodfills(Hash us) { + Set ffs = _context.peerManager().getPeersByCapability('f'); + List ris = new ArrayList(ffs.size()); + for (Hash ff : ffs) { + if (ff.equals(us)) + continue; + RouterInfo ri = _context.netDb().lookupRouterInfoLocally(ff); + if (ri != null) + ris.add(ri); + } + return ris; + } + + public double getAvgMinDist(List ris) { + double tot = 0; + int count = 200; + byte[] b = new byte[32]; + for (int i = 0; i < count; i++) { + _context.random().nextBytes(b); + Hash h = new Hash(b); + double d = closestDistance(h, ris); + tot += d; + } + double avgMinDist = tot / count; + return avgMinDist; + } + + /** + * Analyze threats. No output. + * Return separate maps for each cause instead? + * @since 0.9.38 + */ + public synchronized Map backgroundAnalysis() { + _wasRun = true; + Map points = new HashMap(64); + Hash us = _context.routerHash(); + if (us == null) + return points; + List ris = getFloodfills(us); + if (ris.isEmpty()) + return points; + + double avgMinDist = getAvgMinDist(ris); + + // IP analysis + calculateIPGroupsFamily(ris, points); + List ri32 = new ArrayList(4); + List ri24 = new ArrayList(4); + List ri16 = new ArrayList(4); + calculateIPGroupsUs(ris, points, ri32, ri24, ri16); + calculateIPGroups32(ris, points); + calculateIPGroups24(ris, points); + calculateIPGroups16(ris, points); + + // Pairwise distance analysis + List pairs = new ArrayList(PAIRMAX); + calculatePairDistance(ris, points, pairs); + + // Distance to our router analysis + // closest to our routing key today + Hash ourRKey = _context.router().getRouterInfo().getRoutingKey(); + calculateRouterInfo(ourRKey, "our rkey", ris, points); + // closest to our routing key tomorrow + RouterKeyGenerator rkgen = _context.routerKeyGenerator(); + Hash nkey = rkgen.getNextRoutingKey(us); + calculateRouterInfo(nkey, "our rkey (tomorrow)", ris, points); + // closest to us + calculateRouterInfo(us, "our router", ris, points); + + // Distance to our published destinations analysis + Map clientInboundPools = _context.tunnelManager().getInboundClientPools(); + List destinations = new ArrayList(clientInboundPools.keySet()); + for (Hash client : destinations) { + boolean isLocal = _context.clientManager().isLocal(client); + if (!isLocal) + continue; + if (! _context.clientManager().shouldPublishLeaseSet(client)) + continue; + LeaseSet ls = _context.netDb().lookupLeaseSetLocally(client); + if (ls == null) + continue; + Hash rkey = ls.getRoutingKey(); + TunnelPool in = clientInboundPools.get(client); + String name = (in != null) ? DataHelper.escapeHTML(in.getSettings().getDestinationNickname()) : client.toBase64().substring(0,4); + // closest to routing key today + calculateRouterInfo(rkey, name, ris, points); + // closest to routing key tomorrow + nkey = rkgen.getNextRoutingKey(ls.getHash()); + calculateRouterInfo(nkey, name + " (tomorrow)", ris, points); + } + + // Profile analysis + addProfilePoints(ris, points); + addVersionPoints(ris, points); + return points; + } + + /** + * @param pairs out parameter, sorted + * @return average distance + * @since 0.9.38 split out from renderPairDistance() + */ + public double calculatePairDistance(List ris, Map points, + List pairs) { + int sz = ris.size(); + double total = 0; + for (int i = 0; i < sz; i++) { + RouterInfo info1 = ris.get(i); + for (int j = i + 1; j < sz; j++) { + RouterInfo info2 = ris.get(j); + BigInteger dist = HashDistance.getDistance(info1.getHash(), info2.getHash()); + if (pairs.isEmpty()) { + pairs.add(new Pair(info1, info2, dist)); + } else if (pairs.size() < PAIRMAX) { + pairs.add(new Pair(info1, info2, dist)); + Collections.sort(pairs); + } else if (dist.compareTo(pairs.get(PAIRMAX - 1).dist) < 0) { + pairs.set(PAIRMAX - 1, new Pair(info1, info2, dist)); + Collections.sort(pairs); + } + total += biLog2(dist); + } + } + + double avg = total / (sz * sz / 2d); + for (Pair p : pairs) { + double distance = biLog2(p.dist); + double point = MIN_CLOSE - distance; + if (point < 0) + break; // sorted; + point *= PAIR_DISTANCE_FACTOR; + String b2 = p.r2.getHash().toBase64(); + addPoints(points, p.r1.getHash(), point, "Very close (" + fmt.format(distance) + + ") to other floodfill " + b2 + ""); + String b1 = p.r1.getHash().toBase64(); + addPoints(points, p.r2.getHash(), point, "Very close (" + fmt.format(distance) + + ") to other floodfill " + b1 + ""); + } + return avg; + } + + private static final BigInteger BI_MAX = (new BigInteger("2")).pow(256); + + private static double closestDistance(Hash h, List ris) { + BigInteger min = BI_MAX; + for (RouterInfo info : ris) { + BigInteger dist = HashDistance.getDistance(h, info.getHash()); + if (dist.compareTo(min) < 0) + min = dist; + } + return biLog2(min); + } + + /** v4 only */ + private static byte[] getIP(RouterInfo ri) { + for (RouterAddress ra : ri.getAddresses()) { + byte[] rv = ra.getIP(); + if (rv != null && rv.length == 4) + return rv; + } + return null; + } + + /** + * @param ri32 out parameter + * @param ri24 out parameter + * @param ri16 out parameter + * @since 0.9.38 split out from renderIPGroupsUs() + */ + public void calculateIPGroupsUs(List ris, Map points, + List ri32, List ri24, List ri16) { + RouterInfo us = _context.router().getRouterInfo(); + byte[] ourIP = getIP(us); + if (ourIP == null) { + String last = _context.getProperty("i2np.lastIP"); + if (last == null) + return; + ourIP = Addresses.getIP(last); + if (ourIP == null) + return; + } + String reason32 = "Same IP as us"; + String reason24 = "Same /24 IP as us"; + String reason16 = "Same /16 IP as us"; + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + if (ip[0] == ourIP[0] && ip[1] == ourIP[1]) { + if (ip[2] == ourIP[2]) { + if (ip[3] == ourIP[3]) { + addPoints(points, info.getHash(), POINTS_US32, reason32); + ri32.add(info); + } else { + addPoints(points, info.getHash(), POINTS_US24, reason24); + ri24.add(info); + } + } else { + addPoints(points, info.getHash(), POINTS_US16, reason16); + ri16.add(info); + } + } + } + } + + /** + * @since 0.9.38 split out from renderIPGroups32() + */ + public Map> calculateIPGroups32(List ris, Map points) { + ObjectCounter oc = new ObjectCounter(); + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + Integer x = Integer.valueOf((int) DataHelper.fromLong(ip, 0, 4)); + oc.increment(x); + } + Map> rv = new HashMap>(); + for (Integer ii : oc.objects()) { + int count = oc.count(ii); + if (count >= 2) + rv.put(ii, new ArrayList(4)); + } + for (Map.Entry> e : rv.entrySet()) { + Integer ii = e.getKey(); + int count = oc.count(ii); + double point = POINTS32 * (count - 1); + int i = ii.intValue(); + int i0 = (i >> 24) & 0xff; + int i1 = (i >> 16) & 0xff; + int i2 = (i >> 8) & 0xff; + int i3 = i & 0xff; + String reason = "Same IP with " + + (count - 1) + " other" + (( count > 2) ? "s" : "") + ""; + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + if ((ip[0] & 0xff) != i0) + continue; + if ((ip[1] & 0xff) != i1) + continue; + if ((ip[2] & 0xff) != i2) + continue; + if ((ip[3] & 0xff) != i3) + continue; + e.getValue().add(info); + addPoints(points, info.getHash(), point, reason); + } + } + return rv; + } + + /** + * @since 0.9.38 split out from renderIPGroups24() + */ + public Map> calculateIPGroups24(List ris, Map points) { + ObjectCounter oc = new ObjectCounter(); + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + Integer x = Integer.valueOf((int) DataHelper.fromLong(ip, 0, 3)); + oc.increment(x); + } + Map> rv = new HashMap>(); + for (Integer ii : oc.objects()) { + int count = oc.count(ii); + if (count >= 2) + rv.put(ii, new ArrayList(4)); + } + for (Map.Entry> e : rv.entrySet()) { + Integer ii = e.getKey(); + int count = oc.count(ii); + double point = POINTS24 * (count - 1); + int i = ii.intValue(); + int i0 = i >> 16; + int i1 = (i >> 8) & 0xff; + int i2 = i & 0xff; + String reason = "Same /24 IP with " + + (count - 1) + " other" + (( count > 2) ? "s" : "") + ""; + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + if ((ip[0] & 0xff) != i0) + continue; + if ((ip[1] & 0xff) != i1) + continue; + if ((ip[2] & 0xff) != i2) + continue; + e.getValue().add(info); + addPoints(points, info.getHash(), point, reason); + } + } + return rv; + } + + /** + * @since 0.9.38 split out from renderIPGroups16() + */ + public Map> calculateIPGroups16(List ris, Map points) { + ObjectCounter oc = new ObjectCounter(); + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + Integer x = Integer.valueOf((int) DataHelper.fromLong(ip, 0, 2)); + oc.increment(x); + } + Map> rv = new HashMap>(); + for (Integer ii : oc.objects()) { + int count = oc.count(ii); + if (count >= 4) + rv.put(ii, new ArrayList(8)); + } + for (Map.Entry> e : rv.entrySet()) { + Integer ii = e.getKey(); + int count = oc.count(ii); + double point = POINTS16 * (count - 1); + int i = ii.intValue(); + int i0 = i >> 8; + int i1 = i & 0xff; + String reason = "Same /16 IP with " + + (count - 1) + " other" + (( count > 2) ? "s" : "") + ""; + for (RouterInfo info : ris) { + byte[] ip = getIP(info); + if (ip == null) + continue; + if ((ip[0] & 0xff) != i0) + continue; + if ((ip[1] & 0xff) != i1) + continue; + e.getValue().add(info); + addPoints(points, info.getHash(), point, reason); + } + } + return rv; + } + + /** + * @since 0.9.38 split out from renderIPGroupsFamily() + */ + public Map> calculateIPGroupsFamily(List ris, Map points) { + ObjectCounter oc = new ObjectCounter(); + for (RouterInfo info : ris) { + String fam = info.getOption("family"); + if (fam == null) + continue; + oc.increment(fam); + } + List foo = new ArrayList(oc.objects()); + Map> rv = new HashMap>(foo.size()); + FamilyKeyCrypto fkc = _context.router().getFamilyKeyCrypto(); + String ourFamily = fkc != null ? fkc.getOurFamilyName() : null; + for (String s : foo) { + int count = oc.count(s); + List list = new ArrayList(count); + rv.put(s, list); + String ss = DataHelper.escapeHTML(s); + for (RouterInfo info : ris) { + String fam = info.getOption("family"); + if (fam == null) + continue; + if (!fam.equals(s)) + continue; + list.add(info); + double point = POINTS_FAMILY; + if (fkc != null && s.equals(ourFamily)) { + if (fkc.verifyOurFamily(info)) + addPoints(points, info.getHash(), POINTS_OUR_FAMILY, "Our family \"" + ss + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : "")); + else + addPoints(points, info.getHash(), POINTS_BAD_OUR_FAMILY, "Spoofed our family \"" + ss + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : "")); + } else if (count > 1) { + addPoints(points, info.getHash(), point, "In family \"" + ss + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : "")); + } else { + addPoints(points, info.getHash(), point, "In family \"" + ss + '"'); + } + } + } + return rv; + } + + private static final long DAY = 24*60*60*1000L; + + public void addProfilePoints(List ris, Map points) { + long now = _context.clock().now(); + RateAverages ra = RateAverages.getTemp(); + for (RouterInfo info : ris) { + Hash h = info.getHash(); + if (_context.banlist().isBanlisted(h)) + addPoints(points, h, POINTS_BANLIST, "Banlisted"); + PeerProfile prof = _context.profileOrganizer().getProfileNonblocking(h); + if (prof != null) { + long heard = prof.getFirstHeardAbout(); + if (heard > 0) { + long age = Math.max(now - heard, 1); + if (age < 2 * DAY) { + // (POINTS_NEW / 48) for every hour under 48, max POINTS_NEW + double point = Math.min(POINTS_NEW, (2 * DAY - age) / (2 * DAY / POINTS_NEW)); + addPoints(points, h, point, + "First heard about: " + _t("{0} ago", DataHelper.formatDuration2(age))); + } + } + DBHistory dbh = prof.getDBHistory(); + if (dbh != null) { + RateStat rs = dbh.getFailedLookupRate(); + if (rs != null) { + Rate r = rs.getRate(24*60*60*1000); + if (r != null) { + r.computeAverages(ra, false); + if (ra.getTotalEventCount() > 0) { + double avg = 100 * ra.getAverage(); + if (avg > 40) + addPoints(points, h, (avg - 40) / 6.0, "Lookup fail rate " + ((int) avg) + '%'); + } + } + } + } + } + } + } + + public void addVersionPoints(List ris, Map points) { + RouterInfo us = _context.router().getRouterInfo(); + if (us == null) return; + String ourVer = us.getVersion(); + if (!ourVer.startsWith("0.9.")) return; + ourVer = ourVer.substring(4); + int dot = ourVer.indexOf('.'); + if (dot > 0) + ourVer = ourVer.substring(0, dot); + int minor; + try { + minor = Integer.parseInt(ourVer); + } catch (NumberFormatException nfe) { return; } + for (RouterInfo info : ris) { + Hash h = info.getHash(); + String caps = info.getCapabilities(); + if (!caps.contains("R")) + addPoints(points, h, POINTS_UNREACHABLE, "Unreachable: " + DataHelper.escapeHTML(caps)); + String hisFullVer = info.getVersion(); + if (!hisFullVer.startsWith("0.9.")) { + addPoints(points, h, POINTS_BAD_VERSION, "Strange version " + DataHelper.escapeHTML(hisFullVer)); + continue; + } + String hisVer = hisFullVer.substring(4); + dot = hisVer.indexOf('.'); + if (dot > 0) + hisVer = hisVer.substring(0, dot); + int hisMinor; + try { + hisMinor = Integer.parseInt(hisVer); + } catch (NumberFormatException nfe) { continue; } + int howOld = minor - hisMinor; + if (howOld < 3) + continue; + addPoints(points, h, howOld * VERSION_FACTOR, howOld + " versions behind: " + DataHelper.escapeHTML(hisFullVer)); + } + } + + /** + * @param usName HTML escaped + * @param ris will be re-sorted in place + * @since 0.9.38 split out from renderRouterInfoHTML() + */ + public void calculateRouterInfo(Hash us, String usName, + List ris, Map points) { + Collections.sort(ris, new RouterInfoRoutingKeyComparator(us)); + int count = Math.min(MAX, ris.size()); + for (int i = 0; i < count; i++) { + RouterInfo ri = ris.get(i); + BigInteger bidist = HashDistance.getDistance(us, ri.getHash()); + double dist = biLog2(bidist); + double point = MIN_CLOSE - dist; + if (point <= 0) + break; + point *= OUR_KEY_FACTOR; + addPoints(points, ri.getHash(), point, "Very close (" + fmt.format(dist) + ") to our key " + usName + ": " + us.toBase64()); + } + } + + /** + * For debugging + * http://forums.sun.com/thread.jspa?threadID=597652 + * @since 0.7.14 + */ + private static double biLog2(BigInteger a) { + return Util.biLog2(a); + } + + /** + * translate a string with a parameter + * This is a lot more expensive than _t(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 translate parameter also, use _t("foo {0} bar", _t("baz")) + * Do not double the single quotes in the parameter. + * Use autoboxing to call with ints, longs, floats, etc. + */ + private String _t(String s, Object o) { + return Messages.getString(s, o, _context); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/Pair.java b/apps/routerconsole/java/src/net/i2p/router/sybil/Pair.java new file mode 100644 index 0000000000..adfe81470c --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/Pair.java @@ -0,0 +1,24 @@ +package net.i2p.router.sybil; + +import java.math.BigInteger; + +import net.i2p.data.router.RouterInfo; + +/** + * A pair of routers and the distance between them. + * + * @since 0.9.38 moved from SybilRenderer + */ +public class Pair implements Comparable { + public final RouterInfo r1, r2; + public final BigInteger dist; + + public Pair(RouterInfo ri1, RouterInfo ri2, BigInteger distance) { + r1 = ri1; r2 = ri2; dist = distance; + } + + public int compareTo(Pair p) { + return this.dist.compareTo(p.dist); + } +} + diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java b/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java new file mode 100644 index 0000000000..23ad7f3b05 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/PersistSybil.java @@ -0,0 +1,217 @@ +package net.i2p.router.sybil; + +import java.io.BufferedReader; +import java.util.Collections; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import net.i2p.I2PAppContext; +import net.i2p.data.Base64; +import net.i2p.data.DataFormatException; +import net.i2p.data.DataHelper; +import net.i2p.data.Hash; +import net.i2p.router.web.helpers.SybilRenderer; +import net.i2p.util.Log; +import net.i2p.util.FileSuffixFilter; +import net.i2p.util.SecureDirectory; +import net.i2p.util.SecureFileOutputStream; + +/** + * Store and retrieve analysis files from disk. + * Each file is named with a timestamp. + * + * @since 0.9.38 + */ +public class PersistSybil { + + private final I2PAppContext _context; + private final Log _log; + + private static final String DIR = "sybil-analysis/results"; + private static final String PFX = "sybil-"; + private static final String SFX = ".txt.gz"; + + /** access via Analysis.getPersister() */ + PersistSybil(I2PAppContext ctx) { + _context = ctx; + _log = ctx.logManager().getLog(PersistSybil.class); + } + + /** + * Store each entry. + * + * @param entries each one should be "entry" at the root + */ + public synchronized void store(long date, Map entries) throws IOException { + File dir = new SecureDirectory(_context.getConfigDir(), DIR); + if (!dir.exists()) + dir.mkdirs(); + File file = new File(dir, PFX + date + SFX); + StringBuilder buf = new StringBuilder(128); + Writer out = null; + try { + out = new OutputStreamWriter(new GZIPOutputStream(new SecureFileOutputStream(file))); + for (Map.Entry entry : entries.entrySet()) { + Hash h = entry.getKey(); + Points p = entry.getValue(); + buf.append(h.toBase64()).append(':'); + p.toString(buf); + buf.append('\n'); + out.write(buf.toString()); + buf.setLength(0); + } + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + } + + /** + * The list of stored analysis sets, as a time stamp. + * + * @return non-null, sorted by updated date, newest first + */ + public synchronized List load() { + File dir = new File(_context.getConfigDir(), DIR); + List rv = new ArrayList(); + File[] files = dir.listFiles(new FileSuffixFilter(PFX, SFX)); + if (files == null) + return rv; + for (File file : files) { + try { + String name = file.getName(); + long d = Long.parseLong(name.substring(PFX.length(), name.length() - SFX.length())); + rv.add(Long.valueOf(d)); + } catch (NumberFormatException nfe) {} + } + Collections.sort(rv, Collections.reverseOrder()); + return rv; + } + + /** + * Load the analysis for a certain date. + * + * @return non-null, unsorted + */ + public synchronized Map load(long date) throws IOException { + File dir = new File(_context.getConfigDir(), DIR); + File file = new File(dir, PFX + date + SFX); + Map rv = new HashMap(); + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file)))); + String line; + while ((line = in.readLine()) != null) { + int colon = line.indexOf(':'); + if (colon != 44) + continue; + if (line.length() < 46) + continue; + Hash h = new Hash(); + try { + h.fromBase64(line.substring(0, 44)); + } catch (DataFormatException dfe) { + continue; + } + Points p = Points.fromString(line.substring(45)); + if (p == null) + continue; + rv.put(h, p); + } + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + return rv; + } + + /** + * Load all the analysis for a certain hash. + * + * @return non-null, unsorted + */ + public synchronized Map load(Hash h) throws IOException { + String bh = h.toBase64() + ':'; + File dir = new File(_context.getConfigDir(), DIR); + Map rv = new HashMap(); + List dates = load(); + for (Long date : dates) { + File file = new File(dir, PFX + date + SFX); + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file)))); + String line; + while ((line = in.readLine()) != null) { + if (!line.startsWith(bh)) + continue; + if (line.length() < 46) + continue; + Points p = Points.fromString(line.substring(45)); + if (p == null) + continue; + rv.put(date, p); + } + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + return rv; + } + + /** + * Delete the file for a particular date + * + * @return success + */ + public synchronized boolean delete(long date) { + File dir = new File(_context.getConfigDir(), DIR); + File file = new File(dir, PFX + date + SFX); + return file.delete(); + } + +/**** + public static void main(String[] args) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + PersistSybil ps = new PersistSybil(ctx); + byte[] b = new byte[32]; + ctx.random().nextBytes(b); + Hash h = new Hash(b); + String rsn = "Test reason"; + Points p = new Points(1.234, rsn); + rsn = "Test reason2"; + p.addPoints(2.345, rsn); + Map map = new HashMap(); + map.put(h, p); + b = new byte[32]; + ctx.random().nextBytes(b); + h = new Hash(b); + map.put(h, p); + try { + long now = System.currentTimeMillis(); + System.out.println("storing entries: " + map.size()); + ps.store(System.currentTimeMillis(), map); + List dates = ps.load(); + System.out.println("Found sets: " + dates.size()); + map = ps.load(Long.valueOf(now)); + System.out.println("loaded entries: " + map.size()); + for (Map.Entry e : map.entrySet()) { + System.out.println(e.getKey().toString() + ": " + e.getValue()); + } + } catch (IOException ioe) { + System.out.println("store error from " + args[0]); + ioe.printStackTrace(); + } + } +****/ +} diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/Points.java b/apps/routerconsole/java/src/net/i2p/router/sybil/Points.java new file mode 100644 index 0000000000..b1f7d65ca1 --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/Points.java @@ -0,0 +1,111 @@ +package net.i2p.router.sybil; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import net.i2p.data.DataHelper; + +/** + * A total score and a List of reason Strings + * + * @since 0.9.38 moved from SybilRenderer + */ +public class Points implements Comparable { + private double points; + private final List reasons; + + /** + * @since 0.9.38 + */ + private Points() { + reasons = new ArrayList(4); + } + + /** + * @param reason may not contain '%' + */ + public Points(double d, String reason) { + this(); + addPoints(d, reason); + } + + /** + * @since 0.9.38 + */ + public double getPoints() { + return points; + } + + /** + * @since 0.9.38 + */ + public List getReasons() { + return reasons; + } + + /** + * @param reason may not contain '%' + * @since 0.9.38 + */ + public void addPoints(double d, String reason) { + points += d; + DecimalFormat format = new DecimalFormat("#0.00"); + String rsn = format.format(d) + ": " + reason; + reasons.add(rsn); + } + + public int compareTo(Points r) { + return Double.compare(points, r.points); + } + + /** + * @since 0.9.38 + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + toString(buf); + return buf.toString(); + } + + /** + * For persistence. + * Total points and reasons, '%' separated, no newline. + * The separation character is chosen to not conflict with + * decimal point in various locales, or chars in reasons, including HTML links, + * or special chars in Pattern. + * + * @since 0.9.38 + */ + public void toString(StringBuilder buf) { + buf.append(points); + for (String r : reasons) { + buf.append('%').append(r); + } + } + + /** + * For persistence. + * @return null on failure + * @since 0.9.38 + */ + public static Points fromString(String s) { + String[] ss = DataHelper.split(s, "%"); + if (ss.length < 2) + return null; + double d; + try { + d = Double.parseDouble(ss[0]); + } catch (NumberFormatException nfe) { + return null; + } + Points rv = new Points(); + for (int i = 1; i < ss.length; i++) { + rv.reasons.add(ss[i]); + } + rv.points = d; + return rv; + } +} + diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/Util.java b/apps/routerconsole/java/src/net/i2p/router/sybil/Util.java new file mode 100644 index 0000000000..d18203764d --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/Util.java @@ -0,0 +1,28 @@ +package net.i2p.router.sybil; + +import java.math.BigInteger; + +/** + * For NetDbRenderer and Sybil + * http://forums.sun.com/thread.jspa?threadID=597652 + * @since 0.9.38 moved from NetDbRenderer + */ +public class Util { + + /** + * For debugging + * http://forums.sun.com/thread.jspa?threadID=597652 + * @since 0.7.14 + */ + public 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; + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/sybil/package.html b/apps/routerconsole/java/src/net/i2p/router/sybil/package.html new file mode 100644 index 0000000000..a1219ceb5c --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/sybil/package.html @@ -0,0 +1,9 @@ + +

+Classes to run offline Sybil analysis, and to +store and load the results. +

+

+Since 0.9.38. +

+ diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java index 857d16cb57..55e42b471d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigServiceHandler.java @@ -228,8 +228,6 @@ public class ConfigServiceHandler extends FormHandler { @Override protected void processForm() { - if (_action == null) return; - if (_t("Shutdown gracefully").equals(_action)) { if (_context.hasWrapper()) registerWrapperNotifier(Router.EXIT_GRACEFUL, false); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java index 85b043104c..a1e9c0261d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigUpdateHandler.java @@ -157,8 +157,6 @@ public class ConfigUpdateHandler extends FormHandler { @Override protected void processForm() { - if (_action == null) - return; if (_action.equals(_t("Check for updates"))) { ConsoleUpdateManager mgr = UpdateHandler.updateManager(_context); if (mgr == null) { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java index 7681196748..7be5a8e42e 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java @@ -1,5 +1,6 @@ package net.i2p.router.web; +import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,6 +37,7 @@ public abstract class FormHandler { private final List _notices; private boolean _processed; private boolean _valid; + protected Writer _out; public FormHandler() { _errors = new ArrayList(); @@ -110,6 +112,11 @@ public abstract class FormHandler { */ public void storeMethod(String val) { _method = val; } + /** + * @since 0.9.38 + */ + public void storeWriter(Writer out) { _out = out; } + /** * The old nonces from the session * @since 0.9.4 @@ -120,11 +127,12 @@ public abstract class FormHandler { } /** - * Override this to perform the final processing (in turn, adding formNotice + * Implement this to perform the final processing (in turn, adding formNotice * and formError messages, etc) * + * Will only be called if _action is non-null and the nonce is valid. */ - protected void processForm() {} + protected abstract void processForm(); /** * Add an error message to display diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 7410e7140d..69ee850d27 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -34,6 +34,7 @@ import net.i2p.jetty.I2PLogger; import net.i2p.router.RouterContext; import net.i2p.router.app.RouterApp; import net.i2p.router.news.NewsManager; +import net.i2p.router.sybil.Analysis; import net.i2p.router.update.ConsoleUpdateManager; import net.i2p.util.Addresses; import net.i2p.util.FileSuffixFilter; @@ -342,9 +343,10 @@ public class RouterConsoleRunner implements RouterApp { boolean noJava7 = !SystemVersion.isJava7(); boolean noPack200 = (PluginStarter.pluginsEnabled(_context) || !NewsHelper.isUpdateDisabled(_context)) && !FileUtil.isPack200Supported(); - boolean openARM = SystemVersion.isARM() && SystemVersion.isOpenJDK(); - boolean isJava11 = SystemVersion.isJava11(); - if (noJava7 || noPack200 || openARM || isJava11) { + boolean openARM = SystemVersion.isARM() && SystemVersion.isOpenJDK() && !SystemVersion.isJava9(); + boolean isZero = SystemVersion.isZeroVM(); + boolean isJava11 = false; // SystemVersion.isJava11(); + if (noJava7 || noPack200 || openARM || isZero || isJava11) { String s = "Java version: " + System.getProperty("java.version") + " OS: " + System.getProperty("os.name") + ' ' + System.getProperty("os.arch") + ' ' + @@ -363,15 +365,20 @@ public class RouterConsoleRunner implements RouterApp { System.out.println("Warning: " + s); } if (openARM) { - s = "OpenJDK is not recommended for ARM. Use Oracle Java 8"; + s = "OpenJDK 7/8 are not recommended for ARM. Use OpenJDK 9 (or higher) or Oracle Java 8 (or higher)"; log.logAlways(net.i2p.util.Log.WARN, s); System.out.println("Warning: " + s); } - if (isJava11) { - s = "Java 11+ support is beta, and not recommended for general use"; + if (isZero) { + s = "OpenJDK Zero is a very slow interpreter-only JVM. Not recommended for use with I2P. Please use a faster JVM if possible."; log.logAlways(net.i2p.util.Log.WARN, s); System.out.println("Warning: " + s); } + //if (isJava11) { + // s = "Java 11+ support is beta, and not recommended for general use"; + // log.logAlways(net.i2p.util.Log.WARN, s); + // System.out.println("Warning: " + s); + //} } } @@ -865,6 +872,13 @@ public class RouterConsoleRunner implements RouterApp { if (_mgr == null) _context.addShutdownTask(new ServerShutdown()); ConfigServiceHandler.registerSignalHandler(_context); + + if (_mgr != null && + _context.getBooleanProperty(HelperBase.PROP_ADVANCED) && + _context.getProperty(Analysis.PROP_FREQUENCY, 0L) > 0) { + // registers and starts itself + Analysis.getInstance(_context); + } } /** diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigHomeHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigHomeHandler.java index 3a71f11ea3..eb0c7f74db 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigHomeHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigHomeHandler.java @@ -18,7 +18,6 @@ public class ConfigHomeHandler extends FormHandler { @Override protected void processForm() { - if (_action == null) return; String group = getJettyString("group"); boolean deleting = _action.equals(_t("Delete selected")); boolean adding = _action.equals(_t("Add item")); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigKeyringHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigKeyringHandler.java index 6cfa3885a7..c731770cdc 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigKeyringHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigKeyringHandler.java @@ -15,7 +15,6 @@ public class ConfigKeyringHandler extends FormHandler { @Override protected void processForm() { - if (_action == null) return; boolean adding = _action.equals(_t("Add key")); if (adding || _action.equals(_t("Delete key"))) { if (_peer == null) diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigSummaryHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigSummaryHandler.java index bfe2fb87a9..073a55fea7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigSummaryHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigSummaryHandler.java @@ -20,7 +20,6 @@ public class ConfigSummaryHandler extends FormHandler { @Override protected void processForm() { - if (_action == null) return; String group = getJettyString("group"); boolean deleting = _action.equals(_t("Delete selected")); boolean adding = _action.equals(_t("Add item")); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java index fac2989e08..5aef38ee57 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java @@ -36,7 +36,7 @@ public class ConfigUIHelper extends HelperBase { buf.append(CHECKED); buf.append("value=\"").append(theme).append("\" id=\"").append(theme).append("\">" + "" + - "\"\"" + + "\"\"" + "
" + "
").append(_t(theme)).append("
" + "\n"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/EventLogHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/EventLogHelper.java index e7c0e43124..2f67328c24 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/EventLogHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/EventLogHelper.java @@ -1,7 +1,6 @@ package net.i2p.router.web.helpers; import java.io.IOException; -import java.io.Writer; import java.text.Collator; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -24,7 +23,6 @@ import net.i2p.util.SystemVersion; * /events.jsp */ public class EventLogHelper extends FormHandler { - protected Writer _out; private long _from, _age; //private long _to = Long.MAX_VALUE; private String _event = ALL; @@ -63,6 +61,8 @@ public class EventLogHelper extends FormHandler { _xevents = new HashMap(1 + (_events.length / 2)); } + protected void processForm() {} + /** set the defaults after we have a context */ @Override public void setContextId(String contextId) { @@ -72,8 +72,6 @@ public class EventLogHelper extends FormHandler { } } - public void storeWriter(Writer out) { _out = out; } - public void setFrom(String s) { try { _age = Long.parseLong(s); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/GraphHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/GraphHelper.java index 848e8d1134..69dd7fe325 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/GraphHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/GraphHelper.java @@ -2,7 +2,6 @@ package net.i2p.router.web.helpers; import java.io.IOException; import java.io.Serializable; -import java.io.Writer; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; @@ -25,7 +24,6 @@ import net.i2p.stat.Rate; * /graphs.jsp, including form, and /graph.jsp */ public class GraphHelper extends FormHandler { - protected Writer _out; private int _periodCount; private boolean _showEvents; private int _width; @@ -74,12 +72,6 @@ public class GraphHelper extends FormHandler { return ""; } - /** - * This was a HelperBase but now it's a FormHandler - * @since 0.8.2 - */ - public void storeWriter(Writer out) { _out = out; } - public void setPeriodCount(String str) { setC(str); } @@ -403,10 +395,10 @@ public class GraphHelper extends FormHandler { for (int i = 0; i < times.length; i++) { _out.write("