diff --git a/apps/atalk/src/ATalk.java b/apps/atalk/src/ATalk.java deleted file mode 100644 index 7493f73d56..0000000000 --- a/apps/atalk/src/ATalk.java +++ /dev/null @@ -1,381 +0,0 @@ -package net.i2p.client; - -/* - * 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.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Properties; - -import net.i2p.I2PAppContext; -import net.i2p.I2PException; -import net.i2p.data.DataFormatException; -import net.i2p.data.Destination; -import net.i2p.util.Clock; -import net.i2p.util.I2PThread; -import net.i2p.util.Log; - -/** - * ATalk - anonymous talk, demonstrating a trivial I2P usage scenario. - * Run this class with no arguments for a manual. - * - * @author jrandom - */ -public class ATalk implements I2PSessionListener, Runnable { - /** logging hook - status messages are piped to this */ - private final static Log _log = new Log(ATalk.class); - - /** platform independent newline */ - private final static String NL = System.getProperty("line.separator"); - - /** the current session */ - private I2PSession _session; - - /** who am i */ - private Destination _myDestination; - - /** who are you? */ - private Destination _peerDestination; - - /** location of my secret key file */ - private String _myKeyFile; - - /** location of their public key */ - private String _theirDestinationFile; - - /** where the application reads input from. currently set to standard input */ - private BufferedReader _in; - - /** where the application sends output to. currently set to standard output */ - private BufferedWriter _out; - - /** string that messages must begin with to be treated as files */ - private final static String FILE_COMMAND = ".file: "; - - /** the, erm, manual */ - private final static String MANUAL = "ATalk: Anonymous Talk, a demo program for the Invisible Internet Project SDK" - + NL - + "To generate a new destination:" - + NL - + "\tATalk [fileToSavePrivateKeyIn] [fileToSavePublicKeyIn]" - + NL - + "To talk to another destination:" - + NL - + "\tATalk [myPrivateKeyFile] [peerPublicKey] [shouldLogToScreen]" - + NL - + "shouldLogToScreen is 'true' or 'false', depending on whether you want log info on the screen" - + NL - + "When talking to another destination, messages are sent after you hit return" - + NL - + "To send a file, send a message saying:" - + NL - + "\t" - + FILE_COMMAND - + "[filenameToSend]" - + NL - + "The peer will then recieve the file and be notified of where it has been saved" - + NL - + "To end the talk session, enter a period on a line by itself and hit return" - + NL; - - public final static String PROP_CONFIG_LOCATION = "configFile"; - - private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS"); - - /** Construct the talk engine, but don't connect yet */ - public ATalk(String myKeyFile, String theirDestFile) { - _myKeyFile = myKeyFile; - _theirDestinationFile = theirDestFile; - } - - /** Actually start up the connection to the I2P network. - * Successful connect does not mean the peer is online or reachable. - * - * @throws IOException if there is a problem reading in the keys from the files specified - * @throws DataFormatException if the key files are not in the valid format - * @throws I2PSessionException if there is a problem contacting the I2P router - */ - public void connect() throws IOException, I2PSessionException, DataFormatException { - I2PClient client = I2PClientFactory.createClient(); - File myFile = new File(_myKeyFile); - Properties props = new Properties(); - String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "atalk.config"); - try { - props.load(new FileInputStream(configLocation)); - } catch (FileNotFoundException fnfe) { - _log.warn("Unable to load up the ATalk config file " + configLocation); - } - // Provide any router or client API configuration here. - if (!props.containsKey(I2PClient.PROP_TCP_HOST)) - props.setProperty(I2PClient.PROP_TCP_HOST, "localhost"); - if (!props.containsKey(I2PClient.PROP_TCP_PORT)) - props.setProperty(I2PClient.PROP_TCP_PORT, "7654"); - if (!props.containsKey(I2PClient.PROP_RELIABILITY)) - props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT); - _session = client.createSession(new FileInputStream(myFile), props); - _session.setSessionListener(this); - _session.connect(); - - File peerDestFile = new File(_theirDestinationFile); - _peerDestination = new Destination(); - _peerDestination.readBytes(new FileInputStream(peerDestFile)); - return; - } - - /** Actual bulk processing of the application, reading in user input, - * sending messages, and displaying results. When this function exits, the - * application is complete. - * - */ - public void run() { - try { - connect(); - _in = new BufferedReader(new InputStreamReader(System.in)); - _out = new BufferedWriter(new OutputStreamWriter(System.out)); - - _out.write("Starting up anonymous talk session" + NL); - - while (true) { - String line = _in.readLine(); - if ((line == null) || (line.trim().length() <= 0)) continue; - if (".".equals(line)) { - boolean ok = _session.sendMessage(_peerDestination, ("Peer disconnected at " + now()).getBytes()); - // ignore ok, we're closing - break; - } - if (line.startsWith(FILE_COMMAND) && (line.trim().length() > FILE_COMMAND.length())) { - try { - String file = line.substring(FILE_COMMAND.length()); - boolean sent = sendFile(file); - if (!sent) { - _out.write("Failed sending the file: " + file + NL); - } - } catch (IOException ioe) { - _out.write("Error sending the file: " + ioe.getMessage() + NL); - _log.error("Error sending the file", ioe); - } - } else { - boolean ok = _session.sendMessage(_peerDestination, ("[" + now() + "] " + line).getBytes()); - if (!ok) { - _out.write("Failed sending message. Peer disconnected?" + NL); - } - } - } - } catch (IOException ioe) { - _log.error("Error running", ioe); - } catch (I2PSessionException ise) { - _log.error("Error communicating", ise); - } catch (DataFormatException dfe) { - _log.error("Peer destination file is not valid", dfe); - } finally { - try { - _log.debug("Exiting anonymous talk session"); - if (_out != null) _out.write("Exiting anonymous talk session"); - } catch (IOException ioe) { - // ignored - } - if (_session != null) { - try { - _session.destroySession(); - } catch (I2PSessionException ise) { - // ignored - } - } - try { - Thread.sleep(5000); - } catch (InterruptedException ie) { // nop! - } - } - } - - private String now() { - Date now = new Date(Clock.getInstance().now()); - return _fmt.format(now); - } - - /** Send the given file to the current peer. This works by sending a message - * saying ".file: filename\nbodyOfFile", where filename is the name of the file - * (which the recipient will be shown), and the bodyOfFile is the set of raw - * bytes in the file. - * - * @throws IOException if the file could not be found or read - * @return false if the file could not be sent to the peer - */ - private boolean sendFile(String filename) throws IOException, I2PSessionException { - _log.debug("Sending file [" + filename + "]"); - ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); - baos.write((FILE_COMMAND + filename + "\n").getBytes()); - FileInputStream fin = new FileInputStream(filename); - byte buf[] = new byte[4096]; - try { - while (true) { - int len = fin.read(buf); - if (len == -1) break; - baos.write(buf, 0, len); - } - } catch (IOException ioe) { - _log.debug("Failed reading the file", ioe); - return false; - } - baos.close(); - byte val[] = baos.toByteArray(); - _log.debug("Sending " + filename + " with a full payload of " + val.length); - try { - boolean rv = _session.sendMessage(_peerDestination, val); - _log.debug("Sending " + filename + " complete: rv = " + rv); - return rv; - } catch (Throwable t) { - _log.error("Error sending file", t); - return false; - } - } - - /** I2PSessionListener.messageAvailable requires this method to be called whenever - * I2P wants to tell the session that a message is available. ATalk always grabs - * the message immediately and either processes it as a "send file" command (passing - * it off to handleRecieveFile(..) or simply displays the - * message to the user. - * - */ - public void messageAvailable(I2PSession session, int msgId, long size) { - _log.debug("Message available: id = " + msgId + " size = " + size); - try { - byte msg[] = session.receiveMessage(msgId); - // inefficient way to just read the first line of text, but its easy - BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(msg))); - String line = reader.readLine(); - if (line.startsWith(FILE_COMMAND)) { - handleRecieveFile(line, msg); - } else { - // not a file command, so just plop 'er out on the screen - _out.write(now() + " --> " + new String(msg)); - _out.write(NL); - _out.flush(); - } - } catch (I2PSessionException ise) { - _log.error("Error fetching available message", ise); - } catch (IOException ioe) { - _log.error("Error writing out the message", ioe); - } - } - - /** React to a file being sent our way from the peer via {@link #sendFile sendFile} - * by saving the file to a temporary location and displaying where, what, and how large - * it is. - * - * @param firstline the first line of the message that, according to the sendFile - * implementation, contains the command and the filename that it was stored - * at on the peer's computer - * @param msg the entire message recieved, including the firstline - */ - private void handleRecieveFile(String firstline, byte msg[]) throws IOException { - _log.debug("handleRecieveFile called"); - File f = File.createTempFile("recieve", ".dat", new File(".")); - FileOutputStream fos = new FileOutputStream(f); - int lineLen = firstline.getBytes().length + "\n".getBytes().length; - int lenToCopy = msg.length - lineLen; - byte buf[] = new byte[lenToCopy]; - System.arraycopy(msg, lineLen, buf, 0, lenToCopy); - fos.write(buf); - fos.close(); - String name = firstline.substring(FILE_COMMAND.length()); - _out.write("Recieved a file called [" + name + "] of size [" + lenToCopy + "] bytes, saved as [" - + f.getAbsolutePath() + "]" + NL); - _out.flush(); - } - - /** driver */ - public static void main(String args[]) { - I2PAppContext context = new I2PAppContext(); - if (args.length == 2) { - String myKeyFile = args[0]; - String myDestinationFile = args[1]; - boolean success = generateKeys(myKeyFile, myDestinationFile); - if (success) - _log.debug("Keys generated (private key file: " + myKeyFile + " destination file: " + myDestinationFile - + ")"); - else - _log.debug("Keys generation failed"); - try { - Thread.sleep(5000); - } catch (InterruptedException ie) { // nop - } - } else if (args.length == 3) { - _log.debug("Starting chat"); - String myKeyfile = args[0]; - String peerDestFile = args[1]; - String shouldLog = args[2]; - if (Boolean.TRUE.toString().equalsIgnoreCase(shouldLog)) - context.logManager().setDisplayOnScreen(true); - else - context.logManager().setDisplayOnScreen(false); - String logFile = args[2]; - Thread talkThread = new I2PThread(new ATalk(myKeyfile, peerDestFile)); - talkThread.start(); - } else { - System.out.println(MANUAL); - try { - Thread.sleep(5000); - } catch (InterruptedException ie) { // nop - } - System.exit(-1); - } - } - - /** Generate a new Destination, saving that destination and the associated - * private keys in the privKeyFile, and also saving the destination without - * any private keys to destinationFile. - * - * @param privKeyFile private key file, including the destination and the various - * private keys, as defined by {@link I2PClient#createDestination I2PClient.createDestination} - * @param destinationFile file in which the Destination is serialized in - */ - private static boolean generateKeys(String privKeyFile, String destinationFile) { - try { - Destination d = I2PClientFactory.createClient().createDestination(new FileOutputStream(privKeyFile)); - FileOutputStream fos = new FileOutputStream(destinationFile); - d.writeBytes(fos); - fos.flush(); - fos.close(); - return true; - } catch (IOException ioe) { - _log.error("Error generating keys", ioe); - } catch (I2PException ipe) { - _log.error("Error generating keys", ipe); - } - return false; - } - - /** required by {@link I2PSessionListener I2PSessionListener} to notify of disconnect */ - public void disconnected(I2PSession session) { - _log.debug("Disconnected"); - } - - /** required by {@link I2PSessionListener I2PSessionListener} to notify of error */ - public void errorOccurred(I2PSession session, String message, Throwable error) { - _log.debug("Error occurred: " + message, error); - } - - /** required by {@link I2PSessionListener I2PSessionListener} to notify of abuse */ - public void reportAbuse(I2PSession session, int severity) { - _log.debug("Abuse reported of severity " + severity); - } -} - diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 010c84e0cc..81ca802137 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -1102,8 +1102,12 @@ public class SnarkManager implements Snark.CompleteListener { _log.error("Error in the DirectoryMonitor", e); } if (doMagnets) { - addMagnets(); - doMagnets = false; + try { + addMagnets(); + doMagnets = false; + } catch (Exception e) { + _log.error("Error in the DirectoryMonitor", e); + } } try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} } diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java index 7512f52f48..a0f63459a3 100644 --- a/core/java/src/net/i2p/data/Hash.java +++ b/core/java/src/net/i2p/data/Hash.java @@ -19,7 +19,6 @@ import java.io.InputStream; * @author jrandom */ public class Hash extends SimpleDataStructure { - private volatile String _stringified; private volatile String _base64ed; private int _cachedHashCode; @@ -71,7 +70,6 @@ public class Hash extends SimpleDataStructure { @Override public void setData(byte[] data) { super.setData(data); - _stringified = null; _base64ed = null; _cachedHashCode = super.hashCode(); } @@ -79,7 +77,6 @@ public class Hash extends SimpleDataStructure { @Override public void readBytes(InputStream in) throws DataFormatException, IOException { super.readBytes(in); - _stringified = null; _base64ed = null; _cachedHashCode = super.hashCode(); } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java index ade04a94bb..51d757221a 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java @@ -23,6 +23,10 @@ import net.i2p.stat.Rate; import net.i2p.stat.RateStat; import net.i2p.util.Log; +/** + * Repeatedly test a single tunnel for its entire lifetime, + * or until the pool is shut down or removed from the client manager. + */ class TestJob extends JobImpl { private final Log _log; private final TunnelPool _pool; @@ -35,7 +39,7 @@ class TestJob extends JobImpl { private SessionTag _encryptTag; /** base to randomize the test delay on */ - private static final int TEST_DELAY = 30*1000; + private static final int TEST_DELAY = 40*1000; public TestJob(RouterContext ctx, PooledTunnelCreatorConfig cfg, TunnelPool pool) { super(ctx); @@ -54,7 +58,7 @@ class TestJob extends JobImpl { public String getName() { return "Test tunnel"; } public void runJob() { - if (_pool == null) + if (_pool == null || !_pool.isAlive()) return; long lag = getContext().jobQueue().getMaxLag(); if (lag > 3000) { @@ -155,6 +159,8 @@ class TestJob extends JobImpl { } public void testSuccessful(int ms) { + if (_pool == null || !_pool.isAlive()) + return; getContext().statManager().addRateData("tunnel.testSuccessLength", _cfg.getLength(), 0); getContext().statManager().addRateData("tunnel.testSuccessTime", ms, 0); @@ -182,6 +188,8 @@ class TestJob extends JobImpl { } private void testFailed(long timeToFail) { + if (_pool == null || !_pool.isAlive()) + return; if (_found) { // ok, not really a /success/, but we did find it, even though slowly noteSuccess(timeToFail, _outTunnel); @@ -208,7 +216,7 @@ class TestJob extends JobImpl { } /** randomized time we should wait before testing */ - private int getDelay() { return TEST_DELAY + getContext().random().nextInt(TEST_DELAY); } + private int getDelay() { return TEST_DELAY + getContext().random().nextInt(TEST_DELAY / 3); } /** how long we allow tests to run for before failing them */ private int getTestPeriod() { @@ -234,8 +242,11 @@ class TestJob extends JobImpl { private void scheduleRetest() { scheduleRetest(false); } private void scheduleRetest(boolean asap) { + if (_pool == null || !_pool.isAlive()) + return; if (asap) { - requeue(getContext().random().nextInt(TEST_DELAY)); + if (_cfg.getExpiration() > getContext().clock().now() + (60 * 1000)) + requeue((TEST_DELAY / 4) + getContext().random().nextInt(TEST_DELAY / 4)); } else { int delay = getDelay(); if (_cfg.getExpiration() > getContext().clock().now() + delay + (3 * getTestPeriod())) diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java index 86c6e1ecb0..d9344f0de9 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -23,6 +23,7 @@ import net.i2p.util.Log; /** * A group of tunnels for the router or a particular client, in a single direction. + * Public only for TunnelRenderer in router console. */ public class TunnelPool { private final List _inProgress = new ArrayList(); @@ -43,7 +44,7 @@ public class TunnelPool { private final String _rateName; private static final int TUNNEL_LIFETIME = 10*60*1000; - public TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel) { + TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel) { _context = ctx; _log = ctx.logManager().getLog(TunnelPool.class); _manager = mgr; @@ -59,10 +60,18 @@ public class TunnelPool { refreshSettings(); } - public void startup() { + /** + * Warning, this may be called more than once + * (without an intervening shutdown()) if the + * tunnel is stopped and then restarted by the client manager with the same + * Destination (i.e. for servers or clients w/ persistent key) + */ + void startup() { synchronized (_inProgress) { _inProgress.clear(); } + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": Startup() called, was already alive? " + _alive, new Exception()); _alive = true; _started = System.currentTimeMillis(); _lastRateUpdate = _started; @@ -84,7 +93,9 @@ public class TunnelPool { new long[] { 5*60*1000l }); } - public void shutdown() { + void shutdown() { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": Shutdown called", new Exception()); _alive = false; _lastSelectionPeriod = 0; _lastSelected = null; @@ -132,7 +143,8 @@ public class TunnelPool { * and returns it. * */ - public TunnelInfo selectTunnel() { return selectTunnel(true); } + TunnelInfo selectTunnel() { return selectTunnel(true); } + private TunnelInfo selectTunnel(boolean allowRecurseOnFail) { boolean avoidZeroHop = ((getSettings().getLength() + getSettings().getLengthVariance()) > 0); @@ -239,7 +251,7 @@ public class TunnelPool { * Do we really need more fallbacks? * Used to prevent a zillion of them */ - public boolean needFallback() { + boolean needFallback() { int needed = _settings.getBackupQuantity() + _settings.getQuantity(); int fallbacks = 0; synchronized (_tunnels) { @@ -259,7 +271,8 @@ public class TunnelPool { int getTunnelCount() { synchronized (_tunnels) { return _tunnels.size(); } } public TunnelPoolSettings getSettings() { return _settings; } - public void setSettings(TunnelPoolSettings settings) { + + void setSettings(TunnelPoolSettings settings) { _settings = settings; if (_settings != null) { if (_log.shouldLog(Log.INFO)) @@ -267,8 +280,19 @@ public class TunnelPool { _manager.getExecutor().repoll(); // in case we need more } } - public TunnelPeerSelector getSelector() { return _peerSelector; } - public boolean isAlive() { return _alive; } + + /** + * Is this pool running AND either exploratory, or tracked by the client manager? + * A pool will be alive but not tracked after the client manager removes it + * but before all the tunnels have expired. + */ + public boolean isAlive() { + return _alive && + (_settings.isExploratory() || + (_settings.getDestination() != null && + _context.clientManager().isLocal(_settings.getDestination()))); + } + /** duplicate of getTunnelCount(), let's pick one */ public int size() { synchronized (_tunnels) { @@ -336,17 +360,14 @@ public class TunnelPool { } } - boolean connected = true; - if ( (_settings.getDestination() != null) && (!_context.clientManager().isLocal(_settings.getDestination())) ) - connected = false; - if ( (getTunnelCount() <= 0) && (!connected) ) { + if (getTunnelCount() <= 0 && !isAlive()) { + // this calls both our shutdown() and the other one (inbound/outbound) _manager.removeTunnels(_settings.getDestination()); - return; } } /** This may be called multiple times from TestJob */ - public void tunnelFailed(PooledTunnelCreatorConfig cfg) { + void tunnelFailed(PooledTunnelCreatorConfig cfg) { if (_log.shouldLog(Log.WARN)) _log.warn(toString() + ": Tunnel failed: " + cfg); LeaseSet ls = null; @@ -574,9 +595,8 @@ public class TunnelPool { * the countHowManyToBuild function below) * */ - public int countHowManyToBuild() { - if (_settings.getDestination() != null) { - if (!_context.clientManager().isLocal(_settings.getDestination())) + int countHowManyToBuild() { + if (!isAlive()) { return 0; } int wanted = getSettings().getBackupQuantity() + getSettings().getQuantity(); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java index dc82955729..487e3493d2 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -276,9 +276,9 @@ public class TunnelPoolManager implements TunnelManagerFacade { * Do not use to change settings. */ public void buildTunnels(Destination client, ClientTunnelSettings settings) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Building tunnels for the client " + client.calculateHash().toBase64() + ": " + settings); Hash dest = client.calculateHash(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Building tunnels for the client " + dest + ": " + settings); settings.getInboundSettings().setDestination(dest); settings.getOutboundSettings().setDestination(dest); TunnelPool inbound = null; @@ -311,7 +311,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { private static class DelayedStartup implements SimpleTimer.TimedEvent { - private TunnelPool pool; + private final TunnelPool pool; public DelayedStartup(TunnelPool p) { this.pool = p; @@ -322,9 +322,14 @@ public class TunnelPoolManager implements TunnelManagerFacade { } } - /** synch with buildTunnels() above */ + /** + * This will be called twice, once by the inbound and once by the outbound pool. + * Synched with buildTunnels() above. + */ public synchronized void removeTunnels(Hash destination) { if (destination == null) return; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Removing tunnels for the client " + destination); if (_context.clientManager().isLocal(destination)) { if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT, "wtf, why are you removing the pool for " + destination.toBase64(), new Exception("i did it"));