forked from I2P_Developers/i2p.i2p
beginning of branch i2p.i2p.i2p
This commit is contained in:
35
router/java/build.xml
Normal file
35
router/java/build.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2p_router">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src:./test" debug="true" destdir="./build/obj" classpath="../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/router.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:./test:../../core/java/src:../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2P Router" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
84
router/java/src/net/i2p/data/i2np/DataMessage.java
Normal file
84
router/java/src/net/i2p/data/i2np/DataMessage.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines a message containing arbitrary bytes of data
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DataMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DataMessage.class);
|
||||
public final static int MESSAGE_TYPE = 20;
|
||||
private byte _data[];
|
||||
|
||||
public DataMessage() {
|
||||
_data = null;
|
||||
}
|
||||
|
||||
public byte[] getData() { return _data; }
|
||||
public void setData(byte data[]) { _data = data; }
|
||||
|
||||
public int getSize() { return _data.length; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
int size = (int)DataHelper.readLong(in, 4);
|
||||
_data = new byte[size];
|
||||
int read = read(in, _data);
|
||||
if (read != size)
|
||||
throw new DataFormatException("Not enough bytes to read (read = " + read + ", expected = " + size + ")");
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream((_data != null ? _data.length + 4 : 4));
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, (_data != null ? _data.length : 0));
|
||||
os.write(_data);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getData());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DataMessage) ) {
|
||||
DataMessage msg = (DataMessage)object;
|
||||
return DataHelper.eq(getData(),msg.getData());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DataMessage: ");
|
||||
buf.append("\n\tData: ").append(DataHelper.toString(getData(), 64));
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router to help integrate into
|
||||
* the network by searching for routers in a particular keyspace.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseFindNearestMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseFindNearestMessage.class);
|
||||
public final static int MESSAGE_TYPE = 4;
|
||||
private Hash _key;
|
||||
private Hash _from;
|
||||
|
||||
public DatabaseFindNearestMessage() {
|
||||
setSearchKey(null);
|
||||
setFromHash(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key being searched for
|
||||
*/
|
||||
public Hash getSearchKey() { return _key; }
|
||||
public void setSearchKey(Hash key) { _key = key; }
|
||||
|
||||
/**
|
||||
* Contains the SHA256 Hash of the RouterIdentity sending the message
|
||||
*/
|
||||
public Hash getFromHash() { return _from; }
|
||||
public void setFromHash(Hash from) { _from = from; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
_from = new Hash();
|
||||
_from.readBytes(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_key == null) || (_from == null) ) throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
_from.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getSearchKey()) +
|
||||
DataHelper.hashCode(getFromHash());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseFindNearestMessage) ) {
|
||||
DatabaseFindNearestMessage msg = (DatabaseFindNearestMessage)object;
|
||||
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
|
||||
DataHelper.eq(getFromHash(),msg.getFromHash());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseFindNearestMessage: ");
|
||||
buf.append("\n\tSearch Key: ").append(getSearchKey());
|
||||
buf.append("\n\tFrom: ").append(getFromHash());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
165
router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
Normal file
165
router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router to search for a
|
||||
* key in the network database.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseLookupMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseLookupMessage.class);
|
||||
public final static int MESSAGE_TYPE = 2;
|
||||
private Hash _key;
|
||||
private RouterInfo _from;
|
||||
private TunnelId _replyTunnel;
|
||||
private Set _dontIncludePeers;
|
||||
|
||||
public DatabaseLookupMessage() {
|
||||
setSearchKey(null);
|
||||
setFrom(null);
|
||||
setDontIncludePeers(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key being searched for
|
||||
*/
|
||||
public Hash getSearchKey() { return _key; }
|
||||
public void setSearchKey(Hash key) { _key = key; }
|
||||
|
||||
/**
|
||||
* Contains the current router info of the router who requested this lookup
|
||||
*
|
||||
*/
|
||||
public RouterInfo getFrom() { return _from; }
|
||||
public void setFrom(RouterInfo from) { _from = from; }
|
||||
|
||||
/**
|
||||
* Contains the tunnel ID a reply should be sent to
|
||||
*
|
||||
*/
|
||||
public TunnelId getReplyTunnel() { return _replyTunnel; }
|
||||
public void setReplyTunnel(TunnelId replyTunnel) { _replyTunnel = replyTunnel; }
|
||||
|
||||
/**
|
||||
* Set of peers that a lookup reply should NOT include
|
||||
*
|
||||
* @return Set of Hash objects, each of which is the H(routerIdentity) to skip
|
||||
*/
|
||||
public Set getDontIncludePeers() { return _dontIncludePeers; }
|
||||
public void setDontIncludePeers(Set peers) {
|
||||
if (peers != null)
|
||||
_dontIncludePeers = new HashSet(peers);
|
||||
else
|
||||
_dontIncludePeers = null;
|
||||
}
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
_from = new RouterInfo();
|
||||
_from.readBytes(in);
|
||||
boolean tunnelSpecified = DataHelper.readBoolean(in).booleanValue();
|
||||
if (tunnelSpecified) {
|
||||
_replyTunnel = new TunnelId();
|
||||
_replyTunnel.readBytes(in);
|
||||
}
|
||||
int numPeers = (int)DataHelper.readLong(in, 2);
|
||||
if ( (numPeers < 0) || (numPeers >= (1<<16) ) )
|
||||
throw new DataFormatException("Invalid number of peers - " + numPeers);
|
||||
Set peers = new HashSet(numPeers);
|
||||
for (int i = 0; i < numPeers; i++) {
|
||||
Hash peer = new Hash();
|
||||
peer.readBytes(in);
|
||||
peers.add(peer);
|
||||
}
|
||||
_dontIncludePeers = peers;
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if (_key == null) throw new I2NPMessageException("Key being searched for not specified");
|
||||
if (_from == null) throw new I2NPMessageException("From address not specified");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
_from.writeBytes(os);
|
||||
if (_replyTunnel != null) {
|
||||
DataHelper.writeBoolean(os, Boolean.TRUE);
|
||||
_replyTunnel.writeBytes(os);
|
||||
} else {
|
||||
DataHelper.writeBoolean(os, Boolean.FALSE);
|
||||
}
|
||||
if ( (_dontIncludePeers == null) || (_dontIncludePeers.size() <= 0) ) {
|
||||
DataHelper.writeLong(os, 2, 0);
|
||||
} else {
|
||||
DataHelper.writeLong(os, 2, _dontIncludePeers.size());
|
||||
for (Iterator iter = _dontIncludePeers.iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
peer.writeBytes(os);
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getSearchKey()) +
|
||||
DataHelper.hashCode(getFrom()) +
|
||||
DataHelper.hashCode(getReplyTunnel()) +
|
||||
DataHelper.hashCode(_dontIncludePeers);
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseLookupMessage) ) {
|
||||
DatabaseLookupMessage msg = (DatabaseLookupMessage)object;
|
||||
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
|
||||
DataHelper.eq(getFrom(),msg.getFrom()) &&
|
||||
DataHelper.eq(getReplyTunnel(),msg.getReplyTunnel()) &&
|
||||
DataHelper.eq(_dontIncludePeers,msg.getDontIncludePeers());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseLookupMessage: ");
|
||||
buf.append("\n\tSearch Key: ").append(getSearchKey());
|
||||
buf.append("\n\tFrom: ").append(getFrom());
|
||||
buf.append("\n\tReply Tunnel: ").append(getReplyTunnel());
|
||||
buf.append("\n\tDont Include Peers: ").append(getDontIncludePeers());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router in response to a
|
||||
* search (DatabaseFindNearest or DatabaseLookup) when it doesn't have the value,
|
||||
* specifying what routers it would search.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseSearchReplyMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseSearchReplyMessage.class);
|
||||
public final static int MESSAGE_TYPE = 3;
|
||||
private Hash _key;
|
||||
private List _routerInfoStructures;
|
||||
private Hash _from;
|
||||
|
||||
public DatabaseSearchReplyMessage() {
|
||||
setSearchKey(null);
|
||||
_routerInfoStructures = new ArrayList();
|
||||
setFromHash(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key being searched for
|
||||
*/
|
||||
public Hash getSearchKey() { return _key; }
|
||||
public void setSearchKey(Hash key) { _key = key; }
|
||||
|
||||
public int getNumReplies() { return _routerInfoStructures.size(); }
|
||||
public RouterInfo getReply(int index) { return (RouterInfo)_routerInfoStructures.get(index); }
|
||||
public void addReply(RouterInfo info) { _routerInfoStructures.add(info); }
|
||||
public void addReplies(Collection replies) { _routerInfoStructures.addAll(replies); }
|
||||
|
||||
public Hash getFromHash() { return _from; }
|
||||
public void setFromHash(Hash from) { _from = from; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
|
||||
int compressedLength = (int)DataHelper.readLong(in, 2);
|
||||
byte compressedData[] = new byte[compressedLength];
|
||||
int read = DataHelper.read(in, compressedData);
|
||||
if (read != compressedLength)
|
||||
throw new IOException("Not enough data to decompress");
|
||||
byte decompressedData[] = DataHelper.decompress(compressedData);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(decompressedData);
|
||||
int num = (int)DataHelper.readLong(bais, 1);
|
||||
_routerInfoStructures.clear();
|
||||
for (int i = 0; i < num; i++) {
|
||||
RouterInfo info = new RouterInfo();
|
||||
info.readBytes(bais);
|
||||
addReply(info);
|
||||
}
|
||||
|
||||
_from = new Hash();
|
||||
_from.readBytes(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if (_key == null)
|
||||
throw new I2NPMessageException("Key in reply to not specified");
|
||||
if (_routerInfoStructures == null)
|
||||
throw new I2NPMessageException("RouterInfo replies are null");
|
||||
if (_routerInfoStructures.size() <= 0)
|
||||
throw new I2NPMessageException("No replies specified in SearchReply! Always include oneself!");
|
||||
if (_from == null)
|
||||
throw new I2NPMessageException("No 'from' address specified!");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
DataHelper.writeLong(baos, 1, _routerInfoStructures.size());
|
||||
for (int i = 0; i < getNumReplies(); i++) {
|
||||
RouterInfo info = getReply(i);
|
||||
info.writeBytes(baos);
|
||||
}
|
||||
|
||||
byte compressed[] = DataHelper.compress(baos.toByteArray());
|
||||
DataHelper.writeLong(os, 2, compressed.length);
|
||||
os.write(compressed);
|
||||
_from.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseSearchReplyMessage) ) {
|
||||
DatabaseSearchReplyMessage msg = (DatabaseSearchReplyMessage)object;
|
||||
return DataHelper.eq(getSearchKey(),msg.getSearchKey()) &&
|
||||
DataHelper.eq(getFromHash(),msg.getFromHash()) &&
|
||||
DataHelper.eq(_routerInfoStructures,msg._routerInfoStructures);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getSearchKey()) +
|
||||
DataHelper.hashCode(getFromHash()) +
|
||||
DataHelper.hashCode(_routerInfoStructures);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseSearchReplyMessage: ");
|
||||
buf.append("\n\tSearch Key: ").append(getSearchKey());
|
||||
buf.append("\n\tReplies: # = ").append(getNumReplies());
|
||||
for (int i = 0; i < getNumReplies(); i++) {
|
||||
buf.append("\n\t\tReply [").append(i).append("]: ").append(getReply(i));
|
||||
}
|
||||
buf.append("\n\tFrom: ").append(getFromHash());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
170
router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
Normal file
170
router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router to test the network
|
||||
* database reachability, as well as the reply message sent back.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DatabaseStoreMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DatabaseStoreMessage.class);
|
||||
public final static int MESSAGE_TYPE = 1;
|
||||
private Hash _key;
|
||||
private int _type;
|
||||
private LeaseSet _leaseSet;
|
||||
private RouterInfo _info;
|
||||
|
||||
public final static int KEY_TYPE_ROUTERINFO = 0;
|
||||
public final static int KEY_TYPE_LEASESET = 1;
|
||||
|
||||
public DatabaseStoreMessage() {
|
||||
setValueType(-1);
|
||||
setKey(null);
|
||||
setLeaseSet(null);
|
||||
setRouterInfo(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the key in the network database being stored
|
||||
*
|
||||
*/
|
||||
public Hash getKey() { return _key; }
|
||||
public void setKey(Hash key) { _key = key; }
|
||||
|
||||
/**
|
||||
* Defines the router info value in the network database being stored
|
||||
*
|
||||
*/
|
||||
public RouterInfo getRouterInfo() { return _info; }
|
||||
public void setRouterInfo(RouterInfo routerInfo) {
|
||||
_info = routerInfo;
|
||||
if (_info != null)
|
||||
setValueType(KEY_TYPE_ROUTERINFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the lease set value in the network database being stored
|
||||
*
|
||||
*/
|
||||
public LeaseSet getLeaseSet() { return _leaseSet; }
|
||||
public void setLeaseSet(LeaseSet leaseSet) {
|
||||
_leaseSet = leaseSet;
|
||||
if (_leaseSet != null)
|
||||
setValueType(KEY_TYPE_LEASESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines type of key being stored in the network database -
|
||||
* either KEY_TYPE_ROUTERINFO or KEY_TYPE_LEASESET
|
||||
*
|
||||
*/
|
||||
public int getValueType() { return _type; }
|
||||
public void setValueType(int type) { _type = type; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_key = new Hash();
|
||||
_key.readBytes(in);
|
||||
_log.debug("Hash read: " + _key.toBase64());
|
||||
_type = (int)DataHelper.readLong(in, 1);
|
||||
if (_type == KEY_TYPE_LEASESET) {
|
||||
_leaseSet = new LeaseSet();
|
||||
_leaseSet.readBytes(in);
|
||||
} else if (_type == KEY_TYPE_ROUTERINFO) {
|
||||
_info = new RouterInfo();
|
||||
int compressedSize = (int)DataHelper.readLong(in, 2);
|
||||
byte compressed[] = new byte[compressedSize];
|
||||
int read = DataHelper.read(in, compressed);
|
||||
if (read != compressedSize)
|
||||
throw new I2NPMessageException("Invalid compressed data size");
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(DataHelper.decompress(compressed));
|
||||
_info.readBytes(bais);
|
||||
} else {
|
||||
throw new I2NPMessageException("Invalid type of key read from the structure - " + _type);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if (_key == null) throw new I2NPMessageException("Invalid key");
|
||||
if ( (_type != KEY_TYPE_LEASESET) && (_type != KEY_TYPE_ROUTERINFO) ) throw new I2NPMessageException("Invalid key type");
|
||||
if ( (_type == KEY_TYPE_LEASESET) && (_leaseSet == null) ) throw new I2NPMessageException("Missing lease set");
|
||||
if ( (_type == KEY_TYPE_ROUTERINFO) && (_info == null) ) throw new I2NPMessageException("Missing router info");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(256);
|
||||
try {
|
||||
_key.writeBytes(os);
|
||||
DataHelper.writeLong(os, 1, _type);
|
||||
if (_type == KEY_TYPE_LEASESET) {
|
||||
_leaseSet.writeBytes(os);
|
||||
} else if (_type == KEY_TYPE_ROUTERINFO) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
|
||||
_info.writeBytes(baos);
|
||||
byte uncompressed[] = baos.toByteArray();
|
||||
byte compressed[] = DataHelper.compress(uncompressed);
|
||||
DataHelper.writeLong(os, 2, compressed.length);
|
||||
os.write(compressed);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getKey()) +
|
||||
DataHelper.hashCode(getLeaseSet()) +
|
||||
DataHelper.hashCode(getRouterInfo()) +
|
||||
getValueType();
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DatabaseStoreMessage) ) {
|
||||
DatabaseStoreMessage msg = (DatabaseStoreMessage)object;
|
||||
return DataHelper.eq(getKey(),msg.getKey()) &&
|
||||
DataHelper.eq(getLeaseSet(),msg.getLeaseSet()) &&
|
||||
DataHelper.eq(getRouterInfo(),msg.getRouterInfo()) &&
|
||||
DataHelper.eq(getValueType(),msg.getValueType());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DatabaseStoreMessage: ");
|
||||
buf.append("\n\tExpiration: ").append(getMessageExpiration());
|
||||
buf.append("\n\tUnique ID: ").append(getUniqueId());
|
||||
buf.append("\n\tKey: ").append(getKey());
|
||||
buf.append("\n\tValue Type: ").append(getValueType());
|
||||
buf.append("\n\tRouter Info: ").append(getRouterInfo());
|
||||
buf.append("\n\tLease Set: ").append(getLeaseSet());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
274
router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
Normal file
274
router/java/src/net/i2p/data/i2np/DeliveryInstructions.java
Normal file
@@ -0,0 +1,274 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
|
||||
/**
|
||||
* Contains the delivery instructions
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DeliveryInstructions extends DataStructureImpl {
|
||||
private final static Log _log = new Log(DeliveryInstructions.class);
|
||||
private boolean _encrypted;
|
||||
private SessionKey _encryptionKey;
|
||||
private int _deliveryMode;
|
||||
public final static int DELIVERY_MODE_LOCAL = 0;
|
||||
public final static int DELIVERY_MODE_DESTINATION = 1;
|
||||
public final static int DELIVERY_MODE_ROUTER = 2;
|
||||
public final static int DELIVERY_MODE_TUNNEL = 3;
|
||||
private Hash _destinationHash;
|
||||
private Hash _routerHash;
|
||||
private TunnelId _tunnelId;
|
||||
private boolean _delayRequested;
|
||||
private long _delaySeconds;
|
||||
|
||||
private final static int FLAG_MODE_LOCAL = 0;
|
||||
private final static int FLAG_MODE_DESTINATION = 1;
|
||||
private final static int FLAG_MODE_ROUTER = 2;
|
||||
private final static int FLAG_MODE_TUNNEL = 3;
|
||||
|
||||
private final static long FLAG_ENCRYPTED = 128;
|
||||
private final static long FLAG_MODE = 96;
|
||||
private final static long FLAG_DELAY = 16;
|
||||
|
||||
public DeliveryInstructions() {
|
||||
setEncrypted(false);
|
||||
setEncryptionKey(null);
|
||||
setDeliveryMode(-1);
|
||||
setDestination(null);
|
||||
setRouter(null);
|
||||
setTunnelId(null);
|
||||
setDelayRequested(false);
|
||||
setDelaySeconds(0);
|
||||
}
|
||||
|
||||
public boolean getEncrypted() { return _encrypted; }
|
||||
public void setEncrypted(boolean encrypted) { _encrypted = encrypted; }
|
||||
public SessionKey getEncryptionKey() { return _encryptionKey; }
|
||||
public void setEncryptionKey(SessionKey key) { _encryptionKey = key; }
|
||||
public int getDeliveryMode() { return _deliveryMode; }
|
||||
public void setDeliveryMode(int mode) { _deliveryMode = mode; }
|
||||
public Hash getDestination() { return _destinationHash; }
|
||||
public void setDestination(Hash dest) { _destinationHash = dest; }
|
||||
public Hash getRouter() { return _routerHash; }
|
||||
public void setRouter(Hash router) { _routerHash = router; }
|
||||
public TunnelId getTunnelId() { return _tunnelId; }
|
||||
public void setTunnelId(TunnelId id) { _tunnelId = id; }
|
||||
public boolean getDelayRequested() { return _delayRequested; }
|
||||
public void setDelayRequested(boolean req) { _delayRequested = req; }
|
||||
public long getDelaySeconds() { return _delaySeconds; }
|
||||
public void setDelaySeconds(long seconds) { _delaySeconds = seconds; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
long flags = DataHelper.readLong(in, 1);
|
||||
_log.debug("Read flags: " + flags + " mode: " + flagMode(flags));
|
||||
|
||||
if (flagEncrypted(flags)) {
|
||||
SessionKey k = new SessionKey();
|
||||
k.readBytes(in);
|
||||
setEncryptionKey(k);
|
||||
setEncrypted(true);
|
||||
} else {
|
||||
setEncrypted(false);
|
||||
}
|
||||
|
||||
setDeliveryMode(flagMode(flags));
|
||||
switch (flagMode(flags)) {
|
||||
case FLAG_MODE_LOCAL:
|
||||
break;
|
||||
case FLAG_MODE_DESTINATION:
|
||||
Hash destHash = new Hash();
|
||||
destHash.readBytes(in);
|
||||
setDestination(destHash);
|
||||
break;
|
||||
case FLAG_MODE_ROUTER:
|
||||
Hash routerHash = new Hash();
|
||||
routerHash.readBytes(in);
|
||||
setRouter(routerHash);
|
||||
break;
|
||||
case FLAG_MODE_TUNNEL:
|
||||
Hash tunnelRouterHash = new Hash();
|
||||
tunnelRouterHash.readBytes(in);
|
||||
setRouter(tunnelRouterHash);
|
||||
TunnelId id = new TunnelId();
|
||||
id.readBytes(in);
|
||||
setTunnelId(id);
|
||||
break;
|
||||
}
|
||||
|
||||
if (flagDelay(flags)) {
|
||||
long delay = DataHelper.readLong(in, 4);
|
||||
setDelayRequested(true);
|
||||
setDelaySeconds(delay);
|
||||
} else {
|
||||
setDelayRequested(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean flagEncrypted(long flags) {
|
||||
return (0 != (flags & FLAG_ENCRYPTED));
|
||||
}
|
||||
|
||||
private int flagMode(long flags) {
|
||||
long v = flags & FLAG_MODE;
|
||||
v >>>= 5;
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
private boolean flagDelay(long flags) {
|
||||
return (0 != (flags & FLAG_DELAY));
|
||||
}
|
||||
|
||||
private long getFlags() {
|
||||
long val = 0L;
|
||||
if (getEncrypted())
|
||||
val = val | FLAG_ENCRYPTED;
|
||||
long fmode = 0;
|
||||
switch (getDeliveryMode()) {
|
||||
case FLAG_MODE_LOCAL:
|
||||
break;
|
||||
case FLAG_MODE_DESTINATION:
|
||||
fmode = FLAG_MODE_DESTINATION << 5;
|
||||
break;
|
||||
case FLAG_MODE_ROUTER:
|
||||
fmode = FLAG_MODE_ROUTER << 5;
|
||||
break;
|
||||
case FLAG_MODE_TUNNEL:
|
||||
fmode = FLAG_MODE_TUNNEL << 5;
|
||||
break;
|
||||
}
|
||||
val = val | fmode;
|
||||
if (getDelayRequested())
|
||||
val = val | FLAG_DELAY;
|
||||
_log.debug("getFlags() = " + val);
|
||||
return val;
|
||||
}
|
||||
|
||||
private byte[] getAdditionalInfo() throws DataFormatException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
|
||||
try {
|
||||
if (getEncrypted()) {
|
||||
if (_encryptionKey == null) throw new DataFormatException("Encryption key is not set");
|
||||
_encryptionKey.writeBytes(baos);
|
||||
_log.debug("IsEncrypted");
|
||||
} else {
|
||||
_log.debug("Is NOT Encrypted");
|
||||
}
|
||||
switch (getDeliveryMode()) {
|
||||
case FLAG_MODE_LOCAL:
|
||||
_log.debug("mode = local");
|
||||
break;
|
||||
case FLAG_MODE_DESTINATION:
|
||||
if (_destinationHash == null) throw new DataFormatException("Destination hash is not set");
|
||||
_destinationHash.writeBytes(baos);
|
||||
_log.debug("mode = destination, hash = " + _destinationHash);
|
||||
break;
|
||||
case FLAG_MODE_ROUTER:
|
||||
if (_routerHash == null) throw new DataFormatException("Router hash is not set");
|
||||
_routerHash.writeBytes(baos);
|
||||
_log.debug("mode = router, routerHash = " + _routerHash);
|
||||
break;
|
||||
case FLAG_MODE_TUNNEL:
|
||||
if ( (_routerHash == null) || (_tunnelId == null) ) throw new DataFormatException("Router hash or tunnel ID is not set");
|
||||
_routerHash.writeBytes(baos);
|
||||
_tunnelId.writeBytes(baos);
|
||||
_log.debug("mode = tunnel, tunnelId = " + _tunnelId.getTunnelId() + ", routerHash = " + _routerHash);
|
||||
break;
|
||||
}
|
||||
if (getDelayRequested()) {
|
||||
_log.debug("delay requested: " + getDelaySeconds());
|
||||
DataHelper.writeLong(baos, 4, getDelaySeconds());
|
||||
} else {
|
||||
_log.debug("delay NOT requested");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new DataFormatException("Unable to write out additional info", ioe);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ( (_deliveryMode < 0) || (_deliveryMode > FLAG_MODE_TUNNEL) ) throw new DataFormatException("Invalid data: mode = " + _deliveryMode);
|
||||
long flags = getFlags();
|
||||
_log.debug("Write flags: " + flags + " mode: " + getDeliveryMode() + " =?= " + flagMode(flags));
|
||||
byte additionalInfo[] = getAdditionalInfo();
|
||||
DataHelper.writeLong(out, 1, flags);
|
||||
if (additionalInfo != null) {
|
||||
out.write(additionalInfo);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof DeliveryInstructions))
|
||||
return false;
|
||||
DeliveryInstructions instr = (DeliveryInstructions)obj;
|
||||
return (getDelayRequested() == instr.getDelayRequested()) &&
|
||||
(getDelaySeconds() == instr.getDelaySeconds()) &&
|
||||
(getDeliveryMode() == instr.getDeliveryMode()) &&
|
||||
(getEncrypted() == instr.getEncrypted()) &&
|
||||
DataHelper.eq(getDestination(), instr.getDestination()) &&
|
||||
DataHelper.eq(getEncryptionKey(), instr.getEncryptionKey()) &&
|
||||
DataHelper.eq(getRouter(), instr.getRouter()) &&
|
||||
DataHelper.eq(getTunnelId(), instr.getTunnelId());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (int)getDelaySeconds() +
|
||||
getDeliveryMode() +
|
||||
DataHelper.hashCode(getDestination()) +
|
||||
DataHelper.hashCode(getEncryptionKey()) +
|
||||
DataHelper.hashCode(getRouter()) +
|
||||
DataHelper.hashCode(getTunnelId());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[DeliveryInstructions: ");
|
||||
buf.append("\n\tDelivery mode: ");
|
||||
switch (getDeliveryMode()) {
|
||||
case DELIVERY_MODE_LOCAL:
|
||||
buf.append("local");
|
||||
break;
|
||||
case DELIVERY_MODE_DESTINATION:
|
||||
buf.append("destination");
|
||||
break;
|
||||
case DELIVERY_MODE_ROUTER:
|
||||
buf.append("router");
|
||||
break;
|
||||
case DELIVERY_MODE_TUNNEL:
|
||||
buf.append("tunnel");
|
||||
break;
|
||||
}
|
||||
buf.append("\n\tDelay requested: ").append(getDelayRequested());
|
||||
buf.append("\n\tDelay seconds: ").append(getDelaySeconds());
|
||||
buf.append("\n\tDestination: ").append(getDestination());
|
||||
buf.append("\n\tEncrypted: ").append(getEncrypted());
|
||||
buf.append("\n\tEncryption key: ").append(getEncryptionKey());
|
||||
buf.append("\n\tRouter: ").append(getRouter());
|
||||
buf.append("\n\tTunnelId: ").append(getTunnelId());
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
91
router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java
Normal file
91
router/java/src/net/i2p/data/i2np/DeliveryStatusMessage.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message sent back in reply to a message when requested, containing
|
||||
* the private ack id.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class DeliveryStatusMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(DeliveryStatusMessage.class);
|
||||
public final static int MESSAGE_TYPE = 10;
|
||||
private long _id;
|
||||
private Date _arrival;
|
||||
|
||||
public DeliveryStatusMessage() {
|
||||
setMessageId(-1);
|
||||
setArrival(null);
|
||||
}
|
||||
|
||||
public long getMessageId() { return _id; }
|
||||
public void setMessageId(long id) { _id = id; }
|
||||
|
||||
public Date getArrival() { return _arrival; }
|
||||
public void setArrival(Date arrival) { _arrival = arrival; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_id = DataHelper.readLong(in, 4);
|
||||
_arrival = DataHelper.readDate(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_id < 0) || (_arrival == null) ) throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, _id);
|
||||
DataHelper.writeDate(os, _arrival);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return (int)getMessageId() +
|
||||
DataHelper.hashCode(getArrival());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof DeliveryStatusMessage) ) {
|
||||
DeliveryStatusMessage msg = (DeliveryStatusMessage)object;
|
||||
return DataHelper.eq(getMessageId(),msg.getMessageId()) &&
|
||||
DataHelper.eq(getArrival(),msg.getArrival());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[DeliveryStatusMessage: ");
|
||||
buf.append("\n\tMessage ID: ").append(getMessageId());
|
||||
buf.append("\n\tArrival: ").append(getArrival());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
62
router/java/src/net/i2p/data/i2np/EndPointPrivateKey.java
Normal file
62
router/java/src/net/i2p/data/i2np/EndPointPrivateKey.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PrivateKey;
|
||||
|
||||
/**
|
||||
* Contains the private key which matches the EndPointPublicKey which, in turn,
|
||||
* is published on the LeaseSet and used to encrypt messages to the router to
|
||||
* which a Destination is currently connected.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class EndPointPrivateKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(EndPointPrivateKey.class);
|
||||
private PrivateKey _key;
|
||||
|
||||
public EndPointPrivateKey() { setKey(null); }
|
||||
|
||||
public PrivateKey getKey() { return _key; }
|
||||
public void setKey(PrivateKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new PrivateKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof EndPointPublicKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((EndPointPublicKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[EndPointPrivateKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
||||
62
router/java/src/net/i2p/data/i2np/EndPointPublicKey.java
Normal file
62
router/java/src/net/i2p/data/i2np/EndPointPublicKey.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.PublicKey;
|
||||
|
||||
/**
|
||||
* Contains the public key which matches the EndPointPrivateKey. This is
|
||||
* published on the LeaseSet and used to encrypt messages to the router to
|
||||
* which a Destination is currently connected.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class EndPointPublicKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(EndPointPublicKey.class);
|
||||
private PublicKey _key;
|
||||
|
||||
public EndPointPublicKey() { setKey(null); }
|
||||
|
||||
public PublicKey getKey() { return _key; }
|
||||
public void setKey(PublicKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new PublicKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof EndPointPublicKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((EndPointPublicKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[EndPointPublicKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
||||
171
router/java/src/net/i2p/data/i2np/GarlicClove.java
Normal file
171
router/java/src/net/i2p/data/i2np/GarlicClove.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Certificate;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Contains one deliverable message encrypted to a router along with instructions
|
||||
* and a certificate 'paying for' the delivery.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class GarlicClove extends DataStructureImpl {
|
||||
private final static Log _log = new Log(GarlicClove.class);
|
||||
private DeliveryInstructions _instructions;
|
||||
private I2NPMessage _msg;
|
||||
private long _cloveId;
|
||||
private Date _expiration;
|
||||
private Certificate _certificate;
|
||||
private int _replyAction;
|
||||
private SourceRouteBlock _sourceRouteBlock;
|
||||
|
||||
/** No action requested with the source route block */
|
||||
public final static int ACTION_NONE = 0;
|
||||
/**
|
||||
* A DeliveryStatusMessage is requested with the source route block using
|
||||
* the cloveId as the id received
|
||||
*
|
||||
*/
|
||||
public final static int ACTION_STATUS = 1;
|
||||
/**
|
||||
* No DeliveryStatusMessage is requested, but the source route block is
|
||||
* included for message specific replies
|
||||
*
|
||||
*/
|
||||
public final static int ACTION_MESSAGE_SPECIFIC = 2;
|
||||
|
||||
public GarlicClove() {
|
||||
setInstructions(null);
|
||||
setData(null);
|
||||
setCloveId(-1);
|
||||
setExpiration(null);
|
||||
setCertificate(null);
|
||||
setSourceRouteBlockAction(ACTION_NONE);
|
||||
setSourceRouteBlock(null);
|
||||
}
|
||||
|
||||
public DeliveryInstructions getInstructions() { return _instructions; }
|
||||
public void setInstructions(DeliveryInstructions instr) { _instructions = instr; }
|
||||
public I2NPMessage getData() { return _msg; }
|
||||
public void setData(I2NPMessage msg) { _msg = msg; }
|
||||
public long getCloveId() { return _cloveId; }
|
||||
public void setCloveId(long id) { _cloveId = id; }
|
||||
public Date getExpiration() { return _expiration; }
|
||||
public void setExpiration(Date exp) { _expiration = exp; }
|
||||
public Certificate getCertificate() { return _certificate; }
|
||||
public void setCertificate(Certificate cert) { _certificate = cert; }
|
||||
public int getSourceRouteBlockAction() { return _replyAction; }
|
||||
public void setSourceRouteBlockAction(int action) { _replyAction = action; }
|
||||
public SourceRouteBlock getSourceRouteBlock() { return _sourceRouteBlock; }
|
||||
public void setSourceRouteBlock(SourceRouteBlock block) { _sourceRouteBlock = block; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_instructions = new DeliveryInstructions();
|
||||
_instructions.readBytes(in);
|
||||
_log.debug("Read instructions: " + _instructions);
|
||||
try {
|
||||
_msg = new I2NPMessageHandler().readMessage(in);
|
||||
} catch (I2NPMessageException ime) {
|
||||
throw new DataFormatException("Unable to read the message from a garlic clove", ime);
|
||||
}
|
||||
_cloveId = DataHelper.readLong(in, 4);
|
||||
_expiration = DataHelper.readDate(in);
|
||||
_log.debug("CloveID read: " + _cloveId + " expiration read: " + _expiration);
|
||||
_certificate = new Certificate();
|
||||
_certificate.readBytes(in);
|
||||
_log.debug("Read cert: " + _certificate);
|
||||
int replyStyle = (int)DataHelper.readLong(in, 1);
|
||||
setSourceRouteBlockAction(replyStyle);
|
||||
if (replyStyle != ACTION_NONE) {
|
||||
_sourceRouteBlock = new SourceRouteBlock();
|
||||
_sourceRouteBlock.readBytes(in);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
StringBuffer error = new StringBuffer();
|
||||
if (_instructions == null)
|
||||
error.append("No instructions ");
|
||||
if (_msg == null)
|
||||
error.append("No message ");
|
||||
if (_cloveId < 0)
|
||||
error.append("CloveID < 0 [").append(_cloveId).append("] ");
|
||||
if (_expiration == null)
|
||||
error.append("Expiration is null ");
|
||||
if (_certificate == null)
|
||||
error.append("Certificate is null ");
|
||||
if (_replyAction < 0)
|
||||
error.append("Reply action is < 0 [").append(_replyAction).append("] ");;
|
||||
if (error.length() > 0)
|
||||
throw new DataFormatException(error.toString());
|
||||
if ( (_replyAction != 0) && (_sourceRouteBlock == null) )
|
||||
throw new DataFormatException("Source route block must be specified for non-null action");
|
||||
_instructions.writeBytes(out);
|
||||
|
||||
_log.debug("Wrote instructions: " + _instructions);
|
||||
_msg.writeBytes(out);
|
||||
DataHelper.writeLong(out, 4, _cloveId);
|
||||
DataHelper.writeDate(out, _expiration);
|
||||
_log.debug("CloveID written: " + _cloveId + " expiration written: " + _expiration);
|
||||
_certificate.writeBytes(out);
|
||||
_log.debug("Written cert: " + _certificate);
|
||||
DataHelper.writeLong(out, 1, _replyAction);
|
||||
if ( (_replyAction != 0) && (_sourceRouteBlock != null) )
|
||||
_sourceRouteBlock.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof GarlicClove))
|
||||
return false;
|
||||
GarlicClove clove = (GarlicClove)obj;
|
||||
return DataHelper.eq(getCertificate(), clove.getCertificate()) &&
|
||||
DataHelper.eq(getCloveId(), clove.getCloveId()) &&
|
||||
DataHelper.eq(getData(), clove.getData()) &&
|
||||
DataHelper.eq(getExpiration(), clove.getExpiration()) &&
|
||||
DataHelper.eq(getInstructions(), clove.getInstructions()) &&
|
||||
DataHelper.eq(getSourceRouteBlock(), clove.getSourceRouteBlock()) &&
|
||||
(getSourceRouteBlockAction() == clove.getSourceRouteBlockAction());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getCertificate()) +
|
||||
(int)getCloveId() +
|
||||
DataHelper.hashCode(getData()) +
|
||||
DataHelper.hashCode(getExpiration()) +
|
||||
DataHelper.hashCode(getInstructions()) +
|
||||
DataHelper.hashCode(getSourceRouteBlock()) +
|
||||
getSourceRouteBlockAction();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[GarlicClove: ");
|
||||
buf.append("\n\tInstructions: ").append(getInstructions());
|
||||
buf.append("\n\tCertificate: ").append(getCertificate());
|
||||
buf.append("\n\tClove ID: ").append(getCloveId());
|
||||
buf.append("\n\tExpiration: ").append(getExpiration());
|
||||
buf.append("\n\tSource route style: ").append(getSourceRouteBlockAction());
|
||||
buf.append("\n\tSource route block: ").append(getSourceRouteBlock());
|
||||
buf.append("\n\tData: ").append(getData());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
84
router/java/src/net/i2p/data/i2np/GarlicMessage.java
Normal file
84
router/java/src/net/i2p/data/i2np/GarlicMessage.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the wrapped garlic message
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class GarlicMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(GarlicMessage.class);
|
||||
public final static int MESSAGE_TYPE = 11;
|
||||
private byte[] _data;
|
||||
|
||||
public GarlicMessage() {
|
||||
setData(null);
|
||||
}
|
||||
|
||||
public byte[] getData() { return _data; }
|
||||
public void setData(byte[] data) { _data = data; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
long len = DataHelper.readLong(in, 4);
|
||||
_data = new byte[(int)len];
|
||||
int read = read(in, _data);
|
||||
if (read != len)
|
||||
throw new I2NPMessageException("Incorrect size read");
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_data == null) || (_data.length <= 0) ) throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
DataHelper.writeLong(os, 4, _data.length);
|
||||
os.write(_data);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getData());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof GarlicMessage) ) {
|
||||
GarlicMessage msg = (GarlicMessage)object;
|
||||
return DataHelper.eq(getData(),msg.getData());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[GarlicMessage: ");
|
||||
buf.append("\n\tData length: ").append(getData().length).append(" bytes");
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
51
router/java/src/net/i2p/data/i2np/I2NPMessage.java
Normal file
51
router/java/src/net/i2p/data/i2np/I2NPMessage.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.DataStructure;
|
||||
|
||||
/**
|
||||
* Base interface for all I2NP messages
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public interface I2NPMessage extends DataStructure {
|
||||
/**
|
||||
* Read the body into the data structures, after the initial type byte, using
|
||||
* the current class's format as defined by the I2NP specification
|
||||
*
|
||||
* @param in stream to read from
|
||||
* @param type I2NP message type
|
||||
* @throws I2NPMessageException if the stream doesn't contain a valid message
|
||||
* that this class can read.
|
||||
* @throws IOException if there is a problem reading from the stream
|
||||
*/
|
||||
public void readBytes(InputStream in, int type) throws I2NPMessageException, IOException;
|
||||
|
||||
/**
|
||||
* Return the unique identifier for this type of I2NP message, as defined in
|
||||
* the I2NP spec
|
||||
*/
|
||||
public int getType();
|
||||
|
||||
/**
|
||||
* Replay resistent message Id
|
||||
*/
|
||||
public long getUniqueId();
|
||||
|
||||
/**
|
||||
* Date after which the message should be dropped (and the associated uniqueId forgotten)
|
||||
*
|
||||
*/
|
||||
public Date getMessageExpiration();
|
||||
}
|
||||
28
router/java/src/net/i2p/data/i2np/I2NPMessageException.java
Normal file
28
router/java/src/net/i2p/data/i2np/I2NPMessageException.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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 net.i2p.I2PException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Represent an error serializing or deserializing an APIMessage
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class I2NPMessageException extends I2PException {
|
||||
private final static Log _log = new Log(I2NPMessageException.class);
|
||||
|
||||
public I2NPMessageException(String message, Throwable parent) {
|
||||
super(message, parent);
|
||||
}
|
||||
public I2NPMessageException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
92
router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
Normal file
92
router/java/src/net/i2p/data/i2np/I2NPMessageHandler.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Handle messages from router to router
|
||||
*
|
||||
*/
|
||||
public class I2NPMessageHandler {
|
||||
private final static Log _log = new Log(I2NPMessageHandler.class);
|
||||
private long _lastReadBegin;
|
||||
private long _lastReadEnd;
|
||||
public I2NPMessageHandler() {}
|
||||
|
||||
/**
|
||||
* Read an I2NPMessage from the stream and return the fully populated object.
|
||||
*
|
||||
* @throws IOException if there is an IO problem reading from the stream
|
||||
* @throws I2NPMessageException if there is a problem handling the particular
|
||||
* message - if it is an unknown type or has improper formatting, etc.
|
||||
*/
|
||||
public I2NPMessage readMessage(InputStream in) throws IOException, I2NPMessageException {
|
||||
try {
|
||||
int type = (int)DataHelper.readLong(in, 1);
|
||||
_lastReadBegin = Clock.getInstance().now();
|
||||
I2NPMessage msg = createMessage(in, type);
|
||||
msg.readBytes(in, type);
|
||||
_lastReadEnd = Clock.getInstance().now();
|
||||
return msg;
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error reading the message", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
public long getLastReadTime() { return _lastReadEnd - _lastReadBegin; }
|
||||
|
||||
/**
|
||||
* Yes, this is fairly ugly, but its the only place it ever happens.
|
||||
*
|
||||
*/
|
||||
private static I2NPMessage createMessage(InputStream in, int type) throws IOException, I2NPMessageException {
|
||||
switch (type) {
|
||||
case DatabaseStoreMessage.MESSAGE_TYPE:
|
||||
return new DatabaseStoreMessage();
|
||||
case DatabaseLookupMessage.MESSAGE_TYPE:
|
||||
return new DatabaseLookupMessage();
|
||||
case DatabaseSearchReplyMessage.MESSAGE_TYPE:
|
||||
return new DatabaseSearchReplyMessage();
|
||||
case DeliveryStatusMessage.MESSAGE_TYPE:
|
||||
return new DeliveryStatusMessage();
|
||||
case GarlicMessage.MESSAGE_TYPE:
|
||||
return new GarlicMessage();
|
||||
case TunnelMessage.MESSAGE_TYPE:
|
||||
return new TunnelMessage();
|
||||
case DataMessage.MESSAGE_TYPE:
|
||||
return new DataMessage();
|
||||
case SourceRouteReplyMessage.MESSAGE_TYPE:
|
||||
return new SourceRouteReplyMessage();
|
||||
case TunnelCreateMessage.MESSAGE_TYPE:
|
||||
return new TunnelCreateMessage();
|
||||
case TunnelCreateStatusMessage.MESSAGE_TYPE:
|
||||
return new TunnelCreateStatusMessage();
|
||||
default:
|
||||
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
I2NPMessage msg = new I2NPMessageHandler().readMessage(new FileInputStream(args[0]));
|
||||
System.out.println(msg);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
104
router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
Normal file
104
router/java/src/net/i2p/data/i2np/I2NPMessageImpl.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Defines the base message implementation.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPMessage {
|
||||
private final static Log _log = new Log(I2NPMessageImpl.class);
|
||||
private Date _expiration;
|
||||
private long _uniqueId;
|
||||
|
||||
public final static long DEFAULT_EXPIRATION_MS = 1*60*1000; // 1 minute by default
|
||||
|
||||
public I2NPMessageImpl() {
|
||||
_expiration = new Date(Clock.getInstance().now() + DEFAULT_EXPIRATION_MS);
|
||||
_uniqueId = RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the payload part of the message (not including the initial
|
||||
* 1 byte type)
|
||||
*
|
||||
*/
|
||||
protected abstract byte[] writeMessage() throws I2NPMessageException, IOException;
|
||||
|
||||
/**
|
||||
* Read the body into the data structures, after the initial type byte and
|
||||
* the uniqueId / expiration, using the current class's format as defined by
|
||||
* the I2NP specification
|
||||
*
|
||||
* @param in stream to read from
|
||||
* @param type I2NP message type
|
||||
* @throws I2NPMessageException if the stream doesn't contain a valid message
|
||||
* that this class can read.
|
||||
* @throws IOException if there is a problem reading from the stream
|
||||
*/
|
||||
protected abstract void readMessage(InputStream in, int type) throws I2NPMessageException, IOException;
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
try {
|
||||
readBytes(in, -1);
|
||||
} catch (I2NPMessageException ime) {
|
||||
throw new DataFormatException("Bad bytes", ime);
|
||||
}
|
||||
}
|
||||
public void readBytes(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
try {
|
||||
if (type < 0)
|
||||
type = (int)DataHelper.readLong(in, 1);
|
||||
_uniqueId = DataHelper.readLong(in, 4);
|
||||
_expiration = DataHelper.readDate(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error reading the message header", dfe);
|
||||
}
|
||||
_log.debug("Reading bytes: type = " + type + " / uniqueId : " + _uniqueId + " / expiration : " + _expiration);
|
||||
readMessage(in, type);
|
||||
}
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
try {
|
||||
DataHelper.writeLong(out, 1, getType());
|
||||
DataHelper.writeLong(out, 4, _uniqueId);
|
||||
DataHelper.writeDate(out, _expiration);
|
||||
_log.debug("Writing bytes: type = " + getType() + " / uniqueId : " + _uniqueId + " / expiration : " + _expiration);
|
||||
byte[] data = writeMessage();
|
||||
out.write(data);
|
||||
} catch (I2NPMessageException ime) {
|
||||
throw new DataFormatException("Error writing out the I2NP message data", ime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replay resistent message Id
|
||||
*/
|
||||
public long getUniqueId() { return _uniqueId; }
|
||||
public void setUniqueId(long id) { _uniqueId = id; }
|
||||
|
||||
/**
|
||||
* Date after which the message should be dropped (and the associated uniqueId forgotten)
|
||||
*
|
||||
*/
|
||||
public Date getMessageExpiration() { return _expiration; }
|
||||
public void setMessageExpiration(Date exp) { _expiration = exp; }
|
||||
}
|
||||
139
router/java/src/net/i2p/data/i2np/I2NPMessageReader.java
Normal file
139
router/java/src/net/i2p/data/i2np/I2NPMessageReader.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* The I2NPMessageReader reads an InputStream (using
|
||||
* {@link I2NPMessageHandler I2NPMessageHandler}) and passes out events to a registered
|
||||
* listener, where events are either messages being received, exceptions being
|
||||
* thrown, or the connection being closed. Routers should use this rather
|
||||
* than read from the stream themselves.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class I2NPMessageReader {
|
||||
private final static Log _log = new Log(I2NPMessageReader.class);
|
||||
private InputStream _stream;
|
||||
private I2NPMessageEventListener _listener;
|
||||
private I2NPMessageReaderRunner _reader;
|
||||
private Thread _readerThread;
|
||||
|
||||
public I2NPMessageReader(InputStream stream, I2NPMessageEventListener lsnr) {
|
||||
this(stream, lsnr, "I2NP Reader");
|
||||
}
|
||||
|
||||
public I2NPMessageReader(InputStream stream, I2NPMessageEventListener lsnr, String name) {
|
||||
_stream = stream;
|
||||
setListener(lsnr);
|
||||
_reader = new I2NPMessageReaderRunner();
|
||||
_readerThread = new I2PThread(_reader);
|
||||
_readerThread.setName(name);
|
||||
_readerThread.setDaemon(true);
|
||||
}
|
||||
|
||||
public void setListener(I2NPMessageEventListener lsnr) { _listener = lsnr; }
|
||||
public I2NPMessageEventListener getListener() { return _listener; }
|
||||
|
||||
/**
|
||||
* Instruct the reader to begin reading messages off the stream
|
||||
*
|
||||
*/
|
||||
public void startReading() { _readerThread.start(); }
|
||||
/**
|
||||
* Have the already started reader pause its reading indefinitely
|
||||
*
|
||||
*/
|
||||
public void pauseReading() { _reader.pauseRunner(); }
|
||||
/**
|
||||
* Resume reading after a pause
|
||||
*
|
||||
*/
|
||||
public void resumeReading() { _reader.resumeRunner(); }
|
||||
/**
|
||||
* Cancel reading.
|
||||
*
|
||||
*/
|
||||
public void stopReading() { _reader.cancelRunner(); }
|
||||
|
||||
/**
|
||||
* Defines the different events the reader produces while reading the stream
|
||||
*
|
||||
*/
|
||||
public static interface I2NPMessageEventListener {
|
||||
/**
|
||||
* Notify the listener that a message has been received from the given
|
||||
* reader
|
||||
*
|
||||
*/
|
||||
public void messageReceived(I2NPMessageReader reader, I2NPMessage message, long msToRead);
|
||||
/**
|
||||
* Notify the listener that an exception was thrown while reading from the given
|
||||
* reader
|
||||
*
|
||||
*/
|
||||
public void readError(I2NPMessageReader reader, Exception error);
|
||||
/**
|
||||
* Notify the listener that the stream the given reader was running off
|
||||
* closed
|
||||
*
|
||||
*/
|
||||
public void disconnected(I2NPMessageReader reader);
|
||||
}
|
||||
|
||||
private class I2NPMessageReaderRunner implements Runnable {
|
||||
private boolean _doRun;
|
||||
private boolean _stayAlive;
|
||||
private I2NPMessageHandler _handler;
|
||||
public I2NPMessageReaderRunner() {
|
||||
_doRun = true;
|
||||
_stayAlive = true;
|
||||
_handler = new I2NPMessageHandler();
|
||||
}
|
||||
public void pauseRunner() { _doRun = false; }
|
||||
public void resumeRunner() { _doRun = true; }
|
||||
public void cancelRunner() {
|
||||
_doRun = false;
|
||||
_stayAlive = false;
|
||||
}
|
||||
public void run() {
|
||||
while (_stayAlive) {
|
||||
while (_doRun) {
|
||||
// do read
|
||||
try {
|
||||
I2NPMessage msg = _handler.readMessage(_stream);
|
||||
if (msg != null) {
|
||||
long msToRead = _handler.getLastReadTime();
|
||||
_listener.messageReceived(I2NPMessageReader.this, msg, msToRead);
|
||||
}
|
||||
} catch (I2NPMessageException ime) {
|
||||
//_log.warn("Error handling message", ime);
|
||||
_listener.readError(I2NPMessageReader.this, ime);
|
||||
_listener.disconnected(I2NPMessageReader.this);
|
||||
cancelRunner();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("IO Error handling message", ioe);
|
||||
_listener.disconnected(I2NPMessageReader.this);
|
||||
cancelRunner();
|
||||
}
|
||||
}
|
||||
if (!_doRun) {
|
||||
// pause .5 secs when we're paused
|
||||
try { Thread.sleep(500); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
// boom bye bye bad bwoy
|
||||
}
|
||||
}
|
||||
}
|
||||
225
router/java/src/net/i2p/data/i2np/SourceRouteBlock.java
Normal file
225
router/java/src/net/i2p/data/i2np/SourceRouteBlock.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Defines a single hop of a source routed message, as usable for building a
|
||||
* SourceRouteReplyMessage
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class SourceRouteBlock extends DataStructureImpl {
|
||||
private final static Log _log = new Log(SourceRouteBlock.class);
|
||||
private Hash _router;
|
||||
private byte[] _data;
|
||||
private SessionKey _key;
|
||||
private byte[] _tag;
|
||||
private DeliveryInstructions _decryptedInstructions;
|
||||
private long _decryptedMessageId;
|
||||
private Certificate _decryptedCertificate;
|
||||
private long _decryptedExpiration;
|
||||
|
||||
public SourceRouteBlock() {
|
||||
setRouter(null);
|
||||
setData(null);
|
||||
setKey(null);
|
||||
setTag((byte[])null);
|
||||
_decryptedInstructions = null;
|
||||
_decryptedMessageId = -1;
|
||||
_decryptedCertificate = null;
|
||||
_decryptedExpiration = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the router through which replies using this source route block must
|
||||
* be sent (as the getData() is encrypted for their eyes only)
|
||||
*
|
||||
*/
|
||||
public Hash getRouter() { return _router; }
|
||||
public void setRouter(Hash router) { _router= router; }
|
||||
/**
|
||||
* Get the encrypted header. After decryption (via ElGamal+AES as defined
|
||||
* in the data structures spec), this array contains:
|
||||
* DeliveryInstructions
|
||||
* 4 byte Integer for a message ID
|
||||
* Certificate
|
||||
* Date of expiration for replies
|
||||
*
|
||||
*/
|
||||
public byte[] getData() { return _data; }
|
||||
private void setData(byte data[]) { _data = data; }
|
||||
/**
|
||||
* Retrieve the session key which may be used in conjunction with the tag
|
||||
* to encrypt a garlic message and send it as a reply to this message.
|
||||
* The encryption would follow scenario 2 of the ElGamal+AES encryption method
|
||||
* defined in the data structures spec.
|
||||
*
|
||||
*/
|
||||
public SessionKey getKey() { return _key; }
|
||||
public void setKey(SessionKey key) { _key = key; }
|
||||
/**
|
||||
* Get the tag made available for use in conjunction with the getKey() to
|
||||
* ElGamal+AES encrypt a garlic message without knowing the public key to
|
||||
* which the message is destined
|
||||
*
|
||||
*/
|
||||
public byte[] getTag() { return _tag; }
|
||||
public void setTag(SessionTag tag) { setTag(tag.getData()); }
|
||||
public void setTag(byte tag[]) {
|
||||
if ( (tag != null) && (tag.length != SessionTag.BYTE_LENGTH) )
|
||||
throw new IllegalArgumentException("Tag must be either null or 32 bytes");
|
||||
_tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* After decryptData, this contains the delivery instructions for this block
|
||||
*/
|
||||
public DeliveryInstructions getDecryptedInstructions() { return _decryptedInstructions; }
|
||||
/**
|
||||
* After decryptData, this contains the message ID to be used with this block
|
||||
*/
|
||||
public long getDecryptedMessageId() { return _decryptedMessageId; }
|
||||
/**
|
||||
* After decryptData, this contains the Certificate 'paying' for the forwarding according to
|
||||
* this block
|
||||
*/
|
||||
public Certificate getDecryptedCertificate() { return _decryptedCertificate; }
|
||||
/**
|
||||
* After decryptData, this contains the date after which this block should not be forwarded
|
||||
*/
|
||||
public long getDecryptedExpiration() { return _decryptedExpiration; }
|
||||
|
||||
/**
|
||||
* Set the raw data with the formatted and encrypted options specified
|
||||
*
|
||||
* @param instructions Where a message bearing this block should be sent
|
||||
* @param messageId ID of the message for this block (not repeatable)
|
||||
* @param expiration date after which this block expires
|
||||
* @param replyThrough Encryption key of the router to whom this block is specified (not
|
||||
* the router specified in the delivery instructions!)
|
||||
*
|
||||
* @throws DataFormatException if the data is invalid or could not be encrypted
|
||||
*/
|
||||
public void setData(DeliveryInstructions instructions, long messageId, Certificate cert, long expiration, PublicKey replyThrough) throws DataFormatException {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
|
||||
|
||||
_decryptedInstructions = instructions;
|
||||
_decryptedMessageId = messageId;
|
||||
_decryptedCertificate = cert;
|
||||
_decryptedExpiration = expiration;
|
||||
|
||||
instructions.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 4, messageId);
|
||||
cert.writeBytes(baos);
|
||||
DataHelper.writeDate(baos, new Date(expiration));
|
||||
|
||||
int paddedSize = 256;
|
||||
SessionKey sessKey = null;
|
||||
SessionTag tag = null;
|
||||
if (instructions.getDelayRequested()) {
|
||||
// always use a new key if we're delaying, since the reply block may not be used within the
|
||||
// window of a session
|
||||
sessKey = KeyGenerator.getInstance().generateSessionKey();
|
||||
tag = null;
|
||||
_log.debug("Delay requested - creating a new session key");
|
||||
} else {
|
||||
sessKey = SessionKeyManager.getInstance().getCurrentKey(replyThrough);
|
||||
if (sessKey == null) {
|
||||
sessKey = KeyGenerator.getInstance().generateSessionKey();
|
||||
tag = null;
|
||||
_log.debug("No delay requested, but no session key is known");
|
||||
} else {
|
||||
tag = SessionKeyManager.getInstance().consumeNextAvailableTag(replyThrough, sessKey);
|
||||
}
|
||||
}
|
||||
byte encData[] = ElGamalAESEngine.encrypt(baos.toByteArray(), replyThrough, sessKey, null, tag, paddedSize);
|
||||
setData(encData);
|
||||
} catch (IOException ioe) {
|
||||
throw new DataFormatException("Error writing out the source route block data", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new DataFormatException("Error writing out the source route block data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_router = new Hash();
|
||||
_router.readBytes(in);
|
||||
int size = (int)DataHelper.readLong(in, 2);
|
||||
_data = new byte[size];
|
||||
int read = read(in, _data);
|
||||
if (read != _data.length)
|
||||
throw new DataFormatException("Incorrect # of bytes read for source route block: " + read);
|
||||
_key = new SessionKey();
|
||||
_key.readBytes(in);
|
||||
_tag = new byte[32];
|
||||
read = read(in, _tag);
|
||||
if (read != _tag.length)
|
||||
throw new DataFormatException("Incorrect # of bytes read for session tag: " + read);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ( (_router == null) || (_data == null) || (_key == null) || (_tag == null) || (_tag.length != 32) )
|
||||
throw new DataFormatException("Insufficient data to write");
|
||||
_router.writeBytes(out);
|
||||
DataHelper.writeLong(out, 2, _data.length);
|
||||
out.write(_data);
|
||||
_key.writeBytes(out);
|
||||
out.write(_tag);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof SourceRouteBlock))
|
||||
return false;
|
||||
SourceRouteBlock block = (SourceRouteBlock)obj;
|
||||
return DataHelper.eq(getRouter(), block.getRouter()) &&
|
||||
DataHelper.eq(getData(), block.getData()) &&
|
||||
DataHelper.eq(getKey(), block.getKey()) &&
|
||||
DataHelper.eq(getTag(), block.getTag());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getRouter()) +
|
||||
DataHelper.hashCode(getData()) +
|
||||
DataHelper.hashCode(getKey()) +
|
||||
DataHelper.hashCode(getTag());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[SourceRouteBlock: ");
|
||||
buf.append("\n\tRouter: ").append(getRouter());
|
||||
buf.append("\n\tData: ").append(DataHelper.toString(getData(), getData().length));
|
||||
buf.append("\n\tTag: ").append(DataHelper.toString(getTag(), (getTag() != null ? getTag().length : 0)));
|
||||
buf.append("\n\tKey: ").append(getKey());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
159
router/java/src/net/i2p/data/i2np/SourceRouteReplyMessage.java
Normal file
159
router/java/src/net/i2p/data/i2np/SourceRouteReplyMessage.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines a message directed by a source route block to deliver a message to an
|
||||
* unknown location.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class SourceRouteReplyMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(SourceRouteReplyMessage.class);
|
||||
public final static int MESSAGE_TYPE = 13;
|
||||
private byte _encryptedHeader[];
|
||||
private I2NPMessage _message;
|
||||
private DeliveryInstructions _decryptedInstructions;
|
||||
private long _decryptedMessageId;
|
||||
private Certificate _decryptedCertificate;
|
||||
private long _decryptedExpiration;
|
||||
|
||||
public SourceRouteReplyMessage() {
|
||||
_encryptedHeader = null;
|
||||
_message = null;
|
||||
_decryptedInstructions = null;
|
||||
_decryptedMessageId = -1;
|
||||
_decryptedCertificate = null;
|
||||
_decryptedExpiration = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message being sent as a reply
|
||||
*/
|
||||
public I2NPMessage getMessage() { return _message; }
|
||||
public void setMessage(I2NPMessage message) { _message = message; }
|
||||
|
||||
public void setEncryptedHeader(byte header[]) { _encryptedHeader = header; }
|
||||
|
||||
/**
|
||||
* After decryptHeader, this contains the delivery instructions for this block
|
||||
*/
|
||||
public DeliveryInstructions getDecryptedInstructions() { return _decryptedInstructions; }
|
||||
/**
|
||||
* After decryptHeader, this contains the message ID to be used with this block
|
||||
*/
|
||||
public long getDecryptedMessageId() { return _decryptedMessageId; }
|
||||
/**
|
||||
* After decryptHeader, this contains the Certificate 'paying' for the forwarding according to
|
||||
* this block
|
||||
*/
|
||||
public Certificate getDecryptedCertificate() { return _decryptedCertificate; }
|
||||
/**
|
||||
* After decryptHeader, this contains the date after which this block should not be forwarded
|
||||
*/
|
||||
public long getDecryptedExpiration() { return _decryptedExpiration; }
|
||||
|
||||
/**
|
||||
* Decrypt the header and store it in the various getDecryptedXYZ() properties
|
||||
*
|
||||
* @throws DataFormatException if the decryption fails or if the data is somehow malformed
|
||||
*/
|
||||
public void decryptHeader(PrivateKey key) throws DataFormatException {
|
||||
if ( (_encryptedHeader == null) || (_encryptedHeader.length <= 0) )
|
||||
throw new DataFormatException("No header to decrypt");
|
||||
|
||||
byte decr[] = ElGamalAESEngine.decrypt(_encryptedHeader, key);
|
||||
|
||||
if (decr == null)
|
||||
throw new DataFormatException("Decrypted data is null");
|
||||
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
|
||||
|
||||
_decryptedInstructions = new DeliveryInstructions();
|
||||
_decryptedInstructions.readBytes(bais);
|
||||
_decryptedMessageId = DataHelper.readLong(bais, 4);
|
||||
_decryptedCertificate = new Certificate();
|
||||
_decryptedCertificate.readBytes(bais);
|
||||
_decryptedExpiration = DataHelper.readDate(bais).getTime();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
throw new DataFormatException("Error reading the source route reply header", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new DataFormatException("Error reading the source route reply header", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
int headerSize = (int)DataHelper.readLong(in, 2);
|
||||
_encryptedHeader = new byte[headerSize];
|
||||
int read = read(in, _encryptedHeader);
|
||||
if (read != headerSize)
|
||||
throw new DataFormatException("Not enough bytes to read the header (read = " + read + ", required = " + headerSize + ")");
|
||||
_message = new I2NPMessageHandler().readMessage(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_encryptedHeader == null) || (_message == null) )
|
||||
throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
DataHelper.writeLong(os, 2, _encryptedHeader.length);
|
||||
os.write(_encryptedHeader);
|
||||
_message.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(_encryptedHeader) +
|
||||
DataHelper.hashCode(_message);
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof SourceRouteReplyMessage) ) {
|
||||
SourceRouteReplyMessage msg = (SourceRouteReplyMessage)object;
|
||||
return DataHelper.eq(_message,msg._message) &&
|
||||
DataHelper.eq(_encryptedHeader,msg._encryptedHeader);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[SourceRouteReplyMessage: ");
|
||||
buf.append("\n\tHeader: ").append(DataHelper.toString(_encryptedHeader, 64));
|
||||
buf.append("\n\tMessage: ").append(_message);
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* Contains the session key used by the owner/creator of the tunnel to modify
|
||||
* its operational settings.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelConfigurationSessionKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(TunnelConfigurationSessionKey.class);
|
||||
private SessionKey _key;
|
||||
|
||||
public TunnelConfigurationSessionKey() { setKey(null); }
|
||||
|
||||
public SessionKey getKey() { return _key; }
|
||||
public void setKey(SessionKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new SessionKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof TunnelConfigurationSessionKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((TunnelConfigurationSessionKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[TunnelConfigurationSessionKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
||||
312
router/java/src/net/i2p/data/i2np/TunnelCreateMessage.java
Normal file
312
router/java/src/net/i2p/data/i2np/TunnelCreateMessage.java
Normal file
@@ -0,0 +1,312 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message sent to a router to request that it participate in a
|
||||
* tunnel using the included configuration settings.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelCreateMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(TunnelCreateMessage.class);
|
||||
public final static int MESSAGE_TYPE = 6;
|
||||
private int _participantType;
|
||||
private Hash _nextRouter;
|
||||
private TunnelId _tunnelId;
|
||||
private long _tunnelDuration;
|
||||
private TunnelConfigurationSessionKey _configKey;
|
||||
private long _maxPeakMessagesPerMin;
|
||||
private long _maxAvgMessagesPerMin;
|
||||
private long _maxPeakBytesPerMin;
|
||||
private long _maxAvgBytesPerMin;
|
||||
private boolean _includeDummyTraffic;
|
||||
private boolean _reorderMessages;
|
||||
private TunnelSigningPublicKey _verificationPubKey;
|
||||
private TunnelSigningPrivateKey _verificationPrivKey;
|
||||
private TunnelSessionKey _tunnelKey;
|
||||
private Certificate _certificate;
|
||||
private SourceRouteBlock _replyBlock;
|
||||
|
||||
public static final int PARTICIPANT_TYPE_GATEWAY = 1;
|
||||
public static final int PARTICIPANT_TYPE_ENDPOINT = 2;
|
||||
public static final int PARTICIPANT_TYPE_OTHER = 3;
|
||||
|
||||
private final static long FLAG_DUMMY = 1 << 7;
|
||||
private final static long FLAG_REORDER = 1 << 6;
|
||||
|
||||
public TunnelCreateMessage() {
|
||||
setParticipantType(-1);
|
||||
setNextRouter(null);
|
||||
setTunnelId(null);
|
||||
setTunnelDurationSeconds(-1);
|
||||
setConfigurationKey(null);
|
||||
setMaxPeakMessagesPerMin(-1);
|
||||
setMaxAvgMessagesPerMin(-1);
|
||||
setMaxPeakBytesPerMin(-1);
|
||||
setMaxAvgBytesPerMin(-1);
|
||||
setIncludeDummyTraffic(false);
|
||||
setReorderMessages(false);
|
||||
setVerificationPublicKey(null);
|
||||
setVerificationPrivateKey(null);
|
||||
setTunnelKey(null);
|
||||
setCertificate(null);
|
||||
setReplyBlock(null);
|
||||
}
|
||||
|
||||
public void setParticipantType(int type) { _participantType = type; }
|
||||
public int getParticipantType() { return _participantType; }
|
||||
public void setNextRouter(Hash routerIdentityHash) { _nextRouter = routerIdentityHash; }
|
||||
public Hash getNextRouter() { return _nextRouter; }
|
||||
public void setTunnelId(TunnelId id) { _tunnelId = id; }
|
||||
public TunnelId getTunnelId() { return _tunnelId; }
|
||||
public void setTunnelDurationSeconds(long durationSeconds) { _tunnelDuration = durationSeconds; }
|
||||
public long getTunnelDurationSeconds() { return _tunnelDuration; }
|
||||
public void setConfigurationKey(TunnelConfigurationSessionKey key) { _configKey = key; }
|
||||
public TunnelConfigurationSessionKey getConfigurationKey() { return _configKey; }
|
||||
public void setMaxPeakMessagesPerMin(long msgs) { _maxPeakMessagesPerMin = msgs; }
|
||||
public long getMaxPeakMessagesPerMin() { return _maxPeakMessagesPerMin; }
|
||||
public void setMaxAvgMessagesPerMin(long msgs) { _maxAvgMessagesPerMin = msgs; }
|
||||
public long getMaxAvgMessagesPerMin() { return _maxAvgMessagesPerMin; }
|
||||
public void setMaxPeakBytesPerMin(long bytes) { _maxPeakBytesPerMin = bytes; }
|
||||
public long getMaxPeakBytesPerMin() { return _maxPeakBytesPerMin; }
|
||||
public void setMaxAvgBytesPerMin(long bytes) { _maxAvgBytesPerMin = bytes; }
|
||||
public long getMaxAvgBytesPerMin() { return _maxAvgBytesPerMin; }
|
||||
public void setIncludeDummyTraffic(boolean include) { _includeDummyTraffic = include; }
|
||||
public boolean getIncludeDummyTraffic() { return _includeDummyTraffic; }
|
||||
public void setReorderMessages(boolean reorder) { _reorderMessages = reorder; }
|
||||
public boolean getReorderMessages() { return _reorderMessages; }
|
||||
public void setVerificationPublicKey(TunnelSigningPublicKey key) { _verificationPubKey = key; }
|
||||
public TunnelSigningPublicKey getVerificationPublicKey() { return _verificationPubKey; }
|
||||
public void setVerificationPrivateKey(TunnelSigningPrivateKey key) { _verificationPrivKey = key; }
|
||||
public TunnelSigningPrivateKey getVerificationPrivateKey() { return _verificationPrivKey; }
|
||||
public void setTunnelKey(TunnelSessionKey key) { _tunnelKey = key; }
|
||||
public TunnelSessionKey getTunnelKey() { return _tunnelKey; }
|
||||
public void setCertificate(Certificate cert) { _certificate = cert; }
|
||||
public Certificate getCertificate() { return _certificate; }
|
||||
public void setReplyBlock(SourceRouteBlock block) { _replyBlock = block; }
|
||||
public SourceRouteBlock getReplyBlock() { return _replyBlock; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_participantType = (int)DataHelper.readLong(in, 1);
|
||||
if (_participantType != PARTICIPANT_TYPE_ENDPOINT) {
|
||||
_nextRouter = new Hash();
|
||||
_nextRouter.readBytes(in);
|
||||
}
|
||||
_tunnelId = new TunnelId();
|
||||
_tunnelId.readBytes(in);
|
||||
_tunnelDuration = DataHelper.readLong(in, 4);
|
||||
_configKey = new TunnelConfigurationSessionKey();
|
||||
_configKey.readBytes(in);
|
||||
_maxPeakMessagesPerMin = DataHelper.readLong(in, 4);
|
||||
_maxAvgMessagesPerMin = DataHelper.readLong(in, 4);
|
||||
_maxPeakBytesPerMin = DataHelper.readLong(in, 4);
|
||||
_maxAvgBytesPerMin = DataHelper.readLong(in, 4);
|
||||
|
||||
int flags = (int)DataHelper.readLong(in, 1);
|
||||
_includeDummyTraffic = flagsIncludeDummy(flags);
|
||||
_reorderMessages = flagsReorder(flags);
|
||||
|
||||
_verificationPubKey = new TunnelSigningPublicKey();
|
||||
_verificationPubKey.readBytes(in);
|
||||
if (_participantType == PARTICIPANT_TYPE_GATEWAY) {
|
||||
_verificationPrivKey = new TunnelSigningPrivateKey();
|
||||
_verificationPrivKey.readBytes(in);
|
||||
}
|
||||
if ( (_participantType == PARTICIPANT_TYPE_ENDPOINT) || (_participantType == PARTICIPANT_TYPE_GATEWAY) ) {
|
||||
_tunnelKey = new TunnelSessionKey();
|
||||
_tunnelKey.readBytes(in);
|
||||
}
|
||||
_certificate = new Certificate();
|
||||
_certificate.readBytes(in);
|
||||
_replyBlock = new SourceRouteBlock();
|
||||
_replyBlock.readBytes(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
DataHelper.writeLong(os, 1, _participantType);
|
||||
if (_participantType != PARTICIPANT_TYPE_ENDPOINT) {
|
||||
_nextRouter.writeBytes(os);
|
||||
}
|
||||
_tunnelId.writeBytes(os);
|
||||
DataHelper.writeLong(os, 4, _tunnelDuration);
|
||||
_configKey.writeBytes(os);
|
||||
|
||||
DataHelper.writeLong(os, 4, _maxPeakMessagesPerMin);
|
||||
DataHelper.writeLong(os, 4, _maxAvgMessagesPerMin);
|
||||
DataHelper.writeLong(os, 4, _maxPeakBytesPerMin);
|
||||
DataHelper.writeLong(os, 4, _maxAvgBytesPerMin);
|
||||
|
||||
long flags = getFlags();
|
||||
DataHelper.writeLong(os, 1, flags);
|
||||
|
||||
_verificationPubKey.writeBytes(os);
|
||||
if (_participantType == PARTICIPANT_TYPE_GATEWAY) {
|
||||
_verificationPrivKey.writeBytes(os);
|
||||
}
|
||||
if ( (_participantType == PARTICIPANT_TYPE_ENDPOINT) || (_participantType == PARTICIPANT_TYPE_GATEWAY) ) {
|
||||
_tunnelKey.writeBytes(os);
|
||||
}
|
||||
_certificate.writeBytes(os);
|
||||
_replyBlock.writeBytes(os);
|
||||
} catch (Throwable t) {
|
||||
throw new I2NPMessageException("Error writing out the message data", t);
|
||||
}
|
||||
/*
|
||||
try {
|
||||
DataHelper.writeLong(os, 1, _participantType);
|
||||
if (_participantType != PARTICIPANT_TYPE_ENDPOINT) {
|
||||
if (_nextRouter == null)
|
||||
throw new I2NPMessageException("Next router is not defined");
|
||||
_nextRouter.writeBytes(os);
|
||||
}
|
||||
if (_tunnelId == null)
|
||||
throw new I2NPMessageException("Tunnel ID is not defined");
|
||||
_tunnelId.writeBytes(os);
|
||||
if (_tunnelDuration < 0)
|
||||
throw new I2NPMessageException("Tunnel duration is negative");
|
||||
DataHelper.writeLong(os, 4, _tunnelDuration);
|
||||
if (_configKey == null)
|
||||
throw new I2NPMessageException("Configuration key is not defined");
|
||||
_configKey.writeBytes(os);
|
||||
if ( (_maxPeakMessagesPerMin < 0) || (_maxAvgMessagesPerMin < 0) ||
|
||||
(_maxAvgMessagesPerMin < 0) || (_maxAvgBytesPerMin < 0) )
|
||||
throw new I2NPMessageException("Negative limits defined");
|
||||
|
||||
long flags = getFlags();
|
||||
DataHelper.writeLong(os, 1, flags);
|
||||
|
||||
if (_verificationPubKey == null)
|
||||
throw new I2NPMessageException("Verification public key is not defined");
|
||||
_verificationPubKey.writeBytes(os);
|
||||
if (_participantType == PARTICIPANT_TYPE_GATEWAY) {
|
||||
if (_verificationPrivKey == null)
|
||||
throw new I2NPMessageException("Verification private key is needed and not defined");
|
||||
_verificationPrivKey.writeBytes(os);
|
||||
}
|
||||
if ( (_participantType == PARTICIPANT_TYPE_ENDPOINT) || (_participantType == PARTICIPANT_TYPE_GATEWAY) ) {
|
||||
if (_tunnelKey == null)
|
||||
throw new I2NPMessageException("Tunnel key is needed and not defined");
|
||||
_tunnelKey.writeBytes(os);
|
||||
}
|
||||
if (_certificate == null)
|
||||
throw new I2NPMessageException("Certificate is not defined");
|
||||
_certificate.writeBytes(os);
|
||||
if (_replyBlock == null)
|
||||
throw new I2NPMessageException("Reply block not defined");
|
||||
_replyBlock.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
*/
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
private boolean flagsIncludeDummy(long flags) {
|
||||
return (0 != (flags & FLAG_DUMMY));
|
||||
}
|
||||
private boolean flagsReorder(long flags) {
|
||||
return (0 != (flags & FLAG_REORDER));
|
||||
}
|
||||
|
||||
private long getFlags() {
|
||||
long val = 0L;
|
||||
if (getIncludeDummyTraffic())
|
||||
val = val | FLAG_DUMMY;
|
||||
if (getReorderMessages())
|
||||
val = val | FLAG_REORDER;
|
||||
return val;
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return (int)(DataHelper.hashCode(getCertificate()) +
|
||||
DataHelper.hashCode(getConfigurationKey()) +
|
||||
DataHelper.hashCode(getNextRouter()) +
|
||||
DataHelper.hashCode(getReplyBlock()) +
|
||||
DataHelper.hashCode(getTunnelId()) +
|
||||
DataHelper.hashCode(getTunnelKey()) +
|
||||
DataHelper.hashCode(getVerificationPrivateKey()) +
|
||||
DataHelper.hashCode(getVerificationPublicKey()) +
|
||||
(getIncludeDummyTraffic() ? 1 : 0) +
|
||||
getMaxAvgBytesPerMin() +
|
||||
getMaxAvgMessagesPerMin() +
|
||||
getMaxPeakBytesPerMin() +
|
||||
getMaxPeakMessagesPerMin() +
|
||||
getParticipantType() +
|
||||
(getReorderMessages() ? 1 : 0) +
|
||||
getTunnelDurationSeconds());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof TunnelCreateMessage) ) {
|
||||
TunnelCreateMessage msg = (TunnelCreateMessage)object;
|
||||
return DataHelper.eq(getCertificate(), msg.getCertificate()) &&
|
||||
DataHelper.eq(getConfigurationKey(), msg.getConfigurationKey()) &&
|
||||
DataHelper.eq(getNextRouter(), msg.getNextRouter()) &&
|
||||
DataHelper.eq(getReplyBlock(), msg.getReplyBlock()) &&
|
||||
DataHelper.eq(getTunnelId(), msg.getTunnelId()) &&
|
||||
DataHelper.eq(getTunnelKey(), msg.getTunnelKey()) &&
|
||||
DataHelper.eq(getVerificationPrivateKey(), msg.getVerificationPrivateKey()) &&
|
||||
DataHelper.eq(getVerificationPublicKey(), msg.getVerificationPublicKey()) &&
|
||||
(getIncludeDummyTraffic() == msg.getIncludeDummyTraffic()) &&
|
||||
(getMaxAvgBytesPerMin() == msg.getMaxAvgBytesPerMin()) &&
|
||||
(getMaxAvgMessagesPerMin() == msg.getMaxAvgMessagesPerMin()) &&
|
||||
(getMaxPeakBytesPerMin() == msg.getMaxPeakBytesPerMin()) &&
|
||||
(getMaxPeakMessagesPerMin() == msg.getMaxPeakMessagesPerMin()) &&
|
||||
(getParticipantType() == msg.getParticipantType()) &&
|
||||
(getReorderMessages() == msg.getReorderMessages()) &&
|
||||
(getTunnelDurationSeconds() == msg.getTunnelDurationSeconds());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[TunnelCreateMessage: ");
|
||||
buf.append("\n\tParticipant Type: ").append(getParticipantType());
|
||||
buf.append("\n\tCertificate: ").append(getCertificate());
|
||||
buf.append("\n\tConfiguration Key: ").append(getConfigurationKey());
|
||||
buf.append("\n\tNext Router: ").append(getNextRouter());
|
||||
buf.append("\n\tReply Block: ").append(getReplyBlock());
|
||||
buf.append("\n\tTunnel ID: ").append(getTunnelId());
|
||||
buf.append("\n\tTunnel Key: ").append(getTunnelKey());
|
||||
buf.append("\n\tVerification Private Key: ").append(getVerificationPrivateKey());
|
||||
buf.append("\n\tVerification Public Key: ").append(getVerificationPublicKey());
|
||||
buf.append("\n\tInclude Dummy Traffic: ").append(getIncludeDummyTraffic());
|
||||
buf.append("\n\tMax Avg Bytes / Minute: ").append(getMaxAvgBytesPerMin());
|
||||
buf.append("\n\tMax Peak Bytes / Minute: ").append(getMaxPeakBytesPerMin());
|
||||
buf.append("\n\tMax Avg Messages / Minute: ").append(getMaxAvgMessagesPerMin());
|
||||
buf.append("\n\tMax Peak Messages / Minute: ").append(getMaxPeakMessagesPerMin());
|
||||
buf.append("\n\tReorder Messages: ").append(getReorderMessages());
|
||||
buf.append("\n\tTunnel Duration (seconds): ").append(getTunnelDurationSeconds());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
113
router/java/src/net/i2p/data/i2np/TunnelCreateStatusMessage.java
Normal file
113
router/java/src/net/i2p/data/i2np/TunnelCreateStatusMessage.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message a router sends to another router in reply to a
|
||||
* TunnelCreateMessage
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelCreateStatusMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(TunnelCreateStatusMessage.class);
|
||||
public final static int MESSAGE_TYPE = 7;
|
||||
private TunnelId _tunnelId;
|
||||
private int _status;
|
||||
private Hash _from;
|
||||
|
||||
public final static int STATUS_SUCCESS = 0;
|
||||
public final static int STATUS_FAILED_DUPLICATE_ID = 1;
|
||||
public final static int STATUS_FAILED_OVERLOADED = 2;
|
||||
public final static int STATUS_FAILED_CERTIFICATE = 3;
|
||||
public final static int STATUS_FAILED_DELETED = 100;
|
||||
|
||||
public TunnelCreateStatusMessage() {
|
||||
setTunnelId(null);
|
||||
setStatus(-1);
|
||||
setFromHash(null);
|
||||
}
|
||||
|
||||
public TunnelId getTunnelId() { return _tunnelId; }
|
||||
public void setTunnelId(TunnelId id) { _tunnelId = id; }
|
||||
|
||||
public int getStatus() { return _status; }
|
||||
public void setStatus(int status) { _status = status; }
|
||||
|
||||
/**
|
||||
* Contains the SHA256 Hash of the RouterIdentity sending the message
|
||||
*/
|
||||
public Hash getFromHash() { return _from; }
|
||||
public void setFromHash(Hash from) { _from = from; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_tunnelId = new TunnelId();
|
||||
_tunnelId.readBytes(in);
|
||||
_status = (int)DataHelper.readLong(in, 1);
|
||||
_from = new Hash();
|
||||
_from.readBytes(in);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_tunnelId == null) || (_from == null) ) throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_tunnelId.writeBytes(os);
|
||||
DataHelper.writeLong(os, 1, (_status < 0 ? 255 : _status));
|
||||
_from.writeBytes(os);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getTunnelId()) +
|
||||
getStatus() +
|
||||
DataHelper.hashCode(getFromHash());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof TunnelCreateStatusMessage) ) {
|
||||
TunnelCreateStatusMessage msg = (TunnelCreateStatusMessage)object;
|
||||
return DataHelper.eq(getTunnelId(),msg.getTunnelId()) &&
|
||||
DataHelper.eq(getFromHash(),msg.getFromHash()) &&
|
||||
(getStatus() == msg.getStatus());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[TunnelCreateStatusMessage: ");
|
||||
buf.append("\n\tTunnel ID: ").append(getTunnelId());
|
||||
buf.append("\n\tStatus: ").append(getStatus());
|
||||
buf.append("\n\tFrom: ").append(getFromHash());
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
145
router/java/src/net/i2p/data/i2np/TunnelMessage.java
Normal file
145
router/java/src/net/i2p/data/i2np/TunnelMessage.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Defines the message sent between routers for tunnel delivery
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelMessage extends I2NPMessageImpl {
|
||||
private final static Log _log = new Log(TunnelMessage.class);
|
||||
public final static int MESSAGE_TYPE = 8;
|
||||
private TunnelId _tunnelId;
|
||||
private long _size;
|
||||
private byte[] _data;
|
||||
private TunnelVerificationStructure _verification;
|
||||
private byte[] _encryptedInstructions;
|
||||
|
||||
private final static int FLAG_INCLUDESTRUCTURE = 0;
|
||||
private final static int FLAG_DONT_INCLUDESTRUCTURE = 1;
|
||||
|
||||
public TunnelMessage() {
|
||||
setTunnelId(null);
|
||||
setData(null);
|
||||
setVerificationStructure(null);
|
||||
setEncryptedDeliveryInstructions(null);
|
||||
}
|
||||
|
||||
public TunnelId getTunnelId() { return _tunnelId; }
|
||||
public void setTunnelId(TunnelId id) { _tunnelId = id; }
|
||||
|
||||
public byte[] getData() { return _data; }
|
||||
public void setData(byte data[]) { _data = data; }
|
||||
|
||||
public TunnelVerificationStructure getVerificationStructure() { return _verification; }
|
||||
public void setVerificationStructure(TunnelVerificationStructure verification) { _verification = verification; }
|
||||
|
||||
public byte[] getEncryptedDeliveryInstructions() { return _encryptedInstructions; }
|
||||
public void setEncryptedDeliveryInstructions(byte instructions[]) { _encryptedInstructions = instructions; }
|
||||
|
||||
public void readMessage(InputStream in, int type) throws I2NPMessageException, IOException {
|
||||
if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
|
||||
try {
|
||||
_tunnelId = new TunnelId();
|
||||
_tunnelId.readBytes(in);
|
||||
_log.debug("Read tunnel message for tunnel " + _tunnelId);
|
||||
_size = DataHelper.readLong(in, 4);
|
||||
_log.debug("Read tunnel message size: " + _size);
|
||||
if (_size < 0) throw new I2NPMessageException("Invalid size in the structure: " + _size);
|
||||
_data = new byte[(int)_size];
|
||||
int read = read(in, _data);
|
||||
if (read != _size)
|
||||
throw new I2NPMessageException("Incorrect number of bytes read (" + read + ", expected " + _size);
|
||||
int includeVerification = (int)DataHelper.readLong(in, 1);
|
||||
if (includeVerification == FLAG_INCLUDESTRUCTURE) {
|
||||
_verification = new TunnelVerificationStructure();
|
||||
_verification.readBytes(in);
|
||||
int len = (int)DataHelper.readLong(in, 2);
|
||||
_encryptedInstructions = new byte[len];
|
||||
read = read(in, _encryptedInstructions);
|
||||
if (read != len)
|
||||
throw new I2NPMessageException("Incorrect number of bytes read for instructions (" + read + ", expected " + len + ")");
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Unable to load the message data", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] writeMessage() throws I2NPMessageException, IOException {
|
||||
if ( (_tunnelId == null) || (_data == null) || (_data.length <= 0) )
|
||||
throw new I2NPMessageException("Not enough data to write out");
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream(32);
|
||||
try {
|
||||
_tunnelId.writeBytes(os);
|
||||
_log.debug("Writing tunnel message for tunnel " + _tunnelId);
|
||||
DataHelper.writeLong(os, 4, _data.length);
|
||||
_log.debug("Writing tunnel message length: " + _data.length);
|
||||
os.write(_data);
|
||||
_log.debug("Writing tunnel message data");
|
||||
if ( (_verification == null) || (_encryptedInstructions == null) ) {
|
||||
DataHelper.writeLong(os, 1, FLAG_DONT_INCLUDESTRUCTURE);
|
||||
_log.debug("Writing DontIncludeStructure flag");
|
||||
} else {
|
||||
DataHelper.writeLong(os, 1, FLAG_INCLUDESTRUCTURE);
|
||||
_log.debug("Writing IncludeStructure flag, then the verification structure, then the E(instr).length [" + _encryptedInstructions.length + "], then the E(instr)");
|
||||
_verification.writeBytes(os);
|
||||
DataHelper.writeLong(os, 2, _encryptedInstructions.length);
|
||||
os.write(_encryptedInstructions);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new I2NPMessageException("Error writing out the message data", dfe);
|
||||
}
|
||||
byte rv[] = os.toByteArray();
|
||||
_log.debug("Overall data being written: " + rv.length);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public int getType() { return MESSAGE_TYPE; }
|
||||
|
||||
public int hashCode() {
|
||||
return DataHelper.hashCode(getTunnelId()) +
|
||||
DataHelper.hashCode(_data) +
|
||||
DataHelper.hashCode(getVerificationStructure()) +
|
||||
DataHelper.hashCode(getEncryptedDeliveryInstructions());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if ( (object != null) && (object instanceof TunnelMessage) ) {
|
||||
TunnelMessage msg = (TunnelMessage)object;
|
||||
return DataHelper.eq(getTunnelId(),msg.getTunnelId()) &&
|
||||
DataHelper.eq(getVerificationStructure(),msg.getVerificationStructure()) &&
|
||||
DataHelper.eq(getData(),msg.getData()) &&
|
||||
DataHelper.eq(getEncryptedDeliveryInstructions(), msg.getEncryptedDeliveryInstructions());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[TunnelMessage: ");
|
||||
buf.append("\n\tTunnel ID: ").append(getTunnelId());
|
||||
buf.append("\n\tVerification Structure: ").append(getVerificationStructure());
|
||||
buf.append("\n\tEncrypted Instructions: ").append(getEncryptedDeliveryInstructions());
|
||||
buf.append("\n\tData size: ").append(getData().length);
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
61
router/java/src/net/i2p/data/i2np/TunnelSessionKey.java
Normal file
61
router/java/src/net/i2p/data/i2np/TunnelSessionKey.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* Contains the session key used by the tunnel gateway to encrypt the DeliveryInstructions
|
||||
* and used by the tunnel end point to decrypt those instructions.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelSessionKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(TunnelSessionKey.class);
|
||||
private SessionKey _key;
|
||||
|
||||
public TunnelSessionKey() { setKey(null); }
|
||||
|
||||
public SessionKey getKey() { return _key; }
|
||||
public void setKey(SessionKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new SessionKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof TunnelSessionKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((TunnelSessionKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[TunnelSessionKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
/**
|
||||
* Contains the private key which constructs a signature for the TunnelMessage
|
||||
* which every participant in a tunnel uses to verify the
|
||||
* TunnelVerificationStructure with.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelSigningPrivateKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(EndPointPrivateKey.class);
|
||||
private SigningPrivateKey _key;
|
||||
|
||||
public TunnelSigningPrivateKey() { setKey(null); }
|
||||
|
||||
public SigningPrivateKey getKey() { return _key; }
|
||||
public void setKey(SigningPrivateKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new SigningPrivateKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof TunnelSigningPrivateKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((TunnelSigningPrivateKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[EndPointPrivateKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
|
||||
/**
|
||||
* Contains the public key which every participant in a tunnel uses to verify
|
||||
* the TunnelVerificationStructure for TunnelMessages that pass by.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelSigningPublicKey extends DataStructureImpl {
|
||||
private final static Log _log = new Log(TunnelSigningPublicKey.class);
|
||||
private SigningPublicKey _key;
|
||||
|
||||
public TunnelSigningPublicKey() { setKey(null); }
|
||||
|
||||
public SigningPublicKey getKey() { return _key; }
|
||||
public void setKey(SigningPublicKey key) { _key= key; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_key = new SigningPublicKey();
|
||||
_key.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_key == null) throw new DataFormatException("Invalid key");
|
||||
_key.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof TunnelSigningPublicKey))
|
||||
return false;
|
||||
return DataHelper.eq(getKey(), ((TunnelSigningPublicKey)obj).getKey());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if (_key == null) return 0;
|
||||
return getKey().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[TunnelSigningPublicKey: " + getKey() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.i2p.data.i2np;
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class TunnelVerificationStructure extends DataStructureImpl {
|
||||
private final static Log _log = new Log(TunnelVerificationStructure.class);
|
||||
private Hash _msgHash;
|
||||
private Signature _authSignature;
|
||||
|
||||
public TunnelVerificationStructure() {
|
||||
setMessageHash(null);
|
||||
setAuthorizationSignature(null);
|
||||
}
|
||||
|
||||
public Hash getMessageHash() { return _msgHash; }
|
||||
public void setMessageHash(Hash hash) { _msgHash = hash; }
|
||||
|
||||
public Signature getAuthorizationSignature() { return _authSignature; }
|
||||
public void setAuthorizationSignature(Signature sig) { _authSignature = sig; }
|
||||
|
||||
public void sign(SigningPrivateKey key) {
|
||||
if (_msgHash != null) {
|
||||
Signature sig = DSAEngine.getInstance().sign(_msgHash.getData(), key);
|
||||
setAuthorizationSignature(sig);
|
||||
}
|
||||
}
|
||||
public boolean verifySignature(SigningPublicKey key) {
|
||||
if (_msgHash == null) return false;
|
||||
return DSAEngine.getInstance().verifySignature(_authSignature, _msgHash.getData(), key);
|
||||
}
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_msgHash = new Hash();
|
||||
_msgHash.readBytes(in);
|
||||
_authSignature = new Signature();
|
||||
_authSignature.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_authSignature == null) {
|
||||
_authSignature = new Signature();
|
||||
_authSignature.setData(Signature.FAKE_SIGNATURE);
|
||||
}
|
||||
if ( (_msgHash == null) || (_authSignature == null) ) throw new DataFormatException("Invalid data");
|
||||
_msgHash.writeBytes(out);
|
||||
_authSignature.writeBytes(out);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj == null) || !(obj instanceof TunnelVerificationStructure))
|
||||
return false;
|
||||
TunnelVerificationStructure str = (TunnelVerificationStructure)obj;
|
||||
return DataHelper.eq(getMessageHash(), str.getMessageHash()) &&
|
||||
DataHelper.eq(getAuthorizationSignature(), str.getAuthorizationSignature());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
if ( (_msgHash == null) || (_authSignature == null) ) return 0;
|
||||
return getMessageHash().hashCode() + getAuthorizationSignature().hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[TunnelVerificationStructure: " + getMessageHash() + " " + getAuthorizationSignature() + "]";
|
||||
}
|
||||
}
|
||||
14
router/java/src/net/i2p/router/.nbattrs
Normal file
14
router/java/src/net/i2p/router/.nbattrs
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE attributes PUBLIC "-//NetBeans//DTD DefaultAttributes 1.0//EN" "http://www.netbeans.org/dtds/attributes-1_0.dtd">
|
||||
<attributes version="1.0">
|
||||
<fileobject name="CommSystemFacade.java">
|
||||
<attr name="EA-OpenIDE-Connection" serialvalue="aced0005737200146a6176612e7574696c2e4c696e6b65644c6973740c29535d4a60882203000078707704000000017372002a6f72672e6f70656e6964652e6c6f61646572732e436f6e6e656374696f6e537570706f72742450616972055f8af6f04a3bd80200024c00047479706574002b4c6f72672f6f70656e6964652f636f6f6b6965732f436f6e6e656374696f6e436f6f6b696524547970653b4c000576616c75657400124c6a6176612f6c616e672f4f626a6563743b78707372002e6f72672e6e65746265616e732e6d6f64756c65732e6a6176612e4a617661436f6e6e656374696f6e732454797065a83dd01001306d7402000149000666696c746572787000000050737200436f72672e6e65746265616e732e6d6f64756c65732e6a6176612e4a617661446174614f626a6563742450657273697374656e74436f6e6e656374696f6e48616e646c65ba16f1d2dd4c1cb60200014c000e646174614e6f646548616e646c6574001f4c6f72672f6f70656e6964652f6e6f6465732f4e6f64652448616e646c653b7870737200296f72672e6f70656e6964652e6c6f61646572732e446174614e6f6465244f626a65637448616e646c655bd0f82e01811d2e0200025a0005636c6f6e654c00036f626a7400244c6f72672f6f70656e6964652f66696c6573797374656d732f46696c654f626a6563743b787000737200326f72672e6f70656e6964652e66696c6573797374656d732e416273747261637446696c654f626a656374245265706c616365896fa1bce4b5219f0200024c000866696c654e616d657400124c6a6176612f6c616e672f537472696e673b4c000666734e616d6571007e000f78707400326e65742f6932702f726f757465722f7472616e73706f72742f436f6d6d53797374656d466163616465496d706c2e6a617661740025433a2f6465762f72656e616d696e672f61667465722f726f757465722f6a6176612f73726378"/>
|
||||
</fileobject>
|
||||
<fileobject name="Router.java">
|
||||
<attr name="class_dependency_java.lang.Thread" stringvalue="Router.ShutdownHook"/>
|
||||
<attr name="class_dependency_net.i2p.router.JobImpl" stringvalue="Router.CoallesceStatsJob;Router.PersistRouterInfoJob;Router.UpdateRoutingKeyModifierJob"/>
|
||||
</fileobject>
|
||||
<fileobject name="JobImpl.java">
|
||||
<attr name="EA-OpenIDE-Connection" serialvalue="aced0005737200146a6176612e7574696c2e4c696e6b65644c6973740c29535d4a60882203000078707704000000017372002a6f72672e6f70656e6964652e6c6f61646572732e436f6e6e656374696f6e537570706f72742450616972055f8af6f04a3bd80200024c00047479706574002b4c6f72672f6f70656e6964652f636f6f6b6965732f436f6e6e656374696f6e436f6f6b696524547970653b4c000576616c75657400124c6a6176612f6c616e672f4f626a6563743b78707372002e6f72672e6e65746265616e732e6d6f64756c65732e6a6176612e4a617661436f6e6e656374696f6e732454797065a83dd01001306d7402000149000666696c746572787000000050737200436f72672e6e65746265616e732e6d6f64756c65732e6a6176612e4a617661446174614f626a6563742450657273697374656e74436f6e6e656374696f6e48616e646c65ba16f1d2dd4c1cb60200014c000e646174614e6f646548616e646c6574001f4c6f72672f6f70656e6964652f6e6f6465732f4e6f64652448616e646c653b7870737200296f72672e6f70656e6964652e6c6f61646572732e446174614e6f6465244f626a65637448616e646c655bd0f82e01811d2e0200025a0005636c6f6e654c00036f626a7400244c6f72672f6f70656e6964652f66696c6573797374656d732f46696c654f626a6563743b787000737200326f72672e6f70656e6964652e66696c6573797374656d732e416273747261637446696c654f626a656374245265706c616365896fa1bce4b5219f0200024c000866696c654e616d657400124c6a6176612f6c616e672f537472696e673b4c000666734e616d6571007e000f787074001a6e65742f6932702f726f757465722f526f757465722e6a617661740025433a2f6465762f72656e616d696e672f61667465722f726f757465722f6a6176612f73726378"/>
|
||||
</fileobject>
|
||||
</attributes>
|
||||
95
router/java/src/net/i2p/router/ClientManagerFacade.java
Normal file
95
router/java/src/net/i2p/router/ClientManagerFacade.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.router.client.ClientManagerFacadeImpl;
|
||||
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.Hash;
|
||||
|
||||
/**
|
||||
* Manage all interactions with clients
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public abstract class ClientManagerFacade implements Service {
|
||||
private static ClientManagerFacade _instance = new ClientManagerFacadeImpl();
|
||||
public static ClientManagerFacade getInstance() { return _instance; }
|
||||
|
||||
/**
|
||||
* Request that a particular client authorize the Leases contained in the
|
||||
* LeaseSet, after which the onCreateJob is queued up. If that doesn't occur
|
||||
* within the timeout specified, queue up the onFailedJob. This call does not
|
||||
* block.
|
||||
*
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* @param timeout ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization
|
||||
*/
|
||||
public abstract void requestLeaseSet(Destination dest, LeaseSet set, long timeout, Job onCreateJob, Job onFailedJob);
|
||||
/**
|
||||
* Instruct the client (or all clients) that they are under attack. This call
|
||||
* does not block.
|
||||
*
|
||||
* @param dest Destination under attack, or null if all destinations are affected
|
||||
* @param reason Why the router thinks that there is abusive behavior
|
||||
* @param severity How severe the abuse is, with 0 being not severe and 255 is the max
|
||||
*/
|
||||
public abstract void reportAbuse(Destination dest, String reason, int severity);
|
||||
/**
|
||||
* Determine if the destination specified is managed locally. This call
|
||||
* DOES block.
|
||||
*
|
||||
* @param dest Destination to be checked
|
||||
*/
|
||||
public abstract boolean isLocal(Destination dest);
|
||||
/**
|
||||
* Determine if the destination hash specified is managed locally. This call
|
||||
* DOES block.
|
||||
*
|
||||
* @param destHash Hash of Destination to be checked
|
||||
*/
|
||||
public abstract boolean isLocal(Hash destHash);
|
||||
public abstract void messageDeliveryStatusUpdate(Destination fromDest, MessageId id, boolean delivered);
|
||||
|
||||
public abstract void messageReceived(ClientMessage msg);
|
||||
|
||||
/**
|
||||
* Return the client's current config, or null if not connected
|
||||
*
|
||||
*/
|
||||
public abstract SessionConfig getClientSessionConfig(Destination dest);
|
||||
public String renderStatusHTML() { return ""; }
|
||||
}
|
||||
|
||||
class DummyClientManagerFacade extends ClientManagerFacade {
|
||||
public boolean isLocal(Hash destHash) { return true; }
|
||||
public boolean isLocal(Destination dest) { return true; }
|
||||
public void reportAbuse(Destination dest, String reason, int severity) { }
|
||||
public void messageReceived(ClientMessage msg) {}
|
||||
public void requestLeaseSet(Destination dest, LeaseSet set, long timeout, Job onCreateJob, Job onFailedJob) {
|
||||
JobQueue.getInstance().addJob(onFailedJob);
|
||||
}
|
||||
public void startup() {
|
||||
//JobQueue.getInstance().addJob(new PollOutboundClientMessagesJob());
|
||||
}
|
||||
public void stopAcceptingClients() { }
|
||||
public void shutdown() {}
|
||||
|
||||
public void messageDeliveryStatusUpdate(Destination fromDest, MessageId id, boolean delivered) {}
|
||||
|
||||
public SessionConfig getClientSessionConfig(Destination _dest) { return null; }
|
||||
}
|
||||
94
router/java/src/net/i2p/router/ClientMessage.java
Normal file
94
router/java/src/net/i2p/router/ClientMessage.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.Payload;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
|
||||
/**
|
||||
* Wrap a message either destined for a local client or received from one.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class ClientMessage {
|
||||
private Payload _payload;
|
||||
private Destination _destination;
|
||||
private Destination _fromDestination;
|
||||
private MessageReceptionInfo _receptionInfo;
|
||||
private SessionConfig _senderConfig;
|
||||
private Hash _destinationHash;
|
||||
private MessageId _messageId;
|
||||
|
||||
public ClientMessage() {
|
||||
setPayload(null);
|
||||
setDestination(null);
|
||||
setFromDestination(null);
|
||||
setReceptionInfo(null);
|
||||
setSenderConfig(null);
|
||||
setDestinationHash(null);
|
||||
setMessageId(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payload of the message. All ClientMessage objects should have
|
||||
* a payload
|
||||
*
|
||||
*/
|
||||
public Payload getPayload() { return _payload; }
|
||||
public void setPayload(Payload payload) { _payload = payload; }
|
||||
|
||||
/**
|
||||
* Retrieve the destination to which this message is directed. All ClientMessage
|
||||
* objects should have a destination.
|
||||
*
|
||||
*/
|
||||
public Destination getDestination() { return _destination; }
|
||||
public void setDestination(Destination dest) { _destination = dest; }
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
public Destination getFromDestination() { return _fromDestination; }
|
||||
public void setFromDestination(Destination dest) { _fromDestination = dest; }
|
||||
|
||||
/**
|
||||
* Retrieve the destination to which this message is directed. All ClientMessage
|
||||
* objects should have a destination.
|
||||
*
|
||||
*/
|
||||
public Hash getDestinationHash() { return _destinationHash; }
|
||||
public void setDestinationHash(Hash dest) { _destinationHash = dest; }
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public MessageId getMessageId() { return _messageId; }
|
||||
public void setMessageId(MessageId id) { _messageId = id; }
|
||||
|
||||
/**
|
||||
* Retrieve the information regarding how the router received this message. Only
|
||||
* messages received from the network will have this information, not locally
|
||||
* originated ones.
|
||||
*
|
||||
*/
|
||||
public MessageReceptionInfo getReceptionInfo() { return _receptionInfo; }
|
||||
public void setReceptionInfo(MessageReceptionInfo info) { _receptionInfo = info; }
|
||||
|
||||
/**
|
||||
* Retrieve the session config of the client that sent the message. This will only be available
|
||||
* if the client was local
|
||||
*
|
||||
*/
|
||||
public SessionConfig getSenderConfig() { return _senderConfig; }
|
||||
public void setSenderConfig(SessionConfig config) { _senderConfig = config; }
|
||||
}
|
||||
126
router/java/src/net/i2p/router/ClientMessagePool.java
Normal file
126
router/java/src/net/i2p/router/ClientMessagePool.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.router.message.ProcessOutboundClientMessageJob;
|
||||
import net.i2p.router.message.OutboundClientMessageJob;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Manage all of the inbound and outbound client messages maintained by the router.
|
||||
* The ClientManager subsystem fetches messages from this for locally deliverable
|
||||
* messages and adds in remotely deliverable messages. Remotely deliverable messages
|
||||
* are picked up by interested jobs and processed and transformed into an OutNetMessage
|
||||
* to be eventually placed in the OutNetMessagePool.
|
||||
*
|
||||
*/
|
||||
public class ClientMessagePool {
|
||||
private final static Log _log = new Log(ClientMessagePool.class);
|
||||
private static ClientMessagePool _instance = new ClientMessagePool();
|
||||
public static final ClientMessagePool getInstance() { return _instance; }
|
||||
private List _inMessages;
|
||||
private List _outMessages;
|
||||
|
||||
private ClientMessagePool() {
|
||||
_inMessages = new ArrayList();
|
||||
_outMessages = new ArrayList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new message to the pool. The message can either be locally or
|
||||
* remotely destined.
|
||||
*
|
||||
*/
|
||||
public void add(ClientMessage msg) {
|
||||
if ( (ClientManagerFacade.getInstance().isLocal(msg.getDestination())) ||
|
||||
(ClientManagerFacade.getInstance().isLocal(msg.getDestinationHash())) ) {
|
||||
_log.debug("Adding message for local delivery");
|
||||
ClientManagerFacade.getInstance().messageReceived(msg);
|
||||
//synchronized (_inMessages) {
|
||||
// _inMessages.add(msg);
|
||||
//}
|
||||
} else {
|
||||
_log.debug("Adding message for remote delivery");
|
||||
//JobQueue.getInstance().addJob(new ProcessOutboundClientMessageJob(msg));
|
||||
JobQueue.getInstance().addJob(new OutboundClientMessageJob(msg));
|
||||
//synchronized (_outMessages) {
|
||||
// _outMessages.add(msg);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next locally destined message, or null if none are available.
|
||||
*
|
||||
*/
|
||||
public ClientMessage getNextLocal() {
|
||||
synchronized (_inMessages) {
|
||||
if (_inMessages.size() <= 0) return null;
|
||||
return (ClientMessage)_inMessages.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next remotely destined message, or null if none are available.
|
||||
*
|
||||
*/
|
||||
public ClientMessage getNextRemote() {
|
||||
synchronized (_outMessages) {
|
||||
if (_outMessages.size() <= 0) return null;
|
||||
return (ClientMessage)_outMessages.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how many locally bound messages are in the pool
|
||||
*
|
||||
*/
|
||||
public int getLocalCount() {
|
||||
synchronized (_inMessages) {
|
||||
return _inMessages.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how many remotely bound messages are in the pool.
|
||||
*
|
||||
*/
|
||||
public int getRemoteCount() {
|
||||
synchronized (_outMessages) {
|
||||
return _outMessages.size();
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpPoolInfo() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("\nDumping Client Message Pool. Local messages: ").append(getLocalCount()).append(" Remote messages: ").append(getRemoteCount()).append("\n");
|
||||
buf.append("Inbound messages\n");
|
||||
buf.append("----------------------------\n");
|
||||
synchronized (_inMessages) {
|
||||
for (Iterator iter = _inMessages.iterator(); iter.hasNext();) {
|
||||
ClientMessage msg = (ClientMessage)iter.next();
|
||||
buf.append(msg).append("\n\n");
|
||||
}
|
||||
}
|
||||
buf.append("Outbound messages\n");
|
||||
buf.append("----------------------------\n");
|
||||
synchronized (_outMessages) {
|
||||
for (Iterator iter = _outMessages.iterator(); iter.hasNext();) {
|
||||
ClientMessage msg = (ClientMessage)iter.next();
|
||||
buf.append(msg).append("\n\n");
|
||||
}
|
||||
}
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
}
|
||||
175
router/java/src/net/i2p/router/ClientTunnelSettings.java
Normal file
175
router/java/src/net/i2p/router/ClientTunnelSettings.java
Normal file
@@ -0,0 +1,175 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.Properties;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Wrap up the client settings specifying their tunnel criteria
|
||||
*
|
||||
*/
|
||||
public class ClientTunnelSettings {
|
||||
private int _numInbound;
|
||||
private int _numOutbound;
|
||||
private int _depthInbound;
|
||||
private int _depthOutbound;
|
||||
private long _msgsPerMinuteAvgInbound;
|
||||
private long _bytesPerMinuteAvgInbound;
|
||||
private long _msgsPerMinutePeakInbound;
|
||||
private long _bytesPerMinutePeakInbound;
|
||||
private boolean _includeDummyInbound;
|
||||
private boolean _includeDummyOutbound;
|
||||
private boolean _reorderInbound;
|
||||
private boolean _reorderOutbound;
|
||||
private long _inboundDuration;
|
||||
private boolean _enforceStrictMinimumLength;
|
||||
|
||||
public final static String PROP_NUM_INBOUND = "tunnels.numInbound";
|
||||
public final static String PROP_NUM_OUTBOUND = "tunnels.numOutbound";
|
||||
public final static String PROP_DEPTH_INBOUND = "tunnels.depthInbound";
|
||||
public final static String PROP_DEPTH_OUTBOUND = "tunnels.depthOutbound";
|
||||
public final static String PROP_MSGS_AVG = "tunnels.messagesPerMinuteAverage";
|
||||
public final static String PROP_MSGS_PEAK = "tunnels.messagesPerMinutePeak";
|
||||
public final static String PROP_BYTES_AVG = "tunnels.bytesPerMinuteAverage";
|
||||
public final static String PROP_BYTES_PEAK = "tunnels.bytesPerMinutePeak";
|
||||
public final static String PROP_DUMMY_INBOUND = "tunnels.includeDummyTrafficInbound";
|
||||
public final static String PROP_DUMMY_OUTBOUND = "tunnels.includeDummyTrafficOutbound";
|
||||
public final static String PROP_REORDER_INBOUND = "tunnels.reorderInboundMessages";
|
||||
public final static String PROP_REORDER_OUTBOUND = "tunnels.reoderOutboundMessages";
|
||||
public final static String PROP_DURATION = "tunnels.tunnelDuration";
|
||||
/**
|
||||
* if tunnels.strictMinimumLength=true then never accept a tunnel shorter than the client's
|
||||
* request, otherwise we'll try to meet that minimum, but if we don't have any that length,
|
||||
* we'll accept the longest we do have.
|
||||
*
|
||||
*/
|
||||
public final static String PROP_STRICT_MINIMUM_LENGTH = "tunnels.enforceStrictMinimumLength";
|
||||
|
||||
public final static int DEFAULT_NUM_INBOUND = 2;
|
||||
public final static int DEFAULT_NUM_OUTBOUND = 1;
|
||||
public final static int DEFAULT_DEPTH_INBOUND = 2;
|
||||
public final static int DEFAULT_DEPTH_OUTBOUND = 2;
|
||||
public final static long DEFAULT_MSGS_AVG = 0;
|
||||
public final static long DEFAULT_MSGS_PEAK = 0;
|
||||
public final static long DEFAULT_BYTES_AVG = 0;
|
||||
public final static long DEFAULT_BYTES_PEAK = 0;
|
||||
public final static boolean DEFAULT_DUMMY_INBOUND = false;
|
||||
public final static boolean DEFAULT_DUMMY_OUTBOUND = false;
|
||||
public final static boolean DEFAULT_REORDER_INBOUND = false;
|
||||
public final static boolean DEFAULT_REORDER_OUTBOUND = false;
|
||||
public final static long DEFAULT_DURATION = 10*60*1000;
|
||||
public final static boolean DEFAULT_STRICT_MINIMUM_LENGTH = true;
|
||||
|
||||
public ClientTunnelSettings() {
|
||||
_numInbound = 0;
|
||||
_numOutbound = 0;
|
||||
_depthInbound = 0;
|
||||
_depthOutbound = 0;
|
||||
_msgsPerMinuteAvgInbound = 0;
|
||||
_bytesPerMinuteAvgInbound = 0;
|
||||
_msgsPerMinutePeakInbound = 0;
|
||||
_bytesPerMinutePeakInbound = 0;
|
||||
_includeDummyInbound = false;
|
||||
_includeDummyOutbound = false;
|
||||
_reorderInbound = false;
|
||||
_reorderOutbound = false;
|
||||
_inboundDuration = -1;
|
||||
_enforceStrictMinimumLength = false;
|
||||
}
|
||||
|
||||
public int getNumInboundTunnels() { return _numInbound; }
|
||||
public int getNumOutboundTunnels() { return _numOutbound; }
|
||||
public int getDepthInbound() { return _depthInbound; }
|
||||
public int getDepthOutbound() { return _depthOutbound; }
|
||||
public long getMessagesPerMinuteInboundAverage() { return _msgsPerMinuteAvgInbound; }
|
||||
public long getMessagesPerMinuteInboundPeak() { return _msgsPerMinutePeakInbound; }
|
||||
public long getBytesPerMinuteInboundAverage() { return _bytesPerMinuteAvgInbound; }
|
||||
public long getBytesPerMinuteInboundPeak() { return _bytesPerMinutePeakInbound; }
|
||||
public boolean getIncludeDummyInbound() { return _includeDummyInbound; }
|
||||
public boolean getIncludeDummyOutbound() { return _includeDummyOutbound; }
|
||||
public boolean getReorderInbound() { return _reorderInbound; }
|
||||
public boolean getReorderOutbound() { return _reorderOutbound; }
|
||||
public long getInboundDuration() { return _inboundDuration; }
|
||||
public boolean getEnforceStrictMinimumLength() { return _enforceStrictMinimumLength; }
|
||||
|
||||
public void setNumInboundTunnels(int num) { _numInbound = num; }
|
||||
public void setNumOutboundTunnels(int num) { _numOutbound = num; }
|
||||
public void setEnforceStrictMinimumLength(boolean enforce) { _enforceStrictMinimumLength = enforce; }
|
||||
|
||||
public void readFromProperties(Properties props) {
|
||||
_numInbound = getInt(props.getProperty(PROP_NUM_INBOUND), DEFAULT_NUM_INBOUND);
|
||||
_numOutbound = getInt(props.getProperty(PROP_NUM_OUTBOUND), DEFAULT_NUM_OUTBOUND);
|
||||
_depthInbound = getInt(props.getProperty(PROP_DEPTH_INBOUND), DEFAULT_DEPTH_INBOUND);
|
||||
_depthOutbound = getInt(props.getProperty(PROP_DEPTH_OUTBOUND), DEFAULT_DEPTH_OUTBOUND);
|
||||
_msgsPerMinuteAvgInbound = getLong(props.getProperty(PROP_MSGS_AVG), DEFAULT_MSGS_AVG);
|
||||
_bytesPerMinuteAvgInbound = getLong(props.getProperty(PROP_MSGS_PEAK), DEFAULT_BYTES_AVG);
|
||||
_msgsPerMinutePeakInbound = getLong(props.getProperty(PROP_BYTES_AVG), DEFAULT_MSGS_PEAK);
|
||||
_bytesPerMinutePeakInbound = getLong(props.getProperty(PROP_BYTES_PEAK), DEFAULT_BYTES_PEAK);
|
||||
_includeDummyInbound = getBoolean(props.getProperty(PROP_DUMMY_INBOUND), DEFAULT_DUMMY_INBOUND);
|
||||
_includeDummyOutbound = getBoolean(props.getProperty(PROP_DUMMY_OUTBOUND), DEFAULT_DUMMY_OUTBOUND);
|
||||
_reorderInbound = getBoolean(props.getProperty(PROP_REORDER_INBOUND), DEFAULT_REORDER_INBOUND);
|
||||
_reorderOutbound = getBoolean(props.getProperty(PROP_REORDER_OUTBOUND), DEFAULT_REORDER_OUTBOUND);
|
||||
_inboundDuration = getLong(props.getProperty(PROP_DURATION), DEFAULT_DURATION);
|
||||
_enforceStrictMinimumLength = getBoolean(props.getProperty(PROP_STRICT_MINIMUM_LENGTH), DEFAULT_STRICT_MINIMUM_LENGTH);
|
||||
}
|
||||
|
||||
public void writeToProperties(Properties props) {
|
||||
if (props == null) return;
|
||||
props.setProperty(PROP_NUM_INBOUND, ""+_numInbound);
|
||||
props.setProperty(PROP_NUM_OUTBOUND, ""+_numOutbound);
|
||||
props.setProperty(PROP_DEPTH_INBOUND, ""+_depthInbound);
|
||||
props.setProperty(PROP_DEPTH_OUTBOUND, ""+_depthOutbound);
|
||||
props.setProperty(PROP_MSGS_AVG, ""+_msgsPerMinuteAvgInbound);
|
||||
props.setProperty(PROP_MSGS_PEAK, ""+_msgsPerMinutePeakInbound);
|
||||
props.setProperty(PROP_BYTES_AVG, ""+_bytesPerMinuteAvgInbound);
|
||||
props.setProperty(PROP_BYTES_PEAK, ""+_bytesPerMinutePeakInbound);
|
||||
props.setProperty(PROP_DUMMY_INBOUND, (_includeDummyInbound ? Boolean.TRUE.toString() : Boolean.FALSE.toString()));
|
||||
props.setProperty(PROP_DUMMY_OUTBOUND, (_includeDummyOutbound ? Boolean.TRUE.toString() : Boolean.FALSE.toString()));
|
||||
props.setProperty(PROP_REORDER_INBOUND, (_reorderInbound ? Boolean.TRUE.toString() : Boolean.FALSE.toString()));
|
||||
props.setProperty(PROP_REORDER_OUTBOUND, (_reorderOutbound ? Boolean.TRUE.toString() : Boolean.FALSE.toString()));
|
||||
props.setProperty(PROP_DURATION, ""+_inboundDuration);
|
||||
props.setProperty(PROP_STRICT_MINIMUM_LENGTH, (_enforceStrictMinimumLength ? Boolean.TRUE.toString() : Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
Properties p = new Properties();
|
||||
writeToProperties(p);
|
||||
buf.append("Client tunnel settings:\n");
|
||||
buf.append("====================================\n");
|
||||
for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String val = p.getProperty(name);
|
||||
buf.append(name).append(" = [").append(val).append("]\n");
|
||||
}
|
||||
buf.append("====================================\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
////
|
||||
////
|
||||
|
||||
private static final boolean getBoolean(String str, boolean defaultValue) {
|
||||
if (str == null) return defaultValue;
|
||||
String s = str.toUpperCase();
|
||||
boolean v = "TRUE".equals(s) || "YES".equals(s);
|
||||
return v;
|
||||
}
|
||||
private static final int getInt(String str, int defaultValue) { return (int)getLong(str, defaultValue); }
|
||||
private static final long getLong(String str, long defaultValue) {
|
||||
if (str == null) return defaultValue;
|
||||
try {
|
||||
long val = Long.parseLong(str);
|
||||
return val;
|
||||
} catch (NumberFormatException nfe) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
router/java/src/net/i2p/router/CommSystemFacade.java
Normal file
40
router/java/src/net/i2p/router/CommSystemFacade.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.router.transport.CommSystemFacadeImpl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Manages the communication subsystem between peers, including connections,
|
||||
* listeners, transports, connection keys, etc.
|
||||
*
|
||||
*/
|
||||
public abstract class CommSystemFacade implements Service {
|
||||
private static CommSystemFacade _instance = new CommSystemFacadeImpl();
|
||||
public static CommSystemFacade getInstance() { return _instance; }
|
||||
|
||||
// getAddresses
|
||||
// rotateAddress(address)
|
||||
|
||||
public abstract void processMessage(OutNetMessage msg);
|
||||
|
||||
public String renderStatusHTML() { return ""; }
|
||||
|
||||
/** Create the set of RouterAddress structures based on the router's config */
|
||||
public Set createAddresses() { return new HashSet(); }
|
||||
}
|
||||
|
||||
class DummyCommSystemFacade extends CommSystemFacade {
|
||||
public void shutdown() {}
|
||||
public void startup() {}
|
||||
public void processMessage(OutNetMessage msg) { }
|
||||
}
|
||||
62
router/java/src/net/i2p/router/GenerateStatusConsoleJob.java
Normal file
62
router/java/src/net/i2p/router/GenerateStatusConsoleJob.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.util.Log;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GenerateStatusConsoleJob extends JobImpl {
|
||||
private final static Log _log = new Log(GenerateStatusConsoleJob.class);
|
||||
|
||||
private final static long REGENERATE_DELAY_MS = 60*1000; // once per minute update the console
|
||||
public final static String CONFIG_CONSOLE_LOCATION = "routerConsoleFile";
|
||||
public final static String DEFAULT_CONSOLE_LOCATION = "routerConsole.html";
|
||||
|
||||
public final static String PARAM_GENERATE_CONFIG_CONSOLE = "router.generateConsole";
|
||||
public final static boolean DEFAULT_GENERATE_CONFIG_CONSOLE = true;
|
||||
|
||||
private boolean shouldGenerateConsole() {
|
||||
String str = Router.getInstance().getConfigSetting(PARAM_GENERATE_CONFIG_CONSOLE);
|
||||
if ( (str == null) || (str.trim().length() <= 0) )
|
||||
return DEFAULT_GENERATE_CONFIG_CONSOLE;
|
||||
if (Boolean.TRUE.toString().equalsIgnoreCase(str))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getName() { return "Generate Status Console"; }
|
||||
public void runJob() {
|
||||
if (shouldGenerateConsole()) {
|
||||
String consoleHTML = Router.getInstance().renderStatusHTML();
|
||||
writeConsole(consoleHTML);
|
||||
}
|
||||
requeue(REGENERATE_DELAY_MS);
|
||||
}
|
||||
|
||||
private void writeConsole(String html) {
|
||||
String loc = Router.getInstance().getConfigSetting(CONFIG_CONSOLE_LOCATION);
|
||||
if (loc == null)
|
||||
loc = DEFAULT_CONSOLE_LOCATION;
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(loc);
|
||||
fos.write(html.getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the console", ioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
35
router/java/src/net/i2p/router/HandlerJobBuilder.java
Normal file
35
router/java/src/net/i2p/router/HandlerJobBuilder.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.Hash;
|
||||
|
||||
/**
|
||||
* Defines a class that builds jobs to handle a particular message - these
|
||||
* builders are registered with the InNetMessagePool for various I2NP message
|
||||
* types, allowing immediate queueing of a handler job rather than waiting for
|
||||
* a polling job to come pick it up.
|
||||
*
|
||||
*/
|
||||
public interface HandlerJobBuilder {
|
||||
/**
|
||||
* Create a new job to handle the received message.
|
||||
*
|
||||
* @param receivedMessage I2NP message received
|
||||
* @param from router that sent the message (if available)
|
||||
* @param fromHash hash of the routerIdentity of the router that sent the message (if available)
|
||||
* @param replyBlock block with which a reply could be sent (if available)
|
||||
* @return a job or null if no particular job is appropriate (in which case,
|
||||
* the message should go into the inbound message pool)
|
||||
*/
|
||||
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash, SourceRouteBlock replyBlock);
|
||||
}
|
||||
68
router/java/src/net/i2p/router/InNetMessage.java
Normal file
68
router/java/src/net/i2p/router/InNetMessage.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
|
||||
/**
|
||||
* Wrap an I2NP message received from the network prior to handling and processing.
|
||||
*
|
||||
*/
|
||||
public class InNetMessage {
|
||||
private I2NPMessage _message;
|
||||
private RouterIdentity _fromRouter;
|
||||
private Hash _fromRouterHash;
|
||||
private SourceRouteBlock _replyBlock;
|
||||
|
||||
public InNetMessage() {
|
||||
setMessage(null);
|
||||
setFromRouter(null);
|
||||
setFromRouterHash(null);
|
||||
setReplyBlock(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message
|
||||
*
|
||||
*/
|
||||
public I2NPMessage getMessage() { return _message; }
|
||||
public void setMessage(I2NPMessage msg) { _message = msg; }
|
||||
|
||||
/**
|
||||
* Hash of the router identity from which this message was received, if availale
|
||||
*
|
||||
*/
|
||||
public Hash getFromRouterHash() { return _fromRouterHash; }
|
||||
public void setFromRouterHash(Hash routerIdentHash) { _fromRouterHash = routerIdentHash; }
|
||||
|
||||
/**
|
||||
* Router identity from which this message was received, if availale
|
||||
*
|
||||
*/
|
||||
public RouterIdentity getFromRouter() { return _fromRouter; }
|
||||
public void setFromRouter(RouterIdentity router) { _fromRouter = router; }
|
||||
|
||||
/**
|
||||
* Retrieve any source route block supplied with this message for replies
|
||||
*
|
||||
* @return source route block, or null if it was not supplied /or/ if it was already
|
||||
* used in an ack
|
||||
*/
|
||||
public SourceRouteBlock getReplyBlock() { return _replyBlock; }
|
||||
public void setReplyBlock(SourceRouteBlock block) { _replyBlock = block; }
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append("InNetMessage: from [").append(getFromRouter()).append("] aka [").append(getFromRouterHash()).append("] message: ").append(getMessage());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
181
router/java/src/net/i2p/router/InNetMessagePool.java
Normal file
181
router/java/src/net/i2p/router/InNetMessagePool.java
Normal file
@@ -0,0 +1,181 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.stat.StatManager;
|
||||
|
||||
/**
|
||||
* Manage a pool of inbound InNetMessages. This pool is filled by the
|
||||
* Network communication system when it receives messages, and various jobs
|
||||
* periodically retrieve them for processing.
|
||||
*
|
||||
*/
|
||||
public class InNetMessagePool {
|
||||
private final static Log _log = new Log(InNetMessagePool.class);
|
||||
private static InNetMessagePool _instance = new InNetMessagePool();
|
||||
public final static InNetMessagePool getInstance() { return _instance; }
|
||||
private List _messages;
|
||||
private Map _handlerJobBuilders;
|
||||
|
||||
private InNetMessagePool() {
|
||||
_messages = new ArrayList();
|
||||
_handlerJobBuilders = new HashMap();
|
||||
StatManager.getInstance().createFrequencyStat("inNetPool.dropped", "How frequently we drop a message", "InNetPool", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createFrequencyStat("inNetPool.duplicate", "How frequently we receive a duplicate message", "InNetPool", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
}
|
||||
|
||||
public HandlerJobBuilder registerHandlerJobBuilder(int i2npMessageType, HandlerJobBuilder builder) {
|
||||
return (HandlerJobBuilder)_handlerJobBuilders.put(new Integer(i2npMessageType), builder);
|
||||
}
|
||||
|
||||
public HandlerJobBuilder unregisterHandlerJobBuilder(int i2npMessageType) {
|
||||
return (HandlerJobBuilder)_handlerJobBuilders.remove(new Integer(i2npMessageType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new message to the pool, returning the number of messages in the
|
||||
* pool so that the comm system can throttle inbound messages. If there is
|
||||
* a HandlerJobBuilder for the inbound message type, the message is loaded
|
||||
* into a job created by that builder and queued up for processing instead
|
||||
* (though if the builder doesn't create a job, it is added to the pool)
|
||||
*
|
||||
*/
|
||||
public int add(InNetMessage msg) {
|
||||
Date exp = msg.getMessage().getMessageExpiration();
|
||||
boolean valid = MessageValidator.getInstance().validateMessage(msg.getMessage().getUniqueId(), exp.getTime());
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate message received [" + msg.getMessage().getUniqueId() + " expiring on " + exp + "]: " + msg.getMessage().getClass().getName());
|
||||
StatManager.getInstance().updateFrequency("inNetPool.dropped");
|
||||
StatManager.getInstance().updateFrequency("inNetPool.duplicate");
|
||||
MessageHistory.getInstance().droppedOtherMessage(msg.getMessage());
|
||||
MessageHistory.getInstance().messageProcessingError(msg.getMessage().getUniqueId(), msg.getMessage().getClass().getName(), "Duplicate/expired");
|
||||
return -1;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message received [" + msg.getMessage().getUniqueId() + " expiring on " + exp + "] is NOT a duplicate or exipired");
|
||||
}
|
||||
|
||||
int size = -1;
|
||||
int type = msg.getMessage().getType();
|
||||
HandlerJobBuilder builder = (HandlerJobBuilder)_handlerJobBuilders.get(new Integer(type));
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Add message to the inNetMessage pool - builder: " + builder + " message class: " + msg.getMessage().getClass().getName());
|
||||
|
||||
if (builder != null) {
|
||||
Job job = builder.createJob(msg.getMessage(), msg.getFromRouter(), msg.getFromRouterHash(), msg.getReplyBlock());
|
||||
if (job != null) {
|
||||
JobQueue.getInstance().addJob(job);
|
||||
synchronized (_messages) {
|
||||
size = _messages.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List origMessages = OutboundMessageRegistry.getInstance().getOriginalMessages(msg.getMessage());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Original messages for inbound message: " + origMessages.size());
|
||||
if (origMessages.size() > 1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Orig: " + origMessages + " \nthe above are replies for: " + msg, new Exception("Multiple matches"));
|
||||
}
|
||||
|
||||
for (int i = 0; i < origMessages.size(); i++) {
|
||||
OutNetMessage omsg = (OutNetMessage)origMessages.get(i);
|
||||
ReplyJob job = omsg.getOnReplyJob();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Original message [" + i + "] " + omsg.getReplySelector() + " : " + omsg + ": reply job: " + job);
|
||||
|
||||
if (job != null) {
|
||||
job.setMessage(msg.getMessage());
|
||||
JobQueue.getInstance().addJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (origMessages.size() <= 0) {
|
||||
// not handled as a reply
|
||||
if (size == -1) {
|
||||
// was not handled via HandlerJobBuilder
|
||||
MessageHistory.getInstance().droppedOtherMessage(msg.getMessage());
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message " + msg.getMessage() + " was not handled by a HandlerJobBuilder - DROPPING: " + msg, new Exception("DROPPED MESSAGE"));
|
||||
StatManager.getInstance().updateFrequency("inNetPool.dropped");
|
||||
//_log.error("Pending registry: \n" + OutboundMessageRegistry.getInstance().renderStatusHTML());
|
||||
} else {
|
||||
String mtype = msg.getMessage().getClass().getName();
|
||||
MessageHistory.getInstance().receiveMessage(mtype, msg.getMessage().getUniqueId(), msg.getMessage().getMessageExpiration(), msg.getFromRouterHash(), true);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
String mtype = msg.getMessage().getClass().getName();
|
||||
MessageHistory.getInstance().receiveMessage(mtype, msg.getMessage().getUniqueId(), msg.getMessage().getMessageExpiration(), msg.getFromRouterHash(), true);
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove up to maxNumMessages InNetMessages from the pool and return them.
|
||||
*
|
||||
*/
|
||||
public List getNext(int maxNumMessages) {
|
||||
ArrayList msgs = new ArrayList(maxNumMessages);
|
||||
synchronized (_messages) {
|
||||
for (int i = 0; (i < maxNumMessages) && (_messages.size() > 0); i++)
|
||||
msgs.add(_messages.remove(0));
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next message
|
||||
*
|
||||
*/
|
||||
public InNetMessage getNext() {
|
||||
synchronized (_messages) {
|
||||
if (_messages.size() <= 0) return null;
|
||||
return (InNetMessage)_messages.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the pool
|
||||
*
|
||||
*/
|
||||
public int getCount() {
|
||||
synchronized (_messages) {
|
||||
return _messages.size();
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpPoolInfo() {
|
||||
if (!_log.shouldLog(Log.DEBUG)) return;
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("\nDumping Inbound Network Message Pool. Total # message: ").append(getCount()).append("\n");
|
||||
synchronized (_messages) {
|
||||
for (Iterator iter = _messages.iterator(); iter.hasNext();) {
|
||||
InNetMessage msg = (InNetMessage)iter.next();
|
||||
buf.append("Message ").append(msg.getMessage()).append("\n\n");
|
||||
}
|
||||
}
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
}
|
||||
41
router/java/src/net/i2p/router/Job.java
Normal file
41
router/java/src/net/i2p/router/Job.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Defines an executable task
|
||||
*
|
||||
*/
|
||||
public interface Job {
|
||||
/**
|
||||
* Descriptive name of the task
|
||||
*/
|
||||
public String getName();
|
||||
/** unique id */
|
||||
public int getJobId();
|
||||
/**
|
||||
* Timing criteria for the task
|
||||
*/
|
||||
public JobTiming getTiming();
|
||||
/**
|
||||
* Actually perform the task. This call blocks until the Job is complete.
|
||||
*/
|
||||
public void runJob();
|
||||
|
||||
public Exception getAddedBy();
|
||||
|
||||
/**
|
||||
* the router is extremely overloaded, so this job has been dropped. if for
|
||||
* some reason the job *must* do some cleanup / requeueing of other tasks, it
|
||||
* should do so here.
|
||||
*
|
||||
*/
|
||||
public void dropped();
|
||||
}
|
||||
52
router/java/src/net/i2p/router/JobImpl.java
Normal file
52
router/java/src/net/i2p/router/JobImpl.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.util.Clock;
|
||||
/**
|
||||
* Base implementation of a Job
|
||||
*/
|
||||
public abstract class JobImpl implements Job {
|
||||
private JobTiming _timing;
|
||||
private static int _idSrc = 0;
|
||||
private int _id;
|
||||
private Exception _addedBy;
|
||||
private long _madeReadyOn;
|
||||
|
||||
public JobImpl() {
|
||||
_timing = new JobTiming();
|
||||
_id = ++_idSrc;
|
||||
_addedBy = null;
|
||||
_madeReadyOn = 0;
|
||||
}
|
||||
|
||||
public int getJobId() { return _id; }
|
||||
public JobTiming getTiming() { return _timing; }
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(super.toString());
|
||||
buf.append(": Job ").append(_id).append(": ").append(getName());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
void addedToQueue() {
|
||||
_addedBy = new Exception();
|
||||
}
|
||||
|
||||
public Exception getAddedBy() { return _addedBy; }
|
||||
public long getMadeReadyOn() { return _madeReadyOn; }
|
||||
public void madeReady() { _madeReadyOn = Clock.getInstance().now(); }
|
||||
public void dropped() {}
|
||||
|
||||
protected void requeue(long delayMs) {
|
||||
getTiming().setStartAfter(Clock.getInstance().now() + delayMs);
|
||||
JobQueue.getInstance().addJob(this);
|
||||
}
|
||||
}
|
||||
748
router/java/src/net/i2p/router/JobQueue.java
Normal file
748
router/java/src/net/i2p/router/JobQueue.java
Normal file
@@ -0,0 +1,748 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.router.message.HandleSourceRouteReplyMessageJob;
|
||||
import net.i2p.router.networkdb.HandleDatabaseLookupMessageJob;
|
||||
import net.i2p.router.tunnelmanager.HandleTunnelCreateMessageJob;
|
||||
import net.i2p.router.tunnelmanager.RequestTunnelJob;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Manage the pending jobs according to whatever algorithm is appropriate, giving
|
||||
* preference to earlier scheduled jobs.
|
||||
*
|
||||
*/
|
||||
public class JobQueue {
|
||||
private final static Log _log = new Log(JobQueue.class);
|
||||
private static JobQueue _instance = new JobQueue();
|
||||
public static JobQueue getInstance() { return _instance; }
|
||||
|
||||
/** Integer (runnerId) to JobQueueRunner for created runners */
|
||||
private static HashMap _queueRunners;
|
||||
/** a counter to identify a job runner */
|
||||
private volatile static int _runnerId = 0;
|
||||
/** list of jobs that are ready to run ASAP */
|
||||
private LinkedList _readyJobs;
|
||||
/** list of jobs that are scheduled for running in the future */
|
||||
private LinkedList _timedJobs;
|
||||
/** when true, don't run any new jobs or update any limits, etc */
|
||||
private boolean _paused;
|
||||
/** job name to JobStat for that job */
|
||||
private static TreeMap _jobStats;
|
||||
/** how many job queue runners can go concurrently */
|
||||
private int _maxRunners;
|
||||
private QueuePumper _pumper;
|
||||
/** will we allow the # job runners to grow beyond 1? */
|
||||
private boolean _allowParallelOperation;
|
||||
/** have we been killed or are we alive? */
|
||||
private boolean _alive;
|
||||
|
||||
/** default max # job queue runners operating */
|
||||
private final static int DEFAULT_MAX_RUNNERS = 1;
|
||||
/** router.config parameter to override the max runners */
|
||||
private final static String PROP_MAX_RUNNERS = "router.maxJobRunners";
|
||||
|
||||
/** how frequently should we check and update the max runners */
|
||||
private final static long MAX_LIMIT_UPDATE_DELAY = 60*1000;
|
||||
|
||||
/** if a job is this lagged, spit out a warning, but keep going */
|
||||
private long _lagWarning = DEFAULT_LAG_WARNING;
|
||||
private final static long DEFAULT_LAG_WARNING = 5*1000;
|
||||
private final static String PROP_LAG_WARNING = "router.jobLagWarning";
|
||||
|
||||
/** if a job is this lagged, the router is hosed, so shut it down */
|
||||
private long _lagFatal = DEFAULT_LAG_FATAL;
|
||||
private final static long DEFAULT_LAG_FATAL = 30*1000;
|
||||
private final static String PROP_LAG_FATAL = "router.jobLagFatal";
|
||||
|
||||
/** if a job takes this long to run, spit out a warning, but keep going */
|
||||
private long _runWarning = DEFAULT_RUN_WARNING;
|
||||
private final static long DEFAULT_RUN_WARNING = 5*1000;
|
||||
private final static String PROP_RUN_WARNING = "router.jobRunWarning";
|
||||
|
||||
/** if a job takes this long to run, the router is hosed, so shut it down */
|
||||
private long _runFatal = DEFAULT_RUN_FATAL;
|
||||
private final static long DEFAULT_RUN_FATAL = 30*1000;
|
||||
private final static String PROP_RUN_FATAL = "router.jobRunFatal";
|
||||
|
||||
/** don't enforce fatal limits until the router has been up for this long */
|
||||
private long _warmupTime = DEFAULT_WARMUP_TIME;
|
||||
private final static long DEFAULT_WARMUP_TIME = 10*60*1000;
|
||||
private final static String PROP_WARMUM_TIME = "router.jobWarmupTime";
|
||||
|
||||
/** max ready and waiting jobs before we start dropping 'em */
|
||||
private int _maxWaitingJobs = DEFAULT_MAX_WAITING_JOBS;
|
||||
private final static int DEFAULT_MAX_WAITING_JOBS = 20;
|
||||
private final static String PROP_MAX_WAITING_JOBS = "router.maxWaitingJobs";
|
||||
|
||||
static {
|
||||
StatManager.getInstance().createRateStat("jobQueue.readyJobs", "How many ready and waiting jobs there are?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("jobQueue.droppedJobs", "How many jobs do we drop due to insane overload?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
}
|
||||
|
||||
/**
|
||||
* queue runners wait on this whenever they're not doing anything, and
|
||||
* this gets notified *once* whenever there are ready jobs
|
||||
*/
|
||||
private Object _runnerLock = new Object();
|
||||
|
||||
private JobQueue() {
|
||||
_alive = true;
|
||||
_readyJobs = new LinkedList();
|
||||
_timedJobs = new LinkedList();
|
||||
_queueRunners = new HashMap();
|
||||
_paused = false;
|
||||
_jobStats = new TreeMap();
|
||||
_allowParallelOperation = false;
|
||||
_pumper = new QueuePumper();
|
||||
I2PThread pumperThread = new I2PThread(_pumper);
|
||||
pumperThread.setDaemon(true);
|
||||
pumperThread.setName("QueuePumper");
|
||||
pumperThread.setPriority(I2PThread.MIN_PRIORITY);
|
||||
pumperThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the specified job
|
||||
*
|
||||
*/
|
||||
public void addJob(Job job) {
|
||||
if (job == null) return;
|
||||
|
||||
if (job instanceof JobImpl)
|
||||
((JobImpl)job).addedToQueue();
|
||||
|
||||
boolean isReady = false;
|
||||
long numReady = 0;
|
||||
boolean alreadyExists = false;
|
||||
synchronized (_readyJobs) {
|
||||
if (_readyJobs.contains(job))
|
||||
alreadyExists = true;
|
||||
numReady = _readyJobs.size();
|
||||
}
|
||||
if (!alreadyExists) {
|
||||
synchronized (_timedJobs) {
|
||||
if (_timedJobs.contains(job))
|
||||
alreadyExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
StatManager.getInstance().addRateData("jobQueue.readyJobs", numReady, 0);
|
||||
if (shouldDrop(job, numReady)) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Dropping job due to overload! # ready jobs: " + numReady + ": job = " + job);
|
||||
job.dropped();
|
||||
StatManager.getInstance().addRateData("jobQueue.droppedJobs", 1, 1);
|
||||
awaken(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alreadyExists) {
|
||||
if (job.getTiming().getStartAfter() <= Clock.getInstance().now()) {
|
||||
// don't skew us - its 'start after' its been queued, or later
|
||||
job.getTiming().setStartAfter(Clock.getInstance().now());
|
||||
if (job instanceof JobImpl)
|
||||
((JobImpl)job).madeReady();
|
||||
synchronized (_readyJobs) {
|
||||
_readyJobs.add(job);
|
||||
isReady = true;
|
||||
}
|
||||
} else {
|
||||
synchronized (_timedJobs) {
|
||||
_timedJobs.add(job);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not adding already enqueued job " + job.getName());
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
// wake up at most one runner
|
||||
awaken(1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* are we so overloaded that we should drop the given job?
|
||||
* This is driven both by the numReady and waiting jobs, the type of job
|
||||
* in question, and what the router's router.maxWaitingJobs config parameter
|
||||
* is set to.
|
||||
*
|
||||
*/
|
||||
private boolean shouldDrop(Job job, long numReady) {
|
||||
if (_maxWaitingJobs <= 0) return false; // dont ever drop jobs
|
||||
if (!_allowParallelOperation) return false; // dont drop during startup [duh]
|
||||
Class cls = job.getClass();
|
||||
if (numReady > _maxWaitingJobs) {
|
||||
|
||||
// heavy cpu load, plus we're allowed to be unreliable with these two
|
||||
// [but garlics can contain our payloads, so lets not drop them]
|
||||
//if (cls == HandleGarlicMessageJob.class)
|
||||
// return true;
|
||||
if (cls == HandleSourceRouteReplyMessageJob.class)
|
||||
return true;
|
||||
|
||||
// lets not try to drop too many tunnel messages...
|
||||
//if (cls == HandleTunnelMessageJob.class)
|
||||
// return true;
|
||||
|
||||
// we don't really *need* to answer DB lookup messages
|
||||
if (cls == HandleDatabaseLookupMessageJob.class)
|
||||
return true;
|
||||
|
||||
// tunnels are a bitch, but its dropped() builds a pair of fake ones just in case
|
||||
if (cls == RequestTunnelJob.class)
|
||||
return true;
|
||||
|
||||
// if we're already this loaded, dont take more tunnels
|
||||
if (cls == HandleTunnelCreateMessageJob.class)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void allowParallelOperation() { _allowParallelOperation = true; }
|
||||
void shutdown() { _alive = false; }
|
||||
boolean isAlive() { return _alive; }
|
||||
|
||||
/**
|
||||
* Blocking call to retrieve the next ready job
|
||||
*
|
||||
*/
|
||||
Job getNext() {
|
||||
while (_alive) {
|
||||
while (_paused) {
|
||||
try { Thread.sleep(30); } catch (InterruptedException ie) {}
|
||||
}
|
||||
Job rv = null;
|
||||
int ready = 0;
|
||||
synchronized (_readyJobs) {
|
||||
ready = _readyJobs.size();
|
||||
if (ready > 0)
|
||||
rv = (Job)_readyJobs.remove(0);
|
||||
}
|
||||
if (rv != null) {
|
||||
// we found one, but there may be more, so wake up enough
|
||||
// other runners
|
||||
awaken(ready-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Using a ready job after waking up " + (ready-1) + " others");
|
||||
return rv;
|
||||
}
|
||||
try {
|
||||
synchronized (_runnerLock) {
|
||||
_runnerLock.wait(1000);
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move newly ready timed jobs to the ready queue. Returns the
|
||||
* number of ready jobs after the check is completed
|
||||
*
|
||||
*/
|
||||
private int checkJobTimings() {
|
||||
boolean newJobsReady = false;
|
||||
long now = Clock.getInstance().now();
|
||||
LinkedList toAdd = new LinkedList();
|
||||
synchronized (_timedJobs) {
|
||||
for (int i = 0; i < _timedJobs.size(); i++) {
|
||||
Job j = (Job)_timedJobs.get(i);
|
||||
// find jobs due to start before now
|
||||
if (j.getTiming().getStartAfter() <= now) {
|
||||
if (j instanceof JobImpl)
|
||||
((JobImpl)j).madeReady();
|
||||
|
||||
toAdd.add(j);
|
||||
_timedJobs.remove(i);
|
||||
i--; // so the index stays consistent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ready = 0;
|
||||
synchronized (_readyJobs) {
|
||||
_readyJobs.addAll(toAdd);
|
||||
ready = _readyJobs.size();
|
||||
}
|
||||
|
||||
return ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up the queue with the specified number of concurrent processors.
|
||||
* If this method has already been called, it will adjust the number of
|
||||
* runners to meet the new number. This does not kill jobs running on
|
||||
* excess threads, it merely instructs the threads to die after finishing
|
||||
* the current job.
|
||||
*
|
||||
*/
|
||||
public void runQueue(int numThreads) {
|
||||
synchronized (_queueRunners) {
|
||||
// we're still starting up [serially] and we've got at least one runner,
|
||||
// so dont do anything
|
||||
if ( (_queueRunners.size() > 0) && (!_allowParallelOperation) ) return;
|
||||
|
||||
// we've already enabled parallel operation, so grow to however many are
|
||||
// specified
|
||||
if (_queueRunners.size() < numThreads) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Increasing the number of queue runners from " + _queueRunners.size() + " to " + numThreads);
|
||||
for (int i = _queueRunners.size(); i < numThreads; i++) {
|
||||
JobQueueRunner runner = new JobQueueRunner(i);
|
||||
_queueRunners.put(new Integer(i), runner);
|
||||
Thread t = new I2PThread(runner);
|
||||
t.setName("JobQueue"+(_runnerId++));
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
} else if (_queueRunners.size() == numThreads) {
|
||||
// noop
|
||||
} else { // numThreads < # runners, so shrink
|
||||
//for (int i = _queueRunners.size(); i > numThreads; i++) {
|
||||
// QueueRunner runner = (QueueRunner)_queueRunners.get(new Integer(i));
|
||||
// runner.stopRunning();
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//public void pauseQueue() { _paused = true; }
|
||||
//public void unpauseQueue() { _paused = false; }
|
||||
void removeRunner(int id) { _queueRunners.remove(new Integer(id)); }
|
||||
|
||||
|
||||
/**
|
||||
* Notify a sufficient number of waiting runners, and if necessary, increase
|
||||
* the number of runners (up to maxRunners)
|
||||
*
|
||||
*/
|
||||
private void awaken(int numMadeReady) {
|
||||
// notify a sufficient number of waiting runners
|
||||
for (int i = 0; i < numMadeReady; i++) {
|
||||
synchronized (_runnerLock) {
|
||||
_runnerLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
int numRunners = 0;
|
||||
synchronized (_queueRunners) {
|
||||
numRunners = _queueRunners.size();
|
||||
}
|
||||
|
||||
if (numRunners > 1) {
|
||||
if (numMadeReady > numRunners) {
|
||||
if (numMadeReady < _maxRunners) {
|
||||
_log.info("Too much job contention (" + numMadeReady + " ready and waiting, " + numRunners + " runners exist), adding " + numMadeReady + " new runners (with max " + _maxRunners + ")");
|
||||
runQueue(numMadeReady);
|
||||
} else {
|
||||
_log.info("Too much job contention (" + numMadeReady + " ready and waiting, " + numRunners + " runners exist), increasing to our max of " + _maxRunners + " runners");
|
||||
runQueue(_maxRunners);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for moving jobs from the timed queue to the ready queue,
|
||||
* adjusting the number of queue runners, as well as periodically updating the
|
||||
* max number of runners.
|
||||
*
|
||||
*/
|
||||
private final class QueuePumper implements Runnable, Clock.ClockUpdateListener {
|
||||
private long _lastLimitUpdated;
|
||||
public QueuePumper() {
|
||||
_lastLimitUpdated = 0;
|
||||
Clock.getInstance().addUpdateListener(this);
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
while (_alive) {
|
||||
while (_paused) {
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
// periodically update our max runners limit
|
||||
long now = Clock.getInstance().now();
|
||||
if (now > _lastLimitUpdated + MAX_LIMIT_UPDATE_DELAY) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Updating the limits");
|
||||
updateMaxLimit();
|
||||
updateTimingLimits();
|
||||
_lastLimitUpdated = now;
|
||||
}
|
||||
|
||||
// turn timed jobs into ready jobs
|
||||
int numMadeReady = checkJobTimings();
|
||||
|
||||
awaken(numMadeReady);
|
||||
|
||||
try { Thread.sleep(500); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Clock.getInstance().removeUpdateListener(this);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("wtf, pumper killed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void offsetChanged(long delta) {
|
||||
if (_lastLimitUpdated > 0)
|
||||
_lastLimitUpdated += delta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate and update the job timings
|
||||
* if it was lagged too much or took too long to run, spit out
|
||||
* a warning (and if its really excessive, kill the router)
|
||||
*/
|
||||
void updateStats(Job job, long doStart, long origStartAfter, long duration) {
|
||||
String key = job.getName();
|
||||
long lag = doStart - origStartAfter; // how long were we ready and waiting?
|
||||
MessageHistory hist = MessageHistory.getInstance();
|
||||
long uptime = Router.getInstance().getUptime();
|
||||
|
||||
synchronized (_jobStats) {
|
||||
if (!_jobStats.containsKey(key))
|
||||
_jobStats.put(key, new JobStats(key));
|
||||
JobStats stats = (JobStats)_jobStats.get(key);
|
||||
|
||||
stats.jobRan(duration, lag);
|
||||
}
|
||||
|
||||
String dieMsg = null;
|
||||
boolean dumpRunners = false;
|
||||
|
||||
if (lag > _lagWarning) {
|
||||
dieMsg = "Lag too long for job " + job.getName() + " [" + lag + "ms and a run time of " + duration + "ms]";
|
||||
dumpRunners = true;
|
||||
} else if (duration > _runWarning) {
|
||||
dieMsg = "Job run too long for job " + job.getName() + " [" + lag + "ms lag and run time of " + duration + "ms]";
|
||||
dumpRunners = true;
|
||||
}
|
||||
|
||||
if (dieMsg != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(dieMsg);
|
||||
if (hist != null)
|
||||
hist.messageProcessingError(-1, JobQueue.class.getName(), dieMsg);
|
||||
}
|
||||
|
||||
if (dumpRunners)
|
||||
dumpRunners(true);
|
||||
|
||||
if ( (lag > _lagFatal) && (uptime > _warmupTime) ) {
|
||||
// this is fscking bad - the network at this size shouldn't have this much real contention
|
||||
// so we're going to DIE DIE DIE
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.log(Log.WARN, "The router is either incredibly overloaded or (more likely) there's an error.", new Exception("ttttooooo mmmuuuccccchhhh llllaaagggg"));
|
||||
//try { Thread.sleep(5000); } catch (InterruptedException ie) {}
|
||||
//Router.getInstance().shutdown();
|
||||
return;
|
||||
}
|
||||
if ( (uptime > _warmupTime) && (duration > _runFatal) ) {
|
||||
// slow CPUs can get hosed with ElGamal, but 10s is too much.
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.log(Log.WARN, "The router is incredibly overloaded - either you have a 386, or (more likely) there's an error. ", new Exception("ttttooooo sssllloooowww"));
|
||||
//try { Thread.sleep(5000); } catch (InterruptedException ie) {}
|
||||
//Router.getInstance().shutdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// update config params
|
||||
////
|
||||
|
||||
/**
|
||||
* Update the max number of job queue runners
|
||||
*
|
||||
*/
|
||||
private void updateMaxLimit() {
|
||||
String str = Router.getInstance().getConfigSetting(PROP_MAX_RUNNERS);
|
||||
if (str != null) {
|
||||
try {
|
||||
_maxRunners = Integer.parseInt(str);
|
||||
return;
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid maximum job runners [" + str + "]");
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Defaulting the maximum job runners to " + DEFAULT_MAX_RUNNERS);
|
||||
_maxRunners = DEFAULT_MAX_RUNNERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the job lag and run threshold for warnings and fatalities, as well
|
||||
* as the warmup time before which fatalities will be ignored
|
||||
*
|
||||
*/
|
||||
private void updateTimingLimits() {
|
||||
String str = Router.getInstance().getConfigSetting(PROP_LAG_WARNING);
|
||||
if (str != null) {
|
||||
try {
|
||||
_lagWarning = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid job lag warning [" + str + "]");
|
||||
_lagWarning = DEFAULT_LAG_WARNING;
|
||||
}
|
||||
} else {
|
||||
_lagWarning = DEFAULT_LAG_WARNING;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting the warning job lag time to " + _lagWarning + "ms");
|
||||
|
||||
str = Router.getInstance().getConfigSetting(PROP_LAG_FATAL);
|
||||
if (str != null) {
|
||||
try {
|
||||
_lagFatal = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid job lag fatal [" + str + "]");
|
||||
_lagFatal = DEFAULT_LAG_FATAL;
|
||||
}
|
||||
} else {
|
||||
_lagFatal = DEFAULT_LAG_FATAL;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting the fatal job lag time to " + _lagFatal + "ms");
|
||||
|
||||
str = Router.getInstance().getConfigSetting(PROP_RUN_WARNING);
|
||||
if (str != null) {
|
||||
try {
|
||||
_runWarning = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid job run warning [" + str + "]");
|
||||
_runWarning = DEFAULT_RUN_WARNING;
|
||||
}
|
||||
} else {
|
||||
_runWarning = DEFAULT_RUN_WARNING;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting the warning job run time to " + _runWarning + "ms");
|
||||
|
||||
str = Router.getInstance().getConfigSetting(PROP_RUN_FATAL);
|
||||
if (str != null) {
|
||||
try {
|
||||
_runFatal = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid job run fatal [" + str + "]");
|
||||
_runFatal = DEFAULT_RUN_FATAL;
|
||||
}
|
||||
} else {
|
||||
_runFatal = DEFAULT_RUN_FATAL;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting the fatal job run time to " + _runFatal + "ms");
|
||||
|
||||
str = Router.getInstance().getConfigSetting(PROP_WARMUM_TIME);
|
||||
if (str != null) {
|
||||
try {
|
||||
_warmupTime = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid warmup time [" + str + "]");
|
||||
_warmupTime = DEFAULT_WARMUP_TIME;
|
||||
}
|
||||
} else {
|
||||
_warmupTime = DEFAULT_WARMUP_TIME;
|
||||
}
|
||||
|
||||
str = Router.getInstance().getConfigSetting(PROP_MAX_WAITING_JOBS);
|
||||
if (str != null) {
|
||||
try {
|
||||
_maxWaitingJobs = Integer.parseInt(str);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid max waiting jobs [" + str + "]");
|
||||
_maxWaitingJobs = DEFAULT_MAX_WAITING_JOBS;
|
||||
}
|
||||
} else {
|
||||
_maxWaitingJobs = DEFAULT_MAX_WAITING_JOBS;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Setting the max waiting jobs to " + _maxWaitingJobs);
|
||||
}
|
||||
|
||||
////
|
||||
// the remainder are utility methods for dumping status info
|
||||
////
|
||||
|
||||
public String renderStatusHTML() {
|
||||
LinkedList readyJobs = new LinkedList();
|
||||
LinkedList timedJobs = new LinkedList();
|
||||
LinkedList activeJobs = new LinkedList();
|
||||
synchronized (_readyJobs) { readyJobs.addAll(_readyJobs); }
|
||||
synchronized (_timedJobs) { timedJobs.addAll(_timedJobs); }
|
||||
synchronized (_queueRunners) {
|
||||
for (Iterator iter = _queueRunners.values().iterator(); iter.hasNext();) {
|
||||
JobQueueRunner runner = (JobQueueRunner)iter.next();
|
||||
Job job = runner.getCurrentJob();
|
||||
if (job != null)
|
||||
activeJobs.add(job.getName());
|
||||
}
|
||||
}
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("<h2>JobQueue</h2>");
|
||||
buf.append("# runners: ");
|
||||
synchronized (_queueRunners) {
|
||||
buf.append(_queueRunners.size());
|
||||
}
|
||||
buf.append("<br />\n");
|
||||
buf.append("# active jobs: ").append(activeJobs.size()).append("<ol>\n");
|
||||
for (int i = 0; i < activeJobs.size(); i++) {
|
||||
buf.append("<li>").append(activeJobs.get(i)).append("</li>\n");
|
||||
}
|
||||
buf.append("</ol>\n");
|
||||
buf.append("# ready/waiting jobs: ").append(readyJobs.size()).append(" <i>(lots of these mean there's likely a big problem)</i><ol>\n");
|
||||
for (int i = 0; i < readyJobs.size(); i++) {
|
||||
buf.append("<li>").append(readyJobs.get(i)).append("</li>\n");
|
||||
}
|
||||
buf.append("</ol>\n");
|
||||
|
||||
buf.append("# timed jobs: ").append(timedJobs.size()).append("<ol>\n");
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < timedJobs.size(); i++) {
|
||||
Job j = (Job)timedJobs.get(i);
|
||||
ordered.put(new Long(j.getTiming().getStartAfter()), j);
|
||||
}
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) {
|
||||
Job j = (Job)iter.next();
|
||||
buf.append("<li>").append(j.getName()).append(" @ ").append(new Date(j.getTiming().getStartAfter())).append("</li>\n");
|
||||
}
|
||||
buf.append("</ol>\n");
|
||||
buf.append(getJobStats());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** render the HTML for the job stats */
|
||||
private String getJobStats() {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("<table border=\"1\">\n");
|
||||
buf.append("<tr><td><b>Job</b></td><td><b>Runs</b></td>");
|
||||
buf.append("<td><b>Time</b></td><td><b><i>Avg</i></b></td><td><b><i>Max</i></b></td><td><b><i>Min</i></b></td>");
|
||||
buf.append("<td><b>Pending</b></td><td><b><i>Avg</i></b></td><td><b><i>Max</i></b></td><td><b><i>Min</i></b></td></tr>\n");
|
||||
long totRuns = 0;
|
||||
long totExecTime = 0;
|
||||
long avgExecTime = 0;
|
||||
long maxExecTime = -1;
|
||||
long minExecTime = -1;
|
||||
long totPendingTime = 0;
|
||||
long avgPendingTime = 0;
|
||||
long maxPendingTime = -1;
|
||||
long minPendingTime = -1;
|
||||
|
||||
TreeMap tstats = null;
|
||||
synchronized (_jobStats) {
|
||||
tstats = (TreeMap)_jobStats.clone();
|
||||
}
|
||||
|
||||
for (Iterator iter = tstats.values().iterator(); iter.hasNext(); ) {
|
||||
JobStats stats = (JobStats)iter.next();
|
||||
buf.append("<tr>");
|
||||
buf.append("<td><b>").append(stats.getName()).append("</b></td>");
|
||||
buf.append("<td>").append(stats.getRuns()).append("</td>");
|
||||
buf.append("<td>").append(stats.getTotalTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getAvgTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getMaxTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getMinTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getTotalPendingTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getAvgPendingTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getMaxPendingTime()).append("</td>");
|
||||
buf.append("<td>").append(stats.getMinPendingTime()).append("</td>");
|
||||
buf.append("</tr>\n");
|
||||
totRuns += stats.getRuns();
|
||||
totExecTime += stats.getTotalTime();
|
||||
if (stats.getMaxTime() > maxExecTime)
|
||||
maxExecTime = stats.getMaxTime();
|
||||
if ( (minExecTime < 0) || (minExecTime > stats.getMinTime()) )
|
||||
minExecTime = stats.getMinTime();
|
||||
totPendingTime += stats.getTotalPendingTime();
|
||||
if (stats.getMaxPendingTime() > maxPendingTime)
|
||||
maxPendingTime = stats.getMaxPendingTime();
|
||||
if ( (minPendingTime < 0) || (minPendingTime > stats.getMinPendingTime()) )
|
||||
minPendingTime = stats.getMinPendingTime();
|
||||
}
|
||||
|
||||
if (totRuns != 0) {
|
||||
if (totExecTime != 0)
|
||||
avgExecTime = totExecTime / totRuns;
|
||||
if (totPendingTime != 0)
|
||||
avgPendingTime = totPendingTime / totRuns;
|
||||
}
|
||||
|
||||
buf.append("<tr><td colspan=\"10\"><hr /></td><tr>");
|
||||
buf.append("<tr>");
|
||||
buf.append("<td><i><b>").append("SUMMARY").append("</b></i></td>");
|
||||
buf.append("<td><i>").append(totRuns).append("</i></td>");
|
||||
buf.append("<td><i>").append(totExecTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(avgExecTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(maxExecTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(minExecTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(totPendingTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(avgPendingTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(maxPendingTime).append("</i></td>");
|
||||
buf.append("<td><i>").append(minPendingTime).append("</i></td>");
|
||||
buf.append("</tr>\n");
|
||||
|
||||
buf.append("</table>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log what each queue runner is doing at the moment
|
||||
*
|
||||
*/
|
||||
void dumpRunners() { dumpRunners(false); }
|
||||
/** if asError, dump the job runners in an error message, else as a debug message */
|
||||
void dumpRunners(boolean asError) {
|
||||
if (!asError && (!_log.shouldLog(Log.DEBUG)) ) return;
|
||||
if (asError && (!_log.shouldLog(Log.WARN)) ) return;
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("Queue runners:\n");
|
||||
synchronized (_queueRunners) {
|
||||
for (Iterator iter = _queueRunners.values().iterator(); iter.hasNext(); ) {
|
||||
JobQueueRunner runner = (JobQueueRunner)iter.next();
|
||||
Job job = runner.getCurrentJob();
|
||||
int id = runner.getRunnerId();
|
||||
buf.append("* Runner ").append(id).append(": \t");
|
||||
if (job == null)
|
||||
buf.append("no job\n");
|
||||
else
|
||||
buf.append(job.getName()).append('\n');
|
||||
}
|
||||
}
|
||||
synchronized (_timedJobs) {
|
||||
buf.append("** Timed jobs: \t").append(_timedJobs.size()).append('\n');
|
||||
}
|
||||
synchronized (_readyJobs) {
|
||||
buf.append("** Ready jobs: \t").append(_readyJobs.size()).append('\n');
|
||||
}
|
||||
|
||||
if (asError)
|
||||
_log.warn(buf.toString());
|
||||
else
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
}
|
||||
109
router/java/src/net/i2p/router/JobQueueRunner.java
Normal file
109
router/java/src/net/i2p/router/JobQueueRunner.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import net.i2p.stat.StatManager;
|
||||
|
||||
/** a do run run run a do run run */
|
||||
class JobQueueRunner implements Runnable {
|
||||
private final static Log _log = new Log(JobQueueRunner.class);
|
||||
private boolean _keepRunning;
|
||||
private int _id;
|
||||
private long _numJobs;
|
||||
private Job _currentJob;
|
||||
|
||||
static {
|
||||
StatManager.getInstance().createRateStat("jobQueue.jobRun", "How long jobs take", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("jobQueue.jobLag", "How long jobs have to wait before running", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("jobQueue.jobWait", "How long does a job sat on the job queue?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("jobQueue.jobRunnerInactive", "How long are runners inactive?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
}
|
||||
|
||||
public JobQueueRunner(int id) {
|
||||
_id = id;
|
||||
_keepRunning = true;
|
||||
_numJobs = 0;
|
||||
_currentJob = null;
|
||||
}
|
||||
public Job getCurrentJob() { return _currentJob; }
|
||||
public int getRunnerId() { return _id; }
|
||||
public void stopRunning() { _keepRunning = false; }
|
||||
public void run() {
|
||||
long lastActive = Clock.getInstance().now();;
|
||||
while ( (_keepRunning) && (JobQueue.getInstance().isAlive()) ) {
|
||||
try {
|
||||
Job job = JobQueue.getInstance().getNext();
|
||||
if (job == null) continue;
|
||||
long now = Clock.getInstance().now();
|
||||
|
||||
long enqueuedTime = 0;
|
||||
if (job instanceof JobImpl) {
|
||||
long when = ((JobImpl)job).getMadeReadyOn();
|
||||
if (when <= 0) {
|
||||
_log.error("Job was not made ready?! " + job, new Exception("Not made ready?!"));
|
||||
} else {
|
||||
enqueuedTime = now - when;
|
||||
}
|
||||
}
|
||||
|
||||
long betweenJobs = now - lastActive;
|
||||
StatManager.getInstance().addRateData("jobQueue.jobRunnerInactive", betweenJobs, betweenJobs);
|
||||
_currentJob = job;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Runner " + _id + " running job " + job.getJobId() + ": " + job.getName());
|
||||
long origStartAfter = job.getTiming().getStartAfter();
|
||||
long doStart = Clock.getInstance().now();
|
||||
job.getTiming().start();
|
||||
runCurrentJob();
|
||||
job.getTiming().end();
|
||||
long duration = job.getTiming().getActualEnd() - job.getTiming().getActualStart();
|
||||
|
||||
long beforeUpdate = Clock.getInstance().now();
|
||||
JobQueue.getInstance().updateStats(job, doStart, origStartAfter, duration);
|
||||
long diff = Clock.getInstance().now() - beforeUpdate;
|
||||
|
||||
StatManager.getInstance().addRateData("jobQueue.jobRun", duration, duration);
|
||||
StatManager.getInstance().addRateData("jobQueue.jobLag", doStart - origStartAfter, 0);
|
||||
StatManager.getInstance().addRateData("jobQueue.jobWait", enqueuedTime, enqueuedTime);
|
||||
|
||||
if (diff > 100) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Updating statistics for the job took too long [" + diff + "ms]");
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Job duration " + duration + "ms for " + job.getName() + " with lag of " + (doStart-origStartAfter) + "ms");
|
||||
lastActive = Clock.getInstance().now();
|
||||
_currentJob = null;
|
||||
} catch (Throwable t) {
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "WTF, error running?", t);
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Queue runner " + _id + " exiting");
|
||||
JobQueue.getInstance().removeRunner(_id);
|
||||
}
|
||||
|
||||
private void runCurrentJob() {
|
||||
try {
|
||||
_currentJob.runJob();
|
||||
} catch (OutOfMemoryError oom) {
|
||||
try {
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Router ran out of memory, shutting down", oom);
|
||||
Router.getInstance().shutdown();
|
||||
} catch (Throwable t) {
|
||||
System.err.println("***Router ran out of memory, shutting down hard");
|
||||
}
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
System.exit(-1);
|
||||
} catch (Throwable t) {
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Error processing job [" + _currentJob.getName() + "] on thread " + _id + ": " + t.getMessage(), t);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("The above job was enqueued by: ", _currentJob.getAddedBy());
|
||||
JobQueue.getInstance().dumpRunners(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
router/java/src/net/i2p/router/JobStats.java
Normal file
75
router/java/src/net/i2p/router/JobStats.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/** glorified struct to contain basic job stats */
|
||||
class JobStats {
|
||||
private String _job;
|
||||
private long _numRuns;
|
||||
private long _totalTime;
|
||||
private long _maxTime;
|
||||
private long _minTime;
|
||||
private long _totalPendingTime;
|
||||
private long _maxPendingTime;
|
||||
private long _minPendingTime;
|
||||
|
||||
public JobStats(String name) {
|
||||
_job = name;
|
||||
_numRuns = 0;
|
||||
_totalTime = 0;
|
||||
_maxTime = -1;
|
||||
_minTime = -1;
|
||||
_totalPendingTime = 0;
|
||||
_maxPendingTime = -1;
|
||||
_minPendingTime = -1;
|
||||
}
|
||||
|
||||
public void jobRan(long runTime, long lag) {
|
||||
_numRuns++;
|
||||
_totalTime += runTime;
|
||||
if ( (_maxTime < 0) || (runTime > _maxTime) )
|
||||
_maxTime = runTime;
|
||||
if ( (_minTime < 0) || (runTime < _minTime) )
|
||||
_minTime = runTime;
|
||||
_totalPendingTime += lag;
|
||||
if ( (_maxPendingTime < 0) || (lag > _maxPendingTime) )
|
||||
_maxPendingTime = lag;
|
||||
if ( (_minPendingTime < 0) || (lag < _minPendingTime) )
|
||||
_minPendingTime = lag;
|
||||
}
|
||||
|
||||
public String getName() { return _job; }
|
||||
public long getRuns() { return _numRuns; }
|
||||
public long getTotalTime() { return _totalTime; }
|
||||
public long getMaxTime() { return _maxTime; }
|
||||
public long getMinTime() { return _minTime; }
|
||||
public long getAvgTime() { if (_numRuns > 0) return _totalTime / _numRuns; else return 0; }
|
||||
public long getTotalPendingTime() { return _totalPendingTime; }
|
||||
public long getMaxPendingTime() { return _maxPendingTime; }
|
||||
public long getMinPendingTime() { return _minPendingTime; }
|
||||
public long getAvgPendingTime() { if (_numRuns > 0) return _totalPendingTime / _numRuns; else return 0; }
|
||||
|
||||
public int hashCode() { return _job.hashCode(); }
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj != null) && (obj instanceof JobStats) ) {
|
||||
JobStats stats = (JobStats)obj;
|
||||
return DataHelper.eq(getName(), stats.getName()) &&
|
||||
getRuns() == stats.getRuns() &&
|
||||
getTotalTime() == stats.getTotalTime() &&
|
||||
getMaxTime() == stats.getMaxTime() &&
|
||||
getMinTime() == stats.getMinTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("Over ").append(getRuns()).append(" runs, job <b>").append(getName()).append("</b> took ");
|
||||
buf.append(getTotalTime()).append("ms (").append(getAvgTime()).append("ms/").append(getMaxTime()).append("ms/");
|
||||
buf.append(getMinTime()).append("ms avg/max/min) after a total lag of ");
|
||||
buf.append(getTotalPendingTime()).append("ms (").append(getAvgPendingTime()).append("ms/");
|
||||
buf.append(getMaxPendingTime()).append("ms/").append(getMinPendingTime()).append("ms avg/max/min)");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
69
router/java/src/net/i2p/router/JobTiming.java
Normal file
69
router/java/src/net/i2p/router/JobTiming.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.util.Clock;
|
||||
/**
|
||||
* Define the timing requirements and statistics for a particular job
|
||||
*
|
||||
*/
|
||||
public class JobTiming implements Clock.ClockUpdateListener {
|
||||
private long _start;
|
||||
private long _actualStart;
|
||||
private long _actualEnd;
|
||||
|
||||
public JobTiming() {
|
||||
_start = Clock.getInstance().now();
|
||||
_actualStart = 0;
|
||||
_actualEnd = 0;
|
||||
Clock.getInstance().addUpdateListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* # of milliseconds after the epoch to start the job
|
||||
*
|
||||
*/
|
||||
public long getStartAfter() { return _start; }
|
||||
public void setStartAfter(long startTime) { _start = startTime; }
|
||||
|
||||
/**
|
||||
* # of milliseconds after the epoch the job actually started
|
||||
*
|
||||
*/
|
||||
public long getActualStart() { return _actualStart; }
|
||||
public void setActualStart(long actualStartTime) { _actualStart = actualStartTime; }
|
||||
/**
|
||||
* Notify the timing that the job began
|
||||
*
|
||||
*/
|
||||
public void start() { _actualStart = Clock.getInstance().now(); }
|
||||
/**
|
||||
* # of milliseconds after the epoch the job actually ended
|
||||
*
|
||||
*/
|
||||
public long getActualEnd() { return _actualEnd; }
|
||||
public void setActualEnd(long actualEndTime) { _actualEnd = actualEndTime; }
|
||||
/**
|
||||
* Notify the timing that the job finished
|
||||
*
|
||||
*/
|
||||
public void end() {
|
||||
_actualEnd = Clock.getInstance().now();
|
||||
Clock.getInstance().removeUpdateListener(this);
|
||||
}
|
||||
|
||||
public void offsetChanged(long delta) {
|
||||
if (_start != 0)
|
||||
_start += delta;
|
||||
if (_actualStart != 0)
|
||||
_actualStart += delta;
|
||||
if (_actualEnd != 0)
|
||||
_actualEnd += delta;
|
||||
}
|
||||
}
|
||||
190
router/java/src/net/i2p/router/KeyManager.java
Normal file
190
router/java/src/net/i2p/router/KeyManager.java
Normal file
@@ -0,0 +1,190 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataStructure;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Maintain all of the key pairs for the router.
|
||||
*
|
||||
*/
|
||||
public class KeyManager {
|
||||
private final static Log _log = new Log(KeyManager.class);
|
||||
private static KeyManager _instance = new KeyManager();
|
||||
public static KeyManager getInstance() { return _instance; }
|
||||
private PrivateKey _privateKey;
|
||||
private PublicKey _publicKey;
|
||||
private SigningPrivateKey _signingPrivateKey;
|
||||
private SigningPublicKey _signingPublicKey;
|
||||
private Map _leaseSetKeys; // Destination --> LeaseSetKeys
|
||||
|
||||
public final static String PROP_KEYDIR = "router.keyBackupDir";
|
||||
public final static String DEFAULT_KEYDIR = "keyBackup";
|
||||
private final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
|
||||
private final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
|
||||
private final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
|
||||
private final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
|
||||
private final static long DELAY = 30*1000;
|
||||
|
||||
private KeyManager() {
|
||||
setPrivateKey(null);
|
||||
setPublicKey(null);
|
||||
setSigningPrivateKey(null);
|
||||
setSigningPublicKey(null);
|
||||
_leaseSetKeys = new HashMap();
|
||||
JobQueue.getInstance().addJob(new SynchronizeKeysJob());
|
||||
}
|
||||
|
||||
/** Configure the router's private key */
|
||||
public void setPrivateKey(PrivateKey key) { _privateKey = key; }
|
||||
public PrivateKey getPrivateKey() { return _privateKey; }
|
||||
/** Configure the router's public key */
|
||||
public void setPublicKey(PublicKey key) { _publicKey = key; }
|
||||
public PublicKey getPublicKey() { return _publicKey; }
|
||||
/** Configure the router's signing private key */
|
||||
public void setSigningPrivateKey(SigningPrivateKey key) { _signingPrivateKey = key; }
|
||||
public SigningPrivateKey getSigningPrivateKey() { return _signingPrivateKey; }
|
||||
/** Configure the router's signing public key */
|
||||
public void setSigningPublicKey(SigningPublicKey key) { _signingPublicKey = key; }
|
||||
public SigningPublicKey getSigningPublicKey() { return _signingPublicKey; }
|
||||
|
||||
public void registerKeys(Destination dest, SigningPrivateKey leaseRevocationPrivateKey, PrivateKey endpointDecryptionKey) {
|
||||
_log.info("Registering keys for destination " + dest.calculateHash().toBase64());
|
||||
LeaseSetKeys keys = new LeaseSetKeys(dest, leaseRevocationPrivateKey, endpointDecryptionKey);
|
||||
synchronized (_leaseSetKeys) {
|
||||
_leaseSetKeys.put(dest, keys);
|
||||
}
|
||||
}
|
||||
|
||||
public LeaseSetKeys unregisterKeys(Destination dest) {
|
||||
_log.info("Unregistering keys for destination " + dest.calculateHash().toBase64());
|
||||
synchronized (_leaseSetKeys) {
|
||||
return (LeaseSetKeys)_leaseSetKeys.remove(dest);
|
||||
}
|
||||
}
|
||||
|
||||
public LeaseSetKeys getKeys(Destination dest) {
|
||||
synchronized (_leaseSetKeys) {
|
||||
return (LeaseSetKeys)_leaseSetKeys.get(dest);
|
||||
}
|
||||
}
|
||||
|
||||
public Set getAllKeys() {
|
||||
HashSet keys = new HashSet();
|
||||
synchronized (_leaseSetKeys) {
|
||||
keys.addAll(_leaseSetKeys.values());
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
private class SynchronizeKeysJob extends JobImpl {
|
||||
public void runJob() {
|
||||
String keyDir = Router.getInstance().getConfigSetting(PROP_KEYDIR);
|
||||
if (keyDir == null)
|
||||
keyDir = DEFAULT_KEYDIR;
|
||||
File dir = new File(keyDir);
|
||||
if (!dir.exists())
|
||||
dir.mkdirs();
|
||||
if (dir.exists() && dir.isDirectory() && dir.canRead() && dir.canWrite())
|
||||
syncKeys(dir);
|
||||
|
||||
getTiming().setStartAfter(Clock.getInstance().now()+DELAY);
|
||||
JobQueue.getInstance().addJob(this);
|
||||
}
|
||||
|
||||
private void syncKeys(File keyDir) {
|
||||
syncPrivateKey(keyDir);
|
||||
syncPublicKey(keyDir);
|
||||
syncSigningKey(keyDir);
|
||||
syncVerificationKey(keyDir);
|
||||
}
|
||||
|
||||
private void syncPrivateKey(File keyDir) {
|
||||
File keyFile = new File(keyDir, KeyManager.KEYFILE_PRIVATE_ENC);
|
||||
boolean exists = (_privateKey != null);
|
||||
if (!exists)
|
||||
_privateKey = new PrivateKey();
|
||||
_privateKey = (PrivateKey)syncKey(keyFile, _privateKey, exists);
|
||||
}
|
||||
private void syncPublicKey(File keyDir) {
|
||||
File keyFile = new File(keyDir, KeyManager.KEYFILE_PUBLIC_ENC);
|
||||
boolean exists = (_publicKey != null);
|
||||
if (!exists)
|
||||
_publicKey = new PublicKey();
|
||||
_publicKey = (PublicKey)syncKey(keyFile, _publicKey, exists);
|
||||
}
|
||||
|
||||
private void syncSigningKey(File keyDir) {
|
||||
File keyFile = new File(keyDir, KeyManager.KEYFILE_PRIVATE_SIGNING);
|
||||
boolean exists = (_signingPrivateKey != null);
|
||||
if (!exists)
|
||||
_signingPrivateKey = new SigningPrivateKey();
|
||||
_signingPrivateKey = (SigningPrivateKey)syncKey(keyFile, _signingPrivateKey, exists);
|
||||
}
|
||||
private void syncVerificationKey(File keyDir) {
|
||||
File keyFile = new File(keyDir, KeyManager.KEYFILE_PUBLIC_SIGNING);
|
||||
boolean exists = (_signingPublicKey != null);
|
||||
if (!exists)
|
||||
_signingPublicKey = new SigningPublicKey();
|
||||
_signingPublicKey = (SigningPublicKey)syncKey(keyFile, _signingPublicKey, exists);
|
||||
}
|
||||
|
||||
private DataStructure syncKey(File keyFile, DataStructure structure, boolean exists) {
|
||||
FileOutputStream out = null;
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
if (exists) {
|
||||
out = new FileOutputStream(keyFile);
|
||||
structure.writeBytes(out);
|
||||
return structure;
|
||||
} else {
|
||||
if (keyFile.exists()) {
|
||||
in = new FileInputStream(keyFile);
|
||||
structure.readBytes(in);
|
||||
return structure;
|
||||
} else {
|
||||
// we don't have it, and its not on disk. oh well.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error syncing the structure to " + keyFile.getAbsolutePath(), ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error syncing the structure with " + keyFile.getAbsolutePath(), dfe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
if (exists)
|
||||
return structure;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getName() { return "Synchronize Keys to Disk"; }
|
||||
}
|
||||
}
|
||||
94
router/java/src/net/i2p/router/LeaseSetKeys.java
Normal file
94
router/java/src/net/i2p/router/LeaseSetKeys.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
/**
|
||||
* Wrap up the keys given to the router when a destination connects to it
|
||||
*
|
||||
*/
|
||||
public class LeaseSetKeys extends DataStructureImpl {
|
||||
private Destination _dest;
|
||||
private SigningPrivateKey _revocationKey;
|
||||
private PrivateKey _decryptionKey;
|
||||
|
||||
public LeaseSetKeys() {
|
||||
this(null, null, null);
|
||||
}
|
||||
public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, PrivateKey decryptionKey) {
|
||||
_dest = dest;
|
||||
_revocationKey = revocationKey;
|
||||
_decryptionKey = decryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destination in question
|
||||
*/
|
||||
public Destination getDestination() { return _dest; }
|
||||
/**
|
||||
* Key with which a LeaseSet can be revoked (by republishing it with no Leases)
|
||||
*
|
||||
*/
|
||||
public SigningPrivateKey getRevocationKey() { return _revocationKey; }
|
||||
/**
|
||||
* Decryption key which can open up garlic messages encrypted to the
|
||||
* LeaseSet's public key. This is used because the general public does not
|
||||
* know on what router the destination is connected and as such can't encrypt
|
||||
* to that router's normal public key.
|
||||
*
|
||||
*/
|
||||
public PrivateKey getDecryptionKey() { return _decryptionKey; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_dest = new Destination();
|
||||
_dest.readBytes(in);
|
||||
_decryptionKey = new PrivateKey();
|
||||
_decryptionKey.readBytes(in);
|
||||
_revocationKey = new SigningPrivateKey();
|
||||
_revocationKey.readBytes(in);
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_dest == null) throw new DataFormatException("Null destination");
|
||||
if (_decryptionKey == null) throw new DataFormatException("Null decryption key");
|
||||
if (_revocationKey == null) throw new DataFormatException("Null revocation key");
|
||||
_dest.writeBytes(out);
|
||||
_decryptionKey.writeBytes(out);
|
||||
_revocationKey.writeBytes(out);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int rv = 0;
|
||||
rv += DataHelper.hashCode(_dest);
|
||||
rv += DataHelper.hashCode(_revocationKey);
|
||||
rv += DataHelper.hashCode(_decryptionKey);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj != null) && (obj instanceof LeaseSetKeys) ) {
|
||||
LeaseSetKeys keys = (LeaseSetKeys)obj;
|
||||
return DataHelper.eq(getDestination(), keys.getDestination()) &&
|
||||
DataHelper.eq(getDecryptionKey(), keys.getDecryptionKey()) &&
|
||||
DataHelper.eq(getRevocationKey(), keys.getRevocationKey());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
549
router/java/src/net/i2p/router/MessageHistory.java
Normal file
549
router/java/src/net/i2p/router/MessageHistory.java
Normal file
@@ -0,0 +1,549 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.util.Date;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
/**
|
||||
* Simply act as a pen register of messages sent in and out of the router.
|
||||
* This will be pulled out later on, but is useful now for debugging.
|
||||
* (with clock synchronization, this will generate a log that can be used to
|
||||
* analyze the entire network, if everyone provides their logs honestly)
|
||||
*
|
||||
*/
|
||||
public class MessageHistory {
|
||||
private final static Log _log = new Log(MessageHistory.class);
|
||||
private static MessageHistory _instance;
|
||||
private List _unwrittenEntries; // list of raw entries (strings) yet to be written
|
||||
private String _historyFile; // where to write
|
||||
private String _localIdent; // placed in each entry to uniquely identify the local router
|
||||
private boolean _doLog; // true == we want to log
|
||||
private boolean _doPause; // true == briefly stop writing data to the log (used while submitting it)
|
||||
|
||||
private final static byte[] NL = System.getProperty("line.separator").getBytes();
|
||||
private final static int FLUSH_SIZE = 1000; // write out at least once every 1000 entries
|
||||
|
||||
/** config property determining whether we want to debug with the message history */
|
||||
public final static String PROP_KEEP_MESSAGE_HISTORY = "router.keepHistory";
|
||||
public final static boolean DEFAULT_KEEP_MESSAGE_HISTORY = false;
|
||||
/** config property determining where we want to log the message history, if we're keeping one */
|
||||
public final static String PROP_MESSAGE_HISTORY_FILENAME = "router.historyFilename";
|
||||
public final static String DEFAULT_MESSAGE_HISTORY_FILENAME = "messageHistory.txt";
|
||||
|
||||
public final static MessageHistory getInstance() {
|
||||
if (_instance == null)
|
||||
initialize();
|
||||
return _instance;
|
||||
}
|
||||
private final static void setInstance(MessageHistory hist) {
|
||||
if (_instance != null) {
|
||||
synchronized (_instance._unwrittenEntries) {
|
||||
for (Iterator iter = _instance._unwrittenEntries.iterator(); iter.hasNext(); ) {
|
||||
hist.addEntry((String)iter.next());
|
||||
}
|
||||
_instance._unwrittenEntries.clear();
|
||||
}
|
||||
}
|
||||
_instance = hist;
|
||||
}
|
||||
|
||||
void setDoLog(boolean log) { _doLog = log; }
|
||||
boolean getDoLog() { return _doLog; }
|
||||
|
||||
void setPauseFlushes(boolean doPause) { _doPause = doPause; }
|
||||
String getFilename() { return _historyFile; }
|
||||
|
||||
private void updateSettings() {
|
||||
String keepHistory = Router.getInstance().getConfigSetting(PROP_KEEP_MESSAGE_HISTORY);
|
||||
if (keepHistory != null) {
|
||||
_doLog = Boolean.TRUE.toString().equalsIgnoreCase(keepHistory);
|
||||
} else {
|
||||
_doLog = DEFAULT_KEEP_MESSAGE_HISTORY;
|
||||
}
|
||||
|
||||
String filename = null;
|
||||
if (_doLog) {
|
||||
filename = Router.getInstance().getConfigSetting(PROP_MESSAGE_HISTORY_FILENAME);
|
||||
if ( (filename == null) || (filename.trim().length() <= 0) )
|
||||
filename = DEFAULT_MESSAGE_HISTORY_FILENAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the message history according to the router's configuration.
|
||||
* Call this whenever the router identity changes.
|
||||
*
|
||||
*/
|
||||
public static void initialize() {
|
||||
initialize(false);
|
||||
}
|
||||
public static void initialize(boolean forceReinitialize) {
|
||||
if ( (!forceReinitialize) && (_instance != null) ) return;
|
||||
|
||||
if (Router.getInstance().getRouterInfo() == null) {
|
||||
ReinitializeJob j = ReinitializeJob.getInstance();
|
||||
j.getTiming().setStartAfter(Clock.getInstance().now()+5000);
|
||||
JobQueue.getInstance().addJob(j);
|
||||
} else {
|
||||
String filename = null;
|
||||
filename = Router.getInstance().getConfigSetting(PROP_MESSAGE_HISTORY_FILENAME);
|
||||
if ( (filename == null) || (filename.trim().length() <= 0) )
|
||||
filename = DEFAULT_MESSAGE_HISTORY_FILENAME;
|
||||
MessageHistory hist = new MessageHistory(Router.getInstance().getRouterInfo().getIdentity().getHash(), filename);
|
||||
setInstance(hist);
|
||||
hist.updateSettings();
|
||||
getInstance().addEntry(getInstance().getPrefix() + "** Router initialized (started up or changed identities)");
|
||||
JobQueue.getInstance().addJob(new WriteJob());
|
||||
SubmitMessageHistoryJob histJob = new SubmitMessageHistoryJob();
|
||||
histJob.getTiming().setStartAfter(Clock.getInstance().now() + 2*60*1000);
|
||||
JobQueue.getInstance().addJob(histJob);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ReinitializeJob extends JobImpl {
|
||||
private final static ReinitializeJob _jobInstance = new ReinitializeJob();
|
||||
public final static ReinitializeJob getInstance() { return _jobInstance; }
|
||||
private ReinitializeJob() {
|
||||
super();
|
||||
}
|
||||
public void runJob() {
|
||||
MessageHistory.initialize();
|
||||
}
|
||||
public String getName() { return "Reinitialize message history"; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a component to monitor the message history of the router.
|
||||
*
|
||||
* @param localIdent Hash of local identity
|
||||
* @param filename file to log trace info to
|
||||
*/
|
||||
private MessageHistory(Hash localIdent, String filename) {
|
||||
_doLog = DEFAULT_KEEP_MESSAGE_HISTORY;
|
||||
_historyFile = filename;
|
||||
_localIdent = getName(localIdent);
|
||||
_unwrittenEntries = new LinkedList();
|
||||
}
|
||||
|
||||
/**
|
||||
* We are requesting that the peerRequested create the tunnel specified with the
|
||||
* given nextPeer, and we are sending that request to them through outTunnel with
|
||||
* a request that the reply is sent back to us through replyTunnel on the given
|
||||
* replyThrough router.
|
||||
*
|
||||
* @param createTunnel tunnel being created
|
||||
* @param outTunnel tunnel we are sending this request out
|
||||
* @param peerRequested peer asked to participate in the tunnel
|
||||
* @param nextPeer who peerRequested should forward messages to (or null if it is the endpoint)
|
||||
* @param sourceRoutePeer to whom peerRequested should forward its TunnelCreateStatusMessage through
|
||||
* @param replyTunnel the tunnel sourceRoutePeer should forward the source routed message to
|
||||
* @param replyThrough the gateway of the tunnel that the sourceRoutePeer will be sending to
|
||||
*/
|
||||
public void requestTunnelCreate(TunnelId createTunnel, TunnelId outTunnel, Hash peerRequested, Hash nextPeer, Hash sourceRoutePeer, TunnelId replyTunnel, Hash replyThrough) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("request [").append(getName(peerRequested)).append("] to create tunnel [");
|
||||
buf.append(createTunnel.getTunnelId()).append("] ");
|
||||
if (nextPeer != null)
|
||||
buf.append("(next [").append(getName(nextPeer)).append("]) ");
|
||||
if (outTunnel != null)
|
||||
buf.append("via [").append(outTunnel.getTunnelId()).append("] ");
|
||||
if (sourceRoutePeer != null)
|
||||
buf.append("with replies routed through [").append(getName(sourceRoutePeer)).append("] ");
|
||||
if ( (replyTunnel != null) && (replyThrough != null) )
|
||||
buf.append("who forwards it through [").append(replyTunnel.getTunnelId()).append("] on [").append(getName(replyThrough)).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The local router has received a request to join the createTunnel with the next hop being nextPeer,
|
||||
* and we should send our decision to join it through sourceRoutePeer
|
||||
*
|
||||
* @param createTunnel tunnel being joined
|
||||
* @param nextPeer next hop in the tunnel (or null if this is the endpoint)
|
||||
* @param expire when this tunnel expires
|
||||
* @param ok whether we will join the tunnel
|
||||
* @param sourceRoutePeer peer through whom we should send our garlic routed ok through
|
||||
*/
|
||||
public void receiveTunnelCreate(TunnelId createTunnel, Hash nextPeer, Date expire, boolean ok, Hash sourceRoutePeer) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("receive tunnel create [").append(createTunnel.getTunnelId()).append("] ");
|
||||
if (nextPeer != null)
|
||||
buf.append("(next [").append(getName(nextPeer)).append("]) ");
|
||||
buf.append("ok? ").append(ok).append(" expiring on [").append(getTime(expire)).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The local router has joined the given tunnel operating in the given state.
|
||||
*
|
||||
* @param state {"free inbound", "allocated inbound", "inactive inbound", "outbound", "participant", "pending"}
|
||||
* @param tunnel tunnel joined
|
||||
*/
|
||||
public void tunnelJoined(String state, TunnelInfo tunnel) {
|
||||
if (!_doLog) return;
|
||||
if (tunnel == null) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("joining tunnel [").append(tunnel.getTunnelId().getTunnelId()).append("] as [").append(state).append("] ");
|
||||
buf.append(" (next: ");
|
||||
TunnelInfo cur = tunnel;
|
||||
while (cur.getNextHopInfo() != null) {
|
||||
buf.append('[').append(getName(cur.getNextHopInfo().getThisHop()));
|
||||
buf.append("], ");
|
||||
cur = cur.getNextHopInfo();
|
||||
}
|
||||
if (cur.getNextHop() != null)
|
||||
buf.append('[').append(getName(cur.getNextHop())).append(']');
|
||||
buf.append(") expiring on [").append(getTime(new Date(tunnel.getSettings().getExpiration()))).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The local router has detected a failure in the given tunnel
|
||||
*
|
||||
* @param tunnel tunnel failed
|
||||
*/
|
||||
public void tunnelFailed(TunnelId tunnel) {
|
||||
if (!_doLog) return;
|
||||
if (tunnel == null) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("failing tunnel [").append(tunnel.getTunnelId()).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that we have reason to believe that the given tunnel is valid, since we could do something
|
||||
* through it in the given amount of time
|
||||
*
|
||||
* @param tunnel tunnel in question
|
||||
* @param timeToTest milliseconds to verify the tunnel
|
||||
*/
|
||||
public void tunnelValid(TunnelInfo tunnel, long timeToTest) {
|
||||
if (!_doLog) return;
|
||||
if (tunnel == null) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("tunnel ").append(tunnel.getTunnelId().getTunnelId()).append(" tested ok after ").append(timeToTest).append("ms (containing ");
|
||||
TunnelInfo cur = tunnel;
|
||||
while (cur != null) {
|
||||
buf.append('[').append(getName(cur.getThisHop())).append("], ");
|
||||
if (cur.getNextHopInfo() != null) {
|
||||
cur = cur.getNextHopInfo();
|
||||
} else {
|
||||
if (cur.getNextHop() != null)
|
||||
buf.append('[').append(getName(cur.getNextHop())).append(']');
|
||||
cur = null;
|
||||
}
|
||||
}
|
||||
buf.append(')');
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The peer did not accept the tunnel join for the given reason
|
||||
*
|
||||
*/
|
||||
public void tunnelRejected(Hash peer, TunnelId tunnel, Hash replyThrough, String reason) {
|
||||
if (!_doLog) return;
|
||||
if ( (tunnel == null) || (peer == null) ) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("tunnel [").append(tunnel.getTunnelId()).append("] was rejected by [");
|
||||
buf.append(getName(peer)).append("] for [").append(reason).append("]");
|
||||
if (replyThrough != null)
|
||||
buf.append(" with their reply intended to come through [").append(getName(replyThrough)).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The peer did not accept the tunnel join for the given reason (this may be because
|
||||
* of a timeout or an explicit refusal).
|
||||
*
|
||||
*/
|
||||
public void tunnelRequestTimedOut(Hash peer, TunnelId tunnel, Hash replyThrough) {
|
||||
if (!_doLog) return;
|
||||
if ( (tunnel == null) || (peer == null) ) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("tunnel [").append(tunnel.getTunnelId()).append("] timed out on [");
|
||||
buf.append(getName(peer)).append("]");
|
||||
if (replyThrough != null)
|
||||
buf.append(" with their reply intended to come through [").append(getName(replyThrough)).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't know about the given tunnel, so we are dropping a message sent to us by the
|
||||
* given router
|
||||
*
|
||||
* @param id tunnel ID we received a message for
|
||||
* @param from peer that sent us this message (if known)
|
||||
*/
|
||||
public void droppedTunnelMessage(TunnelId id, Hash from) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("dropped message for unknown tunnel [").append(id.getTunnelId()).append("] from [").append(getName(from)).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* We received another message we weren't waiting for and don't know how to handle
|
||||
*/
|
||||
public void droppedOtherMessage(I2NPMessage message) {
|
||||
if (!_doLog) return;
|
||||
if (message == null) return;
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append(getPrefix());
|
||||
buf.append("dropped [").append(message.getClass().getName()).append("] ").append(message.getUniqueId());
|
||||
buf.append(" [").append(message.toString()).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The message wanted a reply but no reply came in the time expected
|
||||
*
|
||||
* @param sentMessage message sent that didn't receive a reply
|
||||
*/
|
||||
public void replyTimedOut(OutNetMessage sentMessage) {
|
||||
if (!_doLog) return;
|
||||
if (sentMessage == null) return;
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
buf.append(getPrefix());
|
||||
buf.append("timed out waiting for a reply to [").append(sentMessage.getMessage().getClass().getName());
|
||||
buf.append("] [").append(sentMessage.getMessage().getUniqueId()).append("] expiring on [");
|
||||
if (sentMessage != null)
|
||||
buf.append(getTime(new Date(sentMessage.getReplySelector().getExpiration())));
|
||||
buf.append("] ").append(sentMessage.getReplySelector().toString());
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* There was an error processing the given message that was received
|
||||
*
|
||||
* @param messageId message received
|
||||
* @param messageType type of message received
|
||||
* @param error error message related to the processing of the message
|
||||
*/
|
||||
public void messageProcessingError(long messageId, String messageType, String error) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("Error processing [").append(messageType).append("] [").append(messageId).append("] failed with [").append(error).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* We just sent a message to the peer
|
||||
*
|
||||
* @param messageType class name for the message object (e.g. DatabaseFindNearestMessage, TunnelMessage, etc)
|
||||
* @param messageId the unique message id of the message being sent (not including any tunnel or garlic wrapped
|
||||
* message ids)
|
||||
* @param expiration the expiration for the message sent
|
||||
* @param peer router that the message was sent to
|
||||
* @param sentOk whether the message was sent successfully
|
||||
*/
|
||||
public void sendMessage(String messageType, long messageId, Date expiration, Hash peer, boolean sentOk) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("send [").append(messageType).append("] message [").append(messageId).append("] ");
|
||||
buf.append("to [").append(getName(peer)).append("] ");
|
||||
buf.append("expiring on [").append(getTime(expiration)).append("] ");
|
||||
if (sentOk)
|
||||
buf.append("successfully");
|
||||
else
|
||||
buf.append("failed");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* We just received a message from the peer
|
||||
*
|
||||
* @param messageType class name for the message object (e.g. DatabaseFindNearestMessage, TunnelMessage, etc)
|
||||
* @param messageId the unique message id of the message received (not including any tunnel or garlic wrapped
|
||||
* message ids)
|
||||
* @param expiration the expiration for the message received
|
||||
* @param from router that the message was sent from (or null if we don't know)
|
||||
* @param isValid whether the message is valid (non duplicates, etc)
|
||||
*
|
||||
*/
|
||||
public void receiveMessage(String messageType, long messageId, Date expiration, Hash from, boolean isValid) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("receive [").append(messageType).append("] with id [").append(messageId).append("] ");
|
||||
if (from != null)
|
||||
buf.append("from [").append(getName(from)).append("] ");
|
||||
buf.append("expiring on [").append(getTime(expiration)).append("] valid? ").append(isValid);
|
||||
addEntry(buf.toString());
|
||||
if (messageType.equals("net.i2p.data.i2np.TunnelMessage")) {
|
||||
//_log.warn("ReceiveMessage tunnel message ["+messageId+"]", new Exception("Receive tunnel"));
|
||||
}
|
||||
}
|
||||
public void receiveMessage(String messageType, long messageId, Date expiration, boolean isValid) {
|
||||
receiveMessage(messageType, messageId, expiration, null, isValid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that we're wrapping the given message within another message (via tunnel/garlic)
|
||||
*
|
||||
* @param bodyMessageType class name for the message contained (e.g. DatabaseFindNearestMessage, DataMessage, etc)
|
||||
* @param bodyMessageId the unique message id of the message
|
||||
* @param containerMessageType class name for the message containing the body message (e.g. TunnelMessage, GarlicMessage, etc)
|
||||
* @param containerMessageId the unique message id of the message
|
||||
*/
|
||||
public void wrap(String bodyMessageType, long bodyMessageId, String containerMessageType, long containerMessageId) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("Wrap message [").append(bodyMessageType).append("] id [").append(bodyMessageId).append("] ");
|
||||
buf.append("in [").append(containerMessageType).append("] id [").append(containerMessageId).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a payload message to distribute to a client
|
||||
*
|
||||
*/
|
||||
public void receivePayloadMessage(long messageId) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(getPrefix());
|
||||
buf.append("Receive payload message [").append(messageId).append("]");
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that the sending of a payload message completed (successfully or as a failure)
|
||||
*
|
||||
* @param messageId message that the payload message was sent in
|
||||
* @param successfullySent whether the message was delivered to the peer successfully
|
||||
* @param timeToSend how long it took to send the message
|
||||
*/
|
||||
public void sendPayloadMessage(long messageId, boolean successfullySent, long timeToSend) {
|
||||
if (!_doLog) return;
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(getPrefix());
|
||||
buf.append("Send payload message in [").append(messageId).append("] in [").append(timeToSend).append("] successfully? ").append(successfullySent);
|
||||
addEntry(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prettify the hash by doing a base64 and returning the first 6 characters
|
||||
*
|
||||
*/
|
||||
private final static String getName(Hash router) {
|
||||
if (router == null) return "unknown";
|
||||
String str = router.toBase64();
|
||||
if ( (str == null) || (str.length() < 6) ) return "invalid";
|
||||
return str.substring(0, 6);
|
||||
}
|
||||
|
||||
private final String getPrefix() {
|
||||
StringBuffer buf = new StringBuffer(48);
|
||||
buf.append(getTime(new Date(Clock.getInstance().now())));
|
||||
buf.append(' ').append(_localIdent).append(": ");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final static SimpleDateFormat _fmt = new SimpleDateFormat("yy/MM/dd.HH:mm:ss.SSS");
|
||||
static {
|
||||
_fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
}
|
||||
private final static String getTime(Date when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(when);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for adding the entry, flushing if necessary.
|
||||
* This is the only thing that adds to _unwrittenEntries.
|
||||
*
|
||||
*/
|
||||
private void addEntry(String entry) {
|
||||
if (entry == null) return;
|
||||
int sz = 0;
|
||||
synchronized (_unwrittenEntries) {
|
||||
_unwrittenEntries.add(entry);
|
||||
sz = _unwrittenEntries.size();
|
||||
}
|
||||
if (sz > FLUSH_SIZE)
|
||||
flushEntries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out any unwritten entries, and clear the pending list
|
||||
*/
|
||||
private void flushEntries() {
|
||||
if (_doPause) return;
|
||||
List entries = null;
|
||||
synchronized (_unwrittenEntries) {
|
||||
entries = new LinkedList(_unwrittenEntries);
|
||||
_unwrittenEntries.clear();
|
||||
}
|
||||
writeEntries(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually write the specified entries
|
||||
*
|
||||
*/
|
||||
private void writeEntries(List entries) {
|
||||
if (!_doLog) return;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_historyFile, true);
|
||||
for (Iterator iter = entries.iterator(); iter.hasNext(); ) {
|
||||
String entry = (String)iter.next();
|
||||
fos.write(entry.getBytes());
|
||||
fos.write(NL);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing trace entries", ioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** write out the message history once per minute, if not sooner */
|
||||
private final static long WRITE_DELAY = 60*1000;
|
||||
private static class WriteJob extends JobImpl {
|
||||
public String getName() { return "Write History Entries"; }
|
||||
public void runJob() {
|
||||
MessageHistory.getInstance().flushEntries();
|
||||
MessageHistory.getInstance().updateSettings();
|
||||
requeue(WRITE_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
MessageHistory hist = new MessageHistory(new Hash(new byte[32]), "messageHistory.txt");
|
||||
MessageHistory.getInstance().setDoLog(false);
|
||||
hist.addEntry("you smell before");
|
||||
hist.getInstance().setDoLog(true);
|
||||
hist.addEntry("you smell after");
|
||||
hist.getInstance().setDoLog(false);
|
||||
hist.addEntry("you smell finished");
|
||||
hist.flushEntries();
|
||||
}
|
||||
}
|
||||
33
router/java/src/net/i2p/router/MessageReceptionInfo.java
Normal file
33
router/java/src/net/i2p/router/MessageReceptionInfo.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
/**
|
||||
* Wrap up the details of how a ClientMessage was received from the network
|
||||
*
|
||||
*/
|
||||
public class MessageReceptionInfo {
|
||||
private Hash _fromPeer;
|
||||
private TunnelId _fromTunnel;
|
||||
|
||||
public MessageReceptionInfo() {
|
||||
setFromPeer(null);
|
||||
setFromTunnel(null);
|
||||
}
|
||||
|
||||
/** Hash of the RouterIdentity of the peer that sent the message */
|
||||
public Hash getFromPeer() { return _fromPeer; }
|
||||
public void setFromPeer(Hash routerIdentityHash) { _fromPeer = routerIdentityHash; }
|
||||
/** TunnelId the message came in on, if applicable */
|
||||
public TunnelId getFromTunnel() { return _fromTunnel; }
|
||||
public void setFromTunnel(TunnelId fromTunnel) { _fromTunnel = fromTunnel; }
|
||||
}
|
||||
34
router/java/src/net/i2p/router/MessageSelector.java
Normal file
34
router/java/src/net/i2p/router/MessageSelector.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
/**
|
||||
* Define a mechanism to select what messages are associated with a particular
|
||||
* OutNetMessage. This is used for finding replies to messages.
|
||||
*
|
||||
*/
|
||||
public interface MessageSelector {
|
||||
/**
|
||||
* Returns true if the received message matches the selector
|
||||
*/
|
||||
public boolean isMatch(I2NPMessage message);
|
||||
/**
|
||||
* Returns true if the selector should still keep searching for further matches
|
||||
*
|
||||
*/
|
||||
public boolean continueMatching();
|
||||
/**
|
||||
* Returns the # of milliseconds since the epoch after which this selector should
|
||||
* stop searching for matches
|
||||
*
|
||||
*/
|
||||
public long getExpiration();
|
||||
}
|
||||
126
router/java/src/net/i2p/router/MessageValidator.java
Normal file
126
router/java/src/net/i2p/router/MessageValidator.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import java.util.TreeMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Singleton to manage the logic (and historical data) to determine whether a message
|
||||
* is valid or not (meaning it isn't expired and hasn't already been received). We'll
|
||||
* need a revamp once we start dealing with long message expirations (since it might
|
||||
* involve keeping a significant number of entries in memory), but that probably won't
|
||||
* be necessary until I2P 3.0.
|
||||
*
|
||||
*/
|
||||
public class MessageValidator {
|
||||
private final static Log _log = new Log(MessageValidator.class);
|
||||
private final static MessageValidator _instance = new MessageValidator();
|
||||
public final static MessageValidator getInstance() { return _instance; }
|
||||
|
||||
/**
|
||||
* Expiration date (as a Long) to message id (as a Long).
|
||||
* The expiration date (key) must be unique, so on collision, increment the value.
|
||||
* This keeps messageIds around longer than they need to be, but hopefully not by much ;)
|
||||
*
|
||||
*/
|
||||
private TreeMap _receivedIdExpirations = new TreeMap();
|
||||
/** Message id (as a Long) */
|
||||
private Set _receivedIds = new HashSet(1024);
|
||||
/** synchronize on this before adjusting the received id data */
|
||||
private Object _receivedIdLock = new Object();
|
||||
|
||||
/**
|
||||
* Determine if this message should be accepted as valid (not expired, not a duplicate)
|
||||
*
|
||||
* @return true if the message should be accepted as valid, false otherwise
|
||||
*/
|
||||
public boolean validateMessage(long messageId, long expiration) {
|
||||
long now = Clock.getInstance().now();
|
||||
if (now - Router.CLOCK_FUDGE_FACTOR >= expiration) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting message " + messageId + " because it expired " + (now-expiration) + "ms ago");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isDuplicate = noteReception(messageId, expiration);
|
||||
if (isDuplicate) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting message " + messageId + " because it is a duplicate", new Exception("Duplicate origin"));
|
||||
return false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Accepting message " + messageId + " because it is NOT a duplicate", new Exception("Original origin"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that we've received the message (which has the expiration given).
|
||||
* This functionality will need to be reworked for I2P 3.0 when we take into
|
||||
* consideration messages with significant user specified delays (since we dont
|
||||
* want to keep an infinite number of messages in RAM, etc)
|
||||
*
|
||||
* @return true if we HAVE already seen this message, false if not
|
||||
*/
|
||||
private boolean noteReception(long messageId, long messageExpiration) {
|
||||
Long id = new Long(messageId);
|
||||
synchronized (_receivedIdLock) {
|
||||
locked_cleanReceivedIds(Clock.getInstance().now() - Router.CLOCK_FUDGE_FACTOR);
|
||||
if (_receivedIds.contains(id)) {
|
||||
return true;
|
||||
} else {
|
||||
long date = messageExpiration;
|
||||
while (_receivedIdExpirations.containsKey(new Long(date)))
|
||||
date++;
|
||||
_receivedIdExpirations.put(new Long(date), id);
|
||||
_receivedIds.add(id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the ids that we no longer need to keep track of to prevent replay
|
||||
* attacks.
|
||||
*
|
||||
*/
|
||||
private void cleanReceivedIds() {
|
||||
long now = Clock.getInstance().now() - Router.CLOCK_FUDGE_FACTOR ;
|
||||
synchronized (_receivedIdLock) {
|
||||
locked_cleanReceivedIds(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the ids that we no longer need to keep track of to prevent replay
|
||||
* attacks - only call this from within a block synchronized on the received ID lock.
|
||||
*
|
||||
*/
|
||||
private void locked_cleanReceivedIds(long now) {
|
||||
Set toRemoveIds = new HashSet(4);
|
||||
Set toRemoveDates = new HashSet(4);
|
||||
for (Iterator iter = _receivedIdExpirations.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long date = (Long)iter.next();
|
||||
if (date.longValue() <= now) {
|
||||
// no need to keep track of things in the past
|
||||
toRemoveDates.add(date);
|
||||
toRemoveIds.add(_receivedIdExpirations.get(date));
|
||||
} else {
|
||||
// the expiration is in the future, we still need to keep track of
|
||||
// it to prevent replays
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (Iterator iter = toRemoveDates.iterator(); iter.hasNext(); )
|
||||
_receivedIdExpirations.remove(iter.next());
|
||||
for (Iterator iter = toRemoveIds.iterator(); iter.hasNext(); )
|
||||
_receivedIds.remove(iter.next());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Cleaned out " + toRemoveDates.size() + " expired messageIds, leaving " + _receivedIds.size() + " remaining");
|
||||
}
|
||||
|
||||
}
|
||||
89
router/java/src/net/i2p/router/NetworkDatabaseFacade.java
Normal file
89
router/java/src/net/i2p/router/NetworkDatabaseFacade.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.networkdb.kademlia.KademliaNetworkDatabaseFacade;
|
||||
|
||||
/**
|
||||
* Defines the mechanism for interacting with I2P's network database
|
||||
*
|
||||
*/
|
||||
public abstract class NetworkDatabaseFacade implements Service {
|
||||
private static NetworkDatabaseFacade _instance = new KademliaNetworkDatabaseFacade(); // NetworkDatabaseFacadeImpl();
|
||||
public static NetworkDatabaseFacade getInstance() { return _instance; }
|
||||
|
||||
/**
|
||||
* Return the RouterInfo structures for the routers closest to the given key.
|
||||
* At most maxNumRouters will be returned
|
||||
*
|
||||
* @param key The key
|
||||
* @param maxNumRouters The maximum number of routers to return
|
||||
* @param peersToIgnore Hash of routers not to include
|
||||
*/
|
||||
public abstract Set findNearestRouters(Hash key, int maxNumRouters, Set peersToIgnore);
|
||||
|
||||
public abstract void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs);
|
||||
public abstract LeaseSet lookupLeaseSetLocally(Hash key);
|
||||
public abstract void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs);
|
||||
public abstract RouterInfo lookupRouterInfoLocally(Hash key);
|
||||
/** return the leaseSet if another leaseSet already existed at that key */
|
||||
public abstract LeaseSet store(Hash key, LeaseSet leaseSet);
|
||||
/** return the routerInfo if another router already existed at that key */
|
||||
public abstract RouterInfo store(Hash key, RouterInfo routerInfo);
|
||||
public abstract void publish(RouterInfo localRouterInfo);
|
||||
public abstract void publish(LeaseSet localLeaseSet);
|
||||
public abstract void unpublish(LeaseSet localLeaseSet);
|
||||
public abstract void fail(Hash dbEntry);
|
||||
public String renderStatusHTML() { return ""; }
|
||||
}
|
||||
|
||||
|
||||
class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
private Map _routers;
|
||||
|
||||
public DummyNetworkDatabaseFacade() {
|
||||
_routers = new HashMap();
|
||||
}
|
||||
|
||||
public void shutdown() {}
|
||||
public void startup() {
|
||||
RouterInfo info = Router.getInstance().getRouterInfo();
|
||||
_routers.put(info.getIdentity().getHash(), info);
|
||||
}
|
||||
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {}
|
||||
public LeaseSet lookupLeaseSetLocally(Hash key) { return null; }
|
||||
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
RouterInfo info = lookupRouterInfoLocally(key);
|
||||
if (info == null)
|
||||
JobQueue.getInstance().addJob(onFailedLookupJob);
|
||||
else
|
||||
JobQueue.getInstance().addJob(onFindJob);
|
||||
}
|
||||
public RouterInfo lookupRouterInfoLocally(Hash key) { return (RouterInfo)_routers.get(key); }
|
||||
public void publish(LeaseSet localLeaseSet) {}
|
||||
public void publish(RouterInfo localRouterInfo) {}
|
||||
public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; }
|
||||
public RouterInfo store(Hash key, RouterInfo routerInfo) {
|
||||
_routers.put(key, routerInfo);
|
||||
return routerInfo;
|
||||
}
|
||||
public void unpublish(LeaseSet localLeaseSet) {}
|
||||
public void fail(Hash dbEntry) {}
|
||||
|
||||
public Set findNearestRouters(Hash key, int maxNumRouters, Set peersToIgnore) { return new HashSet(_routers.values()); }
|
||||
}
|
||||
272
router/java/src/net/i2p/router/OutNetMessage.java
Normal file
272
router/java/src/net/i2p/router/OutNetMessage.java
Normal file
@@ -0,0 +1,272 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Wrap up an outbound I2NP message, along with the information associated with its
|
||||
* delivery and jobs to be fired off if particular events occur.
|
||||
*
|
||||
*/
|
||||
public class OutNetMessage {
|
||||
private final static Log _log = new Log(OutNetMessage.class);
|
||||
private RouterInfo _target;
|
||||
private I2NPMessage _message;
|
||||
private long _messageSize;
|
||||
private int _priority;
|
||||
private long _expiration;
|
||||
private Job _onSend;
|
||||
private Job _onFailedSend;
|
||||
private ReplyJob _onReply;
|
||||
private Job _onFailedReply;
|
||||
private MessageSelector _replySelector;
|
||||
private Set _failedTransports;
|
||||
private long _sendBegin;
|
||||
private Exception _createdBy;
|
||||
private long _created;
|
||||
/** for debugging, contains a mapping of even name to Long (e.g. "begin sending", "handleOutbound", etc) */
|
||||
private HashMap _timestamps;
|
||||
/**
|
||||
* contains a list of timestamp event names in the order they were fired
|
||||
* (some JVMs have less than 10ms resolution, so the Long above doesn't guarantee order)
|
||||
*/
|
||||
private List _timestampOrder;
|
||||
|
||||
public OutNetMessage() {
|
||||
setTarget(null);
|
||||
_message = null;
|
||||
_messageSize = 0;
|
||||
setPriority(-1);
|
||||
setExpiration(-1);
|
||||
setOnSendJob(null);
|
||||
setOnFailedSendJob(null);
|
||||
setOnReplyJob(null);
|
||||
setOnFailedReplyJob(null);
|
||||
setReplySelector(null);
|
||||
_timestamps = new HashMap(8);
|
||||
_timestampOrder = new LinkedList();
|
||||
_failedTransports = new HashSet();
|
||||
_sendBegin = 0;
|
||||
_createdBy = new Exception("Created by");
|
||||
_created = Clock.getInstance().now();
|
||||
timestamp("Created");
|
||||
}
|
||||
|
||||
public void timestamp(String eventName) {
|
||||
synchronized (_timestamps) {
|
||||
_timestamps.put(eventName, new Long(Clock.getInstance().now()));
|
||||
_timestampOrder.add(eventName);
|
||||
}
|
||||
}
|
||||
public Map getTimestamps() {
|
||||
synchronized (_timestamps) {
|
||||
return (Map)_timestamps.clone();
|
||||
}
|
||||
}
|
||||
public Long getTimestamp(String eventName) {
|
||||
synchronized (_timestamps) {
|
||||
return (Long)_timestamps.get(eventName);
|
||||
}
|
||||
}
|
||||
|
||||
public Exception getCreatedBy() { return _createdBy; }
|
||||
|
||||
/**
|
||||
* Specifies the router to which the message should be delivered.
|
||||
*
|
||||
*/
|
||||
public RouterInfo getTarget() { return _target; }
|
||||
public void setTarget(RouterInfo target) { _target = target; }
|
||||
/**
|
||||
* Specifies the message to be sent
|
||||
*
|
||||
*/
|
||||
public I2NPMessage getMessage() { return _message; }
|
||||
public void setMessage(I2NPMessage msg) {
|
||||
_message = msg;
|
||||
}
|
||||
|
||||
public long getMessageSize() {
|
||||
if (_messageSize <= 0) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); // large enough to hold most messages
|
||||
_message.writeBytes(baos);
|
||||
long sz = baos.size();
|
||||
baos.reset();
|
||||
_messageSize = sz;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error serializing the I2NPMessage for the OutNetMessage", dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error serializing the I2NPMessage for the OutNetMessage", ioe);
|
||||
}
|
||||
}
|
||||
return _messageSize;
|
||||
}
|
||||
public byte[] getMessageData() {
|
||||
if (_message == null) {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); // large enough to hold most messages
|
||||
_message.writeBytes(baos);
|
||||
byte data[] = baos.toByteArray();
|
||||
baos.reset();
|
||||
return data;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error serializing the I2NPMessage for the OutNetMessage", dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error serializing the I2NPMessage for the OutNetMessage", ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the priority of the message, where higher numbers are higher
|
||||
* priority. Higher priority messages should be delivered before lower
|
||||
* priority ones, though some algorithm may be used to avoid starvation.
|
||||
*
|
||||
*/
|
||||
public int getPriority() { return _priority; }
|
||||
public void setPriority(int priority) { _priority = priority; }
|
||||
/**
|
||||
* Specify the # ms since the epoch after which if the message has not been
|
||||
* sent the OnFailedSend job should be fired and the message should be
|
||||
* removed from the pool. If the message has already been sent, this
|
||||
* expiration is ignored and the expiration from the ReplySelector is used.
|
||||
*
|
||||
*/
|
||||
public long getExpiration() { return _expiration; }
|
||||
public void setExpiration(long expiration) { _expiration = expiration; }
|
||||
/**
|
||||
* After the message is successfully passed to the router specified, the
|
||||
* given job is enqueued.
|
||||
*
|
||||
*/
|
||||
public Job getOnSendJob() { return _onSend; }
|
||||
public void setOnSendJob(Job job) { _onSend = job; }
|
||||
/**
|
||||
* If the router could not be reached or the expiration passed, this job
|
||||
* is enqueued.
|
||||
*
|
||||
*/
|
||||
public Job getOnFailedSendJob() { return _onFailedSend; }
|
||||
public void setOnFailedSendJob(Job job) { _onFailedSend = job; }
|
||||
/**
|
||||
* If the MessageSelector detects a reply, this job is enqueued
|
||||
*
|
||||
*/
|
||||
public ReplyJob getOnReplyJob() { return _onReply; }
|
||||
public void setOnReplyJob(ReplyJob job) { _onReply = job; }
|
||||
/**
|
||||
* If the Message selector is specified but it doesn't find a reply before
|
||||
* its expiration passes, this job is enqueued.
|
||||
*/
|
||||
public Job getOnFailedReplyJob() { return _onFailedReply; }
|
||||
public void setOnFailedReplyJob(Job job) { _onFailedReply = job; }
|
||||
/**
|
||||
* Defines a MessageSelector to find a reply to this message.
|
||||
*
|
||||
*/
|
||||
public MessageSelector getReplySelector() { return _replySelector; }
|
||||
public void setReplySelector(MessageSelector selector) { _replySelector = selector; }
|
||||
|
||||
public void transportFailed(String transportStyle) { _failedTransports.add(transportStyle); }
|
||||
public Set getFailedTransports() { return new HashSet(_failedTransports); }
|
||||
|
||||
/** when did the sending process begin */
|
||||
public long getSendBegin() { return _sendBegin; }
|
||||
public void beginSend() { _sendBegin = Clock.getInstance().now(); }
|
||||
|
||||
public long getCreated() { return _created; }
|
||||
public long getLifetime() { return Clock.getInstance().now() - _created; }
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[OutNetMessage contains ");
|
||||
if (_message == null) {
|
||||
buf.append("*no message*");
|
||||
} else {
|
||||
buf.append("a ").append(_messageSize).append(" byte ");
|
||||
buf.append(_message.getClass().getName());
|
||||
}
|
||||
buf.append(" expiring on ").append(new Date(_expiration));
|
||||
buf.append(" failed delivery on transports ").append(_failedTransports);
|
||||
if (_target == null)
|
||||
buf.append(" targetting no one in particular...");
|
||||
else
|
||||
buf.append(" targetting ").append(_target.getIdentity().getHash().toBase64());
|
||||
if (_onReply != null)
|
||||
buf.append(" with onReply job: ").append(_onReply);
|
||||
if (_onSend != null)
|
||||
buf.append(" with onSend job: ").append(_onSend);
|
||||
if (_onFailedReply != null)
|
||||
buf.append(" with onFailedReply job: ").append(_onFailedReply);
|
||||
if (_onFailedSend != null)
|
||||
buf.append(" with onFailedSend job: ").append(_onFailedSend);
|
||||
buf.append(" {timestamps: \n");
|
||||
synchronized (_timestamps) {
|
||||
long lastWhen = -1;
|
||||
for (int i = 0; i < _timestampOrder.size(); i++) {
|
||||
String name = (String)_timestampOrder.get(i);
|
||||
Long when = (Long)_timestamps.get(name);
|
||||
buf.append("\t[");
|
||||
long diff = when.longValue() - lastWhen;
|
||||
if ( (lastWhen > 0) && (diff > 500) )
|
||||
buf.append("**");
|
||||
if (lastWhen > 0)
|
||||
buf.append(diff);
|
||||
else
|
||||
buf.append(0);
|
||||
buf.append("ms: \t").append(name).append('=').append(formatDate(when.longValue())).append("]\n");
|
||||
lastWhen = when.longValue();
|
||||
}
|
||||
}
|
||||
buf.append("}");
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final static SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
private final static String formatDate(long when) {
|
||||
Date d = new Date(when);
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(d);
|
||||
}
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int rv = 0;
|
||||
rv += DataHelper.hashCode(_message);
|
||||
rv += DataHelper.hashCode(_target);
|
||||
// the others are pretty much inconsequential
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this; // two OutNetMessages are different even if they contain the same message
|
||||
}
|
||||
}
|
||||
194
router/java/src/net/i2p/router/OutNetMessagePool.java
Normal file
194
router/java/src/net/i2p/router/OutNetMessagePool.java
Normal file
@@ -0,0 +1,194 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Comparator;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
|
||||
/**
|
||||
* Maintain a pool of OutNetMessages destined for other routers, organized by
|
||||
* priority, expiring messages as necessary. This pool is populated by anything
|
||||
* that wants to send a message, and the communication subsystem periodically
|
||||
* retrieves messages for delivery.
|
||||
*
|
||||
*/
|
||||
public class OutNetMessagePool {
|
||||
private final static Log _log = new Log(OutNetMessagePool.class);
|
||||
private static OutNetMessagePool _instance = new OutNetMessagePool();
|
||||
public static OutNetMessagePool getInstance() { return _instance; }
|
||||
private TreeMap _messageLists; // priority --> List of OutNetMessage objects, where HIGHEST priority first
|
||||
|
||||
private OutNetMessagePool() {
|
||||
_messageLists = new TreeMap(new ReverseIntegerComparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the highest priority message, or null if none are available.
|
||||
*
|
||||
*/
|
||||
public OutNetMessage getNext() {
|
||||
synchronized (_messageLists) {
|
||||
if (_messageLists.size() <= 0) return null;
|
||||
for (Iterator iter = _messageLists.keySet().iterator(); iter.hasNext(); ) {
|
||||
Integer priority = (Integer)iter.next();
|
||||
List messages = (List)_messageLists.get(priority);
|
||||
if (messages.size() > 0) {
|
||||
_log.debug("Found a message of priority " + priority);
|
||||
return (OutNetMessage)messages.remove(0);
|
||||
}
|
||||
}
|
||||
// no messages of any priority
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new message to the pool
|
||||
*
|
||||
*/
|
||||
public void add(OutNetMessage msg) {
|
||||
boolean valid = validate(msg);
|
||||
if (!valid) return;
|
||||
if (true) { // skip the pool
|
||||
MessageSelector selector = msg.getReplySelector();
|
||||
if (selector != null) {
|
||||
OutboundMessageRegistry.getInstance().registerPending(msg);
|
||||
}
|
||||
CommSystemFacade.getInstance().processMessage(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (_messageLists) {
|
||||
Integer pri = new Integer(msg.getPriority());
|
||||
if ( (_messageLists.size() <= 0) || (!_messageLists.containsKey(pri)) )
|
||||
_messageLists.put(new Integer(msg.getPriority()), new ArrayList(32));
|
||||
List messages = (List)_messageLists.get(pri);
|
||||
messages.add(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validate(OutNetMessage msg) {
|
||||
if (msg == null) return false;
|
||||
if (msg.getMessage() == null) {
|
||||
_log.error("Null message in the OutNetMessage: " + msg, new Exception("Someone fucked up"));
|
||||
return false;
|
||||
}
|
||||
if (msg.getTarget() == null) {
|
||||
_log.error("No target in the OutNetMessage: " + msg, new Exception("Definitely a fuckup"));
|
||||
return false;
|
||||
}
|
||||
if (msg.getPriority() < 0) {
|
||||
_log.warn("Priority less than 0? sounds like nonsense to me... " + msg, new Exception("Negative priority"));
|
||||
return false;
|
||||
}
|
||||
if (msg.getExpiration() <= Clock.getInstance().now()) {
|
||||
_log.error("Already expired! wtf: " + msg, new Exception("Expired message"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any messages that have expired, enqueuing any appropriate jobs
|
||||
*
|
||||
*/
|
||||
public void clearExpired() {
|
||||
long now = Clock.getInstance().now();
|
||||
List jobsToEnqueue = new ArrayList();
|
||||
synchronized (_messageLists) {
|
||||
for (Iterator iter = _messageLists.values().iterator(); iter.hasNext();) {
|
||||
List toRemove = new ArrayList();
|
||||
List messages = (List)iter.next();
|
||||
for (Iterator msgIter = messages.iterator(); msgIter.hasNext(); ) {
|
||||
OutNetMessage msg = (OutNetMessage)msgIter.next();
|
||||
if (msg.getExpiration() <= now) {
|
||||
_log.warn("Outbound network message expired: " + msg);
|
||||
toRemove.add(msg);
|
||||
jobsToEnqueue.add(msg.getOnFailedSendJob());
|
||||
}
|
||||
}
|
||||
messages.removeAll(toRemove);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < jobsToEnqueue.size(); i++) {
|
||||
Job j = (Job)jobsToEnqueue.get(i);
|
||||
JobQueue.getInstance().addJob(j);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of messages, regardless of priority.
|
||||
*
|
||||
*/
|
||||
public int getCount() {
|
||||
int size = 0;
|
||||
synchronized (_messageLists) {
|
||||
for (Iterator iter = _messageLists.values().iterator(); iter.hasNext(); ) {
|
||||
List lst = (List)iter.next();
|
||||
size += lst.size();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of messages at the given priority. This can be used for
|
||||
* subsystems that maintain a pool of messages to be sent whenever there is spare time,
|
||||
* where all of these 'spare' messages are of the same priority.
|
||||
*
|
||||
*/
|
||||
public int getCount(int priority) {
|
||||
synchronized (_messageLists) {
|
||||
Integer pri = new Integer(priority);
|
||||
List messages = (List)_messageLists.get(pri);
|
||||
if (messages == null)
|
||||
return 0;
|
||||
else
|
||||
return messages.size();
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpPoolInfo() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("\nDumping Outbound Network Message Pool. Total # message: ").append(getCount()).append("\n");
|
||||
synchronized (_messageLists) {
|
||||
for (Iterator iter = _messageLists.keySet().iterator(); iter.hasNext();) {
|
||||
Integer pri = (Integer)iter.next();
|
||||
List messages = (List)_messageLists.get(pri);
|
||||
if (messages.size() > 0) {
|
||||
buf.append("Messages of priority ").append(pri).append(": ").append(messages.size()).append("\n");
|
||||
buf.append("---------------------------\n");
|
||||
for (Iterator msgIter = messages.iterator(); msgIter.hasNext(); ) {
|
||||
OutNetMessage msg = (OutNetMessage)msgIter.next();
|
||||
buf.append("Message ").append(msg.getMessage()).append("\n\n");
|
||||
}
|
||||
buf.append("---------------------------\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
_log.debug(buf.toString());
|
||||
}
|
||||
|
||||
private static class ReverseIntegerComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
if ( (lhs == null) || (rhs == null) ) return 0; // invalid, but never used
|
||||
if ( !(lhs instanceof Integer) || !(rhs instanceof Integer)) return 0;
|
||||
Integer lv = (Integer)lhs;
|
||||
Integer rv = (Integer)rhs;
|
||||
return - (lv.compareTo(rv));
|
||||
}
|
||||
}
|
||||
}
|
||||
40
router/java/src/net/i2p/router/PeerManagerFacade.java
Normal file
40
router/java/src/net/i2p/router/PeerManagerFacade.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.List;
|
||||
|
||||
import net.i2p.router.peermanager.PeerManagerFacadeImpl;
|
||||
|
||||
/**
|
||||
* Manage peer references and keep them up to date so that when asked for peers,
|
||||
* it can provide appropriate peers according to the criteria provided. This
|
||||
* includes periodically queueing up outbound messages to the peers to test them.
|
||||
*
|
||||
*/
|
||||
public abstract class PeerManagerFacade implements Service {
|
||||
private static PeerManagerFacade _instance = new PeerManagerFacadeImpl();
|
||||
public static PeerManagerFacade getInstance() { return _instance; }
|
||||
|
||||
/**
|
||||
* Select peers from the manager's existing routing tables according to
|
||||
* the specified criteria. This call DOES block.
|
||||
*
|
||||
* @return List of Hash objects of the RouterIdentity for matching peers
|
||||
*/
|
||||
public abstract List selectPeers(PeerSelectionCriteria criteria);
|
||||
public String renderStatusHTML() { return ""; }
|
||||
}
|
||||
|
||||
class DummyPeerManagerFacade extends PeerManagerFacade {
|
||||
public void shutdown() {}
|
||||
public void startup() {}
|
||||
|
||||
public List selectPeers(PeerSelectionCriteria criteria) { return null; }
|
||||
}
|
||||
39
router/java/src/net/i2p/router/PeerSelectionCriteria.java
Normal file
39
router/java/src/net/i2p/router/PeerSelectionCriteria.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the criteria for selecting a set of peers for use when searching the
|
||||
* PeerManager
|
||||
*
|
||||
*/
|
||||
public class PeerSelectionCriteria {
|
||||
/** The peers will be used in a tunnel */
|
||||
public final static int PURPOSE_TUNNEL = 1;
|
||||
/** The peers will be used for garlic routed messages */
|
||||
public final static int PURPOSE_GARLIC = 2;
|
||||
/** The peers will be used for a source routed reply block message */
|
||||
public final static int PURPOSE_SOURCE_ROUTE = 3;
|
||||
/** The peers will be used for a test message */
|
||||
public final static int PURPOSE_TEST = 4;
|
||||
|
||||
private int _minReq;
|
||||
private int _maxReq;
|
||||
private int _purpose;
|
||||
|
||||
/** Minimum number of peers required */
|
||||
public int getMinimumRequired() { return _minReq; }
|
||||
public void setMinimumRequired(int min) { _minReq = min; }
|
||||
/** Maximum number of peers required */
|
||||
public int getMaximumRequired() { return _maxReq; }
|
||||
public void setMaximumRequired(int max) { _maxReq = max; }
|
||||
/** Purpose for which the peers will be used */
|
||||
public int getPurpose() { return _purpose; }
|
||||
public void setPurpose(int purpose) { _purpose = purpose; }
|
||||
}
|
||||
133
router/java/src/net/i2p/router/ProfileManager.java
Normal file
133
router/java/src/net/i2p/router/ProfileManager.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.Properties;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.peermanager.ProfileManagerImpl;
|
||||
|
||||
public abstract class ProfileManager {
|
||||
private final static ProfileManager _instance = new ProfileManagerImpl();
|
||||
public static ProfileManager getInstance() { return _instance; }
|
||||
|
||||
/** is this peer failing or already dropped? */
|
||||
public abstract boolean isFailing(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that it took msToSend to send a message of size bytesSent to the peer over the transport.
|
||||
* This should only be called if the transport considered the send successful.
|
||||
*
|
||||
*/
|
||||
public abstract void messageSent(Hash peer, String transport, long msToSend, long bytesSent);
|
||||
|
||||
/**
|
||||
* Note that the router failed to send a message to the peer over the transport specified
|
||||
*
|
||||
*/
|
||||
public abstract void messageFailed(Hash peer, String transport);
|
||||
|
||||
/**
|
||||
* Note that the router failed to send a message to the peer over any transport
|
||||
*
|
||||
*/
|
||||
public abstract void messageFailed(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that there was some sort of communication error talking with the peer
|
||||
*
|
||||
*/
|
||||
public abstract void commErrorOccurred(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that the router agreed to participate in a tunnel
|
||||
*
|
||||
*/
|
||||
public abstract void tunnelJoined(Hash peer, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that a router explicitly rejected joining a tunnel
|
||||
*
|
||||
*/
|
||||
public abstract void tunnelRejected(Hash peer, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that the peer participated in a tunnel that failed. Its failure may not have
|
||||
* been the peer's fault however.
|
||||
*
|
||||
*/
|
||||
public abstract void tunnelFailed(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that the peer was able to return the valid data for a db lookup
|
||||
*
|
||||
*/
|
||||
public abstract void dbLookupSuccessful(Hash peer, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that the peer was unable to reply to a db lookup - either with data or with
|
||||
* a lookupReply redirecting the user elsewhere
|
||||
*
|
||||
*/
|
||||
public abstract void dbLookupFailed(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that the peer replied to a db lookup with a redirect to other routers, where
|
||||
* the list of redirected users included newPeers routers that the local router didn't
|
||||
* know about, oldPeers routers that the local router already knew about, the given invalid
|
||||
* routers that were invalid in some way, and the duplicate number of routers that we explicitly
|
||||
* asked them not to send us, but they did anyway
|
||||
*
|
||||
*/
|
||||
public abstract void dbLookupReply(Hash peer, int newPeers, int oldPeers, int invalid, int duplicate, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that the local router received a db lookup from the given peer
|
||||
*
|
||||
*/
|
||||
public abstract void dbLookupReceived(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that the local router received an unprompted db store from the given peer
|
||||
*
|
||||
*/
|
||||
public abstract void dbStoreReceived(Hash peer, boolean wasNewKey);
|
||||
|
||||
/**
|
||||
* Note that we've confirmed a successful send of db data to the peer (though we haven't
|
||||
* necessarily requested it again from them, so they /might/ be lying)
|
||||
*
|
||||
*/
|
||||
public abstract void dbStoreSent(Hash peer, long responseTimeMs);
|
||||
|
||||
/**
|
||||
* Note that we were unable to confirm a successful send of db data to
|
||||
* the peer, at least not within our timeout period
|
||||
*
|
||||
*/
|
||||
public abstract void dbStoreFailed(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that the local router received a reference to the given peer, either
|
||||
* through an explicit dbStore or in a dbLookupReply
|
||||
*/
|
||||
public abstract void heardAbout(Hash peer);
|
||||
|
||||
/**
|
||||
* Note that the router received a message from the given peer on the specified
|
||||
* transport. Messages received without any "from" information aren't recorded
|
||||
* through this metric. If msToReceive is negative, there was no timing information
|
||||
* available
|
||||
*
|
||||
*/
|
||||
public abstract void messageReceived(Hash peer, String style, long msToReceive, int bytesRead);
|
||||
|
||||
/** provide a simple summary of a number of peers, suitable for publication in the netDb */
|
||||
public abstract Properties summarizePeers(int numPeers);
|
||||
}
|
||||
19
router/java/src/net/i2p/router/ReplyJob.java
Normal file
19
router/java/src/net/i2p/router/ReplyJob.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
/**
|
||||
* Defines an executable task that can be fired off in reply to a message
|
||||
*
|
||||
*/
|
||||
public interface ReplyJob extends Job {
|
||||
public void setMessage(I2NPMessage message);
|
||||
}
|
||||
425
router/java/src/net/i2p/router/Router.java
Normal file
425
router/java/src/net/i2p/router/Router.java
Normal file
@@ -0,0 +1,425 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.DHSessionKeyBuilder;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.RoutingKeyGenerator;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.SourceRouteReplyMessage;
|
||||
import net.i2p.data.i2np.TunnelMessage;
|
||||
import net.i2p.router.message.GarlicMessageHandler;
|
||||
import net.i2p.router.message.SourceRouteReplyMessageHandler;
|
||||
import net.i2p.router.message.TunnelMessageHandler;
|
||||
import net.i2p.router.startup.StartupJob;
|
||||
import net.i2p.router.transport.BandwidthLimiter;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.LogConsoleBuffer;
|
||||
import net.i2p.util.LogManager;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.router.admin.StatsGenerator;
|
||||
|
||||
/**
|
||||
* Main driver for the router.
|
||||
*
|
||||
*/
|
||||
public class Router {
|
||||
private final static Log _log = new Log(Router.class);
|
||||
private final static Router _instance = new Router();
|
||||
public static Router getInstance() { return _instance; }
|
||||
private Properties _config;
|
||||
private String _configFilename;
|
||||
private RouterInfo _routerInfo;
|
||||
private long _started;
|
||||
private boolean _higherVersionSeen;
|
||||
|
||||
public final static String PROP_CONFIG_FILE = "router.configLocation";
|
||||
|
||||
/** let clocks be off by 1 minute */
|
||||
public final static long CLOCK_FUDGE_FACTOR = 1*60*1000;
|
||||
|
||||
public final static String PROP_INFO_FILENAME = "router.info.location";
|
||||
public final static String PROP_INFO_FILENAME_DEFAULT = "router.info";
|
||||
public final static String PROP_KEYS_FILENAME = "router.keys.location";
|
||||
public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys";
|
||||
|
||||
private Router() {
|
||||
_config = new Properties();
|
||||
_configFilename = System.getProperty(PROP_CONFIG_FILE, "router.config");
|
||||
_routerInfo = null;
|
||||
_higherVersionSeen = false;
|
||||
// grumble about sun's java caching DNS entries *forever*
|
||||
System.setProperty("sun.net.inetaddr.ttl", "0");
|
||||
System.setProperty("networkaddress.cache.ttl", "0");
|
||||
// (no need for keepalive)
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
}
|
||||
|
||||
public String getConfigFilename() { return _configFilename; }
|
||||
public void setConfigFilename(String filename) { _configFilename = filename; }
|
||||
|
||||
public String getConfigSetting(String name) { return _config.getProperty(name); }
|
||||
public void setConfigSetting(String name, String value) { _config.setProperty(name, value); }
|
||||
public Set getConfigSettings() { return new HashSet(_config.keySet()); }
|
||||
public Properties getConfigMap() { return _config; }
|
||||
|
||||
public RouterInfo getRouterInfo() { return _routerInfo; }
|
||||
public void setRouterInfo(RouterInfo info) {
|
||||
_routerInfo = info;
|
||||
if (info != null)
|
||||
JobQueue.getInstance().addJob(new PersistRouterInfoJob());
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the router has tried to communicate with another router who is running a higher
|
||||
* incompatible protocol version.
|
||||
*
|
||||
*/
|
||||
public boolean getHigherVersionSeen() { return _higherVersionSeen; }
|
||||
public void setHigherVersionSeen(boolean seen) { _higherVersionSeen = seen; }
|
||||
|
||||
public long getWhenStarted() { return _started; }
|
||||
/** wall clock uptime */
|
||||
public long getUptime() { return Clock.getInstance().now() - Clock.getInstance().getOffset() - _started; }
|
||||
|
||||
private void runRouter() {
|
||||
_started = Clock.getInstance().now();
|
||||
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
|
||||
I2PThread.setOOMEventListener(new I2PThread.OOMEventListener() {
|
||||
public void outOfMemory(OutOfMemoryError oom) {
|
||||
_log.log(Log.CRIT, "Thread ran out of memory", oom);
|
||||
shutdown();
|
||||
}
|
||||
});
|
||||
setupHandlers();
|
||||
startupQueue();
|
||||
JobQueue.getInstance().addJob(new CoallesceStatsJob());
|
||||
JobQueue.getInstance().addJob(new UpdateRoutingKeyModifierJob());
|
||||
warmupCrypto();
|
||||
SessionKeyPersistenceHelper.getInstance().startup();
|
||||
JobQueue.getInstance().addJob(new StartupJob());
|
||||
}
|
||||
|
||||
/**
|
||||
* coallesce the stats framework every minute
|
||||
*
|
||||
*/
|
||||
private final static class CoallesceStatsJob extends JobImpl {
|
||||
public String getName() { return "Coallesce stats"; }
|
||||
public void runJob() {
|
||||
StatManager.getInstance().coallesceStats();
|
||||
requeue(60*1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the routing Key modifier every day at midnight (plus on startup).
|
||||
* This is done here because we want to make sure the key is updated before anyone
|
||||
* uses it.
|
||||
*/
|
||||
private final static class UpdateRoutingKeyModifierJob extends JobImpl {
|
||||
private Calendar _cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
public String getName() { return "Update Routing Key Modifier"; }
|
||||
public void runJob() {
|
||||
RoutingKeyGenerator.getInstance().generateDateBasedModData();
|
||||
requeue(getTimeTillMidnight());
|
||||
}
|
||||
private long getTimeTillMidnight() {
|
||||
long now = Clock.getInstance().now();
|
||||
_cal.setTime(new Date(now));
|
||||
_cal.add(Calendar.DATE, 1);
|
||||
_cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||
_cal.set(Calendar.MINUTE, 0);
|
||||
_cal.set(Calendar.SECOND, 0);
|
||||
_cal.set(Calendar.MILLISECOND, 0);
|
||||
long then = _cal.getTime().getTime();
|
||||
_log.debug("Time till midnight: " + (then-now) + "ms");
|
||||
if (then - now <= 60*1000) {
|
||||
// everyone wave at kaffe.
|
||||
// "Hi Kaffe"
|
||||
return 60*1000;
|
||||
} else {
|
||||
return then - now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void warmupCrypto() {
|
||||
RandomSource.getInstance().nextBoolean();
|
||||
new DHSessionKeyBuilder(); // load the class so it starts the precalc process
|
||||
}
|
||||
|
||||
private void startupQueue() {
|
||||
JobQueue.getInstance().runQueue(1);
|
||||
}
|
||||
|
||||
private void setupHandlers() {
|
||||
InNetMessagePool.getInstance().registerHandlerJobBuilder(GarlicMessage.MESSAGE_TYPE, new GarlicMessageHandler());
|
||||
InNetMessagePool.getInstance().registerHandlerJobBuilder(TunnelMessage.MESSAGE_TYPE, new TunnelMessageHandler());
|
||||
InNetMessagePool.getInstance().registerHandlerJobBuilder(SourceRouteReplyMessage.MESSAGE_TYPE, new SourceRouteReplyMessageHandler());
|
||||
}
|
||||
|
||||
public String renderStatusHTML() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("<html><head><title>I2P Router Console</title></head><body>\n");
|
||||
buf.append("<h1>Router console</h1>\n");
|
||||
buf.append("<i><a href=\"/routerConsole.html\">console</a> | <a href=\"/routerStats.html\">stats</a></i><br>\n");
|
||||
|
||||
buf.append("<form action=\"/routerConsole.html\">");
|
||||
buf.append("<select name=\"go\" onChange='location.href=this.value'>");
|
||||
buf.append("<option value=\"/routerConsole.html#bandwidth\">Bandwidth</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#clients\">Clients</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#transports\">Transports</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#profiles\">Peer Profiles</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#tunnels\">Tunnels</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#jobs\">Jobs</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#shitlist\">Shitlist</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#pending\">Pending messages</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#netdb\">Network Database</option>\n");
|
||||
buf.append("<option value=\"/routerConsole.html#logs\">Log messages</option>\n");
|
||||
buf.append("</select>");
|
||||
buf.append("</form>");
|
||||
|
||||
buf.append("<hr />\n");
|
||||
|
||||
if ( (_routerInfo != null) && (_routerInfo.getIdentity() != null) )
|
||||
buf.append("<b>Router: </b> ").append(_routerInfo.getIdentity().getHash().toBase64()).append("<br />\n");
|
||||
buf.append("<b>As of: </b> ").append(new Date(Clock.getInstance().now())).append(" (uptime: ").append(DataHelper.formatDuration(getUptime())).append(") <br />\n");
|
||||
buf.append("<b>Started on: </b> ").append(new Date(getWhenStarted())).append("<br />\n");
|
||||
buf.append("<b>Clock offset: </b> ").append(Clock.getInstance().getOffset()).append("ms (OS time: ").append(new Date(Clock.getInstance().now() - Clock.getInstance().getOffset())).append(")<br />\n");
|
||||
long tot = Runtime.getRuntime().totalMemory()/1024;
|
||||
long free = Runtime.getRuntime().freeMemory()/1024;
|
||||
buf.append("<b>Memory:</b> In use: ").append((tot-free)).append("KB Free: ").append(free).append("KB <br />\n");
|
||||
buf.append("<b>Version:</b> Router: ").append(RouterVersion.VERSION).append(" / SDK: ").append(CoreVersion.VERSION).append("<br />\n");
|
||||
if (_higherVersionSeen)
|
||||
buf.append("<b><font color=\"red\">HIGHER VERSION SEEN</font><b> - please <a href=\"http://i2p.dnsalias.net/\">check</a> to see if there is a new release out<br />\n");
|
||||
|
||||
buf.append("<hr /><a name=\"bandwidth\"> </a><h2>Bandwidth</h2>\n");
|
||||
long sent = BandwidthLimiter.getInstance().getTotalSendBytes();
|
||||
long received = BandwidthLimiter.getInstance().getTotalReceiveBytes();
|
||||
buf.append("<ul>");
|
||||
|
||||
buf.append("<li> ").append(sent).append(" bytes sent, ");
|
||||
buf.append(received).append(" bytes received</li>");
|
||||
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
|
||||
// we use the unadjusted time, since thats what getWhenStarted is based off
|
||||
long lifetime = Clock.getInstance().now()-Clock.getInstance().getOffset() - getWhenStarted();
|
||||
lifetime /= 1000;
|
||||
if ( (sent > 0) && (received > 0) ) {
|
||||
double sendKBps = sent / (lifetime*1024.0);
|
||||
double receivedKBps = received / (lifetime*1024.0);
|
||||
buf.append("<li>Lifetime rate: ");
|
||||
buf.append(fmt.format(sendKBps)).append("KBps sent ");
|
||||
buf.append(fmt.format(receivedKBps)).append("KBps received");
|
||||
buf.append("</li>");
|
||||
}
|
||||
|
||||
RateStat sendRate = StatManager.getInstance().getRate("transport.sendMessageSize");
|
||||
for (int i = 0; i < sendRate.getPeriods().length; i++) {
|
||||
Rate rate = sendRate.getRate(sendRate.getPeriods()[i]);
|
||||
double bytes = rate.getLastTotalValue() + rate.getCurrentTotalValue();
|
||||
long ms = rate.getLastTotalEventTime() + rate.getLastTotalEventTime();
|
||||
if (ms <= 0) {
|
||||
bytes = 0;
|
||||
ms = 1;
|
||||
}
|
||||
buf.append("<li>");
|
||||
buf.append(DataHelper.formatDuration(rate.getPeriod())).append(" instantaneous send avg: ");
|
||||
double bps = bytes*1000.0d/ms;
|
||||
if (bps > 2048) {
|
||||
bps /= 1024.0d;
|
||||
buf.append(fmt.format(bps)).append(" KBps");
|
||||
} else {
|
||||
buf.append(fmt.format(bps)).append(" Bps");
|
||||
}
|
||||
buf.append(" over ").append((long)bytes).append(" bytes");
|
||||
buf.append("</li><li>");
|
||||
buf.append(DataHelper.formatDuration(rate.getPeriod())).append(" period send avg: ");
|
||||
// we include lastPeriod + current *partial* period, and jrandom is too lazy to calculate how
|
||||
// much of that partial is contained here, so 2*period it is.
|
||||
bps = bytes*1000.0d/(2*rate.getPeriod());
|
||||
if (bps > 2048) {
|
||||
bps /= 1024.0d;
|
||||
buf.append(fmt.format(bps)).append(" KBps");
|
||||
} else {
|
||||
buf.append(fmt.format(bps)).append(" Bps");
|
||||
}
|
||||
buf.append(" over ").append((long)bytes).append(" bytes");
|
||||
buf.append("</li>");
|
||||
}
|
||||
|
||||
RateStat receiveRate = StatManager.getInstance().getRate("transport.receiveMessageSize");
|
||||
for (int i = 0; i < receiveRate.getPeriods().length; i++) {
|
||||
Rate rate = receiveRate.getRate(receiveRate.getPeriods()[i]);
|
||||
double bytes = rate.getLastTotalValue() + rate.getCurrentTotalValue();
|
||||
long ms = rate.getLastTotalEventTime() + rate.getLastTotalEventTime();
|
||||
if (ms <= 0) {
|
||||
bytes = 0;
|
||||
ms = 1;
|
||||
}
|
||||
buf.append("<li>");
|
||||
buf.append(DataHelper.formatDuration(rate.getPeriod())).append(" instantaneous receive avg: ");
|
||||
double bps = bytes*1000.0d/ms;
|
||||
if (bps > 2048) {
|
||||
bps /= 1024.0d;
|
||||
buf.append(fmt.format(bps)).append(" KBps ");
|
||||
} else {
|
||||
buf.append(fmt.format(bps)).append(" Bps ");
|
||||
}
|
||||
buf.append(" over ").append((long)bytes).append(" bytes");
|
||||
buf.append("</li><li>");
|
||||
buf.append(DataHelper.formatDuration(rate.getPeriod())).append(" period receive avg: ");
|
||||
// we include lastPeriod + current *partial* period, and jrandom is too lazy to calculate how
|
||||
// much of that partial is contained here, so 2*period it is.
|
||||
bps = bytes*1000.0d/(2*rate.getPeriod());
|
||||
if (bps > 2048) {
|
||||
bps /= 1024.0d;
|
||||
buf.append(fmt.format(bps)).append(" KBps");
|
||||
} else {
|
||||
buf.append(fmt.format(bps)).append(" Bps");
|
||||
}
|
||||
buf.append(" over ").append((long)bytes).append(" bytes");
|
||||
buf.append("</li>");
|
||||
}
|
||||
|
||||
buf.append("</ul>\n");
|
||||
buf.append("<i>Instantaneous averages count how fast the transfers go when we're trying to transfer data, ");
|
||||
buf.append("while period averages count how fast the transfers go across the entire period, even when we're not ");
|
||||
buf.append("trying to transfer data. Lifetime averages count how many elephants there are on the moon [like anyone reads this text]</i>");
|
||||
buf.append("\n");
|
||||
|
||||
buf.append("<hr /><a name=\"clients\"> </a>\n");
|
||||
buf.append(ClientManagerFacade.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"transports\"> </a>\n");
|
||||
buf.append(CommSystemFacade.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"profiles\"> </a>\n");
|
||||
buf.append(PeerManagerFacade.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"tunnels\"> </a>\n");
|
||||
buf.append(TunnelManagerFacade.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"jobs\"> </a>\n");
|
||||
buf.append(JobQueue.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"shitlist\"> </a>\n");
|
||||
buf.append(Shitlist.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"pending\"> </a>\n");
|
||||
buf.append(OutboundMessageRegistry.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"netdb\"> </a>\n");
|
||||
buf.append(NetworkDatabaseFacade.getInstance().renderStatusHTML());
|
||||
buf.append("\n<hr /><a name=\"logs\"> </a>\n");
|
||||
List msgs = LogConsoleBuffer.getInstance().getMostRecentMessages();
|
||||
buf.append("\n<h2>Most recent console messages:</h2><table border=\"1\">\n");
|
||||
for (Iterator iter = msgs.iterator(); iter.hasNext(); ) {
|
||||
String msg = (String)iter.next();
|
||||
buf.append("<tr><td valign=\"top\" align=\"left\"><pre>").append(msg);
|
||||
buf.append("</pre></td></tr>\n");
|
||||
}
|
||||
buf.append("</table>");
|
||||
buf.append("</body></html>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
try { JobQueue.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the job queue", t); }
|
||||
try { StatisticsManager.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the stats manager", t); }
|
||||
try { ClientManagerFacade.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the client manager", t); }
|
||||
try { TunnelManagerFacade.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the tunnel manager", t); }
|
||||
try { NetworkDatabaseFacade.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the networkDb", t); }
|
||||
try { CommSystemFacade.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the comm system", t); }
|
||||
try { PeerManagerFacade.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the peer manager", t); }
|
||||
try { SessionKeyPersistenceHelper.getInstance().shutdown(); } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting down the session key manager", t); }
|
||||
dumpStats();
|
||||
_log.log(Log.CRIT, "Shutdown complete", new Exception("Shutdown"));
|
||||
try { LogManager.getInstance().shutdown(); } catch (Throwable t) { }
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
Runtime.getRuntime().halt(-1);
|
||||
}
|
||||
|
||||
private void dumpStats() {
|
||||
_log.log(Log.CRIT, "Lifetime stats:\n\n" + StatsGenerator.generateStatsPage());
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
Router.getInstance().runRouter();
|
||||
if (args.length > 0) {
|
||||
_log.info("Not interactive");
|
||||
} else {
|
||||
_log.info("Interactive");
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
ClientMessagePool.getInstance().dumpPoolInfo();
|
||||
OutNetMessagePool.getInstance().dumpPoolInfo();
|
||||
InNetMessagePool.getInstance().dumpPoolInfo();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error dumping queue", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ShutdownHook extends Thread {
|
||||
public void run() {
|
||||
_log.log(Log.CRIT, "Shutting down the router...", new Exception("Shutting down"));
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/** update the router.info file whenever its, er, updated */
|
||||
private static class PersistRouterInfoJob extends JobImpl {
|
||||
public String getName() { return "Persist Updated Router Information"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Persisting updated router info");
|
||||
|
||||
String infoFilename = Router.getInstance().getConfigSetting(PROP_INFO_FILENAME);
|
||||
if (infoFilename == null)
|
||||
infoFilename = PROP_INFO_FILENAME_DEFAULT;
|
||||
|
||||
RouterInfo info = Router.getInstance().getRouterInfo();
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(infoFilename);
|
||||
info.writeBytes(fos);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error rebuilding the router information", dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the rebuilt router information", ioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
router/java/src/net/i2p/router/RouterVersion.java
Normal file
26
router/java/src/net/i2p/router/RouterVersion.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.CoreVersion;
|
||||
|
||||
/**
|
||||
* Expose a version string
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.40 $ $Date: 2004/04/04 13:40:34 $";
|
||||
public final static String VERSION = "0.3.0.3";
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION);
|
||||
System.out.println("Router ID: " + RouterVersion.ID);
|
||||
System.out.println("I2P Core version: " + CoreVersion.VERSION);
|
||||
System.out.println("Core ID: " + CoreVersion.ID);
|
||||
}
|
||||
}
|
||||
32
router/java/src/net/i2p/router/Service.java
Normal file
32
router/java/src/net/i2p/router/Service.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Define the manageable service interface for the subsystems in the I2P router
|
||||
*
|
||||
*/
|
||||
public interface Service {
|
||||
/**
|
||||
* Instruct the service that it should start normal operation.
|
||||
* This call DOES block until the service is ready.
|
||||
*
|
||||
*/
|
||||
public void startup();
|
||||
|
||||
/**
|
||||
* Instruct the service that the router is shutting down and that it should do
|
||||
* whatever is necessary to go down gracefully. It should not depend on other
|
||||
* components at this point. This call DOES block.
|
||||
*
|
||||
*/
|
||||
public void shutdown();
|
||||
|
||||
public String renderStatusHTML();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.crypto.PersistentSessionKeyManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Centralize the sessionKeyManager persistence (rather than leave it to a private
|
||||
* job in the startup job)
|
||||
*
|
||||
*/
|
||||
public class SessionKeyPersistenceHelper implements Service {
|
||||
private final static Log _log = new Log(SessionKeyPersistenceHelper.class);
|
||||
private static SessionKeyPersistenceHelper _instance = new SessionKeyPersistenceHelper();
|
||||
public static SessionKeyPersistenceHelper getInstance() { return _instance; }
|
||||
private final static long PERSIST_DELAY = 3*60*1000;
|
||||
private final static String SESSION_KEY_FILE = "sessionKeys.dat";
|
||||
|
||||
public void shutdown() {
|
||||
writeState();
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
SessionKeyManager mgr = SessionKeyManager.getInstance();
|
||||
if (mgr instanceof PersistentSessionKeyManager) {
|
||||
PersistentSessionKeyManager manager = (PersistentSessionKeyManager)mgr;
|
||||
File f = new File(SESSION_KEY_FILE);
|
||||
if (f.exists()) {
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(f);
|
||||
manager.loadState(fin);
|
||||
int expired = manager.aggressiveExpire();
|
||||
_log.debug("Session keys loaded [not error] with " + expired + " sets immediately expired");
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error reading in session key data", t);
|
||||
} finally {
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
JobQueue.getInstance().addJob(new SessionKeyWriterJob());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeState() {
|
||||
Object o = SessionKeyManager.getInstance();
|
||||
if (!(o instanceof PersistentSessionKeyManager)) {
|
||||
_log.error("Unable to persist the session key state - manager is " + o.getClass().getName());
|
||||
return;
|
||||
}
|
||||
PersistentSessionKeyManager mgr = (PersistentSessionKeyManager)o;
|
||||
|
||||
// only need for synchronization is during shutdown()
|
||||
synchronized (mgr) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
int expired = mgr.aggressiveExpire();
|
||||
if (expired > 0) {
|
||||
_log.info("Agressive expired " + expired + " tag sets");
|
||||
}
|
||||
fos = new FileOutputStream(SESSION_KEY_FILE);
|
||||
mgr.saveState(fos);
|
||||
fos.flush();
|
||||
_log.debug("Session keys written");
|
||||
} catch (Throwable t) {
|
||||
_log.debug("Error writing session key state", t);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String renderStatusHTML() { return ""; }
|
||||
|
||||
private class SessionKeyWriterJob extends JobImpl {
|
||||
public SessionKeyWriterJob() {
|
||||
super();
|
||||
getTiming().setStartAfter(PERSIST_DELAY);
|
||||
}
|
||||
public String getName() { return "Write Session Keys"; }
|
||||
public void runJob() {
|
||||
writeState();
|
||||
requeue(PERSIST_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
router/java/src/net/i2p/router/Shitlist.java
Normal file
99
router/java/src/net/i2p/router/Shitlist.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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 net.i2p.data.Hash;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Manage in memory the routers we are oh so fond of.
|
||||
* This needs to get a little bit more sophisticated... currently there is no
|
||||
* way out of the shitlist
|
||||
*
|
||||
*/
|
||||
public class Shitlist {
|
||||
private final static Shitlist _instance = new Shitlist();
|
||||
public final static Shitlist getInstance() { return _instance; }
|
||||
private final static Log _log = new Log(Shitlist.class);
|
||||
private Map _shitlist; // H(routerIdent) --> Date
|
||||
|
||||
public final static long SHITLIST_DURATION_MS = 4*60*1000; // 4 minute shitlist
|
||||
|
||||
private Shitlist() {
|
||||
_shitlist = new HashMap(100);
|
||||
}
|
||||
|
||||
public boolean shitlistRouter(Hash peer) {
|
||||
if (peer == null) return false;
|
||||
boolean wasAlready = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Shitlisting router " + peer.toBase64(), new Exception("Shitlist cause"));
|
||||
|
||||
synchronized (_shitlist) {
|
||||
Date oldDate = (Date)_shitlist.put(peer, new Date(Clock.getInstance().now()));
|
||||
wasAlready = (null == oldDate);
|
||||
}
|
||||
NetworkDatabaseFacade.getInstance().fail(peer);
|
||||
TunnelManagerFacade.getInstance().peerFailed(peer);
|
||||
return wasAlready;
|
||||
}
|
||||
|
||||
public void unshitlistRouter(Hash peer) {
|
||||
if (peer == null) return;
|
||||
_log.info("Unshitlisting router " + peer.toBase64());
|
||||
synchronized (_shitlist) {
|
||||
_shitlist.remove(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShitlisted(Hash peer) {
|
||||
Date shitlistDate = null;
|
||||
synchronized (_shitlist) {
|
||||
shitlistDate = (Date)_shitlist.get(peer);
|
||||
}
|
||||
if (shitlistDate == null) return false;
|
||||
|
||||
// check validity
|
||||
if (shitlistDate.getTime() > Clock.getInstance().now() - SHITLIST_DURATION_MS) {
|
||||
return true;
|
||||
} else {
|
||||
unshitlistRouter(peer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String renderStatusHTML() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("<h2>Shitlist</h2>");
|
||||
Map shitlist = new HashMap();
|
||||
synchronized (_shitlist) {
|
||||
shitlist.putAll(_shitlist);
|
||||
}
|
||||
buf.append("<ul>");
|
||||
|
||||
long limit = Clock.getInstance().now() - SHITLIST_DURATION_MS;
|
||||
|
||||
for (Iterator iter = shitlist.keySet().iterator(); iter.hasNext(); ) {
|
||||
Hash key = (Hash)iter.next();
|
||||
Date shitDate = (Date)shitlist.get(key);
|
||||
if (shitDate.getTime() < limit)
|
||||
unshitlistRouter(key);
|
||||
else
|
||||
buf.append("<li><b>").append(key.toBase64()).append("</b> was shitlisted on ").append(shitDate).append("</li>\n");
|
||||
}
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
147
router/java/src/net/i2p/router/StatisticsManager.java
Normal file
147
router/java/src/net/i2p/router/StatisticsManager.java
Normal file
@@ -0,0 +1,147 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Maintain the statistics about the router
|
||||
*
|
||||
*/
|
||||
public class StatisticsManager implements Service {
|
||||
private final static Log _log = new Log(StatisticsManager.class);
|
||||
private static StatisticsManager _instance = new StatisticsManager();
|
||||
public static StatisticsManager getInstance() { return _instance; }
|
||||
private boolean _includePeerRankings;
|
||||
private int _publishedStats;
|
||||
|
||||
public final static String PROP_PUBLISH_RANKINGS = "router.publishPeerRankings";
|
||||
public final static String DEFAULT_PROP_PUBLISH_RANKINGS = "false";
|
||||
public final static String PROP_MAX_PUBLISHED_PEERS = "router.publishPeerMax";
|
||||
public final static int DEFAULT_MAX_PUBLISHED_PEERS = 20;
|
||||
|
||||
public StatisticsManager() {
|
||||
_includePeerRankings = false;
|
||||
}
|
||||
|
||||
public void shutdown() {}
|
||||
public void startup() {
|
||||
String val = Router.getInstance().getConfigSetting(PROP_PUBLISH_RANKINGS);
|
||||
try {
|
||||
if (val == null) {
|
||||
_log.info("Peer publishing setting " + PROP_PUBLISH_RANKINGS + " not set - using default " + DEFAULT_PROP_PUBLISH_RANKINGS);
|
||||
val = DEFAULT_PROP_PUBLISH_RANKINGS;
|
||||
} else {
|
||||
_log.info("Peer publishing setting " + PROP_PUBLISH_RANKINGS + " set to " + val);
|
||||
}
|
||||
boolean v = Boolean.TRUE.toString().equalsIgnoreCase(val);
|
||||
_includePeerRankings = v;
|
||||
_log.debug("Setting includePeerRankings = " + v);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error determining whether to publish rankings [" + PROP_PUBLISH_RANKINGS + "=" + val + "], so we're defaulting to FALSE");
|
||||
_includePeerRankings = false;
|
||||
}
|
||||
val = Router.getInstance().getConfigSetting(PROP_MAX_PUBLISHED_PEERS);
|
||||
if (val == null) {
|
||||
_publishedStats = DEFAULT_MAX_PUBLISHED_PEERS;
|
||||
} else {
|
||||
try {
|
||||
int num = Integer.parseInt(val);
|
||||
_publishedStats = num;
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Invalid max number of peers to publish [" + val + "], defaulting to " + DEFAULT_MAX_PUBLISHED_PEERS, nfe);
|
||||
_publishedStats = DEFAULT_MAX_PUBLISHED_PEERS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Retrieve a snapshot of the statistics that should be published */
|
||||
public Properties publishStatistics() {
|
||||
Properties stats = new Properties();
|
||||
stats.setProperty("router.version", RouterVersion.VERSION);
|
||||
stats.setProperty("router.id", RouterVersion.ID);
|
||||
stats.setProperty("coreVersion", CoreVersion.VERSION);
|
||||
stats.setProperty("core.id", CoreVersion.ID);
|
||||
|
||||
if (_includePeerRankings) {
|
||||
stats.putAll(ProfileManager.getInstance().summarizePeers(_publishedStats));
|
||||
|
||||
includeRate("transport.sendProcessingTime", stats);
|
||||
includeRate("tcp.queueSize", stats);
|
||||
includeRate("jobQueue.jobLag", stats);
|
||||
includeRate("jobQueue.jobRun", stats);
|
||||
includeRate("crypto.elGamal.encrypt", stats);
|
||||
includeRate("jobQueue.readyJobs", stats);
|
||||
includeRate("jobQueue.droppedJobs", stats);
|
||||
stats.setProperty("stat_uptime", DataHelper.formatDuration(Router.getInstance().getUptime()));
|
||||
stats.setProperty("stat__rateKey", "avg;maxAvg;pctLifetime;[sat;satLim;maxSat;maxSatLim;][num;lifetimeFreq;maxFreq]");
|
||||
_log.debug("Publishing peer rankings");
|
||||
} else {
|
||||
_log.debug("Not publishing peer rankings");
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Building status: " + stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void includeRate(String rateName, Properties stats) {
|
||||
RateStat rate = StatManager.getInstance().getRate(rateName);
|
||||
if (rate == null) return;
|
||||
for (int i = 0; i < rate.getPeriods().length; i++) {
|
||||
Rate curRate = rate.getRate(rate.getPeriods()[i]);
|
||||
if (curRate == null) continue;
|
||||
stats.setProperty("stat_" + rateName + '.' + getPeriod(curRate), renderRate(curRate));
|
||||
}
|
||||
}
|
||||
|
||||
private static String renderRate(Rate rate) {
|
||||
StringBuffer buf = new StringBuffer(255);
|
||||
buf.append(num(rate.getAverageValue())).append(';');
|
||||
buf.append(num(rate.getExtremeAverageValue())).append(';');
|
||||
buf.append(pct(rate.getPercentageOfLifetimeValue())).append(';');
|
||||
if (rate.getLifetimeTotalEventTime() > 0) {
|
||||
buf.append(pct(rate.getLastEventSaturation())).append(';');
|
||||
buf.append(num(rate.getLastSaturationLimit())).append(';');
|
||||
buf.append(pct(rate.getExtremeEventSaturation())).append(';');
|
||||
buf.append(num(rate.getExtremeSaturationLimit())).append(';');
|
||||
}
|
||||
buf.append(num(rate.getLastEventCount())).append(';');
|
||||
long numPeriods = rate.getLifetimePeriods();
|
||||
if (numPeriods > 0) {
|
||||
double avgFrequency = rate.getLifetimeEventCount() / (double)numPeriods;
|
||||
double peakFrequency = rate.getExtremeEventCount();
|
||||
buf.append(num(avgFrequency)).append(';');
|
||||
buf.append(num(rate.getExtremeEventCount())).append(';');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String getPeriod(Rate rate) { return DataHelper.formatDuration(rate.getPeriod()); }
|
||||
|
||||
// TODO: get this to use some random locale, not the user's default (since its published)
|
||||
private final static DecimalFormat _fmt = new DecimalFormat("###,##0.00", new DecimalFormatSymbols(Locale.UK));
|
||||
private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } }
|
||||
|
||||
private final static DecimalFormat _pct = new DecimalFormat("#0.00%", new DecimalFormatSymbols(Locale.UK));
|
||||
private final static String pct(double num) { synchronized (_pct) { return _pct.format(num); } }
|
||||
|
||||
|
||||
public String renderStatusHTML() { return ""; }
|
||||
}
|
||||
118
router/java/src/net/i2p/router/SubmitMessageHistoryJob.java
Normal file
118
router/java/src/net/i2p/router/SubmitMessageHistoryJob.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.HTTPSendData;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.router.transport.BandwidthLimiter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Job that, if its allowed to, will submit the data gathered by the MessageHistory
|
||||
* component to some URL so that the network can be debugged more easily. By default
|
||||
* it does not submit any data or touch the message history file, but if the router
|
||||
* has the line "router.submitHistory=true", it will send the file that the
|
||||
* MessageHistory component is configured to write to once an hour, post it to
|
||||
* http://i2p.net/cgi-bin/submitMessageHistory, and then delete that file
|
||||
* locally. This should only be used if the MessageHistory component is configured to
|
||||
* gather data (via "router.keepHistory=true").
|
||||
*
|
||||
*/
|
||||
public class SubmitMessageHistoryJob extends JobImpl {
|
||||
private final static Log _log = new Log(SubmitMessageHistoryJob.class);
|
||||
|
||||
/** default submitting data every hour */
|
||||
private final static long DEFAULT_REQUEUE_DELAY = 60*60*1000;
|
||||
/**
|
||||
* router config param for whether we want to autosubmit (and delete) the
|
||||
* history data managed by MessageHistory
|
||||
*/
|
||||
public final static String PARAM_SUBMIT_DATA = "router.submitHistory";
|
||||
/** default value for whether we autosubmit the data */
|
||||
public final static boolean DEFAULT_SUBMIT_DATA = true;
|
||||
/** where the data should be submitted to (via HTTP POST) */
|
||||
public final static String PARAM_SUBMIT_URL = "router.submitHistoryURL";
|
||||
/** default location */
|
||||
public final static String DEFAULT_SUBMIT_URL = "http://i2p.net/cgi-bin/submitMessageHistory";
|
||||
|
||||
public void runJob() {
|
||||
if (shouldSubmit()) {
|
||||
submit();
|
||||
} else {
|
||||
_log.debug("Not submitting data");
|
||||
// if we didn't submit we can just requeue
|
||||
requeue(getRequeueDelay());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't want this to be run within the jobqueue itself, so fire off a new thread
|
||||
* to do the actual submission, enqueueing a new submit job when its done
|
||||
*/
|
||||
private void submit() {
|
||||
I2PThread t = new I2PThread(new Runnable() {
|
||||
public void run() {
|
||||
_log.debug("Submitting data");
|
||||
MessageHistory.getInstance().setPauseFlushes(true);
|
||||
String filename = MessageHistory.getInstance().getFilename();
|
||||
send(filename);
|
||||
MessageHistory.getInstance().setPauseFlushes(false);
|
||||
Job job = new SubmitMessageHistoryJob();
|
||||
job.getTiming().setStartAfter(Clock.getInstance().now() + getRequeueDelay());
|
||||
JobQueue.getInstance().addJob(job);
|
||||
}
|
||||
});
|
||||
t.setName("SubmitData");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void send(String filename) {
|
||||
String url = getURL();
|
||||
try {
|
||||
File dataFile = new File(filename);
|
||||
if (!dataFile.exists() || !dataFile.canRead()) {
|
||||
_log.warn("Unable to read the message data file [" + dataFile.getAbsolutePath() + "]");
|
||||
return;
|
||||
}
|
||||
long size = dataFile.length();
|
||||
int expectedSend = 512; // 512 for HTTP overhead
|
||||
if (size > 0)
|
||||
expectedSend += (int)size/10; // compression
|
||||
FileInputStream fin = new FileInputStream(dataFile);
|
||||
BandwidthLimiter.getInstance().delayOutbound(null, expectedSend);
|
||||
boolean sent = HTTPSendData.postData(url, size, fin);
|
||||
fin.close();
|
||||
boolean deleted = dataFile.delete();
|
||||
_log.debug("Submitted " + size + " bytes? " + sent + " and deleted? " + deleted);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error sending the data", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private String getURL() {
|
||||
String str = Router.getInstance().getConfigSetting(PARAM_SUBMIT_URL);
|
||||
if ( (str == null) || (str.trim().length() <= 0) )
|
||||
return DEFAULT_SUBMIT_URL;
|
||||
else
|
||||
return str.trim();
|
||||
}
|
||||
|
||||
private boolean shouldSubmit() {
|
||||
String str = Router.getInstance().getConfigSetting(PARAM_SUBMIT_DATA);
|
||||
if (str == null) {
|
||||
_log.debug("History submit config not specified [" + PARAM_SUBMIT_DATA + "], default = " + DEFAULT_SUBMIT_DATA);
|
||||
return DEFAULT_SUBMIT_DATA;
|
||||
} else {
|
||||
_log.debug("History submit config specified [" + str + "]");
|
||||
}
|
||||
return Boolean.TRUE.toString().equals(str);
|
||||
}
|
||||
private long getRequeueDelay() { return DEFAULT_REQUEUE_DELAY; }
|
||||
public String getName() { return "Submit Message History"; }
|
||||
}
|
||||
348
router/java/src/net/i2p/router/TunnelInfo.java
Normal file
348
router/java/src/net/i2p/router/TunnelInfo.java
Normal file
@@ -0,0 +1,348 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.TunnelConfigurationSessionKey;
|
||||
import net.i2p.data.i2np.TunnelSessionKey;
|
||||
import net.i2p.data.i2np.TunnelSigningPrivateKey;
|
||||
import net.i2p.data.i2np.TunnelSigningPublicKey;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Defines the information associated with a tunnel
|
||||
*/
|
||||
public class TunnelInfo extends DataStructureImpl {
|
||||
private TunnelId _id;
|
||||
private Hash _nextHop;
|
||||
private Hash _thisHop;
|
||||
private TunnelInfo _nextHopInfo;
|
||||
private TunnelConfigurationSessionKey _configurationKey;
|
||||
private TunnelSigningPublicKey _verificationKey;
|
||||
private TunnelSigningPrivateKey _signingKey;
|
||||
private TunnelSessionKey _encryptionKey;
|
||||
private Destination _destination;
|
||||
private Properties _options;
|
||||
private TunnelSettings _settings;
|
||||
private long _created;
|
||||
private boolean _ready;
|
||||
private boolean _wasEverReady;
|
||||
|
||||
public TunnelInfo() {
|
||||
setTunnelId(null);
|
||||
setThisHop(null);
|
||||
setNextHop(null);
|
||||
setNextHopInfo(null);
|
||||
_configurationKey = null;
|
||||
_verificationKey = null;
|
||||
_signingKey = null;
|
||||
_encryptionKey = null;
|
||||
setDestination(null);
|
||||
setSettings(null);
|
||||
_options = new Properties();
|
||||
_ready = false;
|
||||
_wasEverReady = false;
|
||||
_created = Clock.getInstance().now();
|
||||
}
|
||||
|
||||
public TunnelId getTunnelId() { return _id; }
|
||||
public void setTunnelId(TunnelId id) { _id = id; }
|
||||
|
||||
public Hash getNextHop() { return _nextHop; }
|
||||
public void setNextHop(Hash nextHopRouterIdentity) { _nextHop = nextHopRouterIdentity; }
|
||||
|
||||
public Hash getThisHop() { return _thisHop; }
|
||||
public void setThisHop(Hash thisHopRouterIdentity) { _thisHop = thisHopRouterIdentity; }
|
||||
|
||||
public TunnelInfo getNextHopInfo() { return _nextHopInfo; }
|
||||
public void setNextHopInfo(TunnelInfo info) { _nextHopInfo = info; }
|
||||
|
||||
public TunnelConfigurationSessionKey getConfigurationKey() { return _configurationKey; }
|
||||
public void setConfigurationKey(TunnelConfigurationSessionKey key) { _configurationKey = key; }
|
||||
public void setConfigurationKey(SessionKey key) {
|
||||
TunnelConfigurationSessionKey tk = new TunnelConfigurationSessionKey();
|
||||
tk.setKey(key);
|
||||
_configurationKey = tk;
|
||||
}
|
||||
|
||||
public TunnelSigningPublicKey getVerificationKey() { return _verificationKey; }
|
||||
public void setVerificationKey(TunnelSigningPublicKey key) { _verificationKey = key; }
|
||||
public void setVerificationKey(SigningPublicKey key) {
|
||||
TunnelSigningPublicKey tk = new TunnelSigningPublicKey();
|
||||
tk.setKey(key);
|
||||
_verificationKey = tk;
|
||||
}
|
||||
|
||||
public TunnelSigningPrivateKey getSigningKey() { return _signingKey; }
|
||||
public void setSigningKey(TunnelSigningPrivateKey key) { _signingKey = key; }
|
||||
public void setSigningKey(SigningPrivateKey key) {
|
||||
TunnelSigningPrivateKey tk = new TunnelSigningPrivateKey();
|
||||
tk.setKey(key);
|
||||
_signingKey = tk;
|
||||
}
|
||||
|
||||
public TunnelSessionKey getEncryptionKey() { return _encryptionKey; }
|
||||
public void setEncryptionKey(TunnelSessionKey key) { _encryptionKey = key; }
|
||||
public void setEncryptionKey(SessionKey key) {
|
||||
TunnelSessionKey tk = new TunnelSessionKey();
|
||||
tk.setKey(key);
|
||||
_encryptionKey = tk;
|
||||
}
|
||||
|
||||
public Destination getDestination() { return _destination; }
|
||||
public void setDestination(Destination dest) { _destination = dest; }
|
||||
|
||||
public String getProperty(String key) { return _options.getProperty(key); }
|
||||
public void setProperty(String key, String val) { _options.setProperty(key, val); }
|
||||
public void clearProperties() { _options.clear(); }
|
||||
public Set getPropertyNames() { return new HashSet(_options.keySet()); }
|
||||
|
||||
public TunnelSettings getSettings() { return _settings; }
|
||||
public void setSettings(TunnelSettings settings) { _settings = settings; }
|
||||
|
||||
/**
|
||||
* Have all of the routers in this tunnel confirmed participation, and we're ok to
|
||||
* start sending messages through this tunnel?
|
||||
*/
|
||||
public boolean getIsReady() { return _ready; }
|
||||
public void setIsReady(boolean ready) {
|
||||
_ready = ready;
|
||||
if (ready)
|
||||
_wasEverReady = true;
|
||||
}
|
||||
/**
|
||||
* true if this tunnel was ever working (aka rebuildable)
|
||||
*
|
||||
*/
|
||||
public boolean getWasEverReady() { return _wasEverReady; }
|
||||
|
||||
public long getCreated() { return _created; }
|
||||
|
||||
/**
|
||||
* Number of hops left in the tunnel (including this one)
|
||||
*
|
||||
*/
|
||||
public final int getLength() {
|
||||
int len = 0;
|
||||
TunnelInfo info = this;
|
||||
while (info != null) {
|
||||
info = info.getNextHopInfo();
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
_options = DataHelper.readProperties(in);
|
||||
Boolean includeDest = DataHelper.readBoolean(in);
|
||||
if (includeDest.booleanValue()) {
|
||||
_destination = new Destination();
|
||||
_destination.readBytes(in);
|
||||
} else {
|
||||
_destination = null;
|
||||
}
|
||||
Boolean includeThis = DataHelper.readBoolean(in);
|
||||
if (includeThis.booleanValue()) {
|
||||
_thisHop = new Hash();
|
||||
_thisHop.readBytes(in);
|
||||
} else {
|
||||
_thisHop = null;
|
||||
}
|
||||
Boolean includeNext = DataHelper.readBoolean(in);
|
||||
if (includeNext.booleanValue()) {
|
||||
_nextHop = new Hash();
|
||||
_nextHop.readBytes(in);
|
||||
} else {
|
||||
_nextHop = null;
|
||||
}
|
||||
Boolean includeNextInfo = DataHelper.readBoolean(in);
|
||||
if (includeNextInfo.booleanValue()) {
|
||||
_nextHopInfo = new TunnelInfo();
|
||||
_nextHopInfo.readBytes(in);
|
||||
} else {
|
||||
_nextHopInfo = null;
|
||||
}
|
||||
_id = new TunnelId();
|
||||
_id.readBytes(in);
|
||||
Boolean includeConfigKey = DataHelper.readBoolean(in);
|
||||
if (includeConfigKey.booleanValue()) {
|
||||
_configurationKey = new TunnelConfigurationSessionKey();
|
||||
_configurationKey.readBytes(in);
|
||||
} else {
|
||||
_configurationKey = null;
|
||||
}
|
||||
Boolean includeEncryptionKey = DataHelper.readBoolean(in);
|
||||
if (includeEncryptionKey.booleanValue()) {
|
||||
_encryptionKey = new TunnelSessionKey();
|
||||
_encryptionKey.readBytes(in);
|
||||
} else {
|
||||
_encryptionKey = null;
|
||||
}
|
||||
Boolean includeSigningKey = DataHelper.readBoolean(in);
|
||||
if (includeSigningKey.booleanValue()) {
|
||||
_signingKey = new TunnelSigningPrivateKey();
|
||||
_signingKey.readBytes(in);
|
||||
} else {
|
||||
_signingKey = null;
|
||||
}
|
||||
Boolean includeVerificationKey = DataHelper.readBoolean(in);
|
||||
if (includeVerificationKey.booleanValue()) {
|
||||
_verificationKey = new TunnelSigningPublicKey();
|
||||
_verificationKey.readBytes(in);
|
||||
} else {
|
||||
_verificationKey = null;
|
||||
}
|
||||
_settings = new TunnelSettings();
|
||||
_settings.readBytes(in);
|
||||
Boolean ready = DataHelper.readBoolean(in);
|
||||
if (ready != null)
|
||||
setIsReady(ready.booleanValue());
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if (_id == null) throw new DataFormatException("Invalid tunnel ID: " + _id);
|
||||
if (_options == null) throw new DataFormatException("Options are null");
|
||||
if (_settings == null) throw new DataFormatException("Settings are null");
|
||||
// everything else is optional in the serialization
|
||||
|
||||
DataHelper.writeProperties(out, _options);
|
||||
if (_destination != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_destination.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
if (_thisHop != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_thisHop.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
if (_nextHop != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_nextHop.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
if (_nextHopInfo != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_nextHopInfo.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
_id.writeBytes(out);
|
||||
if (_configurationKey != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_configurationKey.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
if (_encryptionKey != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_encryptionKey.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
if (_signingKey != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_signingKey.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
if (_verificationKey != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_verificationKey.writeBytes(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
_settings.writeBytes(out);
|
||||
DataHelper.writeBoolean(out, new Boolean(_ready));
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[Tunnel ").append(_id.getTunnelId());
|
||||
TunnelInfo cur = this;
|
||||
int i = 0;
|
||||
while (cur != null) {
|
||||
buf.append("\n*Hop ").append(i).append(": ").append(cur.getThisHop());
|
||||
if (cur.getEncryptionKey() != null)
|
||||
buf.append("\n Encryption key: ").append(cur.getEncryptionKey());
|
||||
if (cur.getSigningKey() != null)
|
||||
buf.append("\n Signing key: ").append(cur.getSigningKey());
|
||||
if (cur.getVerificationKey() != null)
|
||||
buf.append("\n Verification key: ").append(cur.getVerificationKey());
|
||||
if (cur.getDestination() != null)
|
||||
buf.append("\n Destination: ").append(cur.getDestination().calculateHash().toBase64());
|
||||
if (cur.getNextHop() != null)
|
||||
buf.append("\n Next: ").append(cur.getNextHop());
|
||||
if (cur.getSettings() == null)
|
||||
buf.append("\n Expiration: ").append("none");
|
||||
else
|
||||
buf.append("\n Expiration: ").append(new Date(cur.getSettings().getExpiration()));
|
||||
buf.append("\n Ready: ").append(getIsReady());
|
||||
cur = cur.getNextHopInfo();
|
||||
i++;
|
||||
}
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int rv = 0;
|
||||
rv = 7*rv + DataHelper.hashCode(_options);
|
||||
rv = 7*rv + DataHelper.hashCode(_destination);
|
||||
rv = 7*rv + DataHelper.hashCode(_nextHop);
|
||||
rv = 7*rv + DataHelper.hashCode(_thisHop);
|
||||
rv = 7*rv + DataHelper.hashCode(_id);
|
||||
rv = 7*rv + DataHelper.hashCode(_configurationKey);
|
||||
rv = 7*rv + DataHelper.hashCode(_encryptionKey);
|
||||
rv = 7*rv + DataHelper.hashCode(_signingKey);
|
||||
rv = 7*rv + DataHelper.hashCode(_verificationKey);
|
||||
rv = 7*rv + DataHelper.hashCode(_settings);
|
||||
rv = 7*rv + (_ready ? 0 : 1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj != null) && (obj instanceof TunnelInfo) ) {
|
||||
TunnelInfo info = (TunnelInfo)obj;
|
||||
return DataHelper.eq(getConfigurationKey(), info.getConfigurationKey()) &&
|
||||
DataHelper.eq(getDestination(), info.getDestination()) &&
|
||||
getIsReady() == info.getIsReady() &&
|
||||
DataHelper.eq(getEncryptionKey(), info.getEncryptionKey()) &&
|
||||
DataHelper.eq(getNextHop(), info.getNextHop()) &&
|
||||
DataHelper.eq(getNextHopInfo(), info.getNextHopInfo()) &&
|
||||
DataHelper.eq(getSettings(), info.getSettings()) &&
|
||||
DataHelper.eq(getSigningKey(), info.getSigningKey()) &&
|
||||
DataHelper.eq(getThisHop(), info.getThisHop()) &&
|
||||
DataHelper.eq(getTunnelId(), info.getTunnelId()) &&
|
||||
DataHelper.eq(getVerificationKey(), info.getVerificationKey()) &&
|
||||
DataHelper.eq(_options, info._options);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
router/java/src/net/i2p/router/TunnelManagerFacade.java
Normal file
66
router/java/src/net/i2p/router/TunnelManagerFacade.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.util.List;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.router.tunnelmanager.PoolingTunnelManagerFacade;
|
||||
|
||||
/**
|
||||
* Build and maintain tunnels throughout the network.
|
||||
*
|
||||
*/
|
||||
public abstract class TunnelManagerFacade implements Service {
|
||||
private static TunnelManagerFacade _instance = new PoolingTunnelManagerFacade();
|
||||
public static TunnelManagerFacade getInstance() { return _instance; }
|
||||
|
||||
/**
|
||||
* React to a request to join the specified tunnel.
|
||||
*
|
||||
* @return true if the router will accept participation, else false.
|
||||
*/
|
||||
public abstract boolean joinTunnel(TunnelInfo info);
|
||||
/**
|
||||
* Retrieve the information related to a particular tunnel
|
||||
*
|
||||
*/
|
||||
public abstract TunnelInfo getTunnelInfo(TunnelId id);
|
||||
/**
|
||||
* Retrieve a set of tunnels from the existing ones for various purposes
|
||||
*/
|
||||
public abstract List selectOutboundTunnelIds(TunnelSelectionCriteria criteria);
|
||||
/**
|
||||
* Retrieve a set of tunnels from the existing ones for various purposes
|
||||
*/
|
||||
public abstract List selectInboundTunnelIds(TunnelSelectionCriteria criteria);
|
||||
|
||||
/**
|
||||
* Make sure appropriate outbound tunnels are in place, builds requested
|
||||
* inbound tunnels, then fire off a job to ask the ClientManagerFacade to
|
||||
* validate the leaseSet, then publish it in the network database.
|
||||
*
|
||||
*/
|
||||
public abstract void createTunnels(Destination destination, ClientTunnelSettings clientSettings, long timeoutMs);
|
||||
|
||||
/**
|
||||
* Called when a peer becomes unreachable - go through all of the current
|
||||
* tunnels and rebuild them if we can, or drop them if we can't.
|
||||
*
|
||||
*/
|
||||
public abstract void peerFailed(Hash peer);
|
||||
|
||||
/**
|
||||
* True if the peer currently part of a tunnel
|
||||
*
|
||||
*/
|
||||
public abstract boolean isInUse(Hash peer);
|
||||
}
|
||||
47
router/java/src/net/i2p/router/TunnelSelectionCriteria.java
Normal file
47
router/java/src/net/i2p/router/TunnelSelectionCriteria.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set of criteria for finding a tunnel from the Tunnel Manager
|
||||
*
|
||||
*/
|
||||
public class TunnelSelectionCriteria {
|
||||
public final static int MAX_PRIORITY = 100;
|
||||
public final static int MIN_PRIORITY = 0;
|
||||
private int _latencyPriority;
|
||||
private int _anonymityPriority;
|
||||
private int _reliabilityPriority;
|
||||
private int _maxNeeded;
|
||||
private int _minNeeded;
|
||||
|
||||
public TunnelSelectionCriteria() {
|
||||
setLatencyPriority(0);
|
||||
setAnonymityPriority(0);
|
||||
setReliabilityPriority(0);
|
||||
setMinimumTunnelsRequired(0);
|
||||
setMaximumTunnelsRequired(0);
|
||||
}
|
||||
|
||||
/** priority of the latency for the tunnel */
|
||||
public int getLatencyPriority() { return _latencyPriority; }
|
||||
public void setLatencyPriority(int latencyPriority) { _latencyPriority = latencyPriority; }
|
||||
/** priority of the anonymity for the tunnel */
|
||||
public int getAnonymityPriority() { return _anonymityPriority; }
|
||||
public void setAnonymityPriority(int anonPriority) { _anonymityPriority = anonPriority; }
|
||||
/** priority of the reliability for the tunnel */
|
||||
public int getReliabilityPriority() { return _reliabilityPriority; }
|
||||
public void setReliabilityPriority(int reliabilityPriority) { _reliabilityPriority = reliabilityPriority; }
|
||||
/** max # of tunnels to return */
|
||||
public int getMaximumTunnelsRequired() { return _maxNeeded; }
|
||||
public void setMaximumTunnelsRequired(int maxNeeded) { _maxNeeded = maxNeeded; }
|
||||
/** minimum # of tunnels to return */
|
||||
public int getMinimumTunnelsRequired() { return _minNeeded; }
|
||||
public void setMinimumTunnelsRequired(int minNeeded) { _minNeeded = minNeeded; }
|
||||
}
|
||||
134
router/java/src/net/i2p/router/TunnelSettings.java
Normal file
134
router/java/src/net/i2p/router/TunnelSettings.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package net.i2p.router;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Wrap up the settings specified for a particular tunnel
|
||||
*
|
||||
*/
|
||||
public class TunnelSettings extends DataStructureImpl {
|
||||
private int _depth;
|
||||
private long _msgsPerMinuteAvg;
|
||||
private long _bytesPerMinuteAvg;
|
||||
private long _msgsPerMinutePeak;
|
||||
private long _bytesPerMinutePeak;
|
||||
private boolean _includeDummy;
|
||||
private boolean _reorder;
|
||||
private long _expiration;
|
||||
private long _created;
|
||||
|
||||
public TunnelSettings() {
|
||||
_depth = 0;
|
||||
_msgsPerMinuteAvg = 0;
|
||||
_msgsPerMinutePeak = 0;
|
||||
_bytesPerMinuteAvg = 0;
|
||||
_bytesPerMinutePeak = 0;
|
||||
_includeDummy = false;
|
||||
_reorder = false;
|
||||
_expiration = 0;
|
||||
_created = Clock.getInstance().now();
|
||||
}
|
||||
|
||||
public int getDepth() { return _depth; }
|
||||
public void setDepth(int depth) { _depth = depth; }
|
||||
public long getMessagesPerMinuteAverage() { return _msgsPerMinuteAvg; }
|
||||
public long getMessagesPerMinutePeak() { return _msgsPerMinutePeak; }
|
||||
public long getBytesPerMinuteAverage() { return _bytesPerMinuteAvg; }
|
||||
public long getBytesPerMinutePeak() { return _bytesPerMinutePeak; }
|
||||
public void setMessagesPerMinuteAverage(long msgs) { _msgsPerMinuteAvg = msgs; }
|
||||
public void setMessagesPerMinutePeak(long msgs) { _msgsPerMinutePeak = msgs; }
|
||||
public void setBytesPerMinuteAverage(long bytes) { _bytesPerMinuteAvg = bytes; }
|
||||
public void setBytesPerMinutePeak(long bytes) { _bytesPerMinutePeak = bytes; }
|
||||
public boolean getIncludeDummy() { return _includeDummy; }
|
||||
public void setIncludeDummy(boolean include) { _includeDummy = include; }
|
||||
public boolean getReorder() { return _reorder; }
|
||||
public void setReorder(boolean reorder) { _reorder = reorder; }
|
||||
public long getExpiration() { return _expiration; }
|
||||
public void setExpiration(long expiration) { _expiration = expiration; }
|
||||
public long getCreated() { return _created; }
|
||||
|
||||
public void readBytes(InputStream in) throws DataFormatException, IOException {
|
||||
Boolean b = DataHelper.readBoolean(in);
|
||||
if (b == null) throw new DataFormatException("Null includeDummy boolean value");
|
||||
_includeDummy = b.booleanValue();
|
||||
b = DataHelper.readBoolean(in);
|
||||
if (b == null) throw new DataFormatException("Null reorder boolean value");
|
||||
_reorder = b.booleanValue();
|
||||
_depth = (int)DataHelper.readLong(in, 1);
|
||||
_bytesPerMinuteAvg = DataHelper.readLong(in, 4);
|
||||
_bytesPerMinutePeak = DataHelper.readLong(in, 4);
|
||||
Date exp = DataHelper.readDate(in);
|
||||
if (exp == null)
|
||||
_expiration = 0;
|
||||
else
|
||||
_expiration = exp.getTime();
|
||||
_msgsPerMinuteAvg = DataHelper.readLong(in, 4);
|
||||
_msgsPerMinutePeak = DataHelper.readLong(in, 4);
|
||||
Date created = DataHelper.readDate(in);
|
||||
if (created != null)
|
||||
_created = created.getTime();
|
||||
else
|
||||
_created = Clock.getInstance().now();
|
||||
}
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
DataHelper.writeBoolean(out, _includeDummy ? Boolean.TRUE : Boolean.FALSE);
|
||||
DataHelper.writeBoolean(out, _reorder ? Boolean.TRUE : Boolean.FALSE);
|
||||
DataHelper.writeLong(out, 1, _depth);
|
||||
DataHelper.writeLong(out, 4, _bytesPerMinuteAvg);
|
||||
DataHelper.writeLong(out, 4, _bytesPerMinutePeak);
|
||||
if (_expiration <= 0)
|
||||
DataHelper.writeDate(out, new Date(0));
|
||||
else
|
||||
DataHelper.writeDate(out, new Date(_expiration));
|
||||
DataHelper.writeLong(out, 4, _msgsPerMinuteAvg);
|
||||
DataHelper.writeLong(out, 4, _msgsPerMinutePeak);
|
||||
DataHelper.writeDate(out, new Date(_created));
|
||||
}
|
||||
|
||||
|
||||
public int hashCode() {
|
||||
int rv = 0;
|
||||
rv += _includeDummy ? 100 : 0;
|
||||
rv += _reorder ? 50 : 0;
|
||||
rv += _depth;
|
||||
rv += _bytesPerMinuteAvg;
|
||||
rv += _bytesPerMinutePeak;
|
||||
rv += _expiration;
|
||||
rv += _msgsPerMinuteAvg;
|
||||
rv += _msgsPerMinutePeak;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if ( (obj != null) && (obj instanceof TunnelSettings) ) {
|
||||
TunnelSettings settings = (TunnelSettings)obj;
|
||||
return settings.getBytesPerMinuteAverage() == getBytesPerMinuteAverage() &&
|
||||
settings.getBytesPerMinutePeak() == getBytesPerMinutePeak() &&
|
||||
settings.getDepth() == getDepth() &&
|
||||
settings.getExpiration() == getExpiration() &&
|
||||
settings.getIncludeDummy() == getIncludeDummy() &&
|
||||
settings.getMessagesPerMinuteAverage() == getMessagesPerMinuteAverage() &&
|
||||
settings.getMessagesPerMinutePeak() == getMessagesPerMinutePeak() &&
|
||||
settings.getReorder() == getReorder();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
router/java/src/net/i2p/router/admin/AdminListener.java
Normal file
110
router/java/src/net/i2p/router/admin/AdminListener.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package net.i2p.router.admin;
|
||||
/*
|
||||
* 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.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Listen for connections on the specified port, and toss them onto the client manager's
|
||||
* set of connections once they are established.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class AdminListener implements Runnable {
|
||||
private final static Log _log = new Log(AdminListener.class);
|
||||
private ServerSocket _socket;
|
||||
private int _port;
|
||||
private boolean _running;
|
||||
private long _nextFailDelay = 1000;
|
||||
|
||||
public AdminListener(int port) {
|
||||
_port = port;
|
||||
_running = false;
|
||||
}
|
||||
|
||||
public void setPort(int port) { _port = port; }
|
||||
public int getPort() { return _port; }
|
||||
|
||||
/** max time to bind */
|
||||
private final static int MAX_FAIL_DELAY = 5*60*1000;
|
||||
|
||||
/**
|
||||
* Start up the socket listener, listens for connections, and
|
||||
* fires those connections off via {@link #runConnection runConnection}.
|
||||
* This only returns if the socket cannot be opened or there is a catastrophic
|
||||
* failure.
|
||||
*
|
||||
*/
|
||||
public void startup() {
|
||||
_running = true;
|
||||
int curDelay = 0;
|
||||
while ( (_running) && (curDelay < MAX_FAIL_DELAY) ) {
|
||||
try {
|
||||
_log.info("Starting up listening for connections on port " + _port);
|
||||
_socket = new ServerSocket(_port);
|
||||
curDelay = 0;
|
||||
while (_running) {
|
||||
try {
|
||||
Socket socket = _socket.accept();
|
||||
_log.debug("Connection received");
|
||||
runConnection(socket);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Server error accepting", ioe);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Fatal error running client listener - killing the thread!", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error listening on port " + _port, ioe);
|
||||
}
|
||||
|
||||
if (_socket != null) {
|
||||
try { _socket.close(); } catch (IOException ioe) {}
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
_log.error("Error listening, waiting " + _nextFailDelay + "ms before we try again");
|
||||
try { Thread.sleep(_nextFailDelay); } catch (InterruptedException ie) {}
|
||||
curDelay += _nextFailDelay;
|
||||
_nextFailDelay *= 5;
|
||||
}
|
||||
|
||||
_log.error("CANCELING ADMIN LISTENER. delay = " + curDelay, new Exception("ADMIN LISTENER cancelled!!!"));
|
||||
_running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the connection by passing it off to an AdminRunner
|
||||
*
|
||||
*/
|
||||
protected void runConnection(Socket socket) throws IOException {
|
||||
AdminRunner runner = new AdminRunner(socket);
|
||||
I2PThread t = new I2PThread(runner);
|
||||
t.setName("Admin Runner");
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
_running = false;
|
||||
if (_socket != null) try {
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
public void run() { startup(); }
|
||||
}
|
||||
51
router/java/src/net/i2p/router/admin/AdminManager.java
Normal file
51
router/java/src/net/i2p/router/admin/AdminManager.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package net.i2p.router.admin;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import net.i2p.router.Service;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
public class AdminManager implements Service {
|
||||
private final static Log _log = new Log(AdminManager.class);
|
||||
private final static AdminManager _instance = new AdminManager();
|
||||
public final static AdminManager getInstance() { return _instance; }
|
||||
public final static String PARAM_ADMIN_PORT = "router.adminPort";
|
||||
public final static int DEFAULT_ADMIN_PORT = 7655;
|
||||
|
||||
private AdminListener _listener;
|
||||
|
||||
public String renderStatusHTML() { return ""; }
|
||||
|
||||
public void shutdown() {
|
||||
if (_listener != null) {
|
||||
_log.info("Shutting down admin listener");
|
||||
_listener.shutdown();
|
||||
_listener = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
int port = DEFAULT_ADMIN_PORT;
|
||||
String str = Router.getInstance().getConfigSetting(PARAM_ADMIN_PORT);
|
||||
if (str != null) {
|
||||
try {
|
||||
int val = Integer.parseInt(str);
|
||||
port = val;
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Invalid admin port specified [" + str + "]", nfe);
|
||||
}
|
||||
}
|
||||
_log.info("Starting up admin listener on port " + port);
|
||||
startup(port);
|
||||
}
|
||||
|
||||
private void startup(int port) {
|
||||
_listener = new AdminListener(port);
|
||||
I2PThread t = new I2PThread(_listener);
|
||||
t.setName("Admin Listener");
|
||||
t.setDaemon(true);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
102
router/java/src/net/i2p/router/admin/AdminRunner.java
Normal file
102
router/java/src/net/i2p/router/admin/AdminRunner.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package net.i2p.router.admin;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Set;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.peermanager.ProfileOrganizer;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class AdminRunner implements Runnable {
|
||||
private final static Log _log = new Log(AdminRunner.class);
|
||||
private Socket _socket;
|
||||
|
||||
public AdminRunner(Socket socket) {
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
|
||||
String command = in.readLine();
|
||||
runCommand(command, out);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error running admin command", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private void runCommand(String command, OutputStream out) throws IOException {
|
||||
_log.debug("Command [" + command + "]");
|
||||
if (command.indexOf("favicon") >= 0) {
|
||||
reply(out, "this is not a website");
|
||||
} else if (command.indexOf("routerStats.html") >= 0) {
|
||||
reply(out, StatsGenerator.generateStatsPage());
|
||||
} else if (command.indexOf("/profile/") >= 0) {
|
||||
replyText(out, getProfile(command));
|
||||
} else if (true || command.indexOf("routerConsole.html") > 0) {
|
||||
reply(out, Router.getInstance().renderStatusHTML());
|
||||
}
|
||||
}
|
||||
|
||||
private void reply(OutputStream out, String content) throws IOException {
|
||||
StringBuffer reply = new StringBuffer(10240);
|
||||
reply.append("HTTP/1.1 200 OK\n");
|
||||
reply.append("Connection: close\n");
|
||||
reply.append("Cache-control: no-cache\n");
|
||||
reply.append("Content-type: text/html\n\n");
|
||||
reply.append(content);
|
||||
try {
|
||||
out.write(reply.toString().getBytes());
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error writing out the admin reply:\n" + content);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
private void replyText(OutputStream out, String content) throws IOException {
|
||||
StringBuffer reply = new StringBuffer(10240);
|
||||
reply.append("HTTP/1.1 200 OK\n");
|
||||
reply.append("Connection: close\n");
|
||||
reply.append("Cache-control: no-cache\n");
|
||||
reply.append("Content-type: text/plain\n\n");
|
||||
reply.append(content);
|
||||
try {
|
||||
out.write(reply.toString().getBytes());
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error writing out the admin reply:\n" + content);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
private String getProfile(String cmd) {
|
||||
Set peers = ProfileOrganizer._getInstance().selectAllPeers();
|
||||
for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
if (cmd.indexOf(peer.toBase64().substring(0,10)) >= 0) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(64*1024);
|
||||
ProfileOrganizer._getInstance().exportProfile(peer, baos);
|
||||
return new String(baos.toByteArray());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error exporting the profile", ioe);
|
||||
return "Error exporting the peer profile\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "No such peer is being profiled\n";
|
||||
}
|
||||
}
|
||||
214
router/java/src/net/i2p/router/admin/StatsGenerator.java
Normal file
214
router/java/src/net/i2p/router/admin/StatsGenerator.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package net.i2p.router.admin;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.stat.Frequency;
|
||||
import net.i2p.stat.FrequencyStat;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.Iterator;
|
||||
import java.util.Arrays;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Dump the stats to the web admin interface
|
||||
*/
|
||||
public class StatsGenerator {
|
||||
private final static Log _log = new Log(StatsGenerator.class);
|
||||
|
||||
public static String generateStatsPage() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
try {
|
||||
generateStatsPage(baos);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error generating stats", ioe);
|
||||
}
|
||||
return new String(baos.toByteArray());
|
||||
}
|
||||
|
||||
public static void generateStatsPage(OutputStream out) throws IOException {
|
||||
PrintWriter pw = new PrintWriter(out);
|
||||
pw.println("<html><head><title>I2P Router Stats</title></head><body>");
|
||||
pw.println("<h1>Router statistics</h1>");
|
||||
pw.println("<i><a href=\"/routerConsole.html\">console</a> | <a href=\"/routerStats.html\">stats</a></i><hr />");
|
||||
Map groups = StatManager.getInstance().getStatsByGroup();
|
||||
|
||||
pw.println("<form action=\"/routerStats.html\">");
|
||||
pw.println("<select name=\"go\" onChange='location.href=this.value'>");
|
||||
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
|
||||
String group = (String)iter.next();
|
||||
Set stats = (Set)groups.get(group);
|
||||
pw.print("<option value=\"/routerStats.html#");
|
||||
pw.print(group);
|
||||
pw.print("\">");
|
||||
pw.print(group);
|
||||
pw.println("</option>\n");
|
||||
for (Iterator statIter = stats.iterator(); statIter.hasNext(); ) {
|
||||
String stat = (String)statIter.next();
|
||||
pw.print("<option value=\"/routerStats.html#");
|
||||
pw.print(stat);
|
||||
pw.print("\">...");
|
||||
pw.print(stat);
|
||||
pw.println("</option>\n");
|
||||
}
|
||||
}
|
||||
pw.println("</select>");
|
||||
pw.println("</form>");
|
||||
|
||||
pw.print("Statistics gathered during this router's uptime (");
|
||||
long uptime = Router.getInstance().getUptime();
|
||||
pw.print(DataHelper.formatDuration(uptime));
|
||||
pw.println("). The data gathered is quantized over a 1 minute period, so should just be used as an estimate<p />");
|
||||
|
||||
for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) {
|
||||
String group = (String)iter.next();
|
||||
Set stats = (Set)groups.get(group);
|
||||
pw.print("<h2><a name=\"");
|
||||
pw.print(group);
|
||||
pw.print("\">");
|
||||
pw.print(group);
|
||||
pw.println("</a></h2>");
|
||||
pw.println("<ul>");
|
||||
for (Iterator statIter = stats.iterator(); statIter.hasNext(); ) {
|
||||
String stat = (String)statIter.next();
|
||||
pw.print("<li><b><a name=\"");
|
||||
pw.print(stat);
|
||||
pw.print("\">");
|
||||
pw.print(stat);
|
||||
pw.println("</a></b><br />");
|
||||
if (StatManager.getInstance().isFrequency(stat))
|
||||
renderFrequency(stat, pw);
|
||||
else
|
||||
renderRate(stat, pw);
|
||||
}
|
||||
pw.println("</ul><hr />");
|
||||
}
|
||||
pw.println("</body></html>");
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
private static void renderFrequency(String name, PrintWriter pw) throws IOException {
|
||||
FrequencyStat freq = StatManager.getInstance().getFrequency(name);
|
||||
pw.print("<i>");
|
||||
pw.print(freq.getDescription());
|
||||
pw.println("</i><br />");
|
||||
long periods[] = freq.getPeriods();
|
||||
Arrays.sort(periods);
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
renderPeriod(pw, periods[i], "frequency");
|
||||
Frequency curFreq = freq.getFrequency(periods[i]);
|
||||
pw.print(" <i>avg per period:</i> (");
|
||||
pw.print(num(curFreq.getAverageEventsPerPeriod()));
|
||||
pw.print(", max ");
|
||||
pw.print(num(curFreq.getMaxAverageEventsPerPeriod()));
|
||||
if ( (curFreq.getMaxAverageEventsPerPeriod() > 0) && (curFreq.getAverageEventsPerPeriod() > 0) ) {
|
||||
pw.print(", current is ");
|
||||
pw.print(pct(curFreq.getAverageEventsPerPeriod()/curFreq.getMaxAverageEventsPerPeriod()));
|
||||
pw.print(" of max");
|
||||
}
|
||||
pw.print(")");
|
||||
//buf.append(" <i>avg interval between updates:</i> (").append(num(curFreq.getAverageInterval())).append("ms, min ");
|
||||
//buf.append(num(curFreq.getMinAverageInterval())).append("ms)");
|
||||
pw.print(" <i>strict average per period:</i> ");
|
||||
pw.print(num(curFreq.getStrictAverageEventsPerPeriod()));
|
||||
pw.print(" events (averaged ");
|
||||
pw.print(" using the lifetime of ");
|
||||
pw.print(num(curFreq.getEventCount()));
|
||||
pw.print(" events)");
|
||||
pw.println("<br />");
|
||||
}
|
||||
pw.println("<br />");
|
||||
}
|
||||
|
||||
private static void renderRate(String name, PrintWriter pw) throws IOException {
|
||||
RateStat rate = StatManager.getInstance().getRate(name);
|
||||
pw.print("<i>");
|
||||
pw.print(rate.getDescription());
|
||||
pw.println("</i><br />");
|
||||
long periods[] = rate.getPeriods();
|
||||
Arrays.sort(periods);
|
||||
pw.println("<ul>");
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
pw.println("<li>");
|
||||
renderPeriod(pw, periods[i], "rate");
|
||||
Rate curRate = rate.getRate(periods[i]);
|
||||
pw.print( "<i>avg value:</i> (");
|
||||
pw.print(num(curRate.getAverageValue()));
|
||||
pw.print(" peak ");
|
||||
pw.print(num(curRate.getExtremeAverageValue()));
|
||||
pw.print(", [");
|
||||
pw.print(pct(curRate.getPercentageOfExtremeValue()));
|
||||
pw.print(" of max");
|
||||
pw.print(", and ");
|
||||
pw.print(pct(curRate.getPercentageOfLifetimeValue()));
|
||||
pw.print(" of lifetime average]");
|
||||
|
||||
pw.print(")");
|
||||
pw.print(" <i>highest total period value:</i> (");
|
||||
pw.print(num(curRate.getExtremeTotalValue()));
|
||||
pw.print(")");
|
||||
if (curRate.getLifetimeTotalEventTime() > 0) {
|
||||
pw.print(" <i>saturation:</i> (");
|
||||
pw.print(pct(curRate.getLastEventSaturation()));
|
||||
pw.print(")");
|
||||
pw.print(" <i>saturated limit:</i> (");
|
||||
pw.print(num(curRate.getLastSaturationLimit()));
|
||||
pw.print(")");
|
||||
pw.print(" <i>peak saturation:</i> (");
|
||||
pw.print(pct(curRate.getExtremeEventSaturation()));
|
||||
pw.print(")");
|
||||
pw.print(" <i>peak saturated limit:</i> (");
|
||||
pw.print(num(curRate.getExtremeSaturationLimit()));
|
||||
pw.print(")");
|
||||
}
|
||||
pw.print(" <i>events per period:</i> ");
|
||||
pw.print(num(curRate.getLastEventCount()));
|
||||
long numPeriods = curRate.getLifetimePeriods();
|
||||
if (numPeriods > 0) {
|
||||
double avgFrequency = curRate.getLifetimeEventCount() / (double)numPeriods;
|
||||
double peakFrequency = curRate.getExtremeEventCount();
|
||||
pw.print(" (lifetime average: ");
|
||||
pw.print(num(avgFrequency));
|
||||
pw.print(", peak average: ");
|
||||
pw.print(num(curRate.getExtremeEventCount()));
|
||||
pw.println(")");
|
||||
}
|
||||
pw.print("</li>");
|
||||
if (i + 1 == periods.length) {
|
||||
// last one, so lets display the strict average
|
||||
pw.print("<li><b>lifetime average value:</b> ");
|
||||
pw.print(num(curRate.getLifetimeAverageValue()));
|
||||
pw.print(" over ");
|
||||
pw.print(num(curRate.getLifetimeEventCount()));
|
||||
pw.println(" events<br /></li>");
|
||||
}
|
||||
}
|
||||
pw.print("</ul>");
|
||||
pw.println("<br />");
|
||||
}
|
||||
|
||||
private static void renderPeriod(PrintWriter pw, long period, String name) throws IOException {
|
||||
pw.print("<b>");
|
||||
pw.print(DataHelper.formatDuration(period));
|
||||
pw.print(" ");
|
||||
pw.print(name);
|
||||
pw.print(":</b> ");
|
||||
}
|
||||
|
||||
private final static DecimalFormat _fmt = new DecimalFormat("###,##0.00");
|
||||
private final static String num(double num) { synchronized (_fmt) { return _fmt.format(num); } }
|
||||
|
||||
private final static DecimalFormat _pct = new DecimalFormat("#0.00%");
|
||||
private final static String pct(double num) { synchronized (_pct) { return _pct.format(num); } }
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package net.i2p.router.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.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.i2cp.DisconnectMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.data.i2cp.SendMessageMessage;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Bridge the router and the client - managing state for a client.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class ClientConnectionRunner {
|
||||
private final static Log _log = new Log(ClientConnectionRunner.class);
|
||||
private ClientManager _manager;
|
||||
/** socket for this particular peer connection */
|
||||
private Socket _socket;
|
||||
/** output stream of the socket that I2CP messages bound to the client should be written to */
|
||||
private OutputStream _out;
|
||||
/** session ID of the current client */
|
||||
private SessionId _sessionId;
|
||||
/** user's config */
|
||||
private SessionConfig _config;
|
||||
/** static mapping of MessageId to Payload, storing messages for retrieval */
|
||||
private static Map _messages;
|
||||
/** lease set request state, or null if there is no request pending on at the moment */
|
||||
private LeaseRequestState _leaseRequest;
|
||||
/** currently allocated leaseSet, or null if none is allocated */
|
||||
private LeaseSet _currentLeaseSet;
|
||||
/** set of messageIds created but not yet ACCEPTED */
|
||||
private Set _acceptedPending;
|
||||
/** thingy that does stuff */
|
||||
private I2CPMessageReader _reader;
|
||||
/**
|
||||
* This contains the last 10 MessageIds that have had their (non-ack) status
|
||||
* delivered to the client (so that we can be sure only to update when necessary)
|
||||
*/
|
||||
private List _alreadyProcessed;
|
||||
/** are we, uh, dead */
|
||||
private boolean _dead;
|
||||
|
||||
/**
|
||||
* Create a new runner against the given socket
|
||||
*
|
||||
*/
|
||||
public ClientConnectionRunner(ClientManager manager, Socket socket) {
|
||||
_manager = manager;
|
||||
_socket = socket;
|
||||
_config = null;
|
||||
_messages = new HashMap();
|
||||
_alreadyProcessed = new LinkedList();
|
||||
_acceptedPending = new HashSet();
|
||||
_dead = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually run the connection - listen for I2CP messages and respond. This
|
||||
* is the main driver for this class, though it gets all its meat from the
|
||||
* {@link net.i2p.data.i2cp.I2CPMessageReader I2CPMessageReader}
|
||||
*
|
||||
*/
|
||||
public void startRunning() {
|
||||
try {
|
||||
_reader = new I2CPMessageReader(_socket.getInputStream(), new ClientMessageEventListener(this));
|
||||
_out = _socket.getOutputStream();
|
||||
_reader.startReading();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error starting up the runner", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/** die a horrible death */
|
||||
void stopRunning() {
|
||||
if (_dead) return;
|
||||
_log.error("Stop the I2CP connection! current leaseSet: " + _currentLeaseSet, new Exception("Stop client connection"));
|
||||
_dead = true;
|
||||
// we need these keys to unpublish the leaseSet
|
||||
if (_reader != null) _reader.stopReading();
|
||||
if (_socket != null) try { _socket.close(); } catch (IOException ioe) { }
|
||||
synchronized (_messages) {
|
||||
_messages.clear();
|
||||
}
|
||||
_manager.unregisterConnection(this);
|
||||
if (_currentLeaseSet != null)
|
||||
NetworkDatabaseFacade.getInstance().unpublish(_currentLeaseSet);
|
||||
_leaseRequest = null;
|
||||
synchronized (_alreadyProcessed) {
|
||||
_alreadyProcessed.clear();
|
||||
}
|
||||
_config = null;
|
||||
_manager = null;
|
||||
}
|
||||
|
||||
/** current client's config */
|
||||
public SessionConfig getConfig() { return _config; }
|
||||
/** currently allocated leaseSet */
|
||||
public LeaseSet getLeaseSet() { return _currentLeaseSet; }
|
||||
void setLeaseSet(LeaseSet ls) { _currentLeaseSet = ls; }
|
||||
|
||||
/** current client's sessionId */
|
||||
SessionId getSessionId() { return _sessionId; }
|
||||
void setSessionId(SessionId id) { _sessionId = id; }
|
||||
/** data for the current leaseRequest, or null if there is no active leaseSet request */
|
||||
LeaseRequestState getLeaseRequest() { return _leaseRequest; }
|
||||
void setLeaseRequest(LeaseRequestState req) { _leaseRequest = req; }
|
||||
/** already closed? */
|
||||
boolean isDead() { return _dead; }
|
||||
/** message body */
|
||||
Payload getPayload(MessageId id) { synchronized (_messages) { return (Payload)_messages.get(id); } }
|
||||
void setPayload(MessageId id, Payload payload) { synchronized (_messages) { _messages.put(id, payload); } }
|
||||
void removePayload(MessageId id) { synchronized (_messages) { _messages.remove(id); } }
|
||||
|
||||
void sessionEstablished(SessionConfig config) {
|
||||
_config = config;
|
||||
_manager.destinationEstablished(this);
|
||||
}
|
||||
|
||||
void updateMessageDeliveryStatus(MessageId id, boolean delivered) {
|
||||
if (_dead) return;
|
||||
JobQueue.getInstance().addJob(new MessageDeliveryStatusUpdate(id, delivered));
|
||||
}
|
||||
/**
|
||||
* called after a new leaseSet is granted by the client, the NetworkDb has been
|
||||
* updated. This takes care of all the LeaseRequestState stuff (including firing any jobs)
|
||||
*/
|
||||
void leaseSetCreated(LeaseSet ls) {
|
||||
if (_leaseRequest == null) {
|
||||
_log.error("LeaseRequest is null and we've received a new lease?! WTF");
|
||||
return;
|
||||
} else {
|
||||
_leaseRequest.setIsSuccessful(true);
|
||||
if (_leaseRequest.getOnGranted() != null)
|
||||
JobQueue.getInstance().addJob(_leaseRequest.getOnGranted());
|
||||
_leaseRequest = null;
|
||||
_currentLeaseSet = ls;
|
||||
}
|
||||
}
|
||||
|
||||
void disconnectClient(String reason) {
|
||||
_log.error("Disconnecting the client: " + reason, new Exception("Disconnecting!"));
|
||||
DisconnectMessage msg = new DisconnectMessage();
|
||||
msg.setReason(reason);
|
||||
try {
|
||||
doSend(msg);
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error writing out the disconnect message", ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the disconnect message", ioe);
|
||||
}
|
||||
stopRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute the message. If the dest is local, it blocks until its passed
|
||||
* to the target ClientConnectionRunner (which then fires it into a MessageReceivedJob).
|
||||
* If the dest is remote, it blocks until it is added into the ClientMessagePool
|
||||
*
|
||||
*/
|
||||
MessageId distributeMessage(SendMessageMessage message) {
|
||||
Payload payload = message.getPayload();
|
||||
Destination dest = message.getDestination();
|
||||
MessageId id = new MessageId();
|
||||
id.setMessageId(getNextMessageId());
|
||||
synchronized (_acceptedPending) {
|
||||
_acceptedPending.add(id);
|
||||
}
|
||||
_log.debug("** Recieving message [" + id.getMessageId() + "] with payload of size [" + payload.getSize() + "]" + " for session [" + _sessionId.getSessionId() + "]");
|
||||
// the following blocks as described above
|
||||
_manager.distributeMessage(_config.getDestination(), message.getDestination(), message.getPayload(), id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a notification to the client that their message (id specified) was accepted
|
||||
* for delivery (but not necessarily delivered)
|
||||
*
|
||||
*/
|
||||
void ackSendMessage(MessageId id, long nonce) {
|
||||
_log.debug("Acking message send [accepted]" + id + " / " + nonce + " for sessionId " + _sessionId, new Exception("sendAccepted"));
|
||||
MessageStatusMessage status = new MessageStatusMessage();
|
||||
status.setMessageId(id);
|
||||
status.setSessionId(_sessionId);
|
||||
status.setSize(0L);
|
||||
status.setNonce(nonce);
|
||||
status.setStatus(MessageStatusMessage.STATUS_SEND_ACCEPTED);
|
||||
try {
|
||||
doSend(status);
|
||||
synchronized (_acceptedPending) {
|
||||
_acceptedPending.remove(id);
|
||||
}
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error writing out the message status message", ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the message status message", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously deliver the message to the current runner
|
||||
*
|
||||
*/
|
||||
void receiveMessage(Destination toDest, Destination fromDest, Payload payload) {
|
||||
if (_dead) return;
|
||||
JobQueue.getInstance().addJob(new MessageReceivedJob(this, toDest, fromDest, payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send async abuse message to the client
|
||||
*
|
||||
*/
|
||||
public void reportAbuse(String reason, int severity) {
|
||||
if (_dead) return;
|
||||
JobQueue.getInstance().addJob(new ReportAbuseJob(this, reason, severity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that a particular client authorize the Leases contained in the
|
||||
* LeaseSet, after which the onCreateJob is queued up. If that doesn't occur
|
||||
* within the timeout specified, queue up the onFailedJob. This call does not
|
||||
* block.
|
||||
*
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* @param expirationTime ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization
|
||||
*/
|
||||
void requestLeaseSet(LeaseSet set, long expirationTime, Job onCreateJob, Job onFailedJob) {
|
||||
if (_dead) return;
|
||||
JobQueue.getInstance().addJob(new RequestLeaseSetJob(this, set, expirationTime, onCreateJob, onFailedJob));
|
||||
}
|
||||
|
||||
void disconnected() {
|
||||
_log.error("Disconnected", new Exception("Disconnected?"));
|
||||
stopRunning();
|
||||
}
|
||||
|
||||
////
|
||||
////
|
||||
|
||||
/**
|
||||
* Actually send the I2CPMessage to the peer through the socket
|
||||
*
|
||||
*/
|
||||
void doSend(I2CPMessage msg) throws I2CPMessageException, IOException {
|
||||
if (_out == null) throw new I2CPMessageException("Output stream is not initialized");
|
||||
long before = Clock.getInstance().now();
|
||||
try {
|
||||
synchronized (_out) {
|
||||
msg.writeMessage(_out);
|
||||
_out.flush();
|
||||
}
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Message exception sending I2CP message", ime);
|
||||
throw ime;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO exception sending I2CP message", ioe);
|
||||
throw ioe;
|
||||
} catch (Throwable t) {
|
||||
_log.log(Log.CRIT, "Unhandled exception sending I2CP message", t);
|
||||
throw new IOException("Unhandled exception sending I2CP message: " + t.getMessage());
|
||||
} finally {
|
||||
long after = Clock.getInstance().now();
|
||||
long lag = after - before;
|
||||
if (lag > 300) {
|
||||
_log.error("synchronization on the i2cp message send took too long (" + lag + "ms): " + msg, new Exception("I2CP Lag"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME
|
||||
private final static int MAX_MESSAGE_ID = 32767;
|
||||
private static volatile int _messageId = RandomSource.getInstance().nextInt(MAX_MESSAGE_ID); // messageId counter
|
||||
private static Object _messageIdLock = new Object();
|
||||
|
||||
static int getNextMessageId() {
|
||||
synchronized (_messageIdLock) {
|
||||
int messageId = (++_messageId)%MAX_MESSAGE_ID;
|
||||
if (_messageId >= MAX_MESSAGE_ID)
|
||||
_messageId = 0;
|
||||
return messageId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the client has already been sent the ACCEPTED state for the given
|
||||
* message id, false otherwise.
|
||||
*
|
||||
*/
|
||||
private boolean alreadyAccepted(MessageId id) {
|
||||
if (_dead) return false;
|
||||
boolean isPending = false;
|
||||
int pending = 0;
|
||||
String buf = null;
|
||||
synchronized (_acceptedPending) {
|
||||
if (_acceptedPending.contains(id))
|
||||
isPending = true;
|
||||
pending = _acceptedPending.size();
|
||||
buf = _acceptedPending.toString();
|
||||
}
|
||||
if (pending >= 1) {
|
||||
_log.warn("Pending acks: " + pending + ": " + buf);
|
||||
}
|
||||
return !isPending;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the message hasn't been state=ACCEPTED yet, we shouldn't send an update
|
||||
* since the client doesn't know the message id (and we don't know the nonce).
|
||||
* So, we just wait REQUEUE_DELAY ms before trying again.
|
||||
*
|
||||
*/
|
||||
private final static long REQUEUE_DELAY = 500;
|
||||
|
||||
private class MessageDeliveryStatusUpdate extends JobImpl {
|
||||
private MessageId _messageId;
|
||||
private boolean _success;
|
||||
private long _lastTried;
|
||||
public MessageDeliveryStatusUpdate(MessageId id, boolean success) {
|
||||
_messageId = id;
|
||||
_success = success;
|
||||
_lastTried = 0;
|
||||
}
|
||||
|
||||
public String getName() { return "Update Delivery Status"; }
|
||||
public void runJob() {
|
||||
if (_dead) return;
|
||||
|
||||
MessageStatusMessage msg = new MessageStatusMessage();
|
||||
msg.setMessageId(_messageId);
|
||||
msg.setSessionId(_sessionId);
|
||||
msg.setNonce(2);
|
||||
msg.setSize(0);
|
||||
if (_success)
|
||||
msg.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS);
|
||||
else
|
||||
msg.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE);
|
||||
|
||||
if (!alreadyAccepted(_messageId)) {
|
||||
_log.warn("Almost send an update for message " + _messageId + " to " + MessageStatusMessage.getStatusString(msg.getStatus()) + " for session [" + _sessionId.getSessionId() + "] before they knew the messageId! delaying .5s");
|
||||
_lastTried = Clock.getInstance().now();
|
||||
requeue(REQUEUE_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (_alreadyProcessed) {
|
||||
if (_alreadyProcessed.contains(_messageId)) {
|
||||
_log.warn("Status already updated");
|
||||
return;
|
||||
} else {
|
||||
_alreadyProcessed.add(_messageId);
|
||||
while (_alreadyProcessed.size() > 10)
|
||||
_alreadyProcessed.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (_lastTried > 0)
|
||||
_log.info("Updating message status for message " + _messageId + " to " + MessageStatusMessage.getStatusString(msg.getStatus()) + " for session [" + _sessionId.getSessionId() + "] (with nonce=2), retrying after [" + (Clock.getInstance().now() - _lastTried) + "]", getAddedBy());
|
||||
else
|
||||
_log.debug("Updating message status for message " + _messageId + " to " + MessageStatusMessage.getStatusString(msg.getStatus()) + " for session [" + _sessionId.getSessionId() + "] (with nonce=2)");
|
||||
|
||||
try {
|
||||
doSend(msg);
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.warn("Error updating the status for message ID " + _messageId, ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error updating the status for message ID " + _messageId, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
router/java/src/net/i2p/router/client/ClientListenerRunner.java
Normal file
132
router/java/src/net/i2p/router/client/ClientListenerRunner.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package net.i2p.router.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.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Listen for connections on the specified port, and toss them onto the client manager's
|
||||
* set of connections once they are established.
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class ClientListenerRunner implements Runnable {
|
||||
private final static Log _log = new Log(ClientListenerRunner.class);
|
||||
private ClientManager _manager;
|
||||
private ServerSocket _socket;
|
||||
private int _port;
|
||||
private boolean _running;
|
||||
private long _nextFailDelay = 1000;
|
||||
|
||||
public ClientListenerRunner(ClientManager manager, int port) {
|
||||
_manager = manager;
|
||||
_port = port;
|
||||
_running = false;
|
||||
}
|
||||
|
||||
public void setPort(int port) { _port = port; }
|
||||
public int getPort() { return _port; }
|
||||
|
||||
/** max time to bind */
|
||||
private final static int MAX_FAIL_DELAY = 5*60*1000;
|
||||
|
||||
/**
|
||||
* Start up the socket listener, listens for connections, and
|
||||
* fires those connections off via {@link #runConnection runConnection}.
|
||||
* This only returns if the socket cannot be opened or there is a catastrophic
|
||||
* failure.
|
||||
*
|
||||
*/
|
||||
public void runServer() {
|
||||
_running = true;
|
||||
int curDelay = 0;
|
||||
while ( (_running) && (curDelay < MAX_FAIL_DELAY) ) {
|
||||
try {
|
||||
_log.info("Starting up listening for connections on port " + _port);
|
||||
_socket = new ServerSocket(_port);
|
||||
curDelay = 0;
|
||||
while (_running) {
|
||||
try {
|
||||
Socket socket = _socket.accept();
|
||||
if (validate(socket)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Connection received");
|
||||
runConnection(socket);
|
||||
} else {
|
||||
socket.close();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Refused connection from " + socket.getInetAddress().toString());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Server error accepting", ioe);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Fatal error running client listener - killing the thread!", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error listening on port " + _port, ioe);
|
||||
}
|
||||
|
||||
if (_socket != null) {
|
||||
try { _socket.close(); } catch (IOException ioe) {}
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
_log.error("Error listening, waiting " + _nextFailDelay + "ms before we try again");
|
||||
try { Thread.sleep(_nextFailDelay); } catch (InterruptedException ie) {}
|
||||
curDelay += _nextFailDelay;
|
||||
_nextFailDelay *= 5;
|
||||
}
|
||||
|
||||
_log.error("CANCELING I2CP LISTEN. delay = " + curDelay, new Exception("I2CP Listen cancelled!!!"));
|
||||
_running = false;
|
||||
}
|
||||
|
||||
/** give the i2cp client 5 seconds to show that they're really i2cp clients */
|
||||
private final static int CONNECT_TIMEOUT = 5*1000;
|
||||
|
||||
private boolean validate(Socket socket) {
|
||||
try {
|
||||
socket.setSoTimeout(CONNECT_TIMEOUT);
|
||||
int read = socket.getInputStream().read();
|
||||
if (read != I2PClient.PROTOCOL_BYTE)
|
||||
return false;
|
||||
socket.setSoTimeout(0);
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Peer did not authenticate themselves as I2CP quickly enough, dropping");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle the connection by passing it off to a {@link ClientConnectionRunner ClientConnectionRunner}
|
||||
*
|
||||
*/
|
||||
protected void runConnection(Socket socket) throws IOException {
|
||||
ClientConnectionRunner runner = new ClientConnectionRunner(_manager, socket);
|
||||
_manager.registerConnection(runner);
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
_running = false;
|
||||
if (_socket != null) try {
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
public void run() { runServer(); }
|
||||
}
|
||||
306
router/java/src/net/i2p/router/client/ClientManager.java
Normal file
306
router/java/src/net/i2p/router/client/ClientManager.java
Normal file
@@ -0,0 +1,306 @@
|
||||
package net.i2p.router.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.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.ClientMessagePool;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import net.i2p.stat.StatManager;
|
||||
|
||||
/**
|
||||
* Coordinate connections and various tasks
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class ClientManager {
|
||||
private final static Log _log = new Log(ClientManager.class);
|
||||
private ClientListenerRunner _listener;
|
||||
private HashMap _runners; // Destination --> ClientConnectionRunner
|
||||
private Set _pendingRunners; // ClientConnectionRunner for clients w/out a Dest yet
|
||||
|
||||
/** ms to wait before rechecking for inbound messages to deliver to clients */
|
||||
private final static int INBOUND_POLL_INTERVAL = 300;
|
||||
|
||||
static {
|
||||
StatManager.getInstance().createRateStat("client.receiveMessageSize", "How large are messages received by the client?", "Client Messages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
}
|
||||
|
||||
public ClientManager(int port) {
|
||||
_runners = new HashMap();
|
||||
_pendingRunners = new HashSet();
|
||||
_listener = new ClientListenerRunner(this, port);
|
||||
Thread t = new I2PThread(_listener);
|
||||
t.setName("ClientListener");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
//JobQueue.getInstance().addJob(new CheckInboundMessagesJob());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
_log.info("Shutting down the ClientManager");
|
||||
_listener.stopListening();
|
||||
Set runners = new HashSet();
|
||||
synchronized (_runners) {
|
||||
for (Iterator iter = _runners.values().iterator(); iter.hasNext();) {
|
||||
ClientConnectionRunner runner = (ClientConnectionRunner)iter.next();
|
||||
runners.add(runner);
|
||||
}
|
||||
}
|
||||
synchronized (_pendingRunners) {
|
||||
for (Iterator iter = _pendingRunners.iterator(); iter.hasNext();) {
|
||||
ClientConnectionRunner runner = (ClientConnectionRunner)iter.next();
|
||||
runners.add(runner);
|
||||
}
|
||||
}
|
||||
for (Iterator iter = runners.iterator(); iter.hasNext(); ) {
|
||||
ClientConnectionRunner runner = (ClientConnectionRunner)iter.next();
|
||||
runner.stopRunning();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerConnection(ClientConnectionRunner runner) {
|
||||
synchronized (_pendingRunners) {
|
||||
_pendingRunners.add(runner);
|
||||
}
|
||||
runner.startRunning();
|
||||
}
|
||||
|
||||
public void unregisterConnection(ClientConnectionRunner runner) {
|
||||
_log.warn("Unregistering (dropping) a client connection");
|
||||
synchronized (_pendingRunners) {
|
||||
_pendingRunners.remove(runner);
|
||||
}
|
||||
if ( (runner.getConfig() != null) && (runner.getConfig().getDestination() != null) ) {
|
||||
// after connection establishment
|
||||
synchronized (_runners) {
|
||||
_runners.remove(runner.getConfig().getDestination());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destinationEstablished(ClientConnectionRunner runner) {
|
||||
synchronized (_pendingRunners) {
|
||||
_pendingRunners.remove(runner);
|
||||
}
|
||||
synchronized (_runners) {
|
||||
_runners.put(runner.getConfig().getDestination(), runner);
|
||||
}
|
||||
}
|
||||
|
||||
void distributeMessage(Destination fromDest, Destination toDest, Payload payload, MessageId msgId) {
|
||||
// check if there is a runner for it
|
||||
ClientConnectionRunner runner = getRunner(toDest);
|
||||
if (runner != null) {
|
||||
_log.debug("Message " + msgId + " is targeting a local destination. distribute it as such");
|
||||
runner.receiveMessage(toDest, fromDest, payload);
|
||||
if (fromDest != null) {
|
||||
ClientConnectionRunner sender = getRunner(fromDest);
|
||||
if (sender != null) {
|
||||
sender.updateMessageDeliveryStatus(msgId, true);
|
||||
} else {
|
||||
_log.log(Log.CRIT, "Um, wtf, we're sending a local message, but we can't find who sent it?", new Exception("wtf"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// remote. w00t
|
||||
_log.debug("Message " + msgId + " is targeting a REMOTE destination! Added to the client message pool");
|
||||
runner = getRunner(fromDest);
|
||||
ClientMessage msg = new ClientMessage();
|
||||
msg.setDestination(toDest);
|
||||
msg.setPayload(payload);
|
||||
msg.setReceptionInfo(null);
|
||||
msg.setSenderConfig(runner.getConfig());
|
||||
msg.setFromDestination(runner.getConfig().getDestination());
|
||||
msg.setMessageId(msgId);
|
||||
ClientMessagePool.getInstance().add(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Request that a particular client authorize the Leases contained in the
|
||||
* LeaseSet, after which the onCreateJob is queued up. If that doesn't occur
|
||||
* within the timeout specified, queue up the onFailedJob. This call does not
|
||||
* block.
|
||||
*
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* @param timeout ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization
|
||||
*/
|
||||
public void requestLeaseSet(Destination dest, LeaseSet set, long timeout, Job onCreateJob, Job onFailedJob) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Cannot request the lease set, as we can't find a client runner for " + dest.calculateHash().toBase64() + ". disconnected?");
|
||||
JobQueue.getInstance().addJob(onFailedJob);
|
||||
} else {
|
||||
runner.requestLeaseSet(set, Clock.getInstance().now() + timeout, onCreateJob, onFailedJob);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isLocal(Destination dest) {
|
||||
synchronized (_runners) {
|
||||
return (_runners.containsKey(dest));
|
||||
}
|
||||
}
|
||||
public boolean isLocal(Hash destHash) {
|
||||
if (destHash == null) return false;
|
||||
Set dests = new HashSet();
|
||||
synchronized (_runners) {
|
||||
dests.addAll(_runners.keySet());
|
||||
}
|
||||
for (Iterator iter = dests.iterator(); iter.hasNext();) {
|
||||
Destination d = (Destination)iter.next();
|
||||
if (d.calculateHash().equals(destHash)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ClientConnectionRunner getRunner(Destination dest) {
|
||||
synchronized (_runners) {
|
||||
return (ClientConnectionRunner)_runners.get(dest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the client's current config, or null if not connected
|
||||
*
|
||||
*/
|
||||
public SessionConfig getClientSessionConfig(Destination dest) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner != null)
|
||||
return runner.getConfig();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private ClientConnectionRunner getRunner(Hash destHash) {
|
||||
if (destHash == null)
|
||||
return null;
|
||||
Set dests = new HashSet();
|
||||
synchronized (_runners) {
|
||||
dests.addAll(_runners.keySet());
|
||||
}
|
||||
for (Iterator iter = dests.iterator(); iter.hasNext(); ) {
|
||||
Destination d = (Destination)iter.next();
|
||||
if (d.calculateHash().equals(destHash))
|
||||
return getRunner(d);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void messageDeliveryStatusUpdate(Destination fromDest, MessageId id, boolean delivered) {
|
||||
ClientConnectionRunner runner = getRunner(fromDest);
|
||||
if (runner != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Delivering status [" + (delivered?"success":"failure") + "] to " + fromDest.calculateHash().toBase64() + " for message " + id);
|
||||
runner.updateMessageDeliveryStatus(id, delivered);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Cannot deliver status [" + (delivered?"success":"failure") + "] to " + fromDest.calculateHash().toBase64() + " for message " + id);
|
||||
}
|
||||
}
|
||||
|
||||
private Set getRunnerDestinations() {
|
||||
Set dests = new HashSet();
|
||||
synchronized (_runners) {
|
||||
dests.addAll(_runners.keySet());
|
||||
}
|
||||
return dests;
|
||||
}
|
||||
|
||||
public void reportAbuse(Destination dest, String reason, int severity) {
|
||||
if (dest != null) {
|
||||
ClientConnectionRunner runner = getRunner(dest);
|
||||
if (runner != null) {
|
||||
runner.reportAbuse(reason, severity);
|
||||
}
|
||||
} else {
|
||||
Set dests = getRunnerDestinations();
|
||||
for (Iterator iter = dests.iterator(); iter.hasNext(); ) {
|
||||
Destination d = (Destination)iter.next();
|
||||
reportAbuse(d, reason, severity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String renderStatusHTML() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("<h2>Clients</h2><ul>");
|
||||
Map runners = null;
|
||||
synchronized (_runners) {
|
||||
runners = (Map)_runners.clone();
|
||||
}
|
||||
for (Iterator iter = runners.keySet().iterator(); iter.hasNext(); ) {
|
||||
Destination dest = (Destination)iter.next();
|
||||
ClientConnectionRunner runner = (ClientConnectionRunner)runners.get(dest);
|
||||
buf.append("<li>").append(dest.calculateHash().toBase64()).append("</li>\n");
|
||||
// toss out some general warnings
|
||||
if (runner.getLeaseSet() == null)
|
||||
buf.append("<font color=\"red\"><b>No leases! If you didn't just start a client, please restart it (and perhaps check your router's logs for ERROR messages)</b></font><br />\n");
|
||||
else if (runner.getLeaseSet().getEarliestLeaseDate() < Clock.getInstance().now())
|
||||
buf.append("<font color=\"red\"><b>wtf, lease has already expired! please restart your client</b></font><br />\n");
|
||||
buf.append("<pre>\n");
|
||||
buf.append(runner.getLeaseSet()).append("</pre>\n");
|
||||
}
|
||||
buf.append("</ul>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void messageReceived(ClientMessage msg) {
|
||||
JobQueue.getInstance().addJob(new HandleJob(msg));
|
||||
}
|
||||
|
||||
private class HandleJob extends JobImpl {
|
||||
private ClientMessage _msg;
|
||||
public HandleJob(ClientMessage msg) {
|
||||
_msg = msg;
|
||||
}
|
||||
public String getName() { return "Handle Inbound Client Messages"; }
|
||||
public void runJob() {
|
||||
ClientConnectionRunner runner = null;
|
||||
if (_msg.getDestination() != null)
|
||||
runner = getRunner(_msg.getDestination());
|
||||
else
|
||||
runner = getRunner(_msg.getDestinationHash());
|
||||
|
||||
if (runner != null) {
|
||||
StatManager.getInstance().addRateData("client.receiveMessageSize", _msg.getPayload().getSize(), 0);
|
||||
runner.receiveMessage(_msg.getDestination(), null, _msg.getPayload());
|
||||
} else {
|
||||
// no client connection...
|
||||
// we should pool these somewhere...
|
||||
_log.warn("Message received but we don't have a connection to " + _msg.getDestination() + "/" + _msg.getDestinationHash() + " currently. DROPPED");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package net.i2p.router.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 net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.router.ClientManagerFacade;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Base impl of the client facade
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
public class ClientManagerFacadeImpl extends ClientManagerFacade {
|
||||
private final static Log _log = new Log(ClientManagerFacadeImpl.class);
|
||||
private ClientManager _manager;
|
||||
public final static String PROP_CLIENT_PORT = "i2cp.port";
|
||||
public final static int DEFAULT_PORT = 7654;
|
||||
|
||||
public ClientManagerFacadeImpl() {
|
||||
_manager = null;
|
||||
_log.debug("Client manager facade created");
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
_log.info("Starting up the client subsystem");
|
||||
String portStr = Router.getInstance().getConfigSetting(PROP_CLIENT_PORT);
|
||||
if (portStr != null) {
|
||||
try {
|
||||
int port = Integer.parseInt(portStr);
|
||||
_manager = new ClientManager(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error setting the port: " + portStr + " is not valid", nfe);
|
||||
_manager = new ClientManager(DEFAULT_PORT);
|
||||
}
|
||||
} else {
|
||||
_manager = new ClientManager(DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (_manager != null)
|
||||
_manager.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that a particular client authorize the Leases contained in the
|
||||
* LeaseSet, after which the onCreateJob is queued up. If that doesn't occur
|
||||
* within the timeout specified, queue up the onFailedJob. This call does not
|
||||
* block.
|
||||
*
|
||||
* @param dest Destination from which the LeaseSet's authorization should be requested
|
||||
* @param set LeaseSet with requested leases - this object must be updated to contain the
|
||||
* signed version (as well as any changed/added/removed Leases)
|
||||
* @param timeout ms to wait before failing
|
||||
* @param onCreateJob Job to run after the LeaseSet is authorized
|
||||
* @param onFailedJob Job to run after the timeout passes without receiving authorization
|
||||
*/
|
||||
public void requestLeaseSet(Destination dest, LeaseSet set, long timeout, Job onCreateJob, Job onFailedJob) {
|
||||
if (_manager != null)
|
||||
_manager.requestLeaseSet(dest, set, timeout, onCreateJob, onFailedJob);
|
||||
else
|
||||
_log.error("Null manager on requestLeaseSet!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct the client (or all clients) that they are under attack. This call
|
||||
* does not block.
|
||||
*
|
||||
* @param dest Destination under attack, or null if all destinations are affected
|
||||
* @param reason Why the router thinks that there is abusive behavior
|
||||
* @param severity How severe the abuse is, with 0 being not severe and 255 is the max
|
||||
*/
|
||||
public void reportAbuse(Destination dest, String reason, int severity) {
|
||||
if (_manager != null)
|
||||
_manager.reportAbuse(dest, reason, severity);
|
||||
else
|
||||
_log.error("Null manager on reportAbuse!");
|
||||
}
|
||||
/**
|
||||
* Determine if the destination specified is managed locally. This call
|
||||
* DOES block.
|
||||
*
|
||||
* @param dest Destination to be checked
|
||||
*/
|
||||
public boolean isLocal(Destination dest) {
|
||||
if (_manager != null)
|
||||
return _manager.isLocal(dest);
|
||||
else {
|
||||
_log.debug("Null manager on isLocal(dest)!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determine if the destination specified is managed locally. This call
|
||||
* DOES block.
|
||||
*
|
||||
* @param destHash Hash of Destination to be checked
|
||||
*/
|
||||
public boolean isLocal(Hash destHash) {
|
||||
if (_manager != null)
|
||||
return _manager.isLocal(destHash);
|
||||
else {
|
||||
_log.debug("Null manager on isLocal(hash)!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void messageDeliveryStatusUpdate(Destination fromDest, MessageId id, boolean delivered) {
|
||||
if (_manager != null)
|
||||
_manager.messageDeliveryStatusUpdate(fromDest, id, delivered);
|
||||
else
|
||||
_log.error("Null manager on messageDeliveryStatusUpdate!");
|
||||
}
|
||||
|
||||
public void messageReceived(ClientMessage msg) {
|
||||
if (_manager != null)
|
||||
_manager.messageReceived(msg);
|
||||
else
|
||||
_log.error("Null manager on messageReceived!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the client's current config, or null if not connected
|
||||
*
|
||||
*/
|
||||
public SessionConfig getClientSessionConfig(Destination dest) {
|
||||
if (_manager != null)
|
||||
return _manager.getClientSessionConfig(dest);
|
||||
else {
|
||||
_log.error("Null manager on getClientSessionConfig!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String renderStatusHTML() {
|
||||
if (_manager != null)
|
||||
return _manager.renderStatusHTML();
|
||||
else {
|
||||
_log.error("Null manager on renderStatusHTML!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package net.i2p.router.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.IOException;
|
||||
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.i2cp.CreateLeaseSetMessage;
|
||||
import net.i2p.data.i2cp.CreateSessionMessage;
|
||||
import net.i2p.data.i2cp.DestroySessionMessage;
|
||||
import net.i2p.data.i2cp.GetDateMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
|
||||
import net.i2p.data.i2cp.SendMessageMessage;
|
||||
import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
import net.i2p.data.i2cp.SetDateMessage;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.KeyManager;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Receive events from the client and handle them accordingly (updating the runner when
|
||||
* necessary)
|
||||
*
|
||||
*/
|
||||
class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventListener {
|
||||
private static final Log _log = new Log(ClientMessageEventListener.class);
|
||||
private ClientConnectionRunner _runner;
|
||||
|
||||
public ClientMessageEventListener(ClientConnectionRunner runner) {
|
||||
_runner = runner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming message and dispatch it to the appropriate handler
|
||||
*
|
||||
*/
|
||||
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
|
||||
if (_runner.isDead()) return;
|
||||
_log.info("Message recieved: \n" + message);
|
||||
switch (message.getType()) {
|
||||
case GetDateMessage.MESSAGE_TYPE:
|
||||
handleGetDate(reader, (GetDateMessage)message);
|
||||
break;
|
||||
case SetDateMessage.MESSAGE_TYPE:
|
||||
handleSetDate(reader, (SetDateMessage)message);
|
||||
break;
|
||||
case CreateSessionMessage.MESSAGE_TYPE:
|
||||
handleCreateSession(reader, (CreateSessionMessage)message);
|
||||
break;
|
||||
case SendMessageMessage.MESSAGE_TYPE:
|
||||
handleSendMessage(reader, (SendMessageMessage)message);
|
||||
break;
|
||||
case ReceiveMessageBeginMessage.MESSAGE_TYPE:
|
||||
handleReceiveBegin(reader, (ReceiveMessageBeginMessage)message);
|
||||
break;
|
||||
case ReceiveMessageEndMessage.MESSAGE_TYPE:
|
||||
handleReceiveEnd(reader, (ReceiveMessageEndMessage)message);
|
||||
break;
|
||||
case CreateLeaseSetMessage.MESSAGE_TYPE:
|
||||
handleCreateLeaseSet(reader, (CreateLeaseSetMessage)message);
|
||||
break;
|
||||
case DestroySessionMessage.MESSAGE_TYPE:
|
||||
handleDestroySession(reader, (DestroySessionMessage)message);
|
||||
break;
|
||||
default:
|
||||
_log.warn("Unhandled I2CP type received: " + message.getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle notifiation that there was an error
|
||||
*
|
||||
*/
|
||||
public void readError(I2CPMessageReader reader, Exception error) {
|
||||
if (_runner.isDead()) return;
|
||||
_log.error("Error occurred", error);
|
||||
_runner.stopRunning();
|
||||
}
|
||||
|
||||
public void disconnected(I2CPMessageReader reader) {
|
||||
if (_runner.isDead()) return;
|
||||
_runner.disconnected();
|
||||
}
|
||||
|
||||
private void handleGetDate(I2CPMessageReader reader, GetDateMessage message) {
|
||||
try {
|
||||
_runner.doSend(new SetDateMessage());
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error writing out the setDate message", ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the setDate message", ioe);
|
||||
}
|
||||
}
|
||||
private void handleSetDate(I2CPMessageReader reader, SetDateMessage message) {
|
||||
Clock.getInstance().setNow(message.getDate().getTime());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a CreateSessionMessage
|
||||
*
|
||||
*/
|
||||
private void handleCreateSession(I2CPMessageReader reader, CreateSessionMessage message) {
|
||||
if (message.getSessionConfig().verifySignature()) {
|
||||
_log.debug("Signature verified correctly on create session message");
|
||||
} else {
|
||||
_log.error("Signature verification *FAILED* on a create session message. Hijack attempt?");
|
||||
_runner.disconnectClient("Invalid signature on CreateSessionMessage");
|
||||
return;
|
||||
}
|
||||
|
||||
SessionStatusMessage msg = new SessionStatusMessage();
|
||||
SessionId sessionId = new SessionId();
|
||||
sessionId.setSessionId(getNextSessionId());
|
||||
_runner.setSessionId(sessionId);
|
||||
msg.setSessionId(sessionId);
|
||||
msg.setStatus(SessionStatusMessage.STATUS_CREATED);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
_runner.sessionEstablished(message.getSessionConfig());
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error writing out the session status message", ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the session status message", ioe);
|
||||
}
|
||||
|
||||
JobQueue.getInstance().addJob(new CreateSessionJob(_runner));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle a SendMessageMessage: give it a message Id, have the ClientManager distribute
|
||||
* it, and send the client an ACCEPTED message
|
||||
*
|
||||
*/
|
||||
private void handleSendMessage(I2CPMessageReader reader, SendMessageMessage message) {
|
||||
_log.debug("handleSendMessage called");
|
||||
MessageId id = _runner.distributeMessage(message);
|
||||
_runner.ackSendMessage(id, message.getNonce());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The client asked for a message, so we send it to them.
|
||||
*
|
||||
*/
|
||||
private void handleReceiveBegin(I2CPMessageReader reader, ReceiveMessageBeginMessage message) {
|
||||
if (_runner.isDead()) return;
|
||||
_log.debug("Handling recieve begin: id = " + message.getMessageId());
|
||||
MessagePayloadMessage msg = new MessagePayloadMessage();
|
||||
msg.setMessageId(message.getMessageId());
|
||||
msg.setSessionId(_runner.getSessionId());
|
||||
Payload payload = _runner.getPayload(message.getMessageId());
|
||||
if (payload == null) {
|
||||
_log.error("Payload for message id [" + message.getMessageId() + "] is null! Unknown message id?");
|
||||
return;
|
||||
}
|
||||
msg.setPayload(payload);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error delivering the payload", ioe);
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error delivering the payload", ime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The client told us that the message has been recieved completely. This currently
|
||||
* does not do any security checking prior to removing the message from the
|
||||
* pending queue, though it should.
|
||||
*
|
||||
*/
|
||||
private void handleReceiveEnd(I2CPMessageReader reader, ReceiveMessageEndMessage message) {
|
||||
_runner.removePayload(message.getMessageId());
|
||||
}
|
||||
|
||||
private void handleDestroySession(I2CPMessageReader reader, DestroySessionMessage message) {
|
||||
_log.info("Destroying client session " + _runner.getSessionId());
|
||||
_runner.stopRunning();
|
||||
}
|
||||
|
||||
private void handleCreateLeaseSet(I2CPMessageReader reader, CreateLeaseSetMessage message) {
|
||||
if ( (message.getLeaseSet() == null) || (message.getPrivateKey() == null) || (message.getSigningPrivateKey() == null) ) {
|
||||
_log.error("Null lease set granted: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
_log.info("New lease set granted for destination " + message.getLeaseSet().getDestination().calculateHash().toBase64());
|
||||
KeyManager.getInstance().registerKeys(message.getLeaseSet().getDestination(), message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
NetworkDatabaseFacade.getInstance().publish(message.getLeaseSet());
|
||||
|
||||
// leaseSetCreated takes care of all the LeaseRequestState stuff (including firing any jobs)
|
||||
_runner.leaseSetCreated(message.getLeaseSet());
|
||||
}
|
||||
|
||||
// this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME
|
||||
private final static int MAX_SESSION_ID = 32767;
|
||||
|
||||
private static volatile int _id = RandomSource.getInstance().nextInt(MAX_SESSION_ID); // sessionId counter
|
||||
private final static Object _sessionIdLock = new Object();
|
||||
|
||||
/** generate a new sessionId */
|
||||
private final static int getNextSessionId() {
|
||||
synchronized (_sessionIdLock) {
|
||||
int id = (++_id)%MAX_SESSION_ID;
|
||||
if (_id >= MAX_SESSION_ID)
|
||||
_id = 0;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
router/java/src/net/i2p/router/client/CreateSessionJob.java
Normal file
62
router/java/src/net/i2p/router/client/CreateSessionJob.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.i2p.router.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.util.Properties;
|
||||
|
||||
import net.i2p.router.ClientTunnelSettings;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.data.i2cp.SessionConfig;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Given an established connection, walk through the process of establishing the
|
||||
* lease set. This requests the TunnelManagerFacade to build tunnels for the
|
||||
* client and then once thats done (asynchronously) it requests a lease set from
|
||||
* the client
|
||||
*
|
||||
*/
|
||||
class CreateSessionJob extends JobImpl {
|
||||
private final static Log _log = new Log(CreateSessionJob.class);
|
||||
private ClientConnectionRunner _runner;
|
||||
|
||||
private final static long LEASE_CREATION_TIMEOUT = 30*1000;
|
||||
|
||||
public CreateSessionJob(ClientConnectionRunner runner) {
|
||||
_runner = runner;
|
||||
}
|
||||
|
||||
public String getName() { return "Request tunnels for a new client"; }
|
||||
public void runJob() {
|
||||
SessionConfig cfg = _runner.getConfig();
|
||||
if ( (cfg == null) || (cfg.getDestination() == null) ) return;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Requesting lease set for destination " + cfg.getDestination().calculateHash().toBase64());
|
||||
ClientTunnelSettings settings = new ClientTunnelSettings();
|
||||
Properties props = new Properties();
|
||||
|
||||
// We're NOT going to force all clients to use the router's defaults, since that may be
|
||||
// excessive. This means that unless the user says otherwise, we'll be satisfied with whatever
|
||||
// is available. Otherwise, when the router starts up, if there aren't sufficient tunnels with the
|
||||
// adequate number of hops, the user will have to wait. Once peer profiles are persistent, we can
|
||||
// reenable this, since on startup we'll have a sufficient number of high enough ranked peers to
|
||||
// tunnel through. (perhaps).
|
||||
|
||||
// XXX take the router's defaults
|
||||
// XXX props.putAll(Router.getInstance().getConfigMap());
|
||||
|
||||
// override them by the client's settings
|
||||
props.putAll(_runner.getConfig().getOptions());
|
||||
|
||||
// and load 'em up (using anything not yet set as the software defaults)
|
||||
settings.readFromProperties(props);
|
||||
TunnelManagerFacade.getInstance().createTunnels(_runner.getConfig().getDestination(), settings, LEASE_CREATION_TIMEOUT);
|
||||
}
|
||||
}
|
||||
62
router/java/src/net/i2p/router/client/LeaseRequestState.java
Normal file
62
router/java/src/net/i2p/router/client/LeaseRequestState.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package net.i2p.router.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 net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.router.Job;
|
||||
|
||||
/**
|
||||
* Bundle up the data points necessary when asynchronously requesting a lease
|
||||
* from a client
|
||||
*
|
||||
*/
|
||||
class LeaseRequestState {
|
||||
private LeaseSet _grantedLeaseSet;
|
||||
private LeaseSet _requestedLeaseSet;
|
||||
private PrivateKey _leaseSetPrivateKey;
|
||||
private SigningPrivateKey _leaseSetSigningPrivateKey;
|
||||
private Job _onGranted;
|
||||
private Job _onFailed;
|
||||
private long _expiration;
|
||||
private boolean _successful;
|
||||
|
||||
public LeaseRequestState(Job onGranted, Job onFailed, long expiration, LeaseSet requested) {
|
||||
_onGranted = onGranted;
|
||||
_onFailed = onFailed;
|
||||
_expiration = expiration;
|
||||
_requestedLeaseSet = requested;
|
||||
_successful = false;
|
||||
}
|
||||
|
||||
/** created lease set from client */
|
||||
public LeaseSet getGranted() { return _grantedLeaseSet; }
|
||||
public void setGranted(LeaseSet ls) { _grantedLeaseSet = ls; }
|
||||
/** lease set that is being requested */
|
||||
public LeaseSet getRequested() { return _requestedLeaseSet; }
|
||||
public void setRequested(LeaseSet ls) { _requestedLeaseSet = ls; }
|
||||
/** the private encryption key received regarding the lease set */
|
||||
public PrivateKey getPrivateKey() { return _leaseSetPrivateKey; }
|
||||
public void getPrivateKey(PrivateKey pk) { _leaseSetPrivateKey = pk; }
|
||||
/** the private signing key received regarding the lease set (for revocation) */
|
||||
public SigningPrivateKey getSigningPrivateKey() { return _leaseSetSigningPrivateKey; }
|
||||
public void getSigningPrivateKey(SigningPrivateKey spk) { _leaseSetSigningPrivateKey = spk; }
|
||||
/** what to do once the lease set is created */
|
||||
public Job getOnGranted() { return _onGranted; }
|
||||
public void setOnGranted(Job jb) { _onGranted = jb; }
|
||||
/** what to do if the lease set create fails / times out */
|
||||
public Job getOnFailed() { return _onFailed; }
|
||||
public void setOnFailed(Job jb) { _onFailed = jb; }
|
||||
/** when the request for the lease set expires */
|
||||
public long getExpiration() { return _expiration; }
|
||||
/** whether the request was successful in the time allotted */
|
||||
public boolean getIsSuccessful() { return _successful; }
|
||||
public void setIsSuccessful(boolean is) { _successful = is; }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package net.i2p.router.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 net.i2p.router.JobImpl;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Async job to notify the client that a new message is available for them
|
||||
*
|
||||
*/
|
||||
class MessageReceivedJob extends JobImpl {
|
||||
private final static Log _log = new Log(MessageReceivedJob.class);
|
||||
private ClientConnectionRunner _runner;
|
||||
private Destination _to;
|
||||
private Destination _from;
|
||||
private Payload _payload;
|
||||
public MessageReceivedJob(ClientConnectionRunner runner, Destination toDest, Destination fromDest, Payload payload) {
|
||||
_runner = runner;
|
||||
_to = toDest;
|
||||
_from = fromDest;
|
||||
_payload = payload;
|
||||
}
|
||||
|
||||
public String getName() { return "Deliver New Message"; }
|
||||
public void runJob() {
|
||||
if (_runner.isDead()) return;
|
||||
MessageId id = new MessageId();
|
||||
id.setMessageId(ClientConnectionRunner.getNextMessageId());
|
||||
_runner.setPayload(id, _payload);
|
||||
messageAvailable(id, _payload.getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver notification to the client that the given message is available.
|
||||
* This is synchronous and returns true if the notification was sent safely,
|
||||
* otherwise it returns false
|
||||
*
|
||||
*/
|
||||
public void messageAvailable(MessageId id, long size) {
|
||||
_log.debug("Sending message available: " + id + " to sessionId " + _runner.getSessionId() + " (with nonce=1)", new Exception("available"));
|
||||
MessageStatusMessage msg = new MessageStatusMessage();
|
||||
msg.setMessageId(id);
|
||||
msg.setSessionId(_runner.getSessionId());
|
||||
msg.setSize(size);
|
||||
msg.setNonce(1);
|
||||
msg.setStatus(MessageStatusMessage.STATUS_AVAILABLE);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error writing out the message status message", ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the message status message", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
router/java/src/net/i2p/router/client/ReportAbuseJob.java
Normal file
56
router/java/src/net/i2p/router/client/ReportAbuseJob.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package net.i2p.router.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 net.i2p.data.i2cp.AbuseReason;
|
||||
import net.i2p.data.i2cp.AbuseSeverity;
|
||||
import net.i2p.data.i2cp.ReportAbuseMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Async job to send an abuse message to the client
|
||||
*
|
||||
*/
|
||||
class ReportAbuseJob extends JobImpl {
|
||||
private final static Log _log = new Log(ReportAbuseJob.class);
|
||||
private ClientConnectionRunner _runner;
|
||||
private String _reason;
|
||||
private int _severity;
|
||||
public ReportAbuseJob(ClientConnectionRunner runner, String reason, int severity) {
|
||||
_runner = runner;
|
||||
_reason = reason;
|
||||
_severity = severity;
|
||||
}
|
||||
|
||||
public String getName() { return "Report Abuse"; }
|
||||
public void runJob() {
|
||||
if (_runner.isDead()) return;
|
||||
AbuseReason res = new AbuseReason();
|
||||
res.setReason(_reason);
|
||||
AbuseSeverity sev = new AbuseSeverity();
|
||||
sev.setSeverity(_severity);
|
||||
ReportAbuseMessage msg = new ReportAbuseMessage();
|
||||
msg.setMessageId(null);
|
||||
msg.setReason(res);
|
||||
msg.setSessionId(_runner.getSessionId());
|
||||
msg.setSeverity(sev);
|
||||
try {
|
||||
_runner.doSend(msg);
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error reporting abuse", ime);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reporting abuse", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
Normal file
129
router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package net.i2p.router.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 net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.i2cp.RequestLeaseSetMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Async job to walk the client through generating a lease set. First sends it
|
||||
* to the client and then queues up a CheckLeaseRequestStatus job for
|
||||
* processing after the expiration. When that CheckLeaseRequestStatus is run,
|
||||
* if the client still hasn't provided the signed leaseSet, fire off the onFailed
|
||||
* job from the intermediary LeaseRequestState and drop the client.
|
||||
*
|
||||
*/
|
||||
class RequestLeaseSetJob extends JobImpl {
|
||||
private static final Log _log = new Log(RequestLeaseSetJob.class);
|
||||
private ClientConnectionRunner _runner;
|
||||
private LeaseSet _ls;
|
||||
private long _expiration;
|
||||
private Job _onCreate;
|
||||
private Job _onFail;
|
||||
public RequestLeaseSetJob(ClientConnectionRunner runner, LeaseSet set, long expiration, Job onCreate, Job onFail) {
|
||||
_runner = runner;
|
||||
_ls = set;
|
||||
_expiration = expiration;
|
||||
_onCreate = onCreate;
|
||||
_onFail = onFail;
|
||||
}
|
||||
|
||||
public String getName() { return "Request Lease Set"; }
|
||||
public void runJob() {
|
||||
if (_runner.isDead()) return;
|
||||
LeaseRequestState oldReq = _runner.getLeaseRequest();
|
||||
if (oldReq != null) {
|
||||
if (oldReq.getExpiration() > Clock.getInstance().now()) {
|
||||
_log.error("Old *current* leaseRequest already exists! Why are we trying to request too quickly?", getAddedBy());
|
||||
return;
|
||||
} else {
|
||||
_log.error("Old *expired* leaseRequest exists! Why did the old request not get killed? (expiration = " + new Date(oldReq.getExpiration()) + ")", getAddedBy());
|
||||
}
|
||||
}
|
||||
|
||||
LeaseRequestState state = new LeaseRequestState(_onCreate, _onFail, _expiration, _ls);
|
||||
|
||||
RequestLeaseSetMessage msg = new RequestLeaseSetMessage();
|
||||
Date end = null;
|
||||
// get the earliest end date
|
||||
for (int i = 0; i < state.getRequested().getLeaseCount(); i++) {
|
||||
if ( (end == null) || (end.getTime() > state.getRequested().getLease(i).getEndDate().getTime()) )
|
||||
end = state.getRequested().getLease(i).getEndDate();
|
||||
}
|
||||
|
||||
msg.setEndDate(end);
|
||||
msg.setSessionId(_runner.getSessionId());
|
||||
|
||||
for (int i = 0; i < state.getRequested().getLeaseCount(); i++) {
|
||||
msg.addEndpoint(state.getRequested().getLease(i).getRouterIdentity(), state.getRequested().getLease(i).getTunnelId());
|
||||
}
|
||||
|
||||
try {
|
||||
_runner.setLeaseRequest(state);
|
||||
_runner.doSend(msg);
|
||||
JobQueue.getInstance().addJob(new CheckLeaseRequestStatus(state));
|
||||
return;
|
||||
} catch (I2CPMessageException ime) {
|
||||
_log.error("Error sending I2CP message requesting the lease set", ime);
|
||||
state.setIsSuccessful(false);
|
||||
_runner.setLeaseRequest(null);
|
||||
_runner.disconnectClient("I2CP error requesting leaseSet");
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error sending I2CP message requesting the lease set", ioe);
|
||||
state.setIsSuccessful(false);
|
||||
_runner.setLeaseRequest(null);
|
||||
_runner.disconnectClient("IO error requesting leaseSet");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule this job to be run after the request's expiration, so that if
|
||||
* it wasn't yet successful, we fire off the failure job and disconnect the
|
||||
* client (but if it was, noop)
|
||||
*
|
||||
*/
|
||||
private class CheckLeaseRequestStatus extends JobImpl {
|
||||
private LeaseRequestState _req;
|
||||
|
||||
public CheckLeaseRequestStatus(LeaseRequestState state) {
|
||||
_req = state;
|
||||
getTiming().setStartAfter(state.getExpiration());
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
if (_runner.isDead()) return;
|
||||
if (_req.getIsSuccessful()) {
|
||||
// we didn't fail
|
||||
return;
|
||||
} else {
|
||||
_log.error("Failed to receive a leaseSet in the time allotted (" + new Date(_req.getExpiration()) + ")");
|
||||
_runner.disconnectClient("Took too long to request leaseSet");
|
||||
if (_req.getOnFailed() != null)
|
||||
JobQueue.getInstance().addJob(_req.getOnFailed());
|
||||
|
||||
// only zero out the request if its the one we know about
|
||||
if (_req == _runner.getLeaseRequest())
|
||||
_runner.setLeaseRequest(null);
|
||||
}
|
||||
}
|
||||
public String getName() { return "Check LeaseRequest Status"; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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 net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Build a TunnelCreateMessage that is sent to the target requesting that they
|
||||
* participate in the tunnel. If they reply back saying they will, fire off the
|
||||
* onCreateSuccessful job, otherwise fire off the onCreateFailed job after a timeout.
|
||||
* The test message is sent at the specified priority.
|
||||
*
|
||||
* The message algorithm is:
|
||||
* = check to see if we have working outbound tunnels
|
||||
* - if true, send a tunnel message out the tunnel containing a garlic aimed directly at the peer in question.
|
||||
* - if false, send a message garlic'ed through a few routers before reaching the peer in question.
|
||||
*
|
||||
* the source route block will always point at an inbound tunnel - even if there aren't any real ones (in
|
||||
* which case, the tunnel gateway is the local router)
|
||||
*
|
||||
*/
|
||||
class BuildCreateTunnelMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(BuildCreateTunnelMessageJob.class);
|
||||
private RouterInfo _target;
|
||||
private Hash _replyTo;
|
||||
private TunnelInfo _tunnelConfig;
|
||||
private Job _onCreateSuccessful;
|
||||
private Job _onCreateFailed;
|
||||
private long _timeoutMs;
|
||||
private int _priority;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target router to participate in the tunnel
|
||||
* @param replyTo our address
|
||||
* @param info data regarding the tunnel configuration
|
||||
* @param onCreateSuccessfulJob after the peer replies back saying they'll participate
|
||||
* @param onCreateFailedJob after the peer replies back saying they won't participate, or timeout
|
||||
* @param timeoutMs how long to wait before timing out
|
||||
* @param priority how high priority to send this test
|
||||
*/
|
||||
public BuildCreateTunnelMessageJob(RouterInfo target, Hash replyTo, TunnelInfo info, Job onCreateSuccessfulJob, Job onCreateFailedJob, long timeoutMs, int priority) {
|
||||
super();
|
||||
_target = target;
|
||||
_replyTo = replyTo;
|
||||
_tunnelConfig = info;
|
||||
_onCreateSuccessful = onCreateSuccessfulJob;
|
||||
_onCreateFailed = onCreateFailedJob;
|
||||
_timeoutMs = timeoutMs;
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
public String getName() { return "Build Create Tunnel Message"; }
|
||||
public void runJob() {}
|
||||
}
|
||||
|
||||
202
router/java/src/net/i2p/router/message/BuildTestMessageJob.java
Normal file
202
router/java/src/net/i2p/router/message/BuildTestMessageJob.java
Normal file
@@ -0,0 +1,202 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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 net.i2p.router.Job;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.MessageSelector;
|
||||
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Build a test message that will be sent to the target to make sure they're alive.
|
||||
* Once that is verified, onSendJob is enqueued. If their reachability isn't
|
||||
* known (or they're unreachable) within timeoutMs, onSendFailedJob is enqueued.
|
||||
* The test message is sent at the specified priority.
|
||||
*
|
||||
*/
|
||||
public class BuildTestMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(BuildTestMessageJob.class);
|
||||
private RouterInfo _target;
|
||||
private Hash _replyTo;
|
||||
private Job _onSend;
|
||||
private Job _onSendFailed;
|
||||
private long _timeoutMs;
|
||||
private int _priority;
|
||||
private long _testMessageKey;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param target router being tested
|
||||
* @param onSendJob after the ping is successful
|
||||
* @param onSendFailedJob after the ping fails or times out
|
||||
* @param timeoutMs how long to wait before timing out
|
||||
* @param priority how high priority to send this test
|
||||
*/
|
||||
public BuildTestMessageJob(RouterInfo target, Hash replyTo, Job onSendJob, Job onSendFailedJob, long timeoutMs, int priority) {
|
||||
super();
|
||||
_target = target;
|
||||
_replyTo = replyTo;
|
||||
_onSend = onSendJob;
|
||||
_onSendFailed = onSendFailedJob;
|
||||
_timeoutMs = timeoutMs;
|
||||
_priority = priority;
|
||||
_testMessageKey = -1;
|
||||
}
|
||||
|
||||
public String getName() { return "Build Test Message"; }
|
||||
|
||||
public void runJob() {
|
||||
// This is a test message - build a garlic with a DeliveryStatusMessage that
|
||||
// first goes to the peer then back to us.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Building garlic message to test " + _target.getIdentity().getHash().toBase64());
|
||||
GarlicConfig config = buildGarlicCloveConfig();
|
||||
// TODO: make the last params on this specify the correct sessionKey and tags used
|
||||
ReplyJob replyJob = new JobReplyJob(_onSend, config.getRecipient().getIdentity().getPublicKey(), config.getId(), null, new HashSet());
|
||||
MessageSelector sel = buildMessageSelector();
|
||||
SendGarlicJob job = new SendGarlicJob(config, null, _onSendFailed, replyJob, _onSendFailed, _timeoutMs, _priority, sel);
|
||||
JobQueue.getInstance().addJob(job);
|
||||
}
|
||||
|
||||
private MessageSelector buildMessageSelector() {
|
||||
return new TestMessageSelector(_testMessageKey, _timeoutMs + Clock.getInstance().now());
|
||||
}
|
||||
|
||||
private GarlicConfig buildGarlicCloveConfig() {
|
||||
_testMessageKey = RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Test message key: " + _testMessageKey);
|
||||
GarlicConfig config = new GarlicConfig();
|
||||
|
||||
PayloadGarlicConfig ackClove = buildAckClove();
|
||||
config.addClove(ackClove);
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER);
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setDelaySeconds(0);
|
||||
instructions.setEncrypted(false);
|
||||
instructions.setEncryptionKey(null);
|
||||
instructions.setRouter(_target.getIdentity().getHash());
|
||||
instructions.setTunnelId(null);
|
||||
|
||||
config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
config.setDeliveryInstructions(instructions);
|
||||
config.setId(RandomSource.getInstance().nextInt(Integer.MAX_VALUE));
|
||||
config.setExpiration(_timeoutMs+Clock.getInstance().now()+2*Router.CLOCK_FUDGE_FACTOR);
|
||||
config.setRecipient(_target);
|
||||
config.setRequestAck(false);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a clove that sends a DeliveryStatusMessage to us
|
||||
*/
|
||||
private PayloadGarlicConfig buildAckClove() {
|
||||
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
|
||||
|
||||
DeliveryInstructions ackInstructions = new DeliveryInstructions();
|
||||
ackInstructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER);
|
||||
ackInstructions.setRouter(_replyTo); // yikes!
|
||||
ackInstructions.setDelayRequested(false);
|
||||
ackInstructions.setDelaySeconds(0);
|
||||
ackInstructions.setEncrypted(false);
|
||||
|
||||
DeliveryStatusMessage msg = new DeliveryStatusMessage();
|
||||
msg.setArrival(new Date(Clock.getInstance().now()));
|
||||
msg.setMessageId(_testMessageKey);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Delivery status message key: " + _testMessageKey + " arrival: " + msg.getArrival());
|
||||
|
||||
ackClove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
ackClove.setDeliveryInstructions(ackInstructions);
|
||||
ackClove.setExpiration(_timeoutMs+Clock.getInstance().now());
|
||||
ackClove.setId(RandomSource.getInstance().nextInt(Integer.MAX_VALUE));
|
||||
ackClove.setPayload(msg);
|
||||
ackClove.setRecipient(_target);
|
||||
ackClove.setRequestAck(false);
|
||||
|
||||
return ackClove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search inbound messages for delivery status messages with our key
|
||||
*/
|
||||
private final static class TestMessageSelector implements MessageSelector {
|
||||
private long _testMessageKey;
|
||||
private long _timeout;
|
||||
public TestMessageSelector(long key, long timeout) {
|
||||
_testMessageKey = key;
|
||||
_timeout = timeout;
|
||||
}
|
||||
public boolean continueMatching() { return false; }
|
||||
public long getExpiration() { return _timeout; }
|
||||
public boolean isMatch(I2NPMessage inMsg) {
|
||||
if (inMsg.getType() == DeliveryStatusMessage.MESSAGE_TYPE) {
|
||||
return ((DeliveryStatusMessage)inMsg).getMessageId() == _testMessageKey;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On reply, fire off the specified job
|
||||
*
|
||||
*/
|
||||
private final static class JobReplyJob extends JobImpl implements ReplyJob {
|
||||
private Job _job;
|
||||
private PublicKey _target;
|
||||
private long _msgId;
|
||||
private Set _sessionTagsDelivered;
|
||||
private SessionKey _keyDelivered;
|
||||
public JobReplyJob(Job job, PublicKey target, long msgId, SessionKey keyUsed, Set tagsDelivered) {
|
||||
_job = job;
|
||||
_target = target;
|
||||
_msgId = msgId;
|
||||
_keyDelivered = keyUsed;
|
||||
_sessionTagsDelivered = tagsDelivered;
|
||||
}
|
||||
public String getName() { return "Reply To Test Message Received"; }
|
||||
public void runJob() {
|
||||
if ( (_keyDelivered != null) && (_sessionTagsDelivered != null) && (_sessionTagsDelivered.size() > 0) )
|
||||
SessionKeyManager.getInstance().tagsDelivered(_target, _keyDelivered, _sessionTagsDelivered);
|
||||
|
||||
JobQueue.getInstance().addJob(_job);
|
||||
}
|
||||
|
||||
public void setMessage(I2NPMessage message) {
|
||||
// ignored, this is just a ping
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
58
router/java/src/net/i2p/router/message/CloveSet.java
Normal file
58
router/java/src/net/i2p/router/message/CloveSet.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.i2np.GarlicClove;
|
||||
|
||||
/**
|
||||
* Wrap up the data contained in a CloveMessage after being decrypted
|
||||
*
|
||||
*/
|
||||
public class CloveSet {
|
||||
private List _cloves;
|
||||
private Certificate _cert;
|
||||
private long _msgId;
|
||||
private long _expiration;
|
||||
|
||||
public CloveSet() {
|
||||
_cloves = new ArrayList();
|
||||
_cert = null;
|
||||
_msgId = -1;
|
||||
_expiration = -1;
|
||||
}
|
||||
|
||||
public int getCloveCount() { return _cloves.size(); }
|
||||
public void addClove(GarlicClove clove) { _cloves.add(clove); }
|
||||
public GarlicClove getClove(int index) { return (GarlicClove)_cloves.get(index); }
|
||||
|
||||
public Certificate getCertificate() { return _cert; }
|
||||
public void setCertificate(Certificate cert) { _cert = cert; }
|
||||
public long getMessageId() { return _msgId; }
|
||||
public void setMessageId(long id) { _msgId = id; }
|
||||
public long getExpiration() { return _expiration; }
|
||||
public void setExpiration(long expiration) { _expiration = expiration; }
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("{");
|
||||
for (int i = 0; i < _cloves.size(); i++) {
|
||||
GarlicClove clove = (GarlicClove)_cloves.get(i);
|
||||
if (clove.getData() != null)
|
||||
buf.append(clove.getData().getClass().getName()).append(", ");
|
||||
else
|
||||
buf.append("[null clove], ");
|
||||
}
|
||||
buf.append("}");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
182
router/java/src/net/i2p/router/message/GarlicConfig.java
Normal file
182
router/java/src/net/i2p/router/message/GarlicConfig.java
Normal file
@@ -0,0 +1,182 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
|
||||
/**
|
||||
* Define the contents of a garlic chunk that contains 1 or more sub garlics
|
||||
*
|
||||
*/
|
||||
public class GarlicConfig {
|
||||
private RouterInfo _recipient;
|
||||
private PublicKey _recipientPublicKey;
|
||||
private Certificate _cert;
|
||||
private long _id;
|
||||
private long _expiration;
|
||||
private List _cloveConfigs;
|
||||
private DeliveryInstructions _instructions;
|
||||
private boolean _requestAck;
|
||||
private RouterInfo _replyThroughRouter; // router through which any replies will be sent before delivery to us
|
||||
private DeliveryInstructions _replyInstructions; // how the message will be sent from the replyThroughRouter to us
|
||||
private Certificate _replyBlockCertificate;
|
||||
private long _replyBlockMessageId;
|
||||
private long _replyBlockExpiration;
|
||||
|
||||
public GarlicConfig() {
|
||||
_recipient = null;
|
||||
_recipientPublicKey = null;
|
||||
_cert = null;
|
||||
_id = -1;
|
||||
_expiration = -1;
|
||||
_cloveConfigs = new ArrayList();
|
||||
_instructions = null;
|
||||
_requestAck = false;
|
||||
_replyThroughRouter = null;
|
||||
_replyInstructions = null;
|
||||
_replyBlockCertificate = null;
|
||||
_replyBlockMessageId = -1;
|
||||
_replyBlockExpiration = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Router to receive and process this clove - the router that will open the
|
||||
* delivery instructions and decide what to do process it locally as an I2NPMessage,
|
||||
* forward it as an I2NPMessage to a router, forward it as an I2NPMessage to a Destination,
|
||||
* or forward it as an I2NPMessage to a tunnel.
|
||||
*
|
||||
*/
|
||||
public void setRecipient(RouterInfo info) { _recipient = info; }
|
||||
public RouterInfo getRecipient() { return _recipient; }
|
||||
|
||||
/**
|
||||
* Public key of the router to receive and process this clove. This is useful
|
||||
* for garlic routed messages encrypted to the router at the end of a tunnel,
|
||||
* as their RouterIdentity is not known, but a PublicKey they handle is exposed
|
||||
* via the LeaseSet
|
||||
*
|
||||
*/
|
||||
public void setRecipientPublicKey(PublicKey recipientPublicKey) { _recipientPublicKey = recipientPublicKey; }
|
||||
public PublicKey getRecipientPublicKey() { return _recipientPublicKey; }
|
||||
|
||||
/**
|
||||
* Certificate for the getRecipient() to pay for their processing
|
||||
*
|
||||
*/
|
||||
public void setCertificate(Certificate cert) { _cert = cert; }
|
||||
public Certificate getCertificate() { return _cert; }
|
||||
|
||||
/**
|
||||
* Unique ID of the clove
|
||||
*
|
||||
*/
|
||||
public void setId(long id) { _id = id; }
|
||||
public long getId() { return _id; }
|
||||
|
||||
/**
|
||||
* Expiration of the clove, after which it should be dropped
|
||||
*
|
||||
*/
|
||||
public void setExpiration(long expiration) { _expiration = expiration; }
|
||||
public long getExpiration() { return _expiration; }
|
||||
|
||||
/**
|
||||
* Specify how the I2NPMessage in the clove should be handled.
|
||||
*
|
||||
*/
|
||||
public void setDeliveryInstructions(DeliveryInstructions instructions) { _instructions = instructions; }
|
||||
public DeliveryInstructions getDeliveryInstructions() { return _instructions; }
|
||||
|
||||
/**
|
||||
* If true, the recipient of this clove is requested to send a DeliveryStatusMessage
|
||||
* back via the replyThroughRouter using the getId() value for the status' message Id.
|
||||
* Since those reply blocks are good for one use only, this flag should only be set if
|
||||
* no reply is expected.
|
||||
*
|
||||
*/
|
||||
public void setRequestAck(boolean request) { _requestAck = request; }
|
||||
public boolean getRequestAck() { return _requestAck; }
|
||||
|
||||
/**
|
||||
* Specify the router through which a reply to this clove can be sent. The
|
||||
* getReplyInstructions() are passed to this router during the reply process
|
||||
* and it them uses those to send the reply to this router.
|
||||
*
|
||||
*/
|
||||
public void setReplyThroughRouter(RouterInfo replyThroughRouter) { _replyThroughRouter = replyThroughRouter; }
|
||||
public RouterInfo getReplyThroughRouter() { return _replyThroughRouter; }
|
||||
|
||||
/**
|
||||
* Specify how any reply will be routed so that it reaches this router after being
|
||||
* delivered to the getReplyThroughRouter. These instructions are not exposed to the
|
||||
* router who receives this garlic message in cleartext - they are instead encrypted to
|
||||
* the replyThrough router
|
||||
*
|
||||
*/
|
||||
public void setReplyInstructions(DeliveryInstructions instructions) { _replyInstructions = instructions; }
|
||||
public DeliveryInstructions getReplyInstructions() { return _replyInstructions; }
|
||||
|
||||
public long getReplyBlockMessageId() { return _replyBlockMessageId; }
|
||||
public void setReplyBlockMessageId(long id) { _replyBlockMessageId = id; }
|
||||
|
||||
public Certificate getReplyBlockCertificate() { return _replyBlockCertificate; }
|
||||
public void setReplyBlockCertificate(Certificate cert) { _replyBlockCertificate = cert; }
|
||||
|
||||
public long getReplyBlockExpiration() { return _replyBlockExpiration; }
|
||||
public void setReplyBlockExpiration(long expiration) { _replyBlockExpiration = expiration; }
|
||||
|
||||
/**
|
||||
* Add a clove to the current message - if any cloves are added, an I2NP message
|
||||
* cannot be specified via setPayload. This means that the resulting GarlicClove
|
||||
* represented by this GarlicConfig must be a GarlicMessage itself
|
||||
*
|
||||
*/
|
||||
public void addClove(GarlicConfig config) {
|
||||
if (config != null) {
|
||||
_cloveConfigs.add(config);
|
||||
}
|
||||
}
|
||||
public int getCloveCount() { return _cloveConfigs.size(); }
|
||||
public GarlicConfig getClove(int index) { return (GarlicConfig)_cloveConfigs.get(index); }
|
||||
public void clearCloves() { _cloveConfigs.clear(); }
|
||||
|
||||
|
||||
protected String getSubData() { return ""; }
|
||||
private final static String NL = System.getProperty("line.separator");
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("<garlicConfig>").append(NL);
|
||||
buf.append("<certificate>").append(getCertificate()).append("</certificate>").append(NL);
|
||||
buf.append("<instructions>").append(getDeliveryInstructions()).append("</instructions>").append(NL);
|
||||
buf.append("<expiration>").append(new Date(getExpiration())).append("</expiration>").append(NL);
|
||||
buf.append("<garlicId>").append(getId()).append("</garlicId>").append(NL);
|
||||
buf.append("<recipient>").append(getRecipient()).append("</recipient>").append(NL);
|
||||
buf.append("<recipientPublicKey>").append(getRecipientPublicKey()).append("</recipientPublicKey>").append(NL);
|
||||
buf.append("<replyBlockCertificate>").append(getReplyBlockCertificate()).append("</replyBlockCertificate>").append(NL);
|
||||
buf.append("<replyBlockExpiration>").append(new Date(getReplyBlockExpiration())).append("</replyBlockExpiration>").append(NL);
|
||||
buf.append("<replyBlockMessageId>").append(getReplyBlockMessageId()).append("</replyBlockMessageId>").append(NL);
|
||||
buf.append("<replyInstructions>").append(getReplyInstructions()).append("</replyInstructions>").append(NL);
|
||||
buf.append("<replyThroughRouter>").append(getReplyThroughRouter()).append("</replyThroughRouter>").append(NL);
|
||||
buf.append("<requestAck>").append(getRequestAck()).append("</requestAck>").append(NL);
|
||||
buf.append(getSubData());
|
||||
buf.append("<subcloves>").append(NL);
|
||||
for (int i = 0; i < getCloveCount(); i++)
|
||||
buf.append("<clove>").append(getClove(i)).append("</clove>").append(NL);
|
||||
buf.append("</subcloves>").append(NL);
|
||||
buf.append("</garlicConfig>").append(NL);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
206
router/java/src/net/i2p/router/message/GarlicMessageBuilder.java
Normal file
206
router/java/src/net/i2p/router/message/GarlicMessageBuilder.java
Normal file
@@ -0,0 +1,206 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.i2np.GarlicClove;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Build garlic messages based on a GarlicConfig
|
||||
*
|
||||
*/
|
||||
public class GarlicMessageBuilder {
|
||||
private final static Log _log = new Log(GarlicMessageBuilder.class);
|
||||
|
||||
public static GarlicMessage buildMessage(GarlicConfig config) {
|
||||
return buildMessage(config, new SessionKey(), new HashSet());
|
||||
}
|
||||
public static GarlicMessage buildMessage(GarlicConfig config, SessionKey wrappedKey, Set wrappedTags) {
|
||||
if (config == null)
|
||||
throw new IllegalArgumentException("Null config specified");
|
||||
|
||||
PublicKey key = config.getRecipientPublicKey();
|
||||
if (key == null) {
|
||||
if (config.getRecipient() == null) {
|
||||
throw new IllegalArgumentException("Null recipient specified");
|
||||
} else if (config.getRecipient().getIdentity() == null) {
|
||||
throw new IllegalArgumentException("Null recipient.identity specified");
|
||||
} else if (config.getRecipient().getIdentity().getPublicKey() == null) {
|
||||
throw new IllegalArgumentException("Null recipient.identity.publicKey specified");
|
||||
} else
|
||||
key = config.getRecipient().getIdentity().getPublicKey();
|
||||
}
|
||||
GarlicMessage msg = new GarlicMessage();
|
||||
|
||||
noteWrap(msg, config);
|
||||
|
||||
_log.info("Encrypted with public key " + key + " to expire on " + new Date(config.getExpiration()));
|
||||
|
||||
byte cloveSet[] = buildCloveSet(config);
|
||||
|
||||
SessionKey curKey = SessionKeyManager.getInstance().getCurrentKey(key);
|
||||
if (curKey == null)
|
||||
curKey = SessionKeyManager.getInstance().createSession(key);
|
||||
wrappedKey.setData(curKey.getData());
|
||||
|
||||
int availTags = SessionKeyManager.getInstance().getAvailableTags(key, curKey);
|
||||
_log.debug("Available tags for encryption to " + key + ": " + availTags);
|
||||
|
||||
if (availTags < 10) { // arbitrary threshold
|
||||
for (int i = 0; i < 20; i++)
|
||||
wrappedTags.add(new SessionTag(true));
|
||||
_log.info("Less than 10 tags are available (" + availTags + "), so we're including 20 more");
|
||||
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(key, curKey) < 30*1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
for (int i = 0; i < 20; i++)
|
||||
wrappedTags.add(new SessionTag(true));
|
||||
_log.info("Tags are almost expired, adding 20 new ones");
|
||||
} else {
|
||||
// always tack on at least one more - not necessary.
|
||||
//wrappedTags.add(new SessionTag(true));
|
||||
}
|
||||
SessionTag curTag = SessionKeyManager.getInstance().consumeNextAvailableTag(key, curKey);
|
||||
byte encData[] = ElGamalAESEngine.encrypt(cloveSet, key, curKey, wrappedTags, curTag, 1024);
|
||||
msg.setData(encData);
|
||||
Date exp = new Date(config.getExpiration());
|
||||
msg.setMessageExpiration(exp);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private static void noteWrap(GarlicMessage wrapper, GarlicConfig contained) {
|
||||
for (int i = 0; i < contained.getCloveCount(); i++) {
|
||||
GarlicConfig config = contained.getClove(i);
|
||||
if (config instanceof PayloadGarlicConfig) {
|
||||
I2NPMessage msg = ((PayloadGarlicConfig)config).getPayload();
|
||||
String bodyType = msg.getClass().getName();
|
||||
MessageHistory.getInstance().wrap(bodyType, msg.getUniqueId(), GarlicMessage.class.getName(), wrapper.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an unencrypted set of cloves specified by the config.
|
||||
*
|
||||
*/
|
||||
private static byte[] buildCloveSet(GarlicConfig config) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
if (config instanceof PayloadGarlicConfig) {
|
||||
DataHelper.writeLong(baos, 1, 1);
|
||||
baos.write(buildClove((PayloadGarlicConfig)config));
|
||||
} else {
|
||||
DataHelper.writeLong(baos, 1, config.getCloveCount());
|
||||
for (int i = 0; i < config.getCloveCount(); i++) {
|
||||
GarlicConfig c = config.getClove(i);
|
||||
byte clove[] = null;
|
||||
if (c instanceof PayloadGarlicConfig) {
|
||||
_log.debug("Subclove IS a payload garlic clove");
|
||||
clove = buildClove((PayloadGarlicConfig)c);
|
||||
} else {
|
||||
_log.debug("Subclove IS NOT a payload garlic clove");
|
||||
clove = buildClove(c);
|
||||
}
|
||||
if (clove == null)
|
||||
throw new DataFormatException("Unable to build clove");
|
||||
else
|
||||
baos.write(clove);
|
||||
}
|
||||
}
|
||||
config.getCertificate().writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 4, config.getId());
|
||||
DataHelper.writeDate(baos, new Date(config.getExpiration()));
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error building the clove set", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error building the clove set", dfe);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static byte[] buildClove(PayloadGarlicConfig config) throws DataFormatException, IOException {
|
||||
GarlicClove clove = new GarlicClove();
|
||||
clove.setData(config.getPayload());
|
||||
return buildCommonClove(clove, config);
|
||||
}
|
||||
|
||||
private static byte[] buildClove(GarlicConfig config) throws DataFormatException, IOException {
|
||||
GarlicClove clove = new GarlicClove();
|
||||
GarlicMessage msg = buildMessage(config);
|
||||
if (msg == null)
|
||||
throw new DataFormatException("Unable to build message from clove config");
|
||||
clove.setData(msg);
|
||||
return buildCommonClove(clove, config);
|
||||
}
|
||||
|
||||
|
||||
private static byte[] buildCommonClove(GarlicClove clove, GarlicConfig config) throws DataFormatException, IOException {
|
||||
clove.setCertificate(config.getCertificate());
|
||||
clove.setCloveId(config.getId());
|
||||
clove.setExpiration(new Date(config.getExpiration()));
|
||||
clove.setInstructions(config.getDeliveryInstructions());
|
||||
specifySourceRouteBlock(clove, config);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
clove.writeBytes(baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static void specifySourceRouteBlock(GarlicClove clove, GarlicConfig config) throws DataFormatException {
|
||||
boolean includeBlock = false;
|
||||
if (config.getRequestAck()) {
|
||||
clove.setSourceRouteBlockAction(GarlicClove.ACTION_STATUS);
|
||||
includeBlock = true;
|
||||
} else if (config.getReplyInstructions() != null) {
|
||||
clove.setSourceRouteBlockAction(GarlicClove.ACTION_MESSAGE_SPECIFIC);
|
||||
includeBlock = true;
|
||||
} else {
|
||||
clove.setSourceRouteBlockAction(GarlicClove.ACTION_NONE);
|
||||
}
|
||||
|
||||
if (includeBlock) {
|
||||
_log.debug("Specifying source route block");
|
||||
|
||||
SessionKey replySessionKey = KeyGenerator.getInstance().generateSessionKey();
|
||||
SessionTag tag = new SessionTag(true);
|
||||
|
||||
// make it so we'll read the session tag correctly and use the right session key
|
||||
HashSet tags = new HashSet(1);
|
||||
tags.add(tag);
|
||||
SessionKeyManager.getInstance().tagsReceived(replySessionKey, tags);
|
||||
|
||||
SourceRouteBlock block = new SourceRouteBlock();
|
||||
PublicKey pk = config.getReplyThroughRouter().getIdentity().getPublicKey();
|
||||
block.setData(config.getReplyInstructions(), config.getReplyBlockMessageId(),
|
||||
config.getReplyBlockCertificate(), config.getReplyBlockExpiration(), pk);
|
||||
block.setRouter(config.getReplyThroughRouter().getIdentity().getHash());
|
||||
block.setKey(replySessionKey);
|
||||
block.setTag(tag);
|
||||
clove.setSourceRouteBlock(block);
|
||||
} else {
|
||||
clove.setSourceRouteBlock(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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 net.i2p.router.HandlerJobBuilder;
|
||||
import net.i2p.router.Job;
|
||||
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
|
||||
/**
|
||||
* HandlerJobBuilder to build jobs to handle GarlicMessages
|
||||
*
|
||||
*/
|
||||
public class GarlicMessageHandler implements HandlerJobBuilder {
|
||||
|
||||
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash, SourceRouteBlock replyBlock) {
|
||||
// ignore the reply block for the moment
|
||||
HandleGarlicMessageJob job = new HandleGarlicMessageJob((GarlicMessage)receivedMessage, from, fromHash);
|
||||
return job;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.i2np.GarlicClove;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Read a GarlicMessage, decrypt it, and return the resulting CloveSet
|
||||
*
|
||||
*/
|
||||
public class GarlicMessageParser {
|
||||
private final static Log _log = new Log(GarlicMessageParser.class);
|
||||
private static GarlicMessageParser _instance = new GarlicMessageParser();
|
||||
public static GarlicMessageParser getInstance() { return _instance; }
|
||||
private GarlicMessageParser() {}
|
||||
|
||||
public CloveSet getGarlicCloves(GarlicMessage message, PrivateKey encryptionKey) {
|
||||
byte encData[] = message.getData();
|
||||
byte decrData[] = null;
|
||||
try {
|
||||
_log.debug("Decrypting with private key " + encryptionKey);
|
||||
decrData = ElGamalAESEngine.decrypt(encData, encryptionKey);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Error decrypting", dfe);
|
||||
}
|
||||
if (decrData == null) {
|
||||
_log.debug("Decryption of garlic message failed");
|
||||
return null;
|
||||
} else {
|
||||
return readCloveSet(decrData);
|
||||
}
|
||||
}
|
||||
|
||||
private CloveSet readCloveSet(byte data[]) {
|
||||
Set cloves = new HashSet();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
try {
|
||||
CloveSet set = new CloveSet();
|
||||
|
||||
int numCloves = (int)DataHelper.readLong(bais, 1);
|
||||
_log.debug("# cloves to read: " + numCloves);
|
||||
for (int i = 0; i < numCloves; i++) {
|
||||
_log.debug("Reading clove " + i);
|
||||
try {
|
||||
GarlicClove clove = new GarlicClove();
|
||||
clove.readBytes(bais);
|
||||
set.addClove(clove);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Unable to read clove " + i, dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Unable to read clove " + i, ioe);
|
||||
}
|
||||
_log.debug("After reading clove " + i);
|
||||
}
|
||||
Certificate cert = new Certificate();
|
||||
cert.readBytes(bais);
|
||||
long msgId = DataHelper.readLong(bais, 4);
|
||||
Date expiration = DataHelper.readDate(bais);
|
||||
|
||||
set.setCertificate(cert);
|
||||
set.setMessageId(msgId);
|
||||
set.setExpiration(expiration.getTime());
|
||||
|
||||
return set;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading clove set", ioe);
|
||||
return null;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error reading clove set", dfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.i2np.GarlicClove;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.KeyManager;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Unencrypt a garlic message and handle each of the cloves - locally destined
|
||||
* messages are tossed into the inbound network message pool so they're handled
|
||||
* as if they arrived locally. Other instructions are not yet implemented (but
|
||||
* need to be. soon)
|
||||
*
|
||||
*/
|
||||
public class HandleGarlicMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(HandleGarlicMessageJob.class);
|
||||
private GarlicMessage _message;
|
||||
private RouterIdentity _from;
|
||||
private Hash _fromHash;
|
||||
private static Map _cloves; // map of clove Id --> Expiration of cloves we've already seen
|
||||
|
||||
private final static int FORWARD_PRIORITY = 50;
|
||||
|
||||
public HandleGarlicMessageJob(GarlicMessage msg, RouterIdentity from, Hash fromHash) {
|
||||
super();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New handle garlicMessageJob called w/ message from [" + from + "]", new Exception("Debug"));
|
||||
_message = msg;
|
||||
_from = from;
|
||||
_fromHash = fromHash;
|
||||
_cloves = new HashMap();
|
||||
}
|
||||
|
||||
public String getName() { return "Handle Inbound Garlic Message"; }
|
||||
public void runJob() {
|
||||
CloveSet set = GarlicMessageParser.getInstance().getGarlicCloves(_message, KeyManager.getInstance().getPrivateKey());
|
||||
if (set == null) {
|
||||
Set keys = KeyManager.getInstance().getAllKeys();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Decryption with the router's key failed, now try with the " + keys.size() + " leaseSet keys");
|
||||
// our router key failed, which means that it was either encrypted wrong
|
||||
// or it was encrypted to a LeaseSet's PublicKey
|
||||
for (Iterator iter = keys.iterator(); iter.hasNext();) {
|
||||
LeaseSetKeys lskeys = (LeaseSetKeys)iter.next();
|
||||
set = GarlicMessageParser.getInstance().getGarlicCloves(_message, lskeys.getDecryptionKey());
|
||||
if (set != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Decrypted garlic message with lease set key for destination " + lskeys.getDestination().calculateHash().toBase64() + " SUCCEEDED: " + set);
|
||||
break;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Decrypting garlic message with lease set key for destination " + lskeys.getDestination().calculateHash().toBase64() + " failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Decrypted clove set found " + set.getCloveCount() + " cloves: " + set);
|
||||
}
|
||||
if (set != null) {
|
||||
for (int i = 0; i < set.getCloveCount(); i++) {
|
||||
GarlicClove clove = set.getClove(i);
|
||||
handleClove(clove);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("CloveMessageParser failed to decrypt the message [" + _message.getUniqueId() + "] to us when received from [" + _fromHash + "] / [" + _from + "]", new Exception("Decrypt garlic failed"));
|
||||
MessageHistory.getInstance().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), "Garlic could not be decrypted");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isKnown(long cloveId) {
|
||||
boolean known = false;
|
||||
synchronized (_cloves) {
|
||||
known = _cloves.containsKey(new Long(cloveId));
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("isKnown("+cloveId+"): " + known);
|
||||
return known;
|
||||
}
|
||||
|
||||
private static void cleanupCloves() {
|
||||
// this should be in its own thread perhaps? and maybe _cloves should be
|
||||
// synced to disk?
|
||||
List toRemove = new ArrayList(32);
|
||||
long now = Clock.getInstance().now();
|
||||
synchronized (_cloves) {
|
||||
for (Iterator iter = _cloves.keySet().iterator(); iter.hasNext();) {
|
||||
Long id = (Long)iter.next();
|
||||
Date exp = (Date)_cloves.get(id);
|
||||
if (exp == null) continue; // wtf, not sure how this can happen yet, but i've seen it. grr.
|
||||
if (now > exp.getTime())
|
||||
toRemove.add(id);
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++)
|
||||
_cloves.remove(toRemove.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValid(GarlicClove clove) {
|
||||
if (isKnown(clove.getCloveId())) {
|
||||
_log.error("Duplicate garlic clove received - replay attack in progress? [cloveId = " +
|
||||
clove.getCloveId() + " expiration = " + clove.getExpiration());
|
||||
return false;
|
||||
} else {
|
||||
_log.debug("Clove " + clove.getCloveId() + " expiring on " + clove.getExpiration() + " is not known");
|
||||
}
|
||||
long now = Clock.getInstance().now();
|
||||
if (clove.getExpiration().getTime() < now) {
|
||||
if (clove.getExpiration().getTime() < now + Router.CLOCK_FUDGE_FACTOR) {
|
||||
_log.warn("Expired garlic received, but within our fudge factor [" + clove.getExpiration() + "]");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.error("Expired garlic clove received - replay attack in progress? [cloveId = " +
|
||||
clove.getCloveId() + " expiration = " + clove.getExpiration() + " now = " + (new Date(Clock.getInstance().now())));
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
synchronized (_cloves) {
|
||||
_cloves.put(new Long(clove.getCloveId()), clove.getExpiration());
|
||||
}
|
||||
cleanupCloves();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleClove(GarlicClove clove) {
|
||||
if (!isValid(clove)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.warn("Invalid clove " + clove);
|
||||
return;
|
||||
}
|
||||
boolean requestAck = (clove.getSourceRouteBlockAction() == GarlicClove.ACTION_STATUS);
|
||||
long sendExpiration = clove.getExpiration().getTime();
|
||||
MessageHandler.getInstance().handleMessage(clove.getInstructions(), clove.getData(), requestAck, clove.getSourceRouteBlock(),
|
||||
clove.getCloveId(), _from, _fromHash, sendExpiration, FORWARD_PRIORITY);
|
||||
}
|
||||
|
||||
public void dropped() {
|
||||
MessageHistory.getInstance().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), "Dropped due to overload");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.SourceRouteReplyMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.KeyManager;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Handle a source route reply - decrypt the instructions and forward the message
|
||||
* accordingly
|
||||
*
|
||||
*/
|
||||
public class HandleSourceRouteReplyMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(HandleSourceRouteReplyMessageJob.class);
|
||||
private SourceRouteReplyMessage _message;
|
||||
private RouterIdentity _from;
|
||||
private Hash _fromHash;
|
||||
private static Map _seenMessages; // Long msgId --> Date seen
|
||||
|
||||
public final static int PRIORITY = 150;
|
||||
|
||||
public HandleSourceRouteReplyMessageJob(SourceRouteReplyMessage msg, RouterIdentity from, Hash fromHash) {
|
||||
super();
|
||||
_message = msg;
|
||||
_from = from;
|
||||
_fromHash = fromHash;
|
||||
_seenMessages = new HashMap();
|
||||
}
|
||||
|
||||
public String getName() { return "Handle Source Route Reply Message"; }
|
||||
public void runJob() {
|
||||
try {
|
||||
long before = Clock.getInstance().now();
|
||||
_message.decryptHeader(KeyManager.getInstance().getPrivateKey());
|
||||
long after = Clock.getInstance().now();
|
||||
if ( (after-before) > 1000) {
|
||||
_log.warn("Took more than a second (" + (after-before) + ") to decrypt the sourceRoute header");
|
||||
} else {
|
||||
_log.debug("Took LESS than a second (" + (after-before) + ") to decrypt the sourceRoute header");
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error decrypting the source route message's header (message " + _message.getUniqueId() + ")", dfe);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Message header could not be decrypted: " + _message, getAddedBy());
|
||||
MessageHistory.getInstance().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), "Source route message header could not be decrypted");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValid()) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error validating source route message, dropping: " + _message);
|
||||
return;
|
||||
}
|
||||
|
||||
DeliveryInstructions instructions = _message.getDecryptedInstructions();
|
||||
|
||||
long now = Clock.getInstance().now();
|
||||
long expiration = _message.getDecryptedExpiration();
|
||||
// if its expiring really soon, jack the expiration 30 seconds
|
||||
if (expiration < now+10*1000)
|
||||
expiration = now + 60*1000;
|
||||
|
||||
boolean requestAck = false;
|
||||
MessageHandler.getInstance().handleMessage(instructions, _message.getMessage(), requestAck, null,
|
||||
_message.getDecryptedMessageId(), _from, _fromHash, expiration, PRIORITY);
|
||||
}
|
||||
|
||||
private boolean isValid() {
|
||||
long now = Clock.getInstance().now();
|
||||
if (_message.getDecryptedExpiration() < now) {
|
||||
if (_message.getDecryptedExpiration() < now + Router.CLOCK_FUDGE_FACTOR) {
|
||||
_log.info("Expired message received, but within our fudge factor");
|
||||
} else {
|
||||
_log.error("Source route reply message expired. Replay attack? msgId = " + _message.getDecryptedMessageId() + " expiration = " + new Date(_message.getDecryptedExpiration()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!isValidMessageId(_message.getDecryptedMessageId(), _message.getDecryptedExpiration())) {
|
||||
_log.error("Source route reply message already received! Replay attack? msgId = " + _message.getDecryptedMessageId() + " expiration = " + new Date(_message.getDecryptedExpiration()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidMessageId(long msgId, long expiration) {
|
||||
synchronized (_seenMessages) {
|
||||
if (_seenMessages.containsKey(new Long(msgId)))
|
||||
return false;
|
||||
|
||||
|
||||
_seenMessages.put(new Long(msgId), new Date(expiration));
|
||||
}
|
||||
// essentially random
|
||||
if ((msgId % 10) == 0) {
|
||||
cleanupMessages();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void cleanupMessages() {
|
||||
// this should be in its own thread perhaps, or job? and maybe _seenMessages should be
|
||||
// synced to disk?
|
||||
List toRemove = new ArrayList(32);
|
||||
long now = Clock.getInstance().now()-Router.CLOCK_FUDGE_FACTOR;
|
||||
synchronized (_seenMessages) {
|
||||
for (Iterator iter = _seenMessages.keySet().iterator(); iter.hasNext();) {
|
||||
Long id = (Long)iter.next();
|
||||
Date exp = (Date)_seenMessages.get(id);
|
||||
if (now > exp.getTime())
|
||||
toRemove.add(id);
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++)
|
||||
_seenMessages.remove(toRemove.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void dropped() {
|
||||
MessageHistory.getInstance().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), "Dropped due to overload");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.crypto.AESEngine;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.I2NPMessageException;
|
||||
import net.i2p.data.i2np.I2NPMessageHandler;
|
||||
import net.i2p.data.i2np.TunnelMessage;
|
||||
import net.i2p.data.i2np.TunnelVerificationStructure;
|
||||
import net.i2p.router.ClientManagerFacade;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.ClientMessagePool;
|
||||
import net.i2p.router.InNetMessage;
|
||||
import net.i2p.router.InNetMessagePool;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.MessageReceptionInfo;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.router.MessageValidator;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import net.i2p.stat.StatManager;
|
||||
|
||||
public class HandleTunnelMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(HandleTunnelMessageJob.class);
|
||||
private TunnelMessage _message;
|
||||
private RouterIdentity _from;
|
||||
private Hash _fromHash;
|
||||
private final static I2NPMessageHandler _handler = new I2NPMessageHandler();
|
||||
|
||||
private final static long FORWARD_TIMEOUT = 60*1000;
|
||||
private final static int FORWARD_PRIORITY = 400;
|
||||
|
||||
static {
|
||||
StatManager.getInstance().createFrequencyStat("tunnel.unknownTunnelFrequency", "How often do we receive tunnel messages for unknown tunnels?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("tunnel.gatewayMessageSize", "How large are the messages we are forwarding on as an inbound gateway?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("tunnel.relayMessageSize", "How large are the messages we are forwarding on as a participant in a tunnel?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("tunnel.endpointMessageSize", "How large are the messages we are forwarding in as an outbound endpoint?", "Tunnels", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
}
|
||||
|
||||
public HandleTunnelMessageJob(TunnelMessage msg, RouterIdentity from, Hash fromHash) {
|
||||
super();
|
||||
_message = msg;
|
||||
_from = from;
|
||||
_fromHash = fromHash;
|
||||
}
|
||||
|
||||
public String getName() { return "Handle Inbound Tunnel Message"; }
|
||||
public void runJob() {
|
||||
TunnelId id = _message.getTunnelId();
|
||||
TunnelInfo info = TunnelManagerFacade.getInstance().getTunnelInfo(id);
|
||||
|
||||
if (info == null) {
|
||||
Hash from = _fromHash;
|
||||
if (_from != null)
|
||||
from = _from.getHash();
|
||||
MessageHistory.getInstance().droppedTunnelMessage(id, from);
|
||||
_log.error("Received a message for an unknown tunnel [" + id.getTunnelId() + "], dropping it: " + _message, getAddedBy());
|
||||
StatManager.getInstance().updateFrequency("tunnel.unknownTunnelFrequency");
|
||||
return;
|
||||
}
|
||||
|
||||
info = getUs(info);
|
||||
if (info == null) {
|
||||
_log.error("We are not part of a known tunnel?? wtf! drop.", getAddedBy());
|
||||
StatManager.getInstance().updateFrequency("tunnel.unknownTunnelFrequency");
|
||||
return;
|
||||
} else {
|
||||
_log.debug("Tunnel message received for tunnel: \n" + info);
|
||||
}
|
||||
|
||||
//if ( (_message.getVerificationStructure() == null) && (info.getSigningKey() != null) ) {
|
||||
if (_message.getVerificationStructure() == null) {
|
||||
if (info.getSigningKey() != null) {
|
||||
if (info.getNextHop() != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We are the gateway to tunnel " + id.getTunnelId());
|
||||
byte data[] = _message.getData();
|
||||
I2NPMessage msg = getBody(data);
|
||||
JobQueue.getInstance().addJob(new HandleGatewayMessageJob(msg, info, data.length));
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("We are the gateway and the endpoint for tunnel " + id.getTunnelId());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Process locally");
|
||||
if (info.getDestination() != null) {
|
||||
if (!ClientManagerFacade.getInstance().isLocal(info.getDestination())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a message on a tunnel allocated to a client that has disconnected - dropping it!");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Dropping message for disconnected client: " + _message);
|
||||
|
||||
MessageHistory.getInstance().droppedOtherMessage(_message);
|
||||
MessageHistory.getInstance().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), "Disconnected client");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
I2NPMessage body = getBody(_message.getData());
|
||||
if (body != null) {
|
||||
JobQueue.getInstance().addJob(new HandleLocallyJob(body, info));
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Body is null! content of message.getData() = [" + DataHelper.toString(_message.getData()) + "]", getAddedBy());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message that failed: " + _message, getAddedBy());
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Received a message that we are not the gateway for on tunnel "
|
||||
+ id.getTunnelId() + " without a verification structure: " + _message, getAddedBy());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// participant
|
||||
TunnelVerificationStructure struct = _message.getVerificationStructure();
|
||||
boolean ok = struct.verifySignature(info.getVerificationKey().getKey());
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed tunnel verification! Spoofing / tagging attack? " + _message, getAddedBy());
|
||||
return;
|
||||
} else {
|
||||
if (info.getNextHop() != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + id.getTunnelId() + " received where we're not the gateway and there are remaining hops, so forward it on to "
|
||||
+ info.getNextHop().toBase64() + " via SendTunnelMessageJob");
|
||||
|
||||
StatManager.getInstance().addRateData("tunnel.relayMessageSize", _message.getData().length, 0);
|
||||
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(_message, info.getNextHop(), Clock.getInstance().now() + FORWARD_TIMEOUT, FORWARD_PRIORITY));
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No more hops, unwrap and follow the instructions");
|
||||
JobQueue.getInstance().addJob(new HandleEndpointJob(info));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processLocally(TunnelInfo ourPlace) {
|
||||
if (ourPlace.getEncryptionKey() == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Argh, somehow we don't have the decryption key and we have no more steps", getAddedBy());
|
||||
return;
|
||||
}
|
||||
DeliveryInstructions instructions = getInstructions(_message.getEncryptedDeliveryInstructions(), ourPlace.getEncryptionKey().getKey());
|
||||
if (instructions == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("We are the endpoint of a non-zero length tunnel and we don't have instructions. DROP.", getAddedBy());
|
||||
return;
|
||||
} else {
|
||||
I2NPMessage body = null;
|
||||
if (instructions.getEncrypted()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Body in the tunnel IS encrypted");
|
||||
body = decryptBody(_message.getData(), instructions.getEncryptionKey());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Body in the tunnel is NOT encrypted: " + instructions + "\n" + _message, new Exception("Hmmm..."));
|
||||
body = getBody(_message.getData());
|
||||
}
|
||||
|
||||
if (body == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to recover the body from the tunnel", getAddedBy());
|
||||
return;
|
||||
} else {
|
||||
JobQueue.getInstance().addJob(new ProcessBodyLocallyJob(body, instructions, ourPlace));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void honorInstructions(DeliveryInstructions instructions, I2NPMessage body) {
|
||||
StatManager.getInstance().addRateData("tunnel.endpointMessageSize", _message.getData().length, 0);
|
||||
|
||||
switch (instructions.getDeliveryMode()) {
|
||||
case DeliveryInstructions.DELIVERY_MODE_LOCAL:
|
||||
sendToLocal(body);
|
||||
break;
|
||||
case DeliveryInstructions.DELIVERY_MODE_ROUTER:
|
||||
if (Router.getInstance().getRouterInfo().getIdentity().getHash().equals(instructions.getRouter())) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Delivery instructions point at a router, but we're that router, so send to local");
|
||||
sendToLocal(body);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Delivery instructions point at a router, and we're not that router, so forward it off");
|
||||
sendToRouter(instructions.getRouter(), body);
|
||||
}
|
||||
break;
|
||||
case DeliveryInstructions.DELIVERY_MODE_TUNNEL:
|
||||
sendToTunnel(instructions.getRouter(), instructions.getTunnelId(), body);
|
||||
break;
|
||||
case DeliveryInstructions.DELIVERY_MODE_DESTINATION:
|
||||
sendToDest(instructions.getDestination(), body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToDest(Hash dest, I2NPMessage body) {
|
||||
if (body instanceof DataMessage) {
|
||||
boolean isLocal = ClientManagerFacade.getInstance().isLocal(dest);
|
||||
if (isLocal) {
|
||||
deliverMessage(null, dest, (DataMessage)body);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Delivery to remote destinations is not yet supported", getAddedBy());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Deliver something other than a DataMessage to a Destination? I don't think so.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToTunnel(Hash router, TunnelId id, I2NPMessage body) {
|
||||
// TODO: we may want to send it via a tunnel later on, but for now, direct will do.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending on to requested tunnel " + id.getTunnelId() + " on router " + router.toBase64());
|
||||
TunnelMessage msg = new TunnelMessage();
|
||||
msg.setTunnelId(id);
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
body.writeBytes(baos);
|
||||
msg.setData(baos.toByteArray());
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(msg, router, Clock.getInstance().now() + FORWARD_TIMEOUT, FORWARD_PRIORITY));
|
||||
|
||||
String bodyType = body.getClass().getName();
|
||||
MessageHistory.getInstance().wrap(bodyType, body.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId());
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error writing out the message to forward to the tunnel", dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the message to forward to the tunnel", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToRouter(Hash router, I2NPMessage body) {
|
||||
// TODO: we may want to send it via a tunnel later on, but for now, direct will do.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending on to requested router " + router.toBase64());
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(body, router, Clock.getInstance().now() + FORWARD_TIMEOUT, FORWARD_PRIORITY));
|
||||
}
|
||||
|
||||
private void sendToLocal(I2NPMessage body) {
|
||||
InNetMessage msg = new InNetMessage();
|
||||
msg.setMessage(body);
|
||||
msg.setFromRouter(_from);
|
||||
msg.setFromRouterHash(_fromHash);
|
||||
InNetMessagePool.getInstance().add(msg);
|
||||
}
|
||||
|
||||
private void deliverMessage(Destination dest, Hash destHash, DataMessage msg) {
|
||||
boolean valid = MessageValidator.getInstance().validateMessage(msg.getUniqueId(), msg.getMessageExpiration().getTime());
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate data message received [" + msg.getUniqueId() + " expiring on " + msg.getMessageExpiration() + "]");
|
||||
MessageHistory.getInstance().droppedOtherMessage(msg);
|
||||
MessageHistory.getInstance().messageProcessingError(msg.getUniqueId(), msg.getClass().getName(), "Duplicate payload");
|
||||
return;
|
||||
}
|
||||
|
||||
ClientMessage cmsg = new ClientMessage();
|
||||
|
||||
Payload payload = new Payload();
|
||||
payload.setEncryptedData(msg.getData());
|
||||
|
||||
MessageReceptionInfo info = new MessageReceptionInfo();
|
||||
info.setFromPeer(_fromHash);
|
||||
info.setFromTunnel(_message.getTunnelId());
|
||||
|
||||
cmsg.setDestination(dest);
|
||||
cmsg.setDestinationHash(destHash);
|
||||
cmsg.setPayload(payload);
|
||||
cmsg.setReceptionInfo(info);
|
||||
|
||||
MessageHistory.getInstance().receivePayloadMessage(msg.getUniqueId());
|
||||
// if the destination isn't local, the ClientMessagePool forwards it off as an OutboundClientMessageJob
|
||||
ClientMessagePool.getInstance().add(cmsg);
|
||||
}
|
||||
|
||||
private I2NPMessage getBody(byte body[]) {
|
||||
try {
|
||||
return _handler.readMessage(new ByteArrayInputStream(body));
|
||||
} catch (I2NPMessageException ime) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error parsing the message body", ime);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error reading the message body", ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private I2NPMessage decryptBody(byte encryptedMessage[], SessionKey key) {
|
||||
byte iv[] = new byte[16];
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(key.getData());
|
||||
System.arraycopy(h.getData(), 0, iv, 0, iv.length);
|
||||
byte decrypted[] = AESEngine.getInstance().safeDecrypt(encryptedMessage, key, iv);
|
||||
if (decrypted == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error decrypting the message", getAddedBy());
|
||||
return null;
|
||||
}
|
||||
return getBody(decrypted);
|
||||
}
|
||||
|
||||
private DeliveryInstructions getInstructions(byte encryptedInstructions[], SessionKey key) {
|
||||
try {
|
||||
byte iv[] = new byte[16];
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(key.getData());
|
||||
System.arraycopy(h.getData(), 0, iv, 0, iv.length);
|
||||
byte decrypted[] = AESEngine.getInstance().safeDecrypt(encryptedInstructions, key, iv);
|
||||
if (decrypted == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error decrypting the instructions", getAddedBy());
|
||||
return null;
|
||||
}
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.readBytes(new ByteArrayInputStream(decrypted));
|
||||
return instructions;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error parsing the decrypted instructions", dfe);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error reading the decrypted instructions", ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TunnelInfo getUs(TunnelInfo info) {
|
||||
Hash us = Router.getInstance().getRouterInfo().getIdentity().getHash();
|
||||
while (info != null) {
|
||||
if (us.equals(info.getThisHop()))
|
||||
return info;
|
||||
info = info.getNextHopInfo();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean validateMessage(TunnelMessage msg, TunnelInfo info) {
|
||||
TunnelVerificationStructure vstruct = _message.getVerificationStructure();
|
||||
if (vstruct == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Verification structure missing. invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( (info.getVerificationKey() == null) || (info.getVerificationKey().getKey() == null) ) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("wtf, no verification key for the tunnel? " + info, getAddedBy());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!vstruct.verifySignature(info.getVerificationKey().getKey())) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Received a tunnel message with an invalid signature!");
|
||||
// shitlist the sender?
|
||||
return false;
|
||||
}
|
||||
|
||||
// now validate the message
|
||||
Hash msgHash = SHA256Generator.getInstance().calculateHash(_message.getData());
|
||||
if (msgHash.equals(vstruct.getMessageHash())) {
|
||||
// hash matches. good.
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("validateMessage: Signed hash does not match real hash. Data has been tampered with!");
|
||||
// shitlist the sender!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void dropped() {
|
||||
MessageHistory.getInstance().messageProcessingError(_message.getUniqueId(), _message.getClass().getName(), "Dropped due to overload");
|
||||
}
|
||||
|
||||
////
|
||||
// series of subjobs for breaking this task into smaller components
|
||||
////
|
||||
|
||||
/** we're the gateway, lets deal */
|
||||
private class HandleGatewayMessageJob extends JobImpl {
|
||||
private I2NPMessage _body;
|
||||
private int _length;
|
||||
private TunnelInfo _info;
|
||||
|
||||
public HandleGatewayMessageJob(I2NPMessage body, TunnelInfo tunnel, int length) {
|
||||
_body = body;
|
||||
_length = length;
|
||||
_info = tunnel;
|
||||
}
|
||||
public void runJob() {
|
||||
if (_body != null) {
|
||||
StatManager.getInstance().addRateData("tunnel.gatewayMessageSize", _length, 0);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + _info.getTunnelId() + " received at the gateway (us), and since its > 0 length, forward the "
|
||||
+ _body.getClass().getName() + " message on to " + _info.getNextHop().toBase64() + " via SendTunnelMessageJob");
|
||||
JobQueue.getInstance().addJob(new SendTunnelMessageJob(_body, _info.getTunnelId(), null, null, null, null, FORWARD_TIMEOUT, FORWARD_PRIORITY));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Body of the message for the tunnel could not be parsed");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message that failed: " + _message);
|
||||
}
|
||||
}
|
||||
public String getName() { return "Handle Tunnel Message (gateway)"; }
|
||||
}
|
||||
|
||||
/** zero hop tunnel */
|
||||
private class HandleLocallyJob extends JobImpl {
|
||||
private I2NPMessage _body;
|
||||
private TunnelInfo _info;
|
||||
|
||||
public HandleLocallyJob(I2NPMessage body, TunnelInfo tunnel) {
|
||||
_body = body;
|
||||
_info = tunnel;
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
if (_body instanceof DataMessage) {
|
||||
// we know where to send it and its something a client can handle, so lets send 'er to the client
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Deliver the message to a local client, as its a payload message and we know the destination");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + _info.getTunnelId() + " received at the gateway (us), but its a 0 length tunnel and the message is a DataMessage, so send it to "
|
||||
+ _info.getDestination().calculateHash().toBase64());
|
||||
deliverMessage(_info.getDestination(), null, (DataMessage)_body);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + _info.getTunnelId() +
|
||||
" received at the gateway (us), but its a 0 length tunnel though it is a " + _body.getClass().getName() + ", so process it locally");
|
||||
InNetMessage msg = new InNetMessage();
|
||||
msg.setFromRouter(_from);
|
||||
msg.setFromRouterHash(_fromHash);
|
||||
msg.setMessage(_body);
|
||||
InNetMessagePool.getInstance().add(msg);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Message added to Inbound network pool for local processing: " + _message);
|
||||
}
|
||||
}
|
||||
public String getName() { return "Handle Tunnel Message (0 hop)"; }
|
||||
}
|
||||
|
||||
/** we're the endpoint of the inbound tunnel */
|
||||
private class HandleEndpointJob extends JobImpl {
|
||||
private TunnelInfo _info;
|
||||
public HandleEndpointJob(TunnelInfo info) {
|
||||
_info = info;
|
||||
}
|
||||
public void runJob() {
|
||||
processLocally(_info);
|
||||
}
|
||||
public String getName() { return "Handle Tunnel Message (inbound endpoint)"; }
|
||||
}
|
||||
|
||||
/** endpoint of outbound 1+ hop tunnel with instructions */
|
||||
private class ProcessBodyLocallyJob extends JobImpl {
|
||||
private I2NPMessage _body;
|
||||
private TunnelInfo _ourPlace;
|
||||
private DeliveryInstructions _instructions;
|
||||
public ProcessBodyLocallyJob(I2NPMessage body, DeliveryInstructions instructions, TunnelInfo ourPlace) {
|
||||
_body = body;
|
||||
_instructions = instructions;
|
||||
_ourPlace = ourPlace;
|
||||
}
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Body read: " + _body);
|
||||
if ( (_ourPlace.getDestination() != null) && (_body instanceof DataMessage) ) {
|
||||
// we know where to send it and its something a client can handle, so lets send 'er to the client
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Deliver the message to a local client, as its a payload message and we know the destination");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + _ourPlace.getTunnelId().getTunnelId()
|
||||
+ " received where we're the endpoint containing a DataMessage message, so deliver it to "
|
||||
+ _ourPlace.getDestination().calculateHash().toBase64());
|
||||
deliverMessage(_ourPlace.getDestination(), null, (DataMessage)_body);
|
||||
return;
|
||||
} else {
|
||||
// Honor the delivery instructions
|
||||
//TunnelMonitor.endpointReceive(ourPlace.getTunnelId(), body.getClass().getName(), instructions, ourPlace.getDestination());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + _ourPlace.getTunnelId().getTunnelId() + " received where we're the endpoint containing a "
|
||||
+ _body.getClass().getName() + " message, so honor the delivery instructions: " + _instructions.toString());
|
||||
honorInstructions(_instructions, _body);
|
||||
return;
|
||||
}
|
||||
}
|
||||
public String getName() { return "Handle Tunnel Message (outbound endpoint)"; }
|
||||
}
|
||||
}
|
||||
179
router/java/src/net/i2p/router/message/MessageHandler.java
Normal file
179
router/java/src/net/i2p/router/message/MessageHandler.java
Normal file
@@ -0,0 +1,179 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
import net.i2p.data.i2np.TunnelMessage;
|
||||
import net.i2p.router.ClientManagerFacade;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.ClientMessagePool;
|
||||
import net.i2p.router.InNetMessage;
|
||||
import net.i2p.router.InNetMessagePool;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.router.MessageReceptionInfo;
|
||||
import net.i2p.router.MessageValidator;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Implement the inbound message processing logic to forward based on delivery instructions and
|
||||
* send acks.
|
||||
*
|
||||
*/
|
||||
class MessageHandler {
|
||||
private final static Log _log = new Log(MessageHandler.class);
|
||||
private static MessageHandler _instance = new MessageHandler();
|
||||
public static MessageHandler getInstance() { return _instance; }
|
||||
|
||||
public void handleMessage(DeliveryInstructions instructions, I2NPMessage message, boolean requestAck, SourceRouteBlock replyBlock,
|
||||
long replyId, RouterIdentity from, Hash fromHash, long expiration, int priority) {
|
||||
switch (instructions.getDeliveryMode()) {
|
||||
case DeliveryInstructions.DELIVERY_MODE_LOCAL:
|
||||
_log.debug("Instructions for LOCAL DELIVERY");
|
||||
if (message.getType() == DataMessage.MESSAGE_TYPE) {
|
||||
handleLocalDestination(instructions, message, fromHash);
|
||||
} else {
|
||||
handleLocalRouter(message, from, fromHash, replyBlock, requestAck);
|
||||
}
|
||||
break;
|
||||
case DeliveryInstructions.DELIVERY_MODE_ROUTER:
|
||||
_log.debug("Instructions for ROUTER DELIVERY to " + instructions.getRouter().toBase64());
|
||||
if (Router.getInstance().getRouterInfo().getIdentity().getHash().equals(instructions.getRouter())) {
|
||||
handleLocalRouter(message, from, fromHash, replyBlock, requestAck);
|
||||
} else {
|
||||
handleRemoteRouter(message, instructions, expiration, priority);
|
||||
}
|
||||
break;
|
||||
case DeliveryInstructions.DELIVERY_MODE_DESTINATION:
|
||||
_log.debug("Instructions for DESTINATION DELIVERY to " + instructions.getDestination().toBase64());
|
||||
if (ClientManagerFacade.getInstance().isLocal(instructions.getDestination())) {
|
||||
handleLocalDestination(instructions, message, fromHash);
|
||||
} else {
|
||||
_log.error("Instructions requests forwarding on to a non-local destination. Not yet supported");
|
||||
}
|
||||
break;
|
||||
case DeliveryInstructions.DELIVERY_MODE_TUNNEL:
|
||||
_log.debug("Instructions for TUNNEL DELIVERY to" + instructions.getTunnelId().getTunnelId() + " on " + instructions.getRouter().toBase64());
|
||||
handleTunnel(instructions, expiration, message, priority);
|
||||
break;
|
||||
default:
|
||||
_log.error("Message has instructions that are not yet implemented: mode = " + instructions.getDeliveryMode());
|
||||
}
|
||||
|
||||
if (requestAck) {
|
||||
_log.debug("SEND ACK REQUESTED");
|
||||
sendAck(replyBlock, replyId);
|
||||
} else {
|
||||
_log.debug("No ack requested");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAck(SourceRouteBlock replyBlock, long replyId) {
|
||||
_log.info("Queueing up ack job via reply block " + replyBlock);
|
||||
Job ackJob = new SendMessageAckJob(replyBlock, replyId);
|
||||
JobQueue.getInstance().addJob(ackJob);
|
||||
}
|
||||
|
||||
private void handleLocalRouter(I2NPMessage message, RouterIdentity from, Hash fromHash, SourceRouteBlock replyBlock, boolean ackUsed) {
|
||||
_log.info("Handle " + message.getClass().getName() + " to a local router - toss it on the inbound network pool");
|
||||
InNetMessage msg = new InNetMessage();
|
||||
msg.setFromRouter(from);
|
||||
msg.setFromRouterHash(fromHash);
|
||||
msg.setMessage(message);
|
||||
if (!ackUsed)
|
||||
msg.setReplyBlock(replyBlock);
|
||||
InNetMessagePool.getInstance().add(msg);
|
||||
}
|
||||
|
||||
private void handleRemoteRouter(I2NPMessage message, DeliveryInstructions instructions, long expiration, int priority) {
|
||||
|
||||
boolean valid = MessageValidator.getInstance().validateMessage(message.getUniqueId(), message.getMessageExpiration().getTime());
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate / expired message received to remote router [" + message.getUniqueId() + " expiring on " + message.getMessageExpiration() + "]");
|
||||
MessageHistory.getInstance().droppedOtherMessage(message);
|
||||
MessageHistory.getInstance().messageProcessingError(message.getUniqueId(), message.getClass().getName(), "Duplicate/expired to remote router");
|
||||
return;
|
||||
}
|
||||
|
||||
_log.info("Handle " + message.getClass().getName() + " to a remote router " + instructions.getRouter().toBase64() + " - fire a SendMessageDirectJob");
|
||||
SendMessageDirectJob j = new SendMessageDirectJob(message, instructions.getRouter(), expiration, priority);
|
||||
JobQueue.getInstance().addJob(j);
|
||||
}
|
||||
|
||||
private void handleTunnel(DeliveryInstructions instructions, long expiration, I2NPMessage message, int priority) {
|
||||
Hash to = instructions.getRouter();
|
||||
long timeoutMs = expiration - Clock.getInstance().now();
|
||||
TunnelId tunnelId = instructions.getTunnelId();
|
||||
|
||||
if (!Router.getInstance().getRouterInfo().getIdentity().getHash().equals(to)) {
|
||||
// don't validate locally targetted tunnel messages, since then we'd have to tweak
|
||||
// around message validation thats already in place for SendMessageDirectJob
|
||||
boolean valid = MessageValidator.getInstance().validateMessage(message.getUniqueId(), message.getMessageExpiration().getTime());
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate / expired tunnel message received [" + message.getUniqueId() + " expiring on " + message.getMessageExpiration() + "]");
|
||||
MessageHistory.getInstance().droppedOtherMessage(message);
|
||||
MessageHistory.getInstance().messageProcessingError(message.getUniqueId(), message.getClass().getName(), "Duplicate/expired");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_log.info("Handle " + message.getClass().getName() + " to send to remote tunnel " + tunnelId.getTunnelId() + " on router " + to.toBase64());
|
||||
TunnelMessage msg = new TunnelMessage();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
message.writeBytes(baos);
|
||||
msg.setData(baos.toByteArray());
|
||||
msg.setTunnelId(tunnelId);
|
||||
_log.debug("Placing message of type " + message.getClass().getName() + " into the new tunnel message bound for " + tunnelId.getTunnelId() + " on " + to.toBase64());
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(msg, to, expiration, priority));
|
||||
|
||||
String bodyType = message.getClass().getName();
|
||||
MessageHistory.getInstance().wrap(bodyType, message.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId());
|
||||
} catch (Exception e) {
|
||||
_log.warn("Unable to forward on according to the instructions to the remote tunnel", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalDestination(DeliveryInstructions instructions, I2NPMessage message, Hash fromHash) {
|
||||
boolean valid = MessageValidator.getInstance().validateMessage(message.getUniqueId(), message.getMessageExpiration().getTime());
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate / expired client message received [" + message.getUniqueId() + " expiring on " + message.getMessageExpiration() + "]");
|
||||
MessageHistory.getInstance().droppedOtherMessage(message);
|
||||
MessageHistory.getInstance().messageProcessingError(message.getUniqueId(), message.getClass().getName(), "Duplicate/expired client message");
|
||||
return;
|
||||
}
|
||||
|
||||
_log.debug("Handle " + message.getClass().getName() + " to a local destination - build a ClientMessage and pool it");
|
||||
ClientMessage msg = new ClientMessage();
|
||||
msg.setDestinationHash(instructions.getDestination());
|
||||
Payload payload = new Payload();
|
||||
payload.setEncryptedData(((DataMessage)message).getData());
|
||||
msg.setPayload(payload);
|
||||
MessageReceptionInfo info = new MessageReceptionInfo();
|
||||
info.setFromPeer(fromHash);
|
||||
msg.setReceptionInfo(info);
|
||||
MessageHistory.getInstance().receivePayloadMessage(message.getUniqueId());
|
||||
ClientMessagePool.getInstance().add(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,595 @@
|
||||
package net.i2p.router.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.ClientManagerFacade;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.TunnelSelectionCriteria;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
import net.i2p.stat.StatManager;
|
||||
|
||||
/**
|
||||
* Send a client message, taking into consideration the fact that there may be
|
||||
* multiple inbound tunnels that the target provides. This job sends it to one
|
||||
* of them and if it doesnt get a confirmation within 15 seconds (SEND_TIMEOUT_MS),
|
||||
* it tries the next, continuing on until a confirmation is received, the full
|
||||
* timeout has been reached (60 seconds, or the ms defined in the client's or
|
||||
* router's "clientMessageTimeout" option).
|
||||
*
|
||||
* After sending through all of the leases without success, if there's still
|
||||
* time left it fails the leaseSet itself, does a new search for that leaseSet,
|
||||
* and continues sending down any newly found leases.
|
||||
*
|
||||
*/
|
||||
public class OutboundClientMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(OutboundClientMessageJob.class);
|
||||
private OutboundClientMessageStatus _status;
|
||||
private NextStepJob _nextStep;
|
||||
private LookupLeaseSetFailedJob _lookupLeaseSetFailed;
|
||||
private long _overallExpiration;
|
||||
|
||||
/**
|
||||
* final timeout (in milliseconds) that the outbound message will fail in.
|
||||
* This can be overridden in the router.config or the client's session config
|
||||
* (the client's session config takes precedence)
|
||||
*/
|
||||
public final static String OVERALL_TIMEOUT_MS_PARAM = "clientMessageTimeout";
|
||||
private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000;
|
||||
|
||||
/** how long for each send do we allow before going on to the next? */
|
||||
private final static long SEND_TIMEOUT_MS = 10*1000;
|
||||
/** priority of messages, that might get honored some day... */
|
||||
private final static int SEND_PRIORITY = 500;
|
||||
|
||||
/** dont search for the lease more than 3 times */
|
||||
private final static int MAX_LEASE_LOOKUPS = 3;
|
||||
|
||||
static {
|
||||
StatManager.getInstance().createFrequencyStat("client.sendMessageFailFrequency", "How often does a client fail to send a message?", "Client Messages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("client.sendMessageSize", "How large are messages sent by the client?", "Client Messages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
StatManager.getInstance().createRateStat("client.sendAttemptAverage", "How many different tunnels do we have to try when sending a client message?", "Client Messages", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the sucker
|
||||
*/
|
||||
public OutboundClientMessageJob(ClientMessage msg) {
|
||||
super();
|
||||
|
||||
long timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
|
||||
|
||||
String param = msg.getSenderConfig().getOptions().getProperty(OVERALL_TIMEOUT_MS_PARAM);
|
||||
if (param == null)
|
||||
param = Router.getInstance().getConfigSetting(OVERALL_TIMEOUT_MS_PARAM);
|
||||
if (param != null) {
|
||||
try {
|
||||
timeoutMs = Long.parseLong(param);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid client message timeout specified [" + param + "], defaulting to " + OVERALL_TIMEOUT_MS_DEFAULT, nfe);
|
||||
timeoutMs = OVERALL_TIMEOUT_MS_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
_overallExpiration = timeoutMs + Clock.getInstance().now();
|
||||
_status = new OutboundClientMessageStatus(msg);
|
||||
_nextStep = new NextStepJob();
|
||||
_lookupLeaseSetFailed = new LookupLeaseSetFailedJob();
|
||||
}
|
||||
|
||||
public String getName() { return "Outbound client message"; }
|
||||
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send outbound client message job beginning");
|
||||
buildClove();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Clove built");
|
||||
Hash to = _status.getTo().calculateHash();
|
||||
long timeoutMs = _overallExpiration - Clock.getInstance().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send outbound client message - sending off leaseSet lookup job");
|
||||
_status.incrementLookups();
|
||||
NetworkDatabaseFacade.getInstance().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue on sending through the next tunnel
|
||||
*/
|
||||
private void sendNext() {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("sendNext() called with " + _status.getNumSent() + " already sent");
|
||||
}
|
||||
|
||||
if (_status.getSuccess()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sendNext() - already successful!");
|
||||
return;
|
||||
}
|
||||
if (_status.getFailure()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("sendNext() - already failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
long now = Clock.getInstance().now();
|
||||
if (now >= _overallExpiration) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sendNext() - Expired");
|
||||
dieFatal();
|
||||
return;
|
||||
}
|
||||
|
||||
Lease nextLease = getNextLease();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send outbound client message - next lease found for [" + _status.getTo().calculateHash().toBase64() + "] - " + nextLease);
|
||||
|
||||
if (nextLease == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No more leases, and we still haven't heard back from the peer, refetching the leaseSet to try again");
|
||||
_status.setLeaseSet(null);
|
||||
long remainingMs = _overallExpiration - Clock.getInstance().now();
|
||||
if (_status.getNumLookups() < MAX_LEASE_LOOKUPS) {
|
||||
_status.incrementLookups();
|
||||
Hash to = _status.getMessage().getDestination().calculateHash();
|
||||
_status.clearAlreadySent();
|
||||
NetworkDatabaseFacade.getInstance().fail(to);
|
||||
NetworkDatabaseFacade.getInstance().lookupLeaseSet(to, _nextStep, _lookupLeaseSetFailed, remainingMs);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sendNext() - max # lease lookups exceeded! " + _status.getNumLookups());
|
||||
dieFatal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JobQueue.getInstance().addJob(new SendJob(nextLease));
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the next lease that we should try sending through, or null if there
|
||||
* are no remaining leases available (or there weren't any in the first place...).
|
||||
* This implements the logic to determine which lease should be next by picking a
|
||||
* random one that has been failing the least (e.g. if there are 3 leases in the leaseSet
|
||||
* and one has failed, the other two are randomly chosen as the 'next')
|
||||
*
|
||||
*/
|
||||
private Lease getNextLease() {
|
||||
LeaseSet ls = _status.getLeaseSet();
|
||||
if (ls == null) {
|
||||
ls = NetworkDatabaseFacade.getInstance().lookupLeaseSetLocally(_status.getTo().calculateHash());
|
||||
if (ls == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Lookup locally didn't find the leaseSet");
|
||||
return null;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Lookup locally DID find the leaseSet");
|
||||
}
|
||||
_status.setLeaseSet(ls);
|
||||
}
|
||||
long now = Clock.getInstance().now();
|
||||
|
||||
// get the possible leases
|
||||
List leases = new ArrayList(4);
|
||||
for (int i = 0; i < ls.getLeaseCount(); i++) {
|
||||
Lease lease = ls.getLease(i);
|
||||
if (lease.isExpired(Router.CLOCK_FUDGE_FACTOR)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("getNextLease() - expired lease! - " + lease);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_status.alreadySent(lease.getRouterIdentity().getHash(), lease.getTunnelId())) {
|
||||
leases.add(lease);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getNextLease() - skipping lease we've already sent it down - " + lease);
|
||||
}
|
||||
}
|
||||
|
||||
// randomize the ordering (so leases with equal # of failures per next sort are randomly ordered)
|
||||
Collections.shuffle(leases);
|
||||
|
||||
// ordered by lease number of failures
|
||||
TreeMap orderedLeases = new TreeMap();
|
||||
for (Iterator iter = leases.iterator(); iter.hasNext(); ) {
|
||||
Lease lease = (Lease)iter.next();
|
||||
long id = lease.getNumFailure();
|
||||
while (orderedLeases.containsKey(new Long(id)))
|
||||
id++;
|
||||
orderedLeases.put(new Long(id), lease);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getNextLease() - ranking lease we havent sent it down as " + id);
|
||||
}
|
||||
|
||||
if (orderedLeases.size() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No leases in the ordered set found! all = " + leases.size());
|
||||
return null;
|
||||
} else {
|
||||
return (Lease)orderedLeases.get(orderedLeases.firstKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message to the specified tunnel by creating a new garlic message containing
|
||||
* the (already created) payload clove as well as a new delivery status message. This garlic
|
||||
* message is sent out one of our tunnels, destined for the lease (tunnel+router) specified, and the delivery
|
||||
* status message is targetting one of our free inbound tunnels as well. We use a new
|
||||
* reply selector to keep an eye out for that delivery status message's token
|
||||
*
|
||||
*/
|
||||
private void send(Lease lease) {
|
||||
// send it as a garlic with a DeliveryStatusMessage clove and a message selector w/ successJob on reply
|
||||
long token = RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
|
||||
PublicKey key = _status.getLeaseSet().getEncryptionKey();
|
||||
SessionKey sessKey = new SessionKey();
|
||||
Set tags = new HashSet();
|
||||
GarlicMessage msg = OutboundClientMessageJobHelper.createGarlicMessage(token, _overallExpiration, key, _status.getClove(), _status.getTo(), sessKey, tags, true);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("send(lease) - token expected " + token);
|
||||
|
||||
_status.sent(lease.getRouterIdentity().getHash(), lease.getTunnelId());
|
||||
|
||||
SendSuccessJob onReply = new SendSuccessJob(lease, sessKey, tags);
|
||||
SendTimeoutJob onFail = new SendTimeoutJob(lease);
|
||||
ReplySelector selector = new ReplySelector(token);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Placing GarlicMessage into the new tunnel message bound for " + lease.getTunnelId() + " on " + lease.getRouterIdentity().getHash().toBase64());
|
||||
|
||||
TunnelId outTunnelId = selectOutboundTunnel();
|
||||
if (outTunnelId != null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending tunnel message out " + outTunnelId + " to " + lease.getTunnelId() + " on " + lease.getRouterIdentity().getHash().toBase64());
|
||||
SendTunnelMessageJob j = new SendTunnelMessageJob(msg, outTunnelId, lease.getRouterIdentity().getHash(), lease.getTunnelId(), null, onReply, onFail, selector, SEND_TIMEOUT_MS, SEND_PRIORITY);
|
||||
JobQueue.getInstance().addJob(j);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Could not find any outbound tunnels to send the payload through... wtf?");
|
||||
JobQueue.getInstance().addJob(onFail);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick an arbitrary outbound tunnel to send the message through, or null if
|
||||
* there aren't any around
|
||||
*
|
||||
*/
|
||||
private TunnelId selectOutboundTunnel() {
|
||||
TunnelSelectionCriteria crit = new TunnelSelectionCriteria();
|
||||
crit.setMaximumTunnelsRequired(1);
|
||||
crit.setMinimumTunnelsRequired(1);
|
||||
List tunnelIds = TunnelManagerFacade.getInstance().selectOutboundTunnelIds(crit);
|
||||
if (tunnelIds.size() <= 0)
|
||||
return null;
|
||||
else
|
||||
return (TunnelId)tunnelIds.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* give up the ghost, this message just aint going through. tell the client to fuck off.
|
||||
*
|
||||
* this is safe to call multiple times (only tells the client once)
|
||||
*/
|
||||
private void dieFatal() {
|
||||
if (_status.getSuccess()) return;
|
||||
boolean alreadyFailed = _status.failed();
|
||||
long sendTime = Clock.getInstance().now() - _status.getStart();
|
||||
ClientMessage msg = _status.getMessage();
|
||||
if (alreadyFailed) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("dieFatal() - already failed sending " + msg.getMessageId()+ ", no need to do it again", new Exception("Duplicate death?"));
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to send the message " + msg.getMessageId() + " after " + _status.getNumSent() + " sends and " + _status.getNumLookups() + " lookups (and " + sendTime + "ms)", new Exception("Message send failure"));
|
||||
}
|
||||
|
||||
MessageHistory.getInstance().sendPayloadMessage(msg.getMessageId().getMessageId(), false, sendTime);
|
||||
ClientManagerFacade.getInstance().messageDeliveryStatusUpdate(msg.getFromDestination(), msg.getMessageId(), false);
|
||||
StatManager.getInstance().updateFrequency("client.sendMessageFailFrequency");
|
||||
StatManager.getInstance().addRateData("client.sendAttemptAverage", _status.getNumSent(), sendTime);
|
||||
}
|
||||
|
||||
/** build the payload clove that will be used for all of the messages, placing the clove in the status structure */
|
||||
private void buildClove() {
|
||||
PayloadGarlicConfig clove = new PayloadGarlicConfig();
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_DESTINATION);
|
||||
instructions.setDestination(_status.getTo().calculateHash());
|
||||
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setDelaySeconds(0);
|
||||
instructions.setEncrypted(false);
|
||||
|
||||
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
clove.setDeliveryInstructions(instructions);
|
||||
clove.setExpiration(_overallExpiration);
|
||||
clove.setId(RandomSource.getInstance().nextInt(Integer.MAX_VALUE));
|
||||
|
||||
DataMessage msg = new DataMessage();
|
||||
msg.setData(_status.getMessage().getPayload().getEncryptedData());
|
||||
|
||||
clove.setPayload(msg);
|
||||
clove.setRecipientPublicKey(null);
|
||||
clove.setRequestAck(false);
|
||||
|
||||
_status.setClove(clove);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Built payload clove with id " + clove.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Good ol' fashioned struct with the send status
|
||||
*
|
||||
*/
|
||||
private class OutboundClientMessageStatus {
|
||||
private ClientMessage _msg;
|
||||
private PayloadGarlicConfig _clove;
|
||||
private LeaseSet _leaseSet;
|
||||
private Set _sent;
|
||||
private int _numLookups;
|
||||
private boolean _success;
|
||||
private boolean _failure;
|
||||
private long _start;
|
||||
private int _previousSent;
|
||||
|
||||
public OutboundClientMessageStatus(ClientMessage msg) {
|
||||
_msg = msg;
|
||||
_clove = null;
|
||||
_leaseSet = null;
|
||||
_sent = new HashSet(4);
|
||||
_success = false;
|
||||
_failure = false;
|
||||
_numLookups = 0;
|
||||
_previousSent = 0;
|
||||
_start = Clock.getInstance().now();
|
||||
}
|
||||
|
||||
/** raw payload */
|
||||
public Payload getPayload() { return _msg.getPayload(); }
|
||||
/** clove, if we've built it */
|
||||
public PayloadGarlicConfig getClove() { return _clove; }
|
||||
public void setClove(PayloadGarlicConfig clove) { _clove = clove; }
|
||||
public ClientMessage getMessage() { return _msg; }
|
||||
/** date we started the process on */
|
||||
public long getStart() { return _start; }
|
||||
|
||||
public int getNumLookups() { return _numLookups; }
|
||||
public void incrementLookups() { _numLookups++; }
|
||||
public void clearAlreadySent() {
|
||||
synchronized (_sent) {
|
||||
_previousSent += _sent.size();
|
||||
_sent.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** who sent the message? */
|
||||
public Destination getFrom() { return _msg.getFromDestination(); }
|
||||
/** who is the message going to? */
|
||||
public Destination getTo() { return _msg.getDestination(); }
|
||||
/** what is the target's current leaseSet (or null if we don't know yet) */
|
||||
public LeaseSet getLeaseSet() { return _leaseSet; }
|
||||
public void setLeaseSet(LeaseSet ls) { _leaseSet = ls; }
|
||||
/** have we already sent the message down this tunnel? */
|
||||
public boolean alreadySent(Hash gateway, TunnelId tunnelId) {
|
||||
Tunnel t = new Tunnel(gateway, tunnelId);
|
||||
synchronized (_sent) {
|
||||
return _sent.contains(t);
|
||||
}
|
||||
}
|
||||
public void sent(Hash gateway, TunnelId tunnelId) {
|
||||
Tunnel t = new Tunnel(gateway, tunnelId);
|
||||
synchronized (_sent) {
|
||||
_sent.add(t);
|
||||
}
|
||||
}
|
||||
/** how many messages have we sent through various leases? */
|
||||
public int getNumSent() {
|
||||
synchronized (_sent) {
|
||||
return _sent.size() + _previousSent;
|
||||
}
|
||||
}
|
||||
/** did we totally fail? */
|
||||
public boolean getFailure() { return _failure; }
|
||||
/** we failed. returns true if we had already failed before */
|
||||
public boolean failed() {
|
||||
boolean already = _failure;
|
||||
_failure = true;
|
||||
return already;
|
||||
}
|
||||
/** have we totally succeeded? */
|
||||
public boolean getSuccess() { return _success; }
|
||||
/** we succeeded. returns true if we had already succeeded before */
|
||||
public boolean success() {
|
||||
boolean already = _success;
|
||||
_success = true;
|
||||
return already;
|
||||
}
|
||||
|
||||
/** represent a unique tunnel at any given time */
|
||||
private class Tunnel {
|
||||
private Hash _gateway;
|
||||
private TunnelId _tunnel;
|
||||
|
||||
public Tunnel(Hash tunnelGateway, TunnelId tunnel) {
|
||||
_gateway = tunnelGateway;
|
||||
_tunnel = tunnel;
|
||||
}
|
||||
|
||||
public Hash getGateway() { return _gateway; }
|
||||
public TunnelId getTunnel() { return _tunnel; }
|
||||
|
||||
public int hashCode() {
|
||||
int rv = 0;
|
||||
if (_gateway != null)
|
||||
rv += _gateway.hashCode();
|
||||
if (_tunnel != null)
|
||||
rv += 7*_tunnel.getTunnelId();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
if (o.getClass() != Tunnel.class) return false;
|
||||
Tunnel t = (Tunnel)o;
|
||||
return (getTunnel() == t.getTunnel()) &&
|
||||
getGateway().equals(t.getGateway());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep an eye out for any of the delivery status message tokens that have been
|
||||
* sent down the various tunnels to deliver this message
|
||||
*
|
||||
*/
|
||||
private class ReplySelector implements MessageSelector {
|
||||
private long _pendingToken;
|
||||
public ReplySelector(long token) {
|
||||
_pendingToken = token;
|
||||
}
|
||||
|
||||
public boolean continueMatching() { return false; }
|
||||
public long getExpiration() { return _overallExpiration; }
|
||||
|
||||
public boolean isMatch(I2NPMessage inMsg) {
|
||||
if (inMsg.getType() == DeliveryStatusMessage.MESSAGE_TYPE) {
|
||||
return _pendingToken == ((DeliveryStatusMessage)inMsg).getMessageId();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** queued by the db lookup success and the send timeout to get us to try the next lease */
|
||||
private class NextStepJob extends JobImpl {
|
||||
public String getName() { return "Process next step for outbound client message"; }
|
||||
public void runJob() { sendNext(); }
|
||||
}
|
||||
|
||||
/** we couldn't even find the leaseSet, fuck off */
|
||||
private class LookupLeaseSetFailedJob extends JobImpl {
|
||||
public String getName() { return "Lookup for outbound client message failed"; }
|
||||
public void runJob() { dieFatal(); }
|
||||
}
|
||||
|
||||
/** send a message to a lease */
|
||||
private class SendJob extends JobImpl {
|
||||
private Lease _lease;
|
||||
public SendJob(Lease lease) { _lease = lease; }
|
||||
public String getName() { return "Send outbound client message through the lease"; }
|
||||
public void runJob() { send(_lease); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after we get a confirmation that the message was delivered safely
|
||||
* (hoo-ray!)
|
||||
*
|
||||
*/
|
||||
private class SendSuccessJob extends JobImpl implements ReplyJob {
|
||||
private Lease _lease;
|
||||
private SessionKey _key;
|
||||
private Set _tags;
|
||||
|
||||
/**
|
||||
* Create a new success job that will be fired when the message encrypted with
|
||||
* the given session key and bearing the specified tags are confirmed delivered.
|
||||
*
|
||||
*/
|
||||
public SendSuccessJob(Lease lease, SessionKey key, Set tags) {
|
||||
_lease = lease;
|
||||
_key = key;
|
||||
_tags = tags;
|
||||
}
|
||||
|
||||
public String getName() { return "Send client message successful to a lease"; }
|
||||
public void runJob() {
|
||||
long sendTime = Clock.getInstance().now() - _status.getStart();
|
||||
boolean alreadySuccessful = _status.success();
|
||||
MessageId msgId = _status.getMessage().getMessageId();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("SUCCESS! Message delivered completely for message " + msgId + " after " + sendTime + "ms [for " + _status.getMessage().getMessageId() + "]");
|
||||
|
||||
if ( (_key != null) && (_tags != null) && (_tags.size() > 0) ) {
|
||||
SessionKeyManager.getInstance().tagsDelivered(_status.getLeaseSet().getEncryptionKey(), _key, _tags);
|
||||
}
|
||||
|
||||
if (alreadySuccessful) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Success is a duplicate for " + _status.getMessage().getMessageId() + ", dont notify again...");
|
||||
return;
|
||||
}
|
||||
long dataMsgId = _status.getClove().getId();
|
||||
MessageHistory.getInstance().sendPayloadMessage(dataMsgId, true, sendTime);
|
||||
ClientManagerFacade.getInstance().messageDeliveryStatusUpdate(_status.getFrom(), msgId, true);
|
||||
_lease.setNumSuccess(_lease.getNumSuccess()+1);
|
||||
|
||||
StatManager.getInstance().addRateData("client.sendMessageSize", _status.getMessage().getPayload().getSize(), sendTime);
|
||||
StatManager.getInstance().addRateData("client.sendAttemptAverage", _status.getNumSent(), sendTime);
|
||||
}
|
||||
|
||||
public void setMessage(I2NPMessage msg) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after the basic timeout for sending through the given tunnel has been reached.
|
||||
* We'll accept successes later, but won't expect them
|
||||
*
|
||||
*/
|
||||
private class SendTimeoutJob extends JobImpl {
|
||||
private Lease _lease;
|
||||
|
||||
public SendTimeoutJob(Lease lease) {
|
||||
_lease = lease;
|
||||
}
|
||||
|
||||
public String getName() { return "Send client message timed out through a lease"; }
|
||||
public void runJob() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Soft timeout through the lease " + _lease);
|
||||
_lease.setNumFailure(_lease.getNumFailure()+1);
|
||||
sendNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.TunnelSelectionCriteria;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
/**
|
||||
* Handle a particular client message that is destined for a remote destination.
|
||||
*
|
||||
*/
|
||||
class OutboundClientMessageJobHelper {
|
||||
private static Log _log = new Log(OutboundClientMessageJobHelper.class);
|
||||
|
||||
/**
|
||||
* Build a garlic message that will be delivered to the router on which the target is located.
|
||||
* Inside the message are two cloves: one containing the payload with instructions for
|
||||
* delivery to the (now local) destination, and the other containing a DeliveryStatusMessage with
|
||||
* instructions for delivery to an inbound tunnel of this router.
|
||||
*
|
||||
* How the DeliveryStatusMessage is wrapped can vary - it can be simply sent to a tunnel (as above),
|
||||
* wrapped in a GarlicMessage and source routed a few hops before being tunneled, source routed the
|
||||
* entire way back, or not wrapped at all - in which case the payload clove contains a SourceRouteBlock
|
||||
* and a request for a reply.
|
||||
*
|
||||
* For now, its just a tunneled DeliveryStatusMessage
|
||||
*
|
||||
*/
|
||||
static GarlicMessage createGarlicMessage(long replyToken, long expiration, PublicKey recipientPK, Payload data, Destination dest, SessionKey wrappedKey, Set wrappedTags, boolean requireAck) {
|
||||
PayloadGarlicConfig dataClove = buildDataClove(data, dest, expiration);
|
||||
return createGarlicMessage(replyToken, expiration, recipientPK, dataClove, dest, wrappedKey, wrappedTags, requireAck);
|
||||
}
|
||||
/**
|
||||
* Allow the app to specify the data clove directly, which enables OutboundClientMessage to resend the
|
||||
* same payload (including expiration and unique id) in different garlics (down different tunnels)
|
||||
*
|
||||
*/
|
||||
static GarlicMessage createGarlicMessage(long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Destination dest, SessionKey wrappedKey, Set wrappedTags, boolean requireAck) {
|
||||
GarlicConfig config = createGarlicConfig(replyToken, expiration, recipientPK, dataClove, dest, requireAck);
|
||||
GarlicMessage msg = GarlicMessageBuilder.buildMessage(config, wrappedKey, wrappedTags);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private static GarlicConfig createGarlicConfig(long replyToken, long expiration, PublicKey recipientPK, PayloadGarlicConfig dataClove, Destination dest, boolean requireAck) {
|
||||
_log.debug("Reply token: " + replyToken);
|
||||
GarlicConfig config = new GarlicConfig();
|
||||
|
||||
config.addClove(dataClove);
|
||||
|
||||
if (requireAck) {
|
||||
PayloadGarlicConfig ackClove = buildAckClove(replyToken, expiration);
|
||||
config.addClove(ackClove);
|
||||
}
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setDelaySeconds(0);
|
||||
instructions.setEncrypted(false);
|
||||
instructions.setEncryptionKey(null);
|
||||
instructions.setRouter(null);
|
||||
instructions.setTunnelId(null);
|
||||
|
||||
config.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
config.setDeliveryInstructions(instructions);
|
||||
config.setId(RandomSource.getInstance().nextInt(Integer.MAX_VALUE));
|
||||
config.setExpiration(expiration+2*Router.CLOCK_FUDGE_FACTOR);
|
||||
config.setRecipientPublicKey(recipientPK);
|
||||
config.setRequestAck(false);
|
||||
|
||||
_log.info("Creating garlic config to be encrypted to " + recipientPK + " for destination " + dest.calculateHash().toBase64());
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a clove that sends a DeliveryStatusMessage to us
|
||||
*/
|
||||
private static PayloadGarlicConfig buildAckClove(long replyToken, long expiration) {
|
||||
PayloadGarlicConfig ackClove = new PayloadGarlicConfig();
|
||||
|
||||
Hash replyToTunnelRouter = null; // inbound tunnel gateway
|
||||
TunnelId replyToTunnelId = null; // tunnel id on that gateway
|
||||
|
||||
TunnelSelectionCriteria criteria = new TunnelSelectionCriteria();
|
||||
criteria.setMaximumTunnelsRequired(1);
|
||||
criteria.setMinimumTunnelsRequired(1);
|
||||
criteria.setReliabilityPriority(50); // arbitrary. fixme
|
||||
criteria.setAnonymityPriority(50); // arbitrary. fixme
|
||||
criteria.setLatencyPriority(50); // arbitrary. fixme
|
||||
List tunnelIds = TunnelManagerFacade.getInstance().selectInboundTunnelIds(criteria);
|
||||
if (tunnelIds.size() <= 0) {
|
||||
_log.error("No inbound tunnels to receive an ack through!?");
|
||||
return null;
|
||||
}
|
||||
replyToTunnelId = (TunnelId)tunnelIds.get(0);
|
||||
TunnelInfo info = TunnelManagerFacade.getInstance().getTunnelInfo(replyToTunnelId);
|
||||
replyToTunnelRouter = info.getThisHop(); // info is the chain, and the first hop is the gateway
|
||||
_log.debug("Ack for the data message will come back along tunnel " + replyToTunnelId + ":\n" + info);
|
||||
|
||||
DeliveryInstructions ackInstructions = new DeliveryInstructions();
|
||||
ackInstructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_TUNNEL);
|
||||
ackInstructions.setRouter(replyToTunnelRouter);
|
||||
ackInstructions.setTunnelId(replyToTunnelId);
|
||||
ackInstructions.setDelayRequested(false);
|
||||
ackInstructions.setDelaySeconds(0);
|
||||
ackInstructions.setEncrypted(false);
|
||||
|
||||
DeliveryStatusMessage msg = new DeliveryStatusMessage();
|
||||
msg.setArrival(new Date(Clock.getInstance().now()));
|
||||
msg.setMessageId(replyToken);
|
||||
_log.debug("Delivery status message key: " + replyToken + " arrival: " + msg.getArrival());
|
||||
|
||||
ackClove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
ackClove.setDeliveryInstructions(ackInstructions);
|
||||
ackClove.setExpiration(expiration);
|
||||
ackClove.setId(RandomSource.getInstance().nextInt(Integer.MAX_VALUE));
|
||||
ackClove.setPayload(msg);
|
||||
ackClove.setRecipient(Router.getInstance().getRouterInfo());
|
||||
ackClove.setRequestAck(false);
|
||||
|
||||
_log.debug("Delivery status message is targetting us [" + ackClove.getRecipient().getIdentity().getHash().toBase64() + "] via tunnel " + replyToTunnelId.getTunnelId() + " on " + replyToTunnelRouter.toBase64());
|
||||
|
||||
return ackClove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a clove that sends the payload to the destination
|
||||
*/
|
||||
static PayloadGarlicConfig buildDataClove(Payload data, Destination dest, long expiration) {
|
||||
PayloadGarlicConfig clove = new PayloadGarlicConfig();
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_DESTINATION);
|
||||
instructions.setDestination(dest.calculateHash());
|
||||
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setDelaySeconds(0);
|
||||
instructions.setEncrypted(false);
|
||||
|
||||
clove.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
clove.setDeliveryInstructions(instructions);
|
||||
clove.setExpiration(expiration);
|
||||
clove.setId(RandomSource.getInstance().nextInt(Integer.MAX_VALUE));
|
||||
DataMessage msg = new DataMessage();
|
||||
msg.setData(data.getEncryptedData());
|
||||
clove.setPayload(msg);
|
||||
clove.setRecipientPublicKey(null);
|
||||
clove.setRequestAck(false);
|
||||
|
||||
return clove;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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 net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
/**
|
||||
* Garlic config containing an I2NP message
|
||||
*
|
||||
*/
|
||||
public class PayloadGarlicConfig extends GarlicConfig {
|
||||
private I2NPMessage _payload;
|
||||
|
||||
public PayloadGarlicConfig() {
|
||||
super();
|
||||
_payload = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the I2NP message to be sent - if this is set, no other cloves can be included
|
||||
* in this block
|
||||
*/
|
||||
public void setPayload(I2NPMessage message) {
|
||||
_payload = message;
|
||||
if (message != null)
|
||||
clearCloves();
|
||||
}
|
||||
public I2NPMessage getPayload() { return _payload; }
|
||||
|
||||
protected String getSubData() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("<payloadMessage>").append(_payload).append("</payloadMessage>");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
122
router/java/src/net/i2p/router/message/SendGarlicJob.java
Normal file
122
router/java/src/net/i2p/router/message/SendGarlicJob.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.OutNetMessagePool;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Build a garlic message from config, encrypt it, and enqueue it for delivery.
|
||||
*
|
||||
*/
|
||||
public class SendGarlicJob extends JobImpl {
|
||||
private final static Log _log = new Log(SendGarlicJob.class);
|
||||
//private RouterInfo _target;
|
||||
private GarlicConfig _config;
|
||||
private Job _onSend;
|
||||
private Job _onSendFailed;
|
||||
private ReplyJob _onReply;
|
||||
private Job _onReplyFailed;
|
||||
private long _timeoutMs;
|
||||
private int _priority;
|
||||
private MessageSelector _replySelector;
|
||||
private GarlicMessage _message;
|
||||
private SessionKey _wrappedKey;
|
||||
private Set _wrappedTags;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param config ???
|
||||
* @param onSend after the ping is successful
|
||||
* @param onSendFailed after the ping fails or times out
|
||||
* @param onReply ???
|
||||
* @param onReplyFailed ???
|
||||
* @param timeoutMs how long to wait before timing out
|
||||
* @param priority how high priority to send this test
|
||||
* @param replySelector ???
|
||||
*/
|
||||
public SendGarlicJob(GarlicConfig config, Job onSend, Job onSendFailed, ReplyJob onReply, Job onReplyFailed, long timeoutMs, int priority, MessageSelector replySelector) {
|
||||
this(config, onSend, onSendFailed, onReply, onReplyFailed, timeoutMs, priority, replySelector, new SessionKey(), new HashSet());
|
||||
}
|
||||
public SendGarlicJob(GarlicConfig config, Job onSend, Job onSendFailed, ReplyJob onReply, Job onReplyFailed, long timeoutMs, int priority, MessageSelector replySelector, SessionKey wrappedKey, Set wrappedTags) {
|
||||
super();
|
||||
if (config == null) throw new IllegalArgumentException("No config specified");
|
||||
if (config.getRecipient() == null) throw new IllegalArgumentException("No recipient in the config");
|
||||
//_target = target;
|
||||
_config = config;
|
||||
_onSend = onSend;
|
||||
_onSendFailed = onSendFailed;
|
||||
_onReply = onReply;
|
||||
_onReplyFailed = onReplyFailed;
|
||||
_timeoutMs = timeoutMs;
|
||||
_priority = priority;
|
||||
_replySelector = replySelector;
|
||||
_message = null;
|
||||
_wrappedKey = wrappedKey;
|
||||
_wrappedTags = wrappedTags;
|
||||
}
|
||||
|
||||
public String getName() { return "Build Garlic Message"; }
|
||||
|
||||
public void runJob() {
|
||||
long before = Clock.getInstance().now();
|
||||
_message = GarlicMessageBuilder.buildMessage(_config, _wrappedKey, _wrappedTags);
|
||||
long after = Clock.getInstance().now();
|
||||
if ( (after - before) > 1000) {
|
||||
_log.warn("Building the garlic took too long [" + (after-before)+" ms]", getAddedBy());
|
||||
} else {
|
||||
_log.debug("Building the garlic was fast! " + (after - before) + " ms");
|
||||
}
|
||||
JobQueue.getInstance().addJob(new SendJob());
|
||||
}
|
||||
|
||||
private class SendJob extends JobImpl {
|
||||
public String getName() { return "Send Built Garlic Message"; }
|
||||
public void runJob() {
|
||||
if (_config.getRecipient() != null)
|
||||
_log.info("sending garlic to recipient " + _config.getRecipient().getIdentity().getHash().toBase64());
|
||||
else
|
||||
_log.info("sending garlic to public key " + _config.getRecipientPublicKey());
|
||||
sendGarlic();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendGarlic() {
|
||||
OutNetMessage msg = new OutNetMessage();
|
||||
long when = _message.getMessageExpiration().getTime() + Router.CLOCK_FUDGE_FACTOR;
|
||||
msg.setExpiration(when);
|
||||
msg.setMessage(_message);
|
||||
msg.setOnFailedReplyJob(_onReplyFailed);
|
||||
msg.setOnFailedSendJob(_onSendFailed);
|
||||
msg.setOnReplyJob(_onReply);
|
||||
msg.setOnSendJob(_onSend);
|
||||
msg.setPriority(_priority);
|
||||
msg.setReplySelector(_replySelector);
|
||||
msg.setTarget(_config.getRecipient());
|
||||
//_log.info("Sending garlic message to [" + _config.getRecipient() + "] encrypted with " + _config.getRecipientPublicKey() + " or " + _config.getRecipient().getIdentity().getPublicKey());
|
||||
//_log.debug("Garlic config data:\n" + _config);
|
||||
//msg.setTarget(_target);
|
||||
OutNetMessagePool.getInstance().add(msg);
|
||||
_log.debug("Garlic message added to outbound network message pool");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.util.Date;
|
||||
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Send a DeliveryStatusMessage to the location specified in the source route block
|
||||
* acknowledging the ackId given. This uses the simplest technique (don't garlic, and
|
||||
* send direct to where the SourceRouteBlock requested), but it could instead garlic it
|
||||
* and send it via a tunnel or garlic route it additionally)
|
||||
*
|
||||
*/
|
||||
public class SendMessageAckJob extends JobImpl {
|
||||
private SourceRouteBlock _block;
|
||||
private long _ackId;
|
||||
|
||||
public final static int ACK_PRIORITY = 100;
|
||||
|
||||
public SendMessageAckJob(SourceRouteBlock block, long ackId) {
|
||||
super();
|
||||
_block = block;
|
||||
_ackId = ackId;
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
JobQueue.getInstance().addJob(new SendReplyMessageJob(_block, createAckMessage(), ACK_PRIORITY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create whatever should be delivered to the intermediary hop so that
|
||||
* a DeliveryStatusMessage gets to the intended recipient.
|
||||
*
|
||||
* Currently this doesn't garlic encrypt the DeliveryStatusMessage with
|
||||
* the block's tag and sessionKey, but it could.
|
||||
*
|
||||
*/
|
||||
protected I2NPMessage createAckMessage() {
|
||||
DeliveryStatusMessage statusMessage = new DeliveryStatusMessage();
|
||||
statusMessage.setArrival(new Date(Clock.getInstance().now()));
|
||||
statusMessage.setMessageId(_ackId);
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
public String getName() { return "Send Message Ack"; }
|
||||
}
|
||||
159
router/java/src/net/i2p/router/message/SendMessageDirectJob.java
Normal file
159
router/java/src/net/i2p/router/message/SendMessageDirectJob.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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 net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.OutNetMessagePool;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
import net.i2p.router.InNetMessage;
|
||||
import net.i2p.router.InNetMessagePool;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class SendMessageDirectJob extends JobImpl {
|
||||
private final static Log _log = new Log(SendMessageDirectJob.class);
|
||||
private I2NPMessage _message;
|
||||
private Hash _targetHash;
|
||||
private RouterInfo _router;
|
||||
private long _expiration;
|
||||
private int _priority;
|
||||
private Job _onSend;
|
||||
private ReplyJob _onSuccess;
|
||||
private Job _onFail;
|
||||
private MessageSelector _selector;
|
||||
private boolean _alreadySearched;
|
||||
private boolean _sent;
|
||||
|
||||
private final static long DEFAULT_TIMEOUT = 60*1000;
|
||||
|
||||
public SendMessageDirectJob(I2NPMessage message, Hash toPeer, long expiration, int priority) {
|
||||
this(message, toPeer, null, null, null, null, expiration, priority);
|
||||
}
|
||||
public SendMessageDirectJob(I2NPMessage message, Hash toPeer, int priority) {
|
||||
this(message, toPeer, DEFAULT_TIMEOUT+Clock.getInstance().now(), priority);
|
||||
}
|
||||
public SendMessageDirectJob(I2NPMessage message, Hash toPeer, ReplyJob onSuccess, Job onFail, MessageSelector selector, long expiration, int priority) {
|
||||
this(message, toPeer, null, onSuccess, onFail, selector, expiration, priority);
|
||||
}
|
||||
public SendMessageDirectJob(I2NPMessage message, Hash toPeer, Job onSend, ReplyJob onSuccess, Job onFail, MessageSelector selector, long expiration, int priority) {
|
||||
super();
|
||||
_message = message;
|
||||
_targetHash = toPeer;
|
||||
_router = null;
|
||||
_expiration = expiration;
|
||||
_priority = priority;
|
||||
_alreadySearched = false;
|
||||
_onSend = onSend;
|
||||
_onSuccess = onSuccess;
|
||||
_onFail = onFail;
|
||||
_selector = selector;
|
||||
if (message == null)
|
||||
throw new IllegalArgumentException("Attempt to send a null message");
|
||||
if (_targetHash == null)
|
||||
throw new IllegalArgumentException("Attempt to send a message to a null peer");
|
||||
_sent = false;
|
||||
long remaining = expiration - Clock.getInstance().now();
|
||||
if (remaining < 50*1000) {
|
||||
_log.info("Sending message to expire in " + remaining + "ms containing " + message.getUniqueId() + " (a " + message.getClass().getName() + ")", new Exception("SendDirect from"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return "Send Message Direct"; }
|
||||
public void runJob() {
|
||||
long now = Clock.getInstance().now();
|
||||
if (_expiration == 0)
|
||||
_expiration = now + DEFAULT_TIMEOUT;
|
||||
|
||||
if (_expiration - 30*1000 < now) {
|
||||
_log.info("Soon to expire sendDirect of " + _message.getClass().getName() + " [expiring in " + (_expiration-now) + "]", getAddedBy());
|
||||
}
|
||||
|
||||
if (_expiration < now) {
|
||||
_log.warn("Timed out sending message " + _message + " directly (expiration = " + new Date(_expiration) + ") to " + _targetHash.toBase64(), getAddedBy());
|
||||
return;
|
||||
}
|
||||
if (_router != null) {
|
||||
_log.debug("Router specified, sending");
|
||||
send();
|
||||
} else {
|
||||
_router = NetworkDatabaseFacade.getInstance().lookupRouterInfoLocally(_targetHash);
|
||||
if (_router != null) {
|
||||
_log.debug("Router not specified but lookup found it");
|
||||
send();
|
||||
} else {
|
||||
if (!_alreadySearched) {
|
||||
_log.debug("Router not specified, so we're looking for it...");
|
||||
NetworkDatabaseFacade.getInstance().lookupRouterInfo(_targetHash, this, this, _expiration - Clock.getInstance().now());
|
||||
_alreadySearched = true;
|
||||
} else {
|
||||
_log.error("Unable to find the router to send to: " + _targetHash + " message: " + _message, getAddedBy());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void send() {
|
||||
if (_sent) { _log.warn("Not resending!", new Exception("blah")); return; }
|
||||
_sent = true;
|
||||
if (Router.getInstance().getRouterInfo().getIdentity().getHash().equals(_router.getIdentity().getHash())) {
|
||||
if (_selector != null) {
|
||||
OutNetMessage outM = new OutNetMessage();
|
||||
outM.setExpiration(_expiration);
|
||||
outM.setMessage(_message);
|
||||
outM.setOnFailedReplyJob(_onFail);
|
||||
outM.setOnFailedSendJob(_onFail);
|
||||
outM.setOnReplyJob(_onSuccess);
|
||||
outM.setOnSendJob(_onSend);
|
||||
outM.setPriority(_priority);
|
||||
outM.setReplySelector(_selector);
|
||||
outM.setTarget(_router);
|
||||
OutboundMessageRegistry.getInstance().registerPending(outM);
|
||||
}
|
||||
|
||||
if (_onSend != null)
|
||||
JobQueue.getInstance().addJob(_onSend);
|
||||
|
||||
InNetMessage msg = new InNetMessage();
|
||||
msg.setFromRouter(_router.getIdentity());
|
||||
msg.setMessage(_message);
|
||||
InNetMessagePool.getInstance().add(msg);
|
||||
|
||||
_log.debug("Adding " + _message.getClass().getName() + " to inbound message pool as it was destined for ourselves");
|
||||
//_log.debug("debug", _createdBy);
|
||||
} else {
|
||||
OutNetMessage msg = new OutNetMessage();
|
||||
msg.setExpiration(_expiration);
|
||||
msg.setMessage(_message);
|
||||
msg.setOnFailedReplyJob(_onFail);
|
||||
msg.setOnFailedSendJob(_onFail);
|
||||
msg.setOnReplyJob(_onSuccess);
|
||||
msg.setOnSendJob(_onSend);
|
||||
msg.setPriority(_priority);
|
||||
msg.setReplySelector(_selector);
|
||||
msg.setTarget(_router);
|
||||
OutNetMessagePool.getInstance().add(msg);
|
||||
_log.debug("Adding " + _message.getClass().getName() + " to outbound message pool targeting " + _router.getIdentity().getHash().toBase64());
|
||||
//_log.debug("Message pooled: " + _message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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 net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.SourceRouteBlock;
|
||||
import net.i2p.data.i2np.SourceRouteReplyMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Send a SourceRouteReplyMessage to the location specified in the source route block.
|
||||
* This uses the simplest technique (don't garlic, and send direct to where the
|
||||
* SourceRouteBlock requested), but it could instead garlic it and send it via a
|
||||
* tunnel or garlic route it additionally)
|
||||
*
|
||||
*/
|
||||
public class SendReplyMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(SendReplyMessageJob.class);
|
||||
private SourceRouteBlock _block;
|
||||
private I2NPMessage _message;
|
||||
private int _priority;
|
||||
|
||||
public SendReplyMessageJob(SourceRouteBlock block, I2NPMessage message, int priority) {
|
||||
super();
|
||||
_block = block;
|
||||
_message = message;
|
||||
_priority = priority;
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
SourceRouteReplyMessage msg = new SourceRouteReplyMessage();
|
||||
msg.setMessage(_message);
|
||||
msg.setEncryptedHeader(_block.getData());
|
||||
msg.setMessageExpiration(_message.getMessageExpiration());
|
||||
|
||||
send(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message on its way.<p />
|
||||
*
|
||||
* This could garlic route the message to the _block.getRouter, or it could
|
||||
* send it there via a tunnel, or it could just send it direct.<p />
|
||||
*
|
||||
* For simplicity, its currently going direct.
|
||||
*
|
||||
*/
|
||||
protected void send(I2NPMessage msg) {
|
||||
_log.info("Sending reply with " + _message.getClass().getName() + " in a sourceRouteeplyMessage to " + _block.getRouter().toBase64());
|
||||
SendMessageDirectJob j = new SendMessageDirectJob(msg, _block.getRouter(), _priority);
|
||||
JobQueue.getInstance().addJob(j);
|
||||
}
|
||||
|
||||
public String getName() { return "Send Reply Message"; }
|
||||
}
|
||||
426
router/java/src/net/i2p/router/message/SendTunnelMessageJob.java
Normal file
426
router/java/src/net/i2p/router/message/SendTunnelMessageJob.java
Normal file
@@ -0,0 +1,426 @@
|
||||
package net.i2p.router.message;
|
||||
/*
|
||||
* 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.crypto.AESEngine;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataStructure;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DataMessage;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.TunnelMessage;
|
||||
import net.i2p.data.i2np.TunnelVerificationStructure;
|
||||
import net.i2p.router.ClientMessage;
|
||||
import net.i2p.router.ClientMessagePool;
|
||||
import net.i2p.router.InNetMessage;
|
||||
import net.i2p.router.InNetMessagePool;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.JobQueue;
|
||||
import net.i2p.router.MessageHistory;
|
||||
import net.i2p.router.MessageReceptionInfo;
|
||||
import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.MessageValidator;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Send a message down a tunnel that we are the gateway for
|
||||
*
|
||||
*/
|
||||
public class SendTunnelMessageJob extends JobImpl {
|
||||
private final static Log _log = new Log(SendTunnelMessageJob.class);
|
||||
private I2NPMessage _message;
|
||||
private Hash _destRouter;
|
||||
private TunnelId _tunnelId;
|
||||
private TunnelId _targetTunnelId;
|
||||
private Job _onSend;
|
||||
private ReplyJob _onReply;
|
||||
private Job _onFailure;
|
||||
private MessageSelector _selector;
|
||||
private long _timeout;
|
||||
private long _expiration;
|
||||
private int _priority;
|
||||
|
||||
public SendTunnelMessageJob(I2NPMessage msg, TunnelId tunnelId, Job onSend, ReplyJob onReply, Job onFailure, MessageSelector selector, long timeoutMs, int priority) {
|
||||
this(msg, tunnelId, null, null, onSend, onReply, onFailure, selector, timeoutMs, priority);
|
||||
}
|
||||
|
||||
public SendTunnelMessageJob(I2NPMessage msg, TunnelId tunnelId, Hash targetRouter, TunnelId targetTunnelId, Job onSend, ReplyJob onReply, Job onFailure, MessageSelector selector, long timeoutMs, int priority) {
|
||||
super();
|
||||
if (msg == null)
|
||||
throw new IllegalArgumentException("wtf, null message? sod off");
|
||||
_message = msg;
|
||||
_destRouter = targetRouter;
|
||||
_tunnelId = tunnelId;
|
||||
_targetTunnelId = targetTunnelId;
|
||||
_onSend = onSend;
|
||||
_onReply = onReply;
|
||||
_onFailure = onFailure;
|
||||
_selector = selector;
|
||||
_timeout = timeoutMs;
|
||||
_priority = priority;
|
||||
|
||||
if (timeoutMs < 50*1000) {
|
||||
_log.info("Sending tunnel message to expire in " + timeoutMs + "ms containing " + msg.getUniqueId() + " (a " + msg.getClass().getName() + ")", new Exception("SendTunnel from"));
|
||||
}
|
||||
//_log.info("Send tunnel message " + msg.getClass().getName() + " to " + _destRouter + " over " + _tunnelId + " targetting tunnel " + _targetTunnelId, new Exception("SendTunnel from"));
|
||||
_expiration = Clock.getInstance().now() + timeoutMs;
|
||||
}
|
||||
|
||||
public void runJob() {
|
||||
TunnelInfo info = TunnelManagerFacade.getInstance().getTunnelInfo(_tunnelId);
|
||||
if (info == null) {
|
||||
_log.debug("Message for unknown tunnel [" + _tunnelId + "] received, forward to " + _destRouter);
|
||||
if ( (_tunnelId == null) || (_destRouter == null) ) {
|
||||
_log.error("Someone br0ke us. where is this message supposed to go again?", getAddedBy());
|
||||
return;
|
||||
}
|
||||
TunnelMessage msg = new TunnelMessage();
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
_message.writeBytes(baos);
|
||||
msg.setData(baos.toByteArray());
|
||||
msg.setTunnelId(_tunnelId);
|
||||
msg.setMessageExpiration(new Date(_expiration));
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(msg, _destRouter, _onSend, _onReply, _onFailure, _selector, _expiration, _priority));
|
||||
|
||||
String bodyType = _message.getClass().getName();
|
||||
MessageHistory.getInstance().wrap(bodyType, _message.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the tunnel message to send to the tunnel", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error writing out the tunnel message to send to the tunnel", dfe);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEndpoint(info)) {
|
||||
_log.info("Tunnel message where we're both the gateway and the endpoint - honor instructions");
|
||||
honorInstructions(info);
|
||||
return;
|
||||
} else if (isGateway(info)) {
|
||||
handleAsGateway(info);
|
||||
return;
|
||||
} else {
|
||||
handleAsParticipant(info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAsGateway(TunnelInfo info) {
|
||||
// since we are the gateway, we don't need to verify the data structures
|
||||
TunnelInfo us = getUs(info);
|
||||
if (us == null) {
|
||||
_log.error("We are not participating in this /known/ tunnel - was the router reset?");
|
||||
if (_onFailure != null)
|
||||
JobQueue.getInstance().addJob(_onFailure);
|
||||
} else {
|
||||
// we're the gateway, so sign, encrypt, and forward to info.getNextHop()
|
||||
TunnelMessage msg = prepareMessage(info);
|
||||
if (msg == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("wtf, unable to prepare a tunnel message to the next hop, when we're the gateway and hops remain? tunnel: " + info);
|
||||
if (_onFailure != null)
|
||||
JobQueue.getInstance().addJob(_onFailure);
|
||||
return;
|
||||
}
|
||||
_log.debug("Tunnel message created: " + msg + " out of encrypted message: " + _message);
|
||||
long now = Clock.getInstance().now();
|
||||
if (_expiration < now + 15*1000) {
|
||||
_log.warn("Adding a tunnel message that will expire shortly [" + new Date(_expiration) + "]", getAddedBy());
|
||||
}
|
||||
msg.setMessageExpiration(new Date(_expiration));
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(msg, info.getNextHop(), _onSend, _onReply, _onFailure, _selector, _expiration, _priority));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAsParticipant(TunnelInfo info) {
|
||||
// SendTunnelMessageJob shouldn't be used for participants!
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("SendTunnelMessageJob for a participant... ", getAddedBy());
|
||||
|
||||
if (!(_message instanceof TunnelMessage)) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot inject non-tunnel messages as a participant!" + _message, getAddedBy());
|
||||
if (_onFailure != null)
|
||||
JobQueue.getInstance().addJob(_onFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
TunnelMessage msg = (TunnelMessage)_message;
|
||||
|
||||
TunnelVerificationStructure struct = msg.getVerificationStructure();
|
||||
if ( (info.getVerificationKey() == null) || (info.getVerificationKey().getKey() == null) ) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("No verification key for the participant? tunnel: " + info, getAddedBy());
|
||||
if (_onFailure != null)
|
||||
JobQueue.getInstance().addJob(_onFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean ok = struct.verifySignature(info.getVerificationKey().getKey());
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed tunnel verification! Spoofing / tagging attack? " + _message, getAddedBy());
|
||||
if (_onFailure != null)
|
||||
JobQueue.getInstance().addJob(_onFailure);
|
||||
return;
|
||||
} else {
|
||||
if (info.getNextHop() != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message for tunnel " + info.getTunnelId().getTunnelId() + " received where we're not the gateway and there are remaining hops, so forward it on to "
|
||||
+ info.getNextHop().toBase64() + " via SendMessageDirectJob");
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(msg, info.getNextHop(), _onSend, null, _onFailure, null, _message.getMessageExpiration().getTime(), _priority));
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Should not be reached - participant, but no more hops?!");
|
||||
if (_onFailure != null)
|
||||
JobQueue.getInstance().addJob(_onFailure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** find our place in the tunnel */
|
||||
private TunnelInfo getUs(TunnelInfo info) {
|
||||
Hash us = Router.getInstance().getRouterInfo().getIdentity().getHash();
|
||||
TunnelInfo lastUs = null;
|
||||
while (info != null) {
|
||||
if (us.equals(info.getThisHop()))
|
||||
lastUs = info;
|
||||
info = info.getNextHopInfo();
|
||||
}
|
||||
return lastUs;
|
||||
}
|
||||
|
||||
/** are we the endpoint for the tunnel? */
|
||||
private boolean isEndpoint(TunnelInfo info) {
|
||||
TunnelInfo us = getUs(info);
|
||||
if (us == null) return false;
|
||||
return (us.getNextHop() == null);
|
||||
}
|
||||
|
||||
/** are we the gateway for the tunnel? */
|
||||
private boolean isGateway(TunnelInfo info) {
|
||||
TunnelInfo us = getUs(info);
|
||||
if (us == null) return false;
|
||||
return (us.getSigningKey() != null); // only the gateway can sign
|
||||
}
|
||||
|
||||
private TunnelMessage prepareMessage(TunnelInfo info) {
|
||||
TunnelMessage msg = new TunnelMessage();
|
||||
|
||||
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
|
||||
|
||||
DeliveryInstructions instructions = new DeliveryInstructions();
|
||||
instructions.setDelayRequested(false);
|
||||
instructions.setEncrypted(true);
|
||||
instructions.setEncryptionKey(key);
|
||||
|
||||
// if we aren't told where to send it, have it be processed locally at the endpoint
|
||||
// but if we are, have the endpoint forward it appropriately.
|
||||
// note that this algorithm does not currently support instructing the endpoint to send to a Destination
|
||||
if (_destRouter != null) {
|
||||
instructions.setRouter(_destRouter);
|
||||
if (_targetTunnelId != null) {
|
||||
_log.debug("Instructions target tunnel " + _targetTunnelId + " on router " + _destRouter.calculateHash());
|
||||
instructions.setTunnelId(_targetTunnelId);
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_TUNNEL);
|
||||
} else {
|
||||
_log.debug("Instructions target router " + _destRouter.toBase64());
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_ROUTER);
|
||||
}
|
||||
} else {
|
||||
if (_message instanceof DataMessage) {
|
||||
_log.debug("Instructions are for local message delivery at the endpoint with a DataMessage to be sent to a Destination");
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
|
||||
} else {
|
||||
_log.debug("Instructions are for local delivery at the endpoint targetting the now-local router");
|
||||
instructions.setDeliveryMode(DeliveryInstructions.DELIVERY_MODE_LOCAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (info == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Tunnel info is null to send message " + _message);
|
||||
return null;
|
||||
} else if ( (info.getEncryptionKey() == null) || (info.getEncryptionKey().getKey() == null) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Tunnel encryption key is null when we're the gateway?! info: " + info);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte encryptedInstructions[] = encrypt(instructions, info.getEncryptionKey().getKey(), 512);
|
||||
byte encryptedMessage[] = encrypt(_message, key, 1024);
|
||||
TunnelVerificationStructure verification = createVerificationStructure(encryptedMessage, info);
|
||||
|
||||
String bodyType = _message.getClass().getName();
|
||||
MessageHistory.getInstance().wrap(bodyType, _message.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId());
|
||||
|
||||
_log.debug("Tunnel message prepared: instructions = " + instructions);
|
||||
|
||||
msg.setData(encryptedMessage);
|
||||
msg.setEncryptedDeliveryInstructions(encryptedInstructions);
|
||||
msg.setTunnelId(_tunnelId);
|
||||
msg.setVerificationStructure(verification);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private TunnelVerificationStructure createVerificationStructure(byte encryptedMessage[], TunnelInfo info) {
|
||||
TunnelVerificationStructure struct = new TunnelVerificationStructure();
|
||||
struct.setMessageHash(SHA256Generator.getInstance().calculateHash(encryptedMessage));
|
||||
struct.sign(info.getSigningKey().getKey());
|
||||
return struct;
|
||||
}
|
||||
|
||||
private byte[] encrypt(DataStructure struct, SessionKey key, int paddedSize) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize);
|
||||
struct.writeBytes(baos);
|
||||
|
||||
byte iv[] = new byte[16];
|
||||
Hash h = SHA256Generator.getInstance().calculateHash(key.getData());
|
||||
System.arraycopy(h.getData(), 0, iv, 0, iv.length);
|
||||
return AESEngine.getInstance().safeEncrypt(baos.toByteArray(), key, iv, paddedSize);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out data to encrypt", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error formatting data to encrypt", dfe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void honorInstructions(TunnelInfo info) {
|
||||
if (_selector != null)
|
||||
createFakeOutNetMessage();
|
||||
|
||||
if (_onSend != null) {
|
||||
_log.debug("Firing onSend as we're honoring the instructions");
|
||||
JobQueue.getInstance().addJob(_onSend);
|
||||
}
|
||||
|
||||
// since we are the gateway, we don't need to decrypt the delivery instructions or the payload
|
||||
|
||||
RouterIdentity ident = Router.getInstance().getRouterInfo().getIdentity();
|
||||
|
||||
if (_destRouter != null) {
|
||||
I2NPMessage msg = null;
|
||||
if (_targetTunnelId != null) {
|
||||
_log.debug("Forward " + _message.getClass().getName() + " message off to remote tunnel " + _targetTunnelId.getTunnelId() + " on router " + _destRouter.toBase64());
|
||||
TunnelMessage tmsg = new TunnelMessage();
|
||||
tmsg.setEncryptedDeliveryInstructions(null);
|
||||
tmsg.setTunnelId(_targetTunnelId);
|
||||
tmsg.setVerificationStructure(null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
_message.writeBytes(baos);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the message to be forwarded...??", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error writing message to be forwarded...???", dfe);
|
||||
}
|
||||
tmsg.setData(baos.toByteArray());
|
||||
msg = tmsg;
|
||||
} else {
|
||||
_log.debug("Forward " + _message.getClass().getName() + " message off to remote router " + _destRouter.toBase64());
|
||||
msg = _message;
|
||||
}
|
||||
long now = Clock.getInstance().now();
|
||||
//if (_expiration < now) {
|
||||
_expiration = now + Router.CLOCK_FUDGE_FACTOR;
|
||||
//_log.info("Fudging the message send so it expires in the fudge factor...");
|
||||
//}
|
||||
|
||||
if (_expiration - 30*1000 < now) {
|
||||
_log.error("Why are we trying to send a " + _message.getClass().getName() + " message with " + (_expiration-now) + "ms left?", getAddedBy());
|
||||
}
|
||||
|
||||
String bodyType = _message.getClass().getName();
|
||||
MessageHistory.getInstance().wrap(bodyType, _message.getUniqueId(), TunnelMessage.class.getName(), msg.getUniqueId());
|
||||
|
||||
// don't specify a selector, since createFakeOutNetMessage already does that
|
||||
JobQueue.getInstance().addJob(new SendMessageDirectJob(msg, _destRouter, _onSend, _onReply, _onFailure, null, _expiration, _priority));
|
||||
} else {
|
||||
if ( (info.getDestination() == null) || !(_message instanceof DataMessage) ) {
|
||||
// its a network message targeting us...
|
||||
_log.debug("Destination is null or its not a DataMessage - pass it off to the InNetMessagePool");
|
||||
InNetMessage msg = new InNetMessage();
|
||||
msg.setFromRouter(ident);
|
||||
msg.setFromRouterHash(ident.getHash());
|
||||
msg.setMessage(_message);
|
||||
msg.setReplyBlock(null);
|
||||
InNetMessagePool.getInstance().add(msg);
|
||||
} else {
|
||||
_log.debug("Destination is not null and it is a DataMessage - pop it into the ClientMessagePool");
|
||||
DataMessage msg = (DataMessage)_message;
|
||||
boolean valid = MessageValidator.getInstance().validateMessage(msg.getUniqueId(), msg.getMessageExpiration().getTime());
|
||||
if (!valid) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate data message received [" + msg.getUniqueId() + " expiring on " + msg.getMessageExpiration() + "]");
|
||||
MessageHistory.getInstance().droppedOtherMessage(msg);
|
||||
MessageHistory.getInstance().messageProcessingError(msg.getUniqueId(), msg.getClass().getName(), "Duplicate");
|
||||
return;
|
||||
}
|
||||
|
||||
Payload payload = new Payload();
|
||||
payload.setEncryptedData(msg.getData());
|
||||
|
||||
MessageReceptionInfo receptionInfo = new MessageReceptionInfo();
|
||||
receptionInfo.setFromPeer(ident.getHash());
|
||||
receptionInfo.setFromTunnel(_tunnelId);
|
||||
|
||||
ClientMessage clientMessage = new ClientMessage();
|
||||
clientMessage.setDestination(info.getDestination());
|
||||
clientMessage.setPayload(payload);
|
||||
clientMessage.setReceptionInfo(receptionInfo);
|
||||
ClientMessagePool.getInstance().add(clientMessage);
|
||||
MessageHistory.getInstance().receivePayloadMessage(msg.getUniqueId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createFakeOutNetMessage() {
|
||||
// now we create a fake outNetMessage to go onto the registry so we can select
|
||||
_log.debug("Registering a fake outNetMessage for the message tunneled locally since we have a selector");
|
||||
OutNetMessage outM = new OutNetMessage();
|
||||
outM.setExpiration(_expiration);
|
||||
outM.setMessage(_message);
|
||||
outM.setOnFailedReplyJob(_onFailure);
|
||||
outM.setOnFailedSendJob(_onFailure);
|
||||
outM.setOnReplyJob(_onReply);
|
||||
outM.setOnSendJob(_onSend);
|
||||
outM.setPriority(_priority);
|
||||
outM.setReplySelector(_selector);
|
||||
outM.setTarget(null);
|
||||
OutboundMessageRegistry.getInstance().registerPending(outM);
|
||||
}
|
||||
|
||||
public String getName() { return "Send Tunnel Message"; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user