beginning of branch i2p.i2p.i2p

This commit is contained in:
cvs_import
2004-04-08 04:41:54 +00:00
committed by zzz
commit 77bd69c5e5
292 changed files with 41035 additions and 0 deletions

35
router/java/build.xml Normal file
View 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>

View 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();
}
}

View File

@ -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();
}
}

View 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();
}
}

View File

@ -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();
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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() + "]";
}
}

View 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() + "]";
}
}

View 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();
}
}

View 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();
}
}

View 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();
}

View 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);
}
}

View 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();
}
}
}

View 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; }
}

View 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
}
}
}

View 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();
}
}

View 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();
}
}

View 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 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() + "]";
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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() + "]";
}
}

View 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.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() + "]";
}
}

View 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.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() + "]";
}
}

View File

@ -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() + "]";
}
}

View 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>

View 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; }
}

View 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; }
}

View 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());
}
}

View 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;
}
}
}

View 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) { }
}

View 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) {}
}
}
}

View 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);
}

View 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();
}
}

View 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());
}
}

View 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();
}

View 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);
}
}

View 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());
}
}

View 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);
}
}
}

View 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();
}
}

View 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;
}
}

View 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"; }
}
}

View 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;
}
}
}

View 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();
}
}

View 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; }
}

View 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();
}

View 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");
}
}

View 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()); }
}

View 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
}
}

View 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));
}
}
}

View 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; }
}

View 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; }
}

View 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);
}

View 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);
}

View 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) {}
}
}
}
}

View 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);
}
}

View 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();
}

View File

@ -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);
}
}
}

View 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();
}
}

View 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 ""; }
}

View 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"; }
}

View 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;
}
}
}

View 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);
}

View 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; }
}

View 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;
}
}
}

View 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(); }
}

View 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();
}
}

View 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";
}
}

View 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); } }
}

View File

@ -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);
}
}
}
}

View 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(); }
}

View 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");
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View 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);
}
}

View 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; }
}

View File

@ -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);
}
}
}

View 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);
}
}
}

View 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"; }
}
}

View File

@ -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() {}
}

View 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
}
}
}

View 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();
}
}

View 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();
}
}

View 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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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)"; }
}
}

View 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);
}
}

View File

@ -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();
}
}
}

View 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.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;
}
}

View File

@ -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();
}
}

View 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");
}
}

View File

@ -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"; }
}

View 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);
}
}
}

View File

@ -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"; }
}

View 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