forked from I2P_Developers/i2p.i2p
merge of 'c0d12629a875b5dddde9fd729e05353d54a1bfc7'
and 'd07ed83f441d4c357e009e25aa4b55aee518dea3'
This commit is contained in:
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1102,8 +1102,12 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
_log.error("Error in the DirectoryMonitor", e);
|
_log.error("Error in the DirectoryMonitor", e);
|
||||||
}
|
}
|
||||||
if (doMagnets) {
|
if (doMagnets) {
|
||||||
addMagnets();
|
try {
|
||||||
doMagnets = false;
|
addMagnets();
|
||||||
|
doMagnets = false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
_log.error("Error in the DirectoryMonitor", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import java.io.InputStream;
|
|||||||
* @author jrandom
|
* @author jrandom
|
||||||
*/
|
*/
|
||||||
public class Hash extends SimpleDataStructure {
|
public class Hash extends SimpleDataStructure {
|
||||||
private volatile String _stringified;
|
|
||||||
private volatile String _base64ed;
|
private volatile String _base64ed;
|
||||||
private int _cachedHashCode;
|
private int _cachedHashCode;
|
||||||
|
|
||||||
@ -71,7 +70,6 @@ public class Hash extends SimpleDataStructure {
|
|||||||
@Override
|
@Override
|
||||||
public void setData(byte[] data) {
|
public void setData(byte[] data) {
|
||||||
super.setData(data);
|
super.setData(data);
|
||||||
_stringified = null;
|
|
||||||
_base64ed = null;
|
_base64ed = null;
|
||||||
_cachedHashCode = super.hashCode();
|
_cachedHashCode = super.hashCode();
|
||||||
}
|
}
|
||||||
@ -79,7 +77,6 @@ public class Hash extends SimpleDataStructure {
|
|||||||
@Override
|
@Override
|
||||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||||
super.readBytes(in);
|
super.readBytes(in);
|
||||||
_stringified = null;
|
|
||||||
_base64ed = null;
|
_base64ed = null;
|
||||||
_cachedHashCode = super.hashCode();
|
_cachedHashCode = super.hashCode();
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@ import net.i2p.stat.Rate;
|
|||||||
import net.i2p.stat.RateStat;
|
import net.i2p.stat.RateStat;
|
||||||
import net.i2p.util.Log;
|
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 {
|
class TestJob extends JobImpl {
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
private final TunnelPool _pool;
|
private final TunnelPool _pool;
|
||||||
@ -35,7 +39,7 @@ class TestJob extends JobImpl {
|
|||||||
private SessionTag _encryptTag;
|
private SessionTag _encryptTag;
|
||||||
|
|
||||||
/** base to randomize the test delay on */
|
/** 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) {
|
public TestJob(RouterContext ctx, PooledTunnelCreatorConfig cfg, TunnelPool pool) {
|
||||||
super(ctx);
|
super(ctx);
|
||||||
@ -54,7 +58,7 @@ class TestJob extends JobImpl {
|
|||||||
public String getName() { return "Test tunnel"; }
|
public String getName() { return "Test tunnel"; }
|
||||||
|
|
||||||
public void runJob() {
|
public void runJob() {
|
||||||
if (_pool == null)
|
if (_pool == null || !_pool.isAlive())
|
||||||
return;
|
return;
|
||||||
long lag = getContext().jobQueue().getMaxLag();
|
long lag = getContext().jobQueue().getMaxLag();
|
||||||
if (lag > 3000) {
|
if (lag > 3000) {
|
||||||
@ -155,6 +159,8 @@ class TestJob extends JobImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testSuccessful(int ms) {
|
public void testSuccessful(int ms) {
|
||||||
|
if (_pool == null || !_pool.isAlive())
|
||||||
|
return;
|
||||||
getContext().statManager().addRateData("tunnel.testSuccessLength", _cfg.getLength(), 0);
|
getContext().statManager().addRateData("tunnel.testSuccessLength", _cfg.getLength(), 0);
|
||||||
getContext().statManager().addRateData("tunnel.testSuccessTime", ms, 0);
|
getContext().statManager().addRateData("tunnel.testSuccessTime", ms, 0);
|
||||||
|
|
||||||
@ -182,6 +188,8 @@ class TestJob extends JobImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void testFailed(long timeToFail) {
|
private void testFailed(long timeToFail) {
|
||||||
|
if (_pool == null || !_pool.isAlive())
|
||||||
|
return;
|
||||||
if (_found) {
|
if (_found) {
|
||||||
// ok, not really a /success/, but we did find it, even though slowly
|
// ok, not really a /success/, but we did find it, even though slowly
|
||||||
noteSuccess(timeToFail, _outTunnel);
|
noteSuccess(timeToFail, _outTunnel);
|
||||||
@ -208,7 +216,7 @@ class TestJob extends JobImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** randomized time we should wait before testing */
|
/** 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 */
|
/** how long we allow tests to run for before failing them */
|
||||||
private int getTestPeriod() {
|
private int getTestPeriod() {
|
||||||
@ -234,8 +242,11 @@ class TestJob extends JobImpl {
|
|||||||
|
|
||||||
private void scheduleRetest() { scheduleRetest(false); }
|
private void scheduleRetest() { scheduleRetest(false); }
|
||||||
private void scheduleRetest(boolean asap) {
|
private void scheduleRetest(boolean asap) {
|
||||||
|
if (_pool == null || !_pool.isAlive())
|
||||||
|
return;
|
||||||
if (asap) {
|
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 {
|
} else {
|
||||||
int delay = getDelay();
|
int delay = getDelay();
|
||||||
if (_cfg.getExpiration() > getContext().clock().now() + delay + (3 * getTestPeriod()))
|
if (_cfg.getExpiration() > getContext().clock().now() + delay + (3 * getTestPeriod()))
|
||||||
|
@ -23,6 +23,7 @@ import net.i2p.util.Log;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A group of tunnels for the router or a particular client, in a single direction.
|
* 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 {
|
public class TunnelPool {
|
||||||
private final List<PooledTunnelCreatorConfig> _inProgress = new ArrayList();
|
private final List<PooledTunnelCreatorConfig> _inProgress = new ArrayList();
|
||||||
@ -43,7 +44,7 @@ public class TunnelPool {
|
|||||||
private final String _rateName;
|
private final String _rateName;
|
||||||
private static final int TUNNEL_LIFETIME = 10*60*1000;
|
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;
|
_context = ctx;
|
||||||
_log = ctx.logManager().getLog(TunnelPool.class);
|
_log = ctx.logManager().getLog(TunnelPool.class);
|
||||||
_manager = mgr;
|
_manager = mgr;
|
||||||
@ -59,10 +60,18 @@ public class TunnelPool {
|
|||||||
refreshSettings();
|
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) {
|
synchronized (_inProgress) {
|
||||||
_inProgress.clear();
|
_inProgress.clear();
|
||||||
}
|
}
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn(toString() + ": Startup() called, was already alive? " + _alive, new Exception());
|
||||||
_alive = true;
|
_alive = true;
|
||||||
_started = System.currentTimeMillis();
|
_started = System.currentTimeMillis();
|
||||||
_lastRateUpdate = _started;
|
_lastRateUpdate = _started;
|
||||||
@ -84,7 +93,9 @@ public class TunnelPool {
|
|||||||
new long[] { 5*60*1000l });
|
new long[] { 5*60*1000l });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
void shutdown() {
|
||||||
|
if (_log.shouldLog(Log.WARN))
|
||||||
|
_log.warn(toString() + ": Shutdown called", new Exception());
|
||||||
_alive = false;
|
_alive = false;
|
||||||
_lastSelectionPeriod = 0;
|
_lastSelectionPeriod = 0;
|
||||||
_lastSelected = null;
|
_lastSelected = null;
|
||||||
@ -132,7 +143,8 @@ public class TunnelPool {
|
|||||||
* and returns it.
|
* and returns it.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public TunnelInfo selectTunnel() { return selectTunnel(true); }
|
TunnelInfo selectTunnel() { return selectTunnel(true); }
|
||||||
|
|
||||||
private TunnelInfo selectTunnel(boolean allowRecurseOnFail) {
|
private TunnelInfo selectTunnel(boolean allowRecurseOnFail) {
|
||||||
boolean avoidZeroHop = ((getSettings().getLength() + getSettings().getLengthVariance()) > 0);
|
boolean avoidZeroHop = ((getSettings().getLength() + getSettings().getLengthVariance()) > 0);
|
||||||
|
|
||||||
@ -239,7 +251,7 @@ public class TunnelPool {
|
|||||||
* Do we really need more fallbacks?
|
* Do we really need more fallbacks?
|
||||||
* Used to prevent a zillion of them
|
* Used to prevent a zillion of them
|
||||||
*/
|
*/
|
||||||
public boolean needFallback() {
|
boolean needFallback() {
|
||||||
int needed = _settings.getBackupQuantity() + _settings.getQuantity();
|
int needed = _settings.getBackupQuantity() + _settings.getQuantity();
|
||||||
int fallbacks = 0;
|
int fallbacks = 0;
|
||||||
synchronized (_tunnels) {
|
synchronized (_tunnels) {
|
||||||
@ -259,7 +271,8 @@ public class TunnelPool {
|
|||||||
int getTunnelCount() { synchronized (_tunnels) { return _tunnels.size(); } }
|
int getTunnelCount() { synchronized (_tunnels) { return _tunnels.size(); } }
|
||||||
|
|
||||||
public TunnelPoolSettings getSettings() { return _settings; }
|
public TunnelPoolSettings getSettings() { return _settings; }
|
||||||
public void setSettings(TunnelPoolSettings settings) {
|
|
||||||
|
void setSettings(TunnelPoolSettings settings) {
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
if (_settings != null) {
|
if (_settings != null) {
|
||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO))
|
||||||
@ -267,8 +280,19 @@ public class TunnelPool {
|
|||||||
_manager.getExecutor().repoll(); // in case we need more
|
_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 */
|
/** duplicate of getTunnelCount(), let's pick one */
|
||||||
public int size() {
|
public int size() {
|
||||||
synchronized (_tunnels) {
|
synchronized (_tunnels) {
|
||||||
@ -336,17 +360,14 @@ public class TunnelPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean connected = true;
|
if (getTunnelCount() <= 0 && !isAlive()) {
|
||||||
if ( (_settings.getDestination() != null) && (!_context.clientManager().isLocal(_settings.getDestination())) )
|
// this calls both our shutdown() and the other one (inbound/outbound)
|
||||||
connected = false;
|
|
||||||
if ( (getTunnelCount() <= 0) && (!connected) ) {
|
|
||||||
_manager.removeTunnels(_settings.getDestination());
|
_manager.removeTunnels(_settings.getDestination());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This may be called multiple times from TestJob */
|
/** This may be called multiple times from TestJob */
|
||||||
public void tunnelFailed(PooledTunnelCreatorConfig cfg) {
|
void tunnelFailed(PooledTunnelCreatorConfig cfg) {
|
||||||
if (_log.shouldLog(Log.WARN))
|
if (_log.shouldLog(Log.WARN))
|
||||||
_log.warn(toString() + ": Tunnel failed: " + cfg);
|
_log.warn(toString() + ": Tunnel failed: " + cfg);
|
||||||
LeaseSet ls = null;
|
LeaseSet ls = null;
|
||||||
@ -574,9 +595,8 @@ public class TunnelPool {
|
|||||||
* the countHowManyToBuild function below)
|
* the countHowManyToBuild function below)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public int countHowManyToBuild() {
|
int countHowManyToBuild() {
|
||||||
if (_settings.getDestination() != null) {
|
if (!isAlive()) {
|
||||||
if (!_context.clientManager().isLocal(_settings.getDestination()))
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int wanted = getSettings().getBackupQuantity() + getSettings().getQuantity();
|
int wanted = getSettings().getBackupQuantity() + getSettings().getQuantity();
|
||||||
|
@ -276,9 +276,9 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
|||||||
* Do not use to change settings.
|
* Do not use to change settings.
|
||||||
*/
|
*/
|
||||||
public void buildTunnels(Destination client, ClientTunnelSettings 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();
|
Hash dest = client.calculateHash();
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Building tunnels for the client " + dest + ": " + settings);
|
||||||
settings.getInboundSettings().setDestination(dest);
|
settings.getInboundSettings().setDestination(dest);
|
||||||
settings.getOutboundSettings().setDestination(dest);
|
settings.getOutboundSettings().setDestination(dest);
|
||||||
TunnelPool inbound = null;
|
TunnelPool inbound = null;
|
||||||
@ -311,7 +311,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
|
|||||||
|
|
||||||
|
|
||||||
private static class DelayedStartup implements SimpleTimer.TimedEvent {
|
private static class DelayedStartup implements SimpleTimer.TimedEvent {
|
||||||
private TunnelPool pool;
|
private final TunnelPool pool;
|
||||||
|
|
||||||
public DelayedStartup(TunnelPool p) {
|
public DelayedStartup(TunnelPool p) {
|
||||||
this.pool = 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) {
|
public synchronized void removeTunnels(Hash destination) {
|
||||||
if (destination == null) return;
|
if (destination == null) return;
|
||||||
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
|
_log.debug("Removing tunnels for the client " + destination);
|
||||||
if (_context.clientManager().isLocal(destination)) {
|
if (_context.clientManager().isLocal(destination)) {
|
||||||
if (_log.shouldLog(Log.CRIT))
|
if (_log.shouldLog(Log.CRIT))
|
||||||
_log.log(Log.CRIT, "wtf, why are you removing the pool for " + destination.toBase64(), new Exception("i did it"));
|
_log.log(Log.CRIT, "wtf, why are you removing the pool for " + destination.toBase64(), new Exception("i did it"));
|
||||||
|
Reference in New Issue
Block a user