forked from I2P_Developers/i2p.i2p
Compare commits
107 Commits
i2p_0_6_1_
...
i2p_0_6_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77310e17d1 | ||
|
|
e54b964929 | ||
|
|
809f3e847b | ||
|
|
f4beebe60d | ||
|
|
827e427f0b | ||
|
|
c02125511d | ||
|
|
1aa1069b6f | ||
|
|
91d281077d | ||
|
|
f339dec024 | ||
|
|
2aeef44f8d | ||
|
|
0fd41a9490 | ||
|
|
58f10d14b2 | ||
|
|
46ca42ddf8 | ||
|
|
e6e6d6f4ee | ||
|
|
8a87df605b | ||
|
|
8ca085bceb | ||
|
|
df47587db0 | ||
|
|
d705e0ad04 | ||
|
|
40d209dd7c | ||
|
|
7f2a0457bf | ||
|
|
f4749f2483 | ||
|
|
9c42830076 | ||
|
|
53ba6c2a64 | ||
|
|
61b3f21f69 | ||
|
|
506fd5f889 | ||
|
|
d538f888b4 | ||
|
|
976c5fdd47 | ||
|
|
b63f3437f2 | ||
|
|
e760f2e538 | ||
|
|
17c8fca779 | ||
|
|
87fda382c3 | ||
|
|
1e404cd7ac | ||
|
|
098f99d806 | ||
|
|
da93f96035 | ||
|
|
ead39cc87e | ||
|
|
e4e3c44459 | ||
|
|
af151e32e5 | ||
|
|
12819a2a17 | ||
|
|
87eedff254 | ||
|
|
4c59cd7621 | ||
|
|
ef707e7956 | ||
|
|
73cf3fb299 | ||
|
|
80b0c97d72 | ||
|
|
5cf85c1d7b | ||
|
|
c14e52ceb5 | ||
|
|
32a579e480 | ||
|
|
0a240a4436 | ||
|
|
9325b806e4 | ||
|
|
ef2e24ea11 | ||
|
|
373934c6e0 | ||
|
|
e8e8bac694 | ||
|
|
23e8a558c2 | ||
|
|
46f2645834 | ||
|
|
2329439034 | ||
|
|
6d400368b9 | ||
|
|
26c13b40fe | ||
|
|
9fd0e95fe8 | ||
|
|
7e21f2c92b | ||
|
|
c9d8e796c6 | ||
|
|
e7203f5d46 | ||
|
|
22d76a1b64 | ||
|
|
0903dc46c6 | ||
|
|
0f56ec8078 | ||
|
|
70ee1df2bf | ||
|
|
61a6a29bec | ||
|
|
678f7d8f72 | ||
|
|
b92ee364bc | ||
|
|
aef19fcd38 | ||
|
|
3b01df1d2c | ||
|
|
4aed23b198 | ||
|
|
03e8875c27 | ||
|
|
48921a0875 | ||
|
|
633fabb09e | ||
|
|
bc42c26d94 | ||
|
|
3c09ca3359 | ||
|
|
1e9e7dd345 | ||
|
|
034803add7 | ||
|
|
b25bb053bb | ||
|
|
9bd0c79441 | ||
|
|
06b8670410 | ||
|
|
6577ae499f | ||
|
|
54bc5485ec | ||
|
|
84b741ac98 | ||
|
|
c48c419d74 | ||
|
|
fb2e795add | ||
|
|
ec215777ec | ||
|
|
d4e0f27c56 | ||
|
|
e1c686baa6 | ||
|
|
d57af1aef4 | ||
|
|
a52dd57215 | ||
|
|
65138357d3 | ||
|
|
f6320696dd | ||
|
|
900d8a2026 | ||
|
|
ccc9a87e8c | ||
|
|
208634e5de | ||
|
|
3d07205c9d | ||
|
|
f0a424a93f | ||
|
|
f9b59ee07d | ||
|
|
b92b9d2618 | ||
|
|
a3db9429a7 | ||
|
|
291a5c9578 | ||
|
|
0a3281c279 | ||
|
|
23f30ba576 | ||
|
|
f3de85c4de | ||
|
|
a3a4888e0b | ||
|
|
6fd7881f8e | ||
|
|
381f716769 |
@@ -318,6 +318,14 @@ public class Peer implements Comparable
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
// try to save partial piece
|
||||
if (this.deregister) {
|
||||
PeerListener p = state.listener;
|
||||
if (p != null) {
|
||||
p.savePeerPartial(state);
|
||||
p.markUnrequested(this);
|
||||
}
|
||||
}
|
||||
state = null;
|
||||
|
||||
PeerConnectionIn in = s.in;
|
||||
@@ -458,4 +466,25 @@ public class Peer implements Comparable
|
||||
return -1; //"no state";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send keepalive
|
||||
*/
|
||||
public void keepAlive()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.keepAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retransmit outstanding requests if necessary
|
||||
*/
|
||||
public void retransmitRequests()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.retransmitRequests();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,10 +48,7 @@ class PeerCheckerTask extends TimerTask
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
int removedCount = 0;
|
||||
|
||||
long uploaded = 0;
|
||||
long downloaded = 0;
|
||||
@@ -80,17 +77,7 @@ class PeerCheckerTask extends TimerTask
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
|
||||
// XXX - We should calculate the up/download rate a bit
|
||||
// more intelligently
|
||||
long upload = peer.getUploaded();
|
||||
uploaded += upload;
|
||||
long download = peer.getDownloaded();
|
||||
@@ -113,7 +100,7 @@ class PeerCheckerTask extends TimerTask
|
||||
// interested peers try to make some room.
|
||||
// (Note use of coordinator.uploaders)
|
||||
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
&& coordinator.interestedAndChoking > 0
|
||||
&& !peer.isChoking())
|
||||
{
|
||||
// Check if it still wants pieces from us.
|
||||
@@ -130,7 +117,7 @@ class PeerCheckerTask extends TimerTask
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isChoked())
|
||||
else if (peer.isInteresting() && peer.isChoked())
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
@@ -138,6 +125,21 @@ class PeerCheckerTask extends TimerTask
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isInteresting() && !coordinator.completed())
|
||||
{
|
||||
// If they aren't interesting make someone else a downloader
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
@@ -154,27 +156,37 @@ class PeerCheckerTask extends TimerTask
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isChoking() && download < worstdownload)
|
||||
else if (peer.isInteresting() && !peer.isChoked() &&
|
||||
download < worstdownload)
|
||||
{
|
||||
// Make sure download is good if we are uploading
|
||||
worstdownload = download;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
else if (upload < worstdownload && coordinator.completed())
|
||||
{
|
||||
// Make sure upload is good if we are seeding
|
||||
worstdownload = upload;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
}
|
||||
peer.retransmitRequests();
|
||||
peer.keepAlive();
|
||||
}
|
||||
|
||||
// Resync actual uploaders value
|
||||
// (can shift a bit by disconnecting peers)
|
||||
coordinator.uploaders = uploaders;
|
||||
|
||||
// Remove the worst downloader if needed.
|
||||
// Remove the worst downloader if needed. (uploader if seeding)
|
||||
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
&& coordinator.interestedAndChoking > 0
|
||||
&& worstDownloader != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
@@ -183,6 +195,7 @@ class PeerCheckerTask extends TimerTask
|
||||
|
||||
worstDownloader.setChoking(true);
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
@@ -196,6 +209,11 @@ class PeerCheckerTask extends TimerTask
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
coordinator.interestedAndChoking += removedCount;
|
||||
|
||||
// store the rates
|
||||
coordinator.setRateHistory(uploaded, downloaded);
|
||||
|
||||
}
|
||||
if (coordinator.halted()) {
|
||||
cancel();
|
||||
|
||||
@@ -33,7 +33,7 @@ class PeerConnectionIn implements Runnable
|
||||
private final DataInputStream din;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
private volatile boolean quit;
|
||||
|
||||
public PeerConnectionIn(Peer peer, DataInputStream din)
|
||||
{
|
||||
@@ -51,6 +51,13 @@ class PeerConnectionIn implements Runnable
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
if (din != null) {
|
||||
try {
|
||||
din.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream from " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run()
|
||||
|
||||
@@ -72,6 +72,16 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = null;
|
||||
PeerState state = null;
|
||||
boolean shouldFlush;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
shouldFlush = !quit && peer.isConnected() && sendQueue.isEmpty();
|
||||
}
|
||||
if (shouldFlush)
|
||||
// Make sure everything will reach the other side.
|
||||
// flush while not holding lock, could take a long time
|
||||
dout.flush();
|
||||
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
while (!quit && peer.isConnected() && sendQueue.isEmpty())
|
||||
@@ -79,7 +89,8 @@ class PeerConnectionOut implements Runnable
|
||||
try
|
||||
{
|
||||
// Make sure everything will reach the other side.
|
||||
dout.flush();
|
||||
// don't flush while holding lock, could take a long time
|
||||
// dout.flush();
|
||||
|
||||
// Wait till more data arrives.
|
||||
sendQueue.wait(60*1000);
|
||||
@@ -259,7 +270,13 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.KEEP_ALIVE;
|
||||
addMessage(m);
|
||||
// addMessage(m);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void sendChoke(boolean choke)
|
||||
@@ -318,6 +335,23 @@ class PeerConnectionOut implements Runnable
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
private static final int REQ_TIMEOUT = (2 * SEND_TIMEOUT) + (60 * 1000);
|
||||
void retransmitRequests(List requests)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
if(now > req.sendTime + REQ_TIMEOUT) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Retransmit request " + req + " to peer " + peer);
|
||||
sendRequest(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendRequests(List requests)
|
||||
{
|
||||
Iterator it = requests.iterator();
|
||||
@@ -330,12 +364,30 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
void sendRequest(Request req)
|
||||
{
|
||||
// Check for duplicate requests to deal with fibrillating i2p-bt
|
||||
// (multiple choke/unchokes received cause duplicate requests in the queue)
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST && m.piece == req.piece &&
|
||||
m.begin == req.off && m.length == req.len)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Discarding duplicate request " + req + " to peer " + peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Message m = new Message();
|
||||
m.type = Message.REQUEST;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
req.sendTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
@@ -346,7 +398,7 @@ class PeerConnectionOut implements Runnable
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.data = bytes;
|
||||
m.off = begin;
|
||||
m.off = 0;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
@@ -37,19 +37,23 @@ public class PeerCoordinator implements PeerListener
|
||||
final Snark snark;
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||
final static long CHECK_PERIOD = 40*1000; // 40 seconds
|
||||
final static int MAX_CONNECTIONS = 24;
|
||||
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
|
||||
final static int MAX_UPLOADERS = 4;
|
||||
|
||||
// Approximation of the number of current uploaders.
|
||||
// Resynced by PeerChecker once in a while.
|
||||
int uploaders = 0;
|
||||
int interestedAndChoking = 0;
|
||||
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {0,0,0,0,0,0};
|
||||
private long downloaded_old[] = {0,0,0,0,0,0};
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
@@ -62,7 +66,7 @@ public class PeerCoordinator implements PeerListener
|
||||
private final byte[] id;
|
||||
|
||||
// Some random wanted pieces
|
||||
private final List wantedPieces;
|
||||
private List wantedPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
|
||||
@@ -80,6 +84,15 @@ public class PeerCoordinator implements PeerListener
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
setWantedPieces();
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
// only called externally from Storage after the double-check fails
|
||||
public void setWantedPieces()
|
||||
{
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
@@ -87,11 +100,8 @@ public class PeerCoordinator implements PeerListener
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
public CoordinatorListener getListener() { return listener; }
|
||||
|
||||
@@ -123,7 +133,7 @@ public class PeerCoordinator implements PeerListener
|
||||
public long getLeft()
|
||||
{
|
||||
// XXX - Only an approximation.
|
||||
return storage.needed() * metainfo.getPieceLength(0);
|
||||
return ((long) storage.needed()) * metainfo.getPieceLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,6 +152,43 @@ public class PeerCoordinator implements PeerListener
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--){
|
||||
uploaded_old[i] = uploaded_old[i-1];
|
||||
downloaded_old[i] = downloaded_old[i-1];
|
||||
}
|
||||
uploaded_old[0] = up;
|
||||
downloaded_old[0] = down;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 4-minute-average rate in Bps
|
||||
*/
|
||||
public long getDownloadRate()
|
||||
{
|
||||
long rate = 0;
|
||||
for (int i = 0; i < RATE_DEPTH; i++){
|
||||
rate += downloaded_old[i];
|
||||
}
|
||||
return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 4-minute-average rate in Bps
|
||||
*/
|
||||
public long getUploadRate()
|
||||
{
|
||||
long rate = 0;
|
||||
for (int i = 0; i < RATE_DEPTH; i++){
|
||||
rate += uploaded_old[i];
|
||||
}
|
||||
return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
@@ -191,8 +238,10 @@ public class PeerCoordinator implements PeerListener
|
||||
synchronized(peers)
|
||||
{
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
|
||||
// idle for 2 minutes, kill the old con
|
||||
if ( (old != null) && (old.getInactiveTime() > 4*60*1000) ) {
|
||||
// idle for 4 minutes, kill the old con (64KB/4min = 273B/sec minimum for one block)
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Remomving old peer: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
peers.remove(old);
|
||||
toDisconnect = old;
|
||||
old = null;
|
||||
@@ -235,12 +284,13 @@ public class PeerCoordinator implements PeerListener
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addPeer(final Peer peer)
|
||||
// returns true if actual attempt to add peer occurs
|
||||
public boolean addPeer(final Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean need_more;
|
||||
@@ -265,6 +315,7 @@ public class PeerCoordinator implements PeerListener
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new I2PThread(r, threadName).start();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
@@ -274,6 +325,7 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -285,19 +337,23 @@ public class PeerCoordinator implements PeerListener
|
||||
// other peer that are interested, but are choking us.
|
||||
List interested = new LinkedList();
|
||||
synchronized (peers) {
|
||||
int count = 0;
|
||||
int unchokedCount = 0;
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
if (peer.isChoking() && peer.isInterested())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
count++;
|
||||
if (uploaders < MAX_UPLOADERS)
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(unchokedCount++, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,11 +364,13 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.debug("Unchoke: " + peer);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
count--;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
}
|
||||
interestedAndChoking = count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,9 +409,10 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i))
|
||||
if (bitfield.get(i)) {
|
||||
p.addPeer(peer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -392,6 +451,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
// let's not all get on the same piece
|
||||
Collections.shuffle(requested);
|
||||
Iterator it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
@@ -403,9 +464,17 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
if (piece == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
+ " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
|
||||
// _log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
return -1; //If we still can't find a piece we want, so be it.
|
||||
} else {
|
||||
// Should be a lot smarter here - limit # of parallel attempts and
|
||||
// share blocks rather than starting from 0 with each peer.
|
||||
// This is where the flaws of the snark data model are really exposed.
|
||||
// Could also randomize within the duplicate set rather than strict rarest-first
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
|
||||
}
|
||||
}
|
||||
piece.setRequested(true);
|
||||
@@ -417,14 +486,14 @@ public class PeerCoordinator implements PeerListener
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*/
|
||||
public byte[] gotRequest(Peer peer, int piece)
|
||||
public byte[] gotRequest(Peer peer, int piece, int off, int len)
|
||||
{
|
||||
if (halted)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return storage.getPiece(piece);
|
||||
return storage.getPiece(piece, off, len);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -582,4 +651,124 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simple method to save a partial piece on peer disconnection
|
||||
* and hopefully restart it later.
|
||||
* Only one partial piece is saved at a time.
|
||||
* Replace it if a new one is bigger or the old one is too old.
|
||||
* Storage method is private so we can expand to save multiple partials
|
||||
* if we wish.
|
||||
*/
|
||||
private Request savedRequest = null;
|
||||
private long savedRequestTime = 0;
|
||||
public void savePeerPartial(PeerState state)
|
||||
{
|
||||
Request req = state.getPartialRequest();
|
||||
if (req == null)
|
||||
return;
|
||||
if (savedRequest == null ||
|
||||
req.off > savedRequest.off ||
|
||||
System.currentTimeMillis() > savedRequestTime + (15 * 60 * 1000)) {
|
||||
if (savedRequest == null || (req.piece != savedRequest.piece && req.off != savedRequest.off)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(" Saving orphaned partial piece " + req);
|
||||
if (savedRequest != null)
|
||||
_log.debug(" (Discarding previously saved orphan) " + savedRequest);
|
||||
}
|
||||
}
|
||||
savedRequest = req;
|
||||
savedRequestTime = System.currentTimeMillis();
|
||||
} else {
|
||||
if (req.piece != savedRequest.piece)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(" Discarding orphaned partial piece " + req);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return partial piece if it's still wanted and peer has it.
|
||||
*/
|
||||
public Request getPeerPartial(BitField havePieces) {
|
||||
if (savedRequest == null)
|
||||
return null;
|
||||
if (! havePieces.get(savedRequest.piece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer doesn't have orphaned piece " + savedRequest);
|
||||
return null;
|
||||
}
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
if (piece.getId() == savedRequest.piece) {
|
||||
Request req = savedRequest;
|
||||
piece.setRequested(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Restoring orphaned partial piece " + req);
|
||||
savedRequest = null;
|
||||
return req;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We no longer want orphaned piece " + savedRequest);
|
||||
savedRequest = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Clear the requested flag for a piece if the peer
|
||||
** is the only one requesting it
|
||||
*/
|
||||
private void markUnrequestedIfOnlyOne(Peer peer, int piece)
|
||||
{
|
||||
// see if anybody else is requesting
|
||||
synchronized (peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.equals(peer))
|
||||
continue;
|
||||
if (p.state == null)
|
||||
continue;
|
||||
int[] arr = p.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
if(arr[i] == piece) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Another peer is requesting piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nobody is, so mark unrequested
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (it.hasNext()) {
|
||||
Piece p = (Piece)it.next();
|
||||
if (p.getId() == piece) {
|
||||
p.setRequested(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Removing from request list piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
** Once for each piece
|
||||
** This is enough trouble, maybe would be easier just to regenerate
|
||||
** the requested list from scratch instead.
|
||||
*/
|
||||
public void markUnrequested(Peer peer)
|
||||
{
|
||||
if (peer.state == null)
|
||||
return;
|
||||
int[] arr = peer.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
markUnrequestedIfOnlyOne(peer, arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,11 +107,13 @@ public interface PeerListener
|
||||
*
|
||||
* @param peer the Peer that wants the piece.
|
||||
* @param piece the piece number requested.
|
||||
* @param off byte offset into the piece.
|
||||
* @param len length of the chunk requested.
|
||||
*
|
||||
* @return a byte array containing the piece or null when the piece
|
||||
* is not available (which is a protocol error).
|
||||
*/
|
||||
byte[] gotRequest(Peer peer, int piece);
|
||||
byte[] gotRequest(Peer peer, int piece, int off, int len);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been downloaded from the peer.
|
||||
@@ -142,4 +144,29 @@ public interface PeerListener
|
||||
* we are no longer interested in the peer.
|
||||
*/
|
||||
int wantPiece(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when the peer has disconnected and the peer task may have a partially
|
||||
* downloaded piece that the PeerCoordinator can save
|
||||
*
|
||||
* @param state the PeerState for the peer
|
||||
*/
|
||||
void savePeerPartial(PeerState state);
|
||||
|
||||
/**
|
||||
* Called when a peer has connected and there may be a partially
|
||||
* downloaded piece that the coordinatorator can give the peer task
|
||||
*
|
||||
* @param havePieces the have-pieces bitmask for the peer
|
||||
*
|
||||
* @return request (contains the partial data and valid length)
|
||||
*/
|
||||
Request getPeerPartial(BitField havePieces);
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
* This prevents premature end game
|
||||
*
|
||||
* @param peer the peer that is disconnecting
|
||||
*/
|
||||
void markUnrequested(Peer peer);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class PeerState
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 1;
|
||||
private final static int MAX_PIPELINE = 2;
|
||||
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
@@ -184,7 +184,7 @@ class PeerState
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece);
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
@@ -194,7 +194,7 @@ class PeerState
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||
if (length != pieceBytes.length)
|
||||
{
|
||||
// XXX - Protocol error-> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -221,6 +221,10 @@ class PeerState
|
||||
listener.uploaded(peer, size);
|
||||
}
|
||||
|
||||
// This is used to flag that we have to back up from the firstOutstandingRequest
|
||||
// when calculating how far we've gotten
|
||||
Request pendingRequest = null;
|
||||
|
||||
/**
|
||||
* Called when a partial piece request has been handled by
|
||||
* PeerConnectionIn.
|
||||
@@ -231,6 +235,8 @@ class PeerState
|
||||
downloaded += size;
|
||||
listener.downloaded(peer, size);
|
||||
|
||||
pendingRequest = null;
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
{
|
||||
@@ -318,14 +324,8 @@ class PeerState
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
// We used to rerequest the missing chunks but that mostly
|
||||
// just confuses the other side. So now we just keep
|
||||
// waiting for them. They will be rerequested when we get
|
||||
// choked/unchoked again.
|
||||
/*
|
||||
if (!choked)
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
*/
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("dropped " + dropReq + " with peer " + peer);
|
||||
}
|
||||
@@ -336,10 +336,58 @@ class PeerState
|
||||
// Request more if necessary to keep the pipeline filled.
|
||||
addRequest();
|
||||
|
||||
pendingRequest = req;
|
||||
return req;
|
||||
|
||||
}
|
||||
|
||||
// get longest partial piece
|
||||
Request getPartialRequest()
|
||||
{
|
||||
Request req = null;
|
||||
for (int i = 0; i < outstandingRequests.size(); i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
int j = getFirstOutstandingRequest(r1.piece);
|
||||
if (j == -1)
|
||||
continue;
|
||||
Request r2 = (Request)outstandingRequests.get(j);
|
||||
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
|
||||
req = r2;
|
||||
}
|
||||
if (pendingRequest != null && req != null && pendingRequest.off < req.off) {
|
||||
if (pendingRequest.off != 0)
|
||||
req = pendingRequest;
|
||||
else
|
||||
req = null;
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
// return array of pieces terminated by -1
|
||||
// remove most duplicates
|
||||
// but still could be some duplicates, not guaranteed
|
||||
int[] getRequestedPieces()
|
||||
{
|
||||
int size = outstandingRequests.size();
|
||||
int[] arr = new int[size+2];
|
||||
int pc = -1;
|
||||
int pos = 0;
|
||||
if (pendingRequest != null) {
|
||||
pc = pendingRequest.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
Request req = null;
|
||||
for (int i = 0; i < size; i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
if (pc != r1.piece) {
|
||||
pc = r1.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
}
|
||||
arr[pos] = -1;
|
||||
return(arr);
|
||||
}
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -414,16 +462,12 @@ class PeerState
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
*/
|
||||
private void addRequest()
|
||||
synchronized private void addRequest()
|
||||
{
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
}
|
||||
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
// We want something and we don't have outstanding requests?
|
||||
if (more_pieces && lastRequest == null)
|
||||
more_pieces = requestNextPiece();
|
||||
@@ -431,19 +475,14 @@ class PeerState
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
synchronized(this)
|
||||
{
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
}
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
|
||||
// Last part of a piece?
|
||||
if (isLastChunk)
|
||||
more_pieces = requestNextPiece();
|
||||
else
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
int nextPiece = lastRequest.piece;
|
||||
int nextBegin = lastRequest.off + PARTSIZE;
|
||||
byte[] bs = lastRequest.bs;
|
||||
@@ -456,7 +495,6 @@ class PeerState
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,16 +510,41 @@ class PeerState
|
||||
// Check that we already know what the other side has.
|
||||
if (bitfield != null)
|
||||
{
|
||||
// Check for adopting an orphaned partial piece
|
||||
Request r = listener.getPeerPartial(bitfield);
|
||||
if (r != null) {
|
||||
// Check that r not already in outstandingRequests
|
||||
int[] arr = getRequestedPieces();
|
||||
boolean found = false;
|
||||
for (int i = 0; arr[i] >= 0; i++) {
|
||||
if (arr[i] == r.piece) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
outstandingRequests.add(r);
|
||||
if (!choked)
|
||||
out.sendRequest(r);
|
||||
lastRequest = r;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
synchronized(this)
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||
byte[] bs = new byte[piece_length];
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
try {
|
||||
bs = new byte[piece_length];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.warn("Out of memory, can't request piece " + nextPiece, oom);
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = Math.min(piece_length, PARTSIZE);
|
||||
Request req = new Request(nextPiece, bs, 0, length);
|
||||
@@ -491,7 +554,6 @@ class PeerState
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -523,4 +585,15 @@ class PeerState
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
}
|
||||
|
||||
void keepAlive()
|
||||
{
|
||||
out.sendAlive();
|
||||
}
|
||||
|
||||
synchronized void retransmitRequests()
|
||||
{
|
||||
if (interesting && !choked)
|
||||
out.retransmitRequests(outstandingRequests);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class Request
|
||||
final byte[] bs;
|
||||
final int off;
|
||||
final int len;
|
||||
long sendTime;
|
||||
|
||||
/**
|
||||
* Creates a new Request.
|
||||
|
||||
@@ -234,6 +234,7 @@ public class Snark
|
||||
public String rootDataDir = ".";
|
||||
public CompleteListener completeListener;
|
||||
public boolean stopped;
|
||||
byte[] id;
|
||||
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
@@ -268,7 +269,7 @@ public class Snark
|
||||
// zeros bytes, then three bytes filled with snark and then
|
||||
// sixteen random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] id = new byte[20];
|
||||
id = new byte[20];
|
||||
Random random = new Random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
@@ -283,6 +284,11 @@ public class Snark
|
||||
|
||||
int port;
|
||||
IOException lastException = null;
|
||||
/*
|
||||
* Don't start a tunnel if the torrent isn't going to be started.
|
||||
* If we are starting,
|
||||
* startTorrent() will call trackerclient.start() which will force a connect.
|
||||
*
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
@@ -292,6 +298,7 @@ public class Snark
|
||||
Destination d = serversocket.getManager().getSession().getMyDestination();
|
||||
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
|
||||
}
|
||||
*/
|
||||
|
||||
// Figure out what the torrent argument represents.
|
||||
meta = null;
|
||||
@@ -371,14 +378,19 @@ public class Snark
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* see comment above
|
||||
*
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
*/
|
||||
|
||||
if (start)
|
||||
startTorrent();
|
||||
}
|
||||
@@ -386,6 +398,26 @@ public class Snark
|
||||
* Start up contacting peers and querying the tracker
|
||||
*/
|
||||
public void startTorrent() {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
if (coordinator == null) {
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else {
|
||||
Destination d = serversocket.getManager().getSession().getMyDestination();
|
||||
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
|
||||
}
|
||||
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, this, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
}
|
||||
|
||||
stopped = false;
|
||||
boolean coordinatorChanged = false;
|
||||
if (coordinator.halted()) {
|
||||
@@ -402,6 +434,17 @@ public class Snark
|
||||
if (!trackerclient.started() && !coordinatorChanged) {
|
||||
trackerclient.start();
|
||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||
try
|
||||
{
|
||||
storage.reopen(rootDataDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not reopen storage", ioe);
|
||||
}
|
||||
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
@@ -693,6 +736,11 @@ public class Snark
|
||||
completeListener.torrentComplete(this);
|
||||
}
|
||||
|
||||
public void setWantedPieces(Storage storage)
|
||||
{
|
||||
coordinator.setWantedPieces();
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
|
||||
@@ -16,6 +16,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private Map _snarks;
|
||||
private Object _addSnarkLock;
|
||||
private String _configFile;
|
||||
private Properties _config;
|
||||
private I2PAppContext _context;
|
||||
@@ -34,12 +35,13 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
private SnarkManager() {
|
||||
_snarks = new HashMap();
|
||||
_addSnarkLock = new Object();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
loadConfig("i2psnark.config");
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
@@ -91,6 +93,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_config.setProperty(PROP_I2CP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_I2CP_PORT))
|
||||
_config.setProperty(PROP_I2CP_PORT, "7654");
|
||||
if (!_config.containsKey(PROP_I2CP_OPTS))
|
||||
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=1 inbound.lengthVariance=1 outbound.length=1 outbound.lengthVariance=1");
|
||||
if (!_config.containsKey(PROP_EEP_HOST))
|
||||
_config.setProperty(PROP_EEP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
@@ -267,7 +271,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
public void addTorrent(String filename, boolean dontAutoStart) {
|
||||
if (!I2PSnarkUtil.instance().connected()) {
|
||||
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
@@ -287,7 +291,16 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
if (torrent == null) {
|
||||
}
|
||||
// don't hold the _snarks lock while verifying the torrent
|
||||
if (torrent == null) {
|
||||
synchronized (_addSnarkLock) {
|
||||
// double-check
|
||||
synchronized (_snarks) {
|
||||
if(_snarks.get(filename) != null)
|
||||
return;
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(sfile);
|
||||
@@ -303,7 +316,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} else {
|
||||
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
|
||||
torrent.completeListener = this;
|
||||
_snarks.put(filename, torrent);
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
|
||||
@@ -313,9 +328,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// ok, snark created, now lets start it up or configure it further
|
||||
File f = new File(filename);
|
||||
@@ -333,7 +348,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
|
||||
} else if (info.getPieces() <= 0) {
|
||||
return "No pieces in " + info.getName() + "? deleting it";
|
||||
} else if (info.getPieceLength(0) > 10*1024*1024) {
|
||||
} else if (info.getPieceLength(0) > 1*1024*1024) {
|
||||
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB, deleting it";
|
||||
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
@@ -444,10 +459,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connect())
|
||||
addTorrent((String)foundNames.get(i));
|
||||
else
|
||||
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
|
||||
addMessage("Unable to connect to I2P");
|
||||
addTorrent((String)foundNames.get(i), !shouldAutoStart());
|
||||
}
|
||||
}
|
||||
// now lets see which ones have been removed...
|
||||
@@ -464,6 +478,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
private static final String DEFAULT_TRACKERS[] = {
|
||||
"Postman's tracker", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php"
|
||||
, "eBook Tracker", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php"
|
||||
, "Gaytorrents Tracker", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php"
|
||||
, "NickyB Tracker", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php"
|
||||
, "Orion's tracker", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php"
|
||||
// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ public class Storage
|
||||
|
||||
private final StorageListener listener;
|
||||
|
||||
private final BitField bitfield; // BitField to represent the pieces
|
||||
private BitField bitfield; // BitField to represent the pieces
|
||||
private int needed; // Number of pieces needed
|
||||
|
||||
// XXX - Not always set correctly
|
||||
@@ -48,6 +48,7 @@ public class Storage
|
||||
|
||||
/** The default piece size. */
|
||||
private static int MIN_PIECE_SIZE = 256*1024;
|
||||
private static int MAX_PIECE_SIZE = 1024*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
private static long MAX_PIECES = 100*1024/20;
|
||||
|
||||
@@ -90,7 +91,7 @@ public class Storage
|
||||
|
||||
piece_size = MIN_PIECE_SIZE;
|
||||
pieces = (int) ((total - 1)/piece_size) + 1;
|
||||
while (pieces > MAX_PIECES)
|
||||
while (pieces > MAX_PIECES && piece_size < MAX_PIECE_SIZE)
|
||||
{
|
||||
piece_size = piece_size*2;
|
||||
pieces = (int) ((total - 1)/piece_size) +1;
|
||||
@@ -155,7 +156,7 @@ public class Storage
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
@@ -183,7 +184,7 @@ public class Storage
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
@@ -218,6 +219,8 @@ public class Storage
|
||||
{
|
||||
File f = (File)it.next();
|
||||
names[i] = f.getPath();
|
||||
if (base.isDirectory() && names[i].startsWith(base.getPath()))
|
||||
names[i] = names[i].substring(base.getPath().length() + 1);
|
||||
lengths[i] = f.length();
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
i++;
|
||||
@@ -334,6 +337,49 @@ public class Storage
|
||||
checkCreateFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopen the file descriptors for a restart
|
||||
* Do existence check but no length check or data reverification
|
||||
*/
|
||||
public void reopen(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// Reopen base as file.
|
||||
Snark.debug("Reopening file: " + base, Snark.NOTICE);
|
||||
if (!base.exists())
|
||||
throw new IOException("Could not reopen file " + base);
|
||||
|
||||
if (!base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reopen base as dir.
|
||||
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
|
||||
if (!base.isDirectory())
|
||||
throw new IOException("Could not reopen directory " + base);
|
||||
|
||||
int size = files.size();
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
if (!f.exists())
|
||||
throw new IOException("Could not reopen file " + f);
|
||||
if (!f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the give file name.
|
||||
*/
|
||||
@@ -399,7 +445,7 @@ public class Storage
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
if (correctHash)
|
||||
{
|
||||
@@ -462,16 +508,23 @@ public class Storage
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null if
|
||||
* Returns a byte array containing a portion of the requested piece or null if
|
||||
* the storage doesn't contain the piece yet.
|
||||
*/
|
||||
public byte[] getPiece(int piece) throws IOException
|
||||
public byte[] getPiece(int piece, int off, int len) throws IOException
|
||||
{
|
||||
if (!bitfield.get(piece))
|
||||
return null;
|
||||
|
||||
byte[] bs = new byte[metainfo.getPieceLength(piece)];
|
||||
getUncheckedPiece(piece, bs, 0);
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
try {
|
||||
bs = new byte[len];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
|
||||
return null;
|
||||
}
|
||||
getUncheckedPiece(piece, bs, off, len);
|
||||
return bs;
|
||||
}
|
||||
|
||||
@@ -482,10 +535,11 @@ public class Storage
|
||||
* matches), otherwise false.
|
||||
* @exception IOException when some storage related error occurs.
|
||||
*/
|
||||
public boolean putPiece(int piece, byte[] bs) throws IOException
|
||||
public boolean putPiece(int piece, byte[] ba) throws IOException
|
||||
{
|
||||
// First check if the piece is correct.
|
||||
// If we were paranoia we could copy the array first.
|
||||
// Copy the array first to be paranoid.
|
||||
byte[] bs = (byte[]) ba.clone();
|
||||
int length = bs.length;
|
||||
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||
if (listener != null)
|
||||
@@ -504,6 +558,7 @@ public class Storage
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
@@ -538,23 +593,44 @@ public class Storage
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
listener.storageCompleted(this);
|
||||
// listener.storageCompleted(this);
|
||||
// do we also need to close all of the files and reopen
|
||||
// them readonly?
|
||||
|
||||
// Do a complete check to be sure.
|
||||
// Temporarily resets the 'needed' variable and 'bitfield', then call
|
||||
// checkCreateFiles() which will set 'needed' and 'bitfield'
|
||||
// and also call listener.storageCompleted() if the double-check
|
||||
// was successful.
|
||||
// Todo: set a listener variable so the web shows "checking" and don't
|
||||
// have the user panic when completed amount goes to zero temporarily?
|
||||
needed = metainfo.getPieces();
|
||||
bitfield = new BitField(needed);
|
||||
checkCreateFiles();
|
||||
if (needed > 0) {
|
||||
listener.setWantedPieces(this);
|
||||
Snark.debug("WARNING: Not really done, missing " + needed
|
||||
+ " pieces", Snark.WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off)
|
||||
private int getUncheckedPiece(int piece, byte[] bs)
|
||||
throws IOException
|
||||
{
|
||||
return getUncheckedPiece(piece, bs, 0, metainfo.getPieceLength(piece));
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off, int length)
|
||||
throws IOException
|
||||
{
|
||||
// XXX - copy/paste code from putPiece().
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) metainfo.getPieceLength(0);
|
||||
long start = ((long) piece * (long) metainfo.getPieceLength(0)) + off;
|
||||
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
@@ -572,7 +648,7 @@ public class Storage
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, off + read, len);
|
||||
rafs[i].readFully(bs, read, len);
|
||||
}
|
||||
read += len;
|
||||
if (need - len > 0)
|
||||
|
||||
@@ -55,4 +55,11 @@ public interface StorageListener
|
||||
*
|
||||
*/
|
||||
void storageCompleted(Storage storage);
|
||||
|
||||
/** Reset the peer's wanted pieces table
|
||||
* Call after the storage double-check fails
|
||||
*
|
||||
* @param peer the peer
|
||||
*/
|
||||
void setWantedPieces(Storage storage);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ public class TrackerClient extends I2PThread
|
||||
private static final String STOPPED_EVENT = "stopped";
|
||||
|
||||
private final static int SLEEP = 5; // 5 minutes.
|
||||
private final static int DELAY_MIN = 2000; // 2 secs.
|
||||
private final static int DELAY_MUL = 1500; // 1.5 secs.
|
||||
|
||||
private final MetaInfo meta;
|
||||
private final PeerCoordinator coordinator;
|
||||
@@ -110,6 +112,7 @@ public class TrackerClient extends I2PThread
|
||||
long left = coordinator.getLeft();
|
||||
|
||||
boolean completed = (left == 0);
|
||||
int sleptTime = 0;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -117,6 +120,7 @@ public class TrackerClient extends I2PThread
|
||||
boolean started = false;
|
||||
while (!started)
|
||||
{
|
||||
sleptTime = 0;
|
||||
try
|
||||
{
|
||||
// Send start.
|
||||
@@ -125,18 +129,20 @@ public class TrackerClient extends I2PThread
|
||||
STARTED_EVENT);
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
coordinator.trackerProblems = null;
|
||||
if (!completed) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
int delay = DELAY_MUL;
|
||||
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
delay += DELAY_MIN;
|
||||
sleptTime += delay;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
started = true;
|
||||
coordinator.trackerProblems = null;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -147,7 +153,10 @@ public class TrackerClient extends I2PThread
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
}
|
||||
|
||||
if (!started && !stop)
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
if (!started)
|
||||
{
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
try
|
||||
@@ -168,8 +177,17 @@ public class TrackerClient extends I2PThread
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
Thread.sleep(delay);
|
||||
int delay;
|
||||
if(coordinator.trackerProblems != null && !completed) {
|
||||
delay = 60*1000;
|
||||
} else if(completed) {
|
||||
delay = 3*SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
} else {
|
||||
delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
delay -= sleptTime;
|
||||
}
|
||||
if (delay > 0)
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
@@ -196,6 +214,7 @@ public class TrackerClient extends I2PThread
|
||||
event = NO_EVENT;
|
||||
|
||||
// Only do a request when necessary.
|
||||
sleptTime = 0;
|
||||
if (event == COMPLETED_EVENT
|
||||
|| coordinator.needPeers()
|
||||
|| System.currentTimeMillis() > lastRequestTime + interval)
|
||||
@@ -206,6 +225,7 @@ public class TrackerClient extends I2PThread
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
|
||||
coordinator.trackerProblems = null;
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
@@ -216,10 +236,14 @@ public class TrackerClient extends I2PThread
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
// only delay if we actually make an attempt to add peer
|
||||
if(coordinator.addPeer(cur)) {
|
||||
int delay = DELAY_MUL;
|
||||
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
delay += DELAY_MIN;
|
||||
sleptTime += delay;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +253,7 @@ public class TrackerClient extends I2PThread
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
long stats[] = {0,0,0,0};
|
||||
|
||||
String nonce = req.getParameter("nonce");
|
||||
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
|
||||
@@ -55,10 +56,19 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write(HEADER);
|
||||
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td width=\"5%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("<tr><td width=\"20%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("I2PSnark<br />\n");
|
||||
out.write("<a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("</td><td width=\"95%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td><a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a><br />\n");
|
||||
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a><br />\n");
|
||||
out.write("<tr><td><a href=\"http://de-ebook-archiv.i2p/pub/bt/\" class=\"snarkRefresh\">eBook</a><br />\n");
|
||||
out.write("<td><a href=\"http://gaytorrents.i2p/\" class=\"snarkRefresh\">GayTorrents</a><br />\n");
|
||||
out.write("<tr><td><a href=\"http://nickyb.i2p/bittorrent/\" class=\"snarkRefresh\">NickyB</a><br />\n");
|
||||
out.write("<td><a href=\"http://orion.i2p/bt/\" class=\"snarkRefresh\">Orion</a><br />\n");
|
||||
out.write("<tr><td><a href=\"http://tracker.postman.i2p/\" class=\"snarkRefresh\">Postman</a><br />\n");
|
||||
out.write("<td> \n");
|
||||
out.write("</table>\n");
|
||||
out.write("</td><td width=\"80%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
List msgs = _manager.getMessages();
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = (String)msgs.get(i);
|
||||
@@ -66,16 +76,30 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
out.write("</pre></td></tr></table>\n");
|
||||
|
||||
out.write(TABLE_HEADER);
|
||||
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
out.write(TABLE_HEADER);
|
||||
out.write("<th align=\"left\" valign=\"top\">");
|
||||
if (I2PSnarkUtil.instance().connected())
|
||||
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
|
||||
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
|
||||
else
|
||||
out.write(" ");
|
||||
out.write("</th></tr></thead>\n");
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
displaySnark(out, snark, uri, i);
|
||||
displaySnark(out, snark, uri, i, stats);
|
||||
}
|
||||
if (snarks.size() <= 0) {
|
||||
out.write(TABLE_EMPTY);
|
||||
} else if (snarks.size() > 1) {
|
||||
out.write(TABLE_TOTAL);
|
||||
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</tfoot>\n");
|
||||
}
|
||||
|
||||
out.write(TABLE_FOOTER);
|
||||
@@ -240,7 +264,9 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
|
||||
announceURL = announceURLOther;
|
||||
|
||||
if (baseFile.exists() && baseFile.isFile()) {
|
||||
if (announceURL == null || announceURL.length() <= 0)
|
||||
_manager.addMessage("Error creating torrent - you must select a tracker");
|
||||
else if (baseFile.exists()) {
|
||||
try {
|
||||
Storage s = new Storage(baseFile, announceURL, null);
|
||||
s.create();
|
||||
@@ -258,12 +284,22 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
|
||||
}
|
||||
} else if (baseFile.exists()) {
|
||||
_manager.addMessage("I2PSnark doesn't yet support creating multifile torrents");
|
||||
} else {
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistant data: " + baseFile.getAbsolutePath());
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistent data: " + baseFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} else if ("StopAll".equals(action)) {
|
||||
_manager.addMessage("Stopping all torrents and closing the I2P tunnel");
|
||||
List snarks = getSortedSnarks(req);
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
if (!snark.stopped)
|
||||
_manager.stopTorrent(snark.torrent, false);
|
||||
}
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().disconnect();
|
||||
_manager.addMessage("I2P tunnel closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,12 +315,16 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
|
||||
private static final int MAX_DISPLAYED_ERROR_LENGTH = 30;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[]) throws IOException {
|
||||
String filename = snark.torrent;
|
||||
File f = new File(filename);
|
||||
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
|
||||
int i = filename.lastIndexOf(".torrent");
|
||||
if (i > 0)
|
||||
filename = filename.substring(0, i);
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
|
||||
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
|
||||
long total = snark.meta.getTotalLength();
|
||||
@@ -292,32 +332,62 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
|
||||
if (remaining > total)
|
||||
remaining = total;
|
||||
int totalBps = 4096; // should probably grab this from the snark...
|
||||
long remainingSeconds = remaining / totalBps;
|
||||
long uploaded = snark.coordinator.getUploaded();
|
||||
|
||||
long downBps = 0;
|
||||
long upBps = 0;
|
||||
if (snark.coordinator != null) {
|
||||
downBps = snark.coordinator.getDownloadRate();
|
||||
upBps = snark.coordinator.getUploadRate();
|
||||
}
|
||||
long remainingSeconds;
|
||||
if (downBps > 0)
|
||||
remainingSeconds = remaining / downBps;
|
||||
else
|
||||
remainingSeconds = -1;
|
||||
boolean isRunning = !snark.stopped;
|
||||
long uploaded = 0;
|
||||
if (snark.coordinator != null) {
|
||||
uploaded = snark.coordinator.getUploaded();
|
||||
stats[0] += snark.coordinator.getDownloaded();
|
||||
}
|
||||
stats[1] += uploaded;
|
||||
if (isRunning) {
|
||||
stats[2] += downBps;
|
||||
stats[3] += upBps;
|
||||
}
|
||||
|
||||
boolean isValid = snark.meta != null;
|
||||
boolean singleFile = snark.meta.getFiles() == null;
|
||||
|
||||
String err = snark.coordinator.trackerProblems;
|
||||
int curPeers = snark.coordinator.getPeerCount();
|
||||
int knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
String err = null;
|
||||
int curPeers = 0;
|
||||
int knownPeers = 0;
|
||||
if (snark.coordinator != null) {
|
||||
err = snark.coordinator.trackerProblems;
|
||||
curPeers = snark.coordinator.getPeerCount();
|
||||
knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
}
|
||||
|
||||
String statusString = "Unknown";
|
||||
if (err != null) {
|
||||
if (isRunning)
|
||||
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
else {
|
||||
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
|
||||
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "...";
|
||||
statusString = "TrackerErr (" + err + ")";
|
||||
}
|
||||
} else if (remaining <= 0) {
|
||||
if (isRunning)
|
||||
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Complete";
|
||||
} else {
|
||||
if (isRunning)
|
||||
if (isRunning && curPeers > 0 && downBps > 0)
|
||||
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else if (isRunning && curPeers > 0)
|
||||
statusString = "Stalled (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else if (isRunning)
|
||||
statusString = "No Peers (0/" + knownPeers + ")";
|
||||
else
|
||||
statusString = "Stopped";
|
||||
}
|
||||
@@ -336,20 +406,26 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</a>");
|
||||
out.write("</td>\n\t");
|
||||
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
// lets hold off on the ETA until we have rates sorted...
|
||||
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
|
||||
} else {
|
||||
out.write(formatSize(total)); // 3GB
|
||||
}
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">");
|
||||
if(isRunning && remainingSeconds > 0)
|
||||
out.write(DataHelper.formatDuration(remainingSeconds*1000)); // (eta 6h)
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0)
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
else
|
||||
out.write(formatSize(total)); // 3GB
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentUploaded " + rowClass
|
||||
+ "\">" + formatSize(uploaded) + "</td>\n\t");
|
||||
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
|
||||
//out.write("n/a"); //2KBps/12KBps/4KBps
|
||||
//out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
|
||||
if(isRunning && remaining > 0)
|
||||
out.write(formatSize(downBps) + "ps");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
|
||||
if(isRunning)
|
||||
out.write(formatSize(upBps) + "ps");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
|
||||
if (isRunning) {
|
||||
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
|
||||
@@ -381,7 +457,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Add Torrent:</span><br />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"80\" value=\"" + newURL + "\" /> \n");
|
||||
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
|
||||
@@ -396,10 +473,11 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if (baseFile == null)
|
||||
baseFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
out.write("<span class=\"snarkNewTorrent\"><hr />\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Create Torrent:</span><br />\n");
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("Data to seed: " + _manager.getDataDir().getAbsolutePath() + File.separatorChar
|
||||
+ "<input type=\"text\" name=\"baseFile\" size=\"20\" value=\"" + baseFile
|
||||
@@ -480,7 +558,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String val = (String)options.get(key);
|
||||
opts.append(key).append('=').append(val).append(' ');
|
||||
}
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"40\" value=\""
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"80\" value=\""
|
||||
+ opts.toString() + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
|
||||
out.write("</span>\n");
|
||||
@@ -540,7 +618,10 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"}\n" +
|
||||
"th {\n" +
|
||||
" background-color: #C7D5D5;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" padding: 0px 7px 0px 3px;\n" +
|
||||
"}\n" +
|
||||
"td {\n" +
|
||||
" padding: 0px 7px 0px 3px;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentEven {\n" +
|
||||
" background-color: #E7E7E7;\n" +
|
||||
@@ -550,8 +631,6 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"}\n" +
|
||||
".snarkNewTorrent {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" background-color: #ADAE9;\n" +
|
||||
"}\n" +
|
||||
".snarkAddInfo {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
@@ -568,19 +647,24 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"<body>\n";
|
||||
|
||||
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
|
||||
"<thead>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
|
||||
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</thead>\n";
|
||||
" <th align=\"right\" valign=\"top\">ETA</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Uploaded</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
|
||||
|
||||
private static final String TABLE_TOTAL = "<tfoot>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
|
||||
" <th> </th>\n" +
|
||||
" <th> </th>\n";
|
||||
|
||||
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"left\"" +
|
||||
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
|
||||
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";
|
||||
|
||||
private static final String TABLE_FOOTER = "</table>\n";
|
||||
|
||||
|
||||
@@ -112,6 +112,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 403 Bad Protocol\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: NON-HTTP PROTOCOL</H1>"+
|
||||
"The request uses a bad protocol. "+
|
||||
"The I2P HTTP Proxy supports http:// requests ONLY. Other protocols such as https:// and ftp:// are not allowed.<BR>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@@ -483,7 +493,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (method == null || destination == null) {
|
||||
l.log("No HTTP method found in the request.");
|
||||
if (out != null) {
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
if ("http://".equalsIgnoreCase(protocol))
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
else
|
||||
out.write(ERR_BAD_PROTOCOL);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
|
||||
@@ -27,8 +27,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
protected List dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
/** this is the pong response the client expects for their last ping. at least, i hope so... */
|
||||
private String _expectedPong;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
@@ -47,8 +45,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
notifyThis,
|
||||
"IRCHandler " + (++__clientId), tunnel);
|
||||
|
||||
_expectedPong = null;
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ",");
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
@@ -85,9 +81,10 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
try {
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
|
||||
in.start();
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@@ -123,10 +120,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote) {
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -153,7 +152,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("in: [" + inmsg + "]");
|
||||
String outmsg = inboundFilter(inmsg);
|
||||
String outmsg = inboundFilter(inmsg, expectedPong);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
@@ -195,10 +194,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote) {
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -225,7 +226,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("out: [" + inmsg + "]");
|
||||
String outmsg = outboundFilter(inmsg);
|
||||
String outmsg = outboundFilter(inmsg, expectedPong);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
@@ -264,7 +265,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
*
|
||||
*/
|
||||
|
||||
public String inboundFilter(String s) {
|
||||
public String inboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",4);
|
||||
String command;
|
||||
@@ -281,6 +282,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"PART",
|
||||
"WALLOPS",
|
||||
"ERROR",
|
||||
"KICK",
|
||||
"H", // "hide operator status" (after kicking an op)
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
@@ -311,9 +314,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
|
||||
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
|
||||
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
|
||||
// is null.
|
||||
String pong = _expectedPong;
|
||||
_expectedPong = null;
|
||||
// is blank.
|
||||
String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
|
||||
expectedPong.setLength(0);
|
||||
return pong;
|
||||
}
|
||||
|
||||
@@ -347,7 +350,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return null;
|
||||
}
|
||||
|
||||
public String outboundFilter(String s) {
|
||||
public String outboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",3);
|
||||
String command;
|
||||
@@ -397,24 +400,24 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
// Yuck.
|
||||
|
||||
String rv = null;
|
||||
expectedPong.setLength(0);
|
||||
if (field.length == 1) { // PING
|
||||
rv = "PING";
|
||||
_expectedPong = "PONG 127.0.0.1";
|
||||
expectedPong.append("PONG 127.0.0.1");
|
||||
} else if (field.length == 2) { // PING nonce
|
||||
rv = "PING " + field[1];
|
||||
_expectedPong = "PONG " + field[1];
|
||||
expectedPong.append("PONG ").append(field[1]);
|
||||
} else if (field.length == 3) { // PING nonce serverLocation
|
||||
rv = "PING " + field[1];
|
||||
_expectedPong = "PONG " + field[1];
|
||||
expectedPong.append("PONG ").append(field[1]);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
|
||||
rv = null;
|
||||
_expectedPong = null;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sending ping " + rv + ", waiting for " + _expectedPong + " orig was [" + s + "]");
|
||||
_log.warn("sending ping " + rv + ", waiting for " + expectedPong + " orig was [" + s + "]");
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@ public class IndexBean {
|
||||
}
|
||||
|
||||
private String saveChanges() {
|
||||
// Get current tunnel controller
|
||||
TunnelController cur = getController(_tunnel);
|
||||
|
||||
Properties config = getConfig();
|
||||
@@ -205,21 +206,28 @@ public class IndexBean {
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
// Only modify other shared tunnels
|
||||
// if the current tunnel is shared, and of supported type
|
||||
if ("true".equalsIgnoreCase(cur.getSharedClient()) &&
|
||||
("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType()))) {
|
||||
// all clients use the same I2CP session, and as such, use the same I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
|
||||
// Current tunnel modified by user, skip
|
||||
if (c == cur) continue;
|
||||
//only change when they really are declared of beeing a sharedClient
|
||||
if (("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType())||
|
||||
"client".equals(c.getType())
|
||||
) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
|
||||
// Only modify this non-current tunnel
|
||||
// if it belongs to a shared destination, and is of supported type
|
||||
if ("true".equalsIgnoreCase(c.getSharedClient()) &&
|
||||
("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType()) ||
|
||||
"client".equals(c.getType()))) {
|
||||
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelQuantity != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
|
||||
|
||||
@@ -37,6 +37,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _requireIntroductions;
|
||||
private boolean _hiddenMode;
|
||||
private boolean _dynamicKeys;
|
||||
private String _ntcpHostname;
|
||||
private String _ntcpPort;
|
||||
private String _tcpPort;
|
||||
private String _udpPort;
|
||||
private String _inboundRate;
|
||||
@@ -51,9 +53,7 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _ratesOnly;
|
||||
|
||||
protected void processForm() {
|
||||
if (_reseedRequested) {
|
||||
reseed();
|
||||
} else if (_saveRequested || ( (_action != null) && ("Save changes".equals(_action)) )) {
|
||||
if (_saveRequested || ( (_action != null) && ("Save changes".equals(_action)) )) {
|
||||
saveChanges();
|
||||
} else if (_recheckReachabilityRequested) {
|
||||
recheckReachability();
|
||||
@@ -62,7 +62,6 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public void setReseed(String moo) { _reseedRequested = true; }
|
||||
public void setSave(String moo) { _saveRequested = true; }
|
||||
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
@@ -78,6 +77,12 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setTcpPort(String port) {
|
||||
_tcpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
public void setNtcphost(String host) {
|
||||
_ntcpHostname = (host != null ? host.trim() : null);
|
||||
}
|
||||
public void setNtcpport(String port) {
|
||||
_ntcpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
public void setUdpPort(String port) {
|
||||
_udpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
@@ -99,95 +104,9 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setOutboundburstfactor(String factor) {
|
||||
_outboundBurst = (factor != null ? factor.trim() : null);
|
||||
}
|
||||
public void setReseedfrom(String url) {
|
||||
_reseedFrom = (url != null ? url.trim() : null);
|
||||
}
|
||||
public void setSharePercentage(String pct) {
|
||||
_sharePct = (pct != null ? pct.trim() : null);
|
||||
}
|
||||
|
||||
|
||||
private static final String DEFAULT_SEED_URL = ReseedHandler.DEFAULT_SEED_URL;
|
||||
/**
|
||||
* Reseed has been requested, so lets go ahead and do it. Fetch all of
|
||||
* the routerInfo-*.dat files from the specified URL (or the default) and
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private void reseed() {
|
||||
String seedURL = DEFAULT_SEED_URL;
|
||||
if (_reseedFrom != null)
|
||||
seedURL = _reseedFrom;
|
||||
try {
|
||||
URL dir = new URL(seedURL);
|
||||
String content = new String(readURL(dir));
|
||||
Set urls = new HashSet();
|
||||
int cur = 0;
|
||||
while (true) {
|
||||
int start = content.indexOf("href=\"routerInfo-", cur);
|
||||
if (start < 0)
|
||||
break;
|
||||
|
||||
int end = content.indexOf(".dat\">", start);
|
||||
String name = content.substring(start+"href=\"routerInfo-".length(), end);
|
||||
urls.add(name);
|
||||
cur = end + 1;
|
||||
}
|
||||
|
||||
int fetched = 0;
|
||||
int errors = 0;
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
addFormNotice("Reseeded with " + fetched + " peers (and " + errors + " failures)");
|
||||
} catch (Throwable t) {
|
||||
_context.logManager().getLog(ConfigNetHandler.class).error("Error reseeding", t);
|
||||
addFormError("Error reseeding (RESEED_EXCEPTION)");
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchSeed(String seedURL, String peer) throws Exception {
|
||||
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
|
||||
|
||||
byte data[] = readURL(url);
|
||||
writeSeed(peer, data);
|
||||
}
|
||||
|
||||
private byte[] readURL(URL url) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
URLConnection con = url.openConnection();
|
||||
InputStream in = con.getInputStream();
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
in.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private void writeSeed(String name, byte data[]) throws Exception {
|
||||
// props taken from KademliaNetworkDatabaseFacade...
|
||||
String dirName = _context.getProperty("router.networkDatabase.dbDir", "netDb");
|
||||
File netDbDir = new File(dirName);
|
||||
if (!netDbDir.exists()) {
|
||||
boolean ok = netDbDir.mkdirs();
|
||||
if (ok)
|
||||
addFormNotice("Network database directory created: " + dirName);
|
||||
else
|
||||
addFormNotice("Error creating network database directory: " + dirName);
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void recheckReachability() {
|
||||
_context.commSystem().recheckReachability();
|
||||
@@ -222,6 +141,30 @@ public class ConfigNetHandler extends FormHandler {
|
||||
restartRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (_ntcpHostname != null) && (_ntcpHostname.length() > 0) && (_ntcpPort != null) && (_ntcpPort.length() > 0) ) {
|
||||
String oldHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
|
||||
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
|
||||
if ( (oldHost == null) || (!oldHost.equalsIgnoreCase(_ntcpHostname)) ||
|
||||
(oldPort == null) || (!oldPort.equalsIgnoreCase(_ntcpPort)) ) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME, _ntcpHostname);
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT, _ntcpPort);
|
||||
addFormNotice("Updating inbound TCP settings from " + oldHost + ":" + oldPort
|
||||
+ " to " + _ntcpHostname + ":" + _ntcpPort);
|
||||
restartRequired = true;
|
||||
}
|
||||
} else {
|
||||
String oldHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
|
||||
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
|
||||
if ( (oldHost != null) || (oldPort != null) ) {
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
|
||||
addFormNotice("Updating inbound TCP settings from " + oldHost + ":" + oldPort
|
||||
+ " so that we no longer receive inbound TCP connections");
|
||||
restartRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (_udpPort != null) && (_udpPort.length() > 0) ) {
|
||||
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_UDP_PORT);
|
||||
if ( (oldPort == null) && (_udpPort.equals("8887")) ) {
|
||||
|
||||
@@ -48,6 +48,18 @@ public class ConfigNetHelper {
|
||||
}
|
||||
return "" + port;
|
||||
}
|
||||
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
|
||||
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
|
||||
public String getNtcphostname() {
|
||||
String hostname = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
|
||||
if (hostname == null) return "";
|
||||
return hostname;
|
||||
}
|
||||
public String getNtcpport() {
|
||||
String port = _context.getProperty(PROP_I2NP_NTCP_PORT);
|
||||
if (port == null) return "";
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getUdpAddress() {
|
||||
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
|
||||
@@ -22,6 +22,7 @@ import net.i2p.util.I2PThread;
|
||||
*
|
||||
*/
|
||||
public class ReseedHandler {
|
||||
|
||||
private static ReseedRunner _reseedRunner = new ReseedRunner();
|
||||
|
||||
public void setReseedNonce(String nonce) {
|
||||
@@ -66,7 +67,7 @@ public class ReseedHandler {
|
||||
*
|
||||
*/
|
||||
private static void reseed(boolean echoStatus) {
|
||||
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
String seedURL = I2PAppContext.getGlobalContext().getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
|
||||
seedURL = DEFAULT_SEED_URL;
|
||||
try {
|
||||
|
||||
@@ -60,6 +60,10 @@ public class StatSummarizer implements Runnable {
|
||||
",tunnel.buildExploratoryExpire.60000" +
|
||||
",client.sendAckTime.60000" +
|
||||
",client.dispatchNoACK.60000" +
|
||||
",ntcp.sendTime.60000" +
|
||||
",ntcp.transmitTime.60000" +
|
||||
",ntcp.sendBacklogTime.60000" +
|
||||
",ntcp.receiveTime.60000" +
|
||||
",transport.sendMessageFailureLifetime.60000" +
|
||||
",transport.sendProcessingTime.60000";
|
||||
|
||||
|
||||
@@ -107,7 +107,8 @@ public class SummaryHelper {
|
||||
}
|
||||
|
||||
public boolean allowReseed() {
|
||||
return (_context.netDb().getKnownRouters() < 30);
|
||||
return (_context.netDb().getKnownRouters() < 30) ||
|
||||
Boolean.valueOf(_context.getProperty("i2p.alwaysAllowReseed", "false")).booleanValue();
|
||||
}
|
||||
|
||||
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
|
||||
|
||||
@@ -66,6 +66,20 @@
|
||||
Users behind symmetric NATs, such as OpenBSD's pf, are not currently supported.</p>
|
||||
<input type="submit" name="recheckReachability" value="Check network reachability..." />
|
||||
<hr />
|
||||
<b>Inbound TCP connection configuration:</b><br />
|
||||
Externally reachable hostname or IP address:
|
||||
<input name ="ntcphost" type="text" size="16" value="<jsp:getProperty name="nethelper" property="ntcphostname" />" />
|
||||
(dyndns and the like are fine)<br />
|
||||
Externally reachable TCP port:
|
||||
<input name ="ntcpport" type="text" size="6" value="<jsp:getProperty name="nethelper" property="ntcpport" />" /><br />
|
||||
<p>You do <i>not</i> need to allow inbound TCP connections - outbound connections work with no
|
||||
configuration. However, if you want to receive inbound TCP connections, you <b>must</b> poke a hole
|
||||
in your NAT or firewall for unsolicited TCP connections. If you specify the wrong IP address or
|
||||
hostname, or do not properly configure your NAT or firewall, your network performance will degrade
|
||||
substantially. When in doubt, leave the hostname and port number blank.</p>
|
||||
<p><b>Note: changing this setting will terminate all of your connections and effectively
|
||||
restart your router.</b>
|
||||
<hr />
|
||||
<b>Dynamic Router Keys: </b>
|
||||
<input type="checkbox" name="dynamicKeys" value="true" <jsp:getProperty name="nethelper" property="dynamicKeysChecked" /> /><br />
|
||||
<p>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<b>Ident:</b> <jsp:getProperty name="helper" property="ident" /><br />
|
||||
<b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
|
||||
<b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
|
||||
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><br />
|
||||
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a><%
|
||||
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><!--<br />
|
||||
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a>--><%
|
||||
if (helper.updateAvailable()) {
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
|
||||
out.print("<br />" + update.getStatus());
|
||||
|
||||
@@ -247,6 +247,7 @@ public class Connection {
|
||||
}
|
||||
long now = _context.clock().now();
|
||||
if (_resetSentOn + 10*1000 > now) return; // don't send resets too fast
|
||||
if (_resetReceived) return;
|
||||
_resetSent = true;
|
||||
if (_resetSentOn <= 0)
|
||||
_resetSentOn = now;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.7 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
@@ -99,7 +99,6 @@ public class AddressbookBean
|
||||
return ConfigBean.addressbookPrefix + filename;
|
||||
}
|
||||
private Object[] entries;
|
||||
|
||||
public Object[] getEntries()
|
||||
{
|
||||
return entries;
|
||||
@@ -130,10 +129,59 @@ public class AddressbookBean
|
||||
public void setSerial(String serial) {
|
||||
this.serial = serial;
|
||||
}
|
||||
/** Load addressbook and apply filter, returning messages about this. */
|
||||
public String getLoadBookMessages()
|
||||
{
|
||||
// Config and addressbook now loaded here, hence not needed in getMessages()
|
||||
loadConfig();
|
||||
addressbook = new Properties();
|
||||
|
||||
String message = "";
|
||||
|
||||
try {
|
||||
addressbook.load( new FileInputStream( getFileName() ) );
|
||||
LinkedList list = new LinkedList();
|
||||
Enumeration e = addressbook.keys();
|
||||
while( e.hasMoreElements() ) {
|
||||
String name = (String)e.nextElement();
|
||||
String destination = addressbook.getProperty( name );
|
||||
if( filter != null && filter.length() > 0 ) {
|
||||
if( filter.compareTo( "0-9" ) == 0 ) {
|
||||
char first = name.charAt(0);
|
||||
if( first < '0' || first > '9' )
|
||||
continue;
|
||||
}
|
||||
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( search != null && search.length() > 0 ) {
|
||||
if( name.indexOf( search ) == -1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.addLast( new AddressBean( name, destination ) );
|
||||
}
|
||||
// Format a message about filtered addressbook size, and the number of displayed entries
|
||||
message = "Filtered list contains " + list.size() + " entries";
|
||||
if (list.size() > 300) message += ", displaying the first 300."; else message += ".";
|
||||
|
||||
Object array[] = list.toArray();
|
||||
Arrays.sort( array, sorter );
|
||||
entries = array;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
|
||||
if( message.length() > 0 )
|
||||
message = "<p>" + message + "</p>";
|
||||
return message;
|
||||
}
|
||||
/** Perform actions, returning messages about this. */
|
||||
public String getMessages()
|
||||
{
|
||||
loadConfig();
|
||||
|
||||
// Loading config and addressbook moved into getLoadBookMessages()
|
||||
String message = "";
|
||||
|
||||
if( action != null ) {
|
||||
@@ -175,42 +223,7 @@ public class AddressbookBean
|
||||
}
|
||||
|
||||
action = null;
|
||||
|
||||
addressbook = new Properties();
|
||||
|
||||
try {
|
||||
addressbook.load( new FileInputStream( getFileName() ) );
|
||||
LinkedList list = new LinkedList();
|
||||
Enumeration e = addressbook.keys();
|
||||
while( e.hasMoreElements() ) {
|
||||
String name = (String)e.nextElement();
|
||||
String destination = addressbook.getProperty( name );
|
||||
if( filter != null && filter.length() > 0 ) {
|
||||
if( filter.compareTo( "0-9" ) == 0 ) {
|
||||
char first = name.charAt(0);
|
||||
if( first < '0' || first > '9' )
|
||||
continue;
|
||||
}
|
||||
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( search != null && search.length() > 0 ) {
|
||||
if( name.indexOf( search ) == -1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.addLast( new AddressBean( name, destination ) );
|
||||
}
|
||||
|
||||
Object array[] = list.toArray();
|
||||
Arrays.sort( array, sorter );
|
||||
entries = array;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
|
||||
if( message.length() > 0 )
|
||||
message = "<p class=\"messages\">" + message + "</p>";
|
||||
return message;
|
||||
@@ -234,6 +247,10 @@ public class AddressbookBean
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "router" ) == 0;
|
||||
}
|
||||
public boolean isPublished()
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "published" ) == 0;
|
||||
}
|
||||
public void setFilter(String filter) {
|
||||
if( filter != null && ( filter.length() == 0 || filter.compareToIgnoreCase( "none" ) == 0 ) ) {
|
||||
filter = null;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
* $Revision: 1.2 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
@@ -60,6 +60,8 @@
|
||||
|
||||
<div id="messages">${book.messages}</div>
|
||||
|
||||
<span>${book.loadBookMessages}</span>
|
||||
|
||||
<div id="filter">
|
||||
<p>Filter: <a href="addressbook.jsp?filter=a">a</a>
|
||||
<a href="addressbook.jsp?filter=b">b</a>
|
||||
@@ -115,16 +117,17 @@
|
||||
<table class="book" cellspacing="0" cellpadding="5">
|
||||
<tr class="head">
|
||||
|
||||
<c:if test="${book.master || book.router}">
|
||||
<c:if test="${book.master || book.router || book.published}">
|
||||
<th> </th>
|
||||
</c:if>
|
||||
|
||||
<th>Name</th>
|
||||
<th>Destination</th>
|
||||
</tr>
|
||||
<c:forEach items="${book.entries}" var="addr">
|
||||
<!-- limit iterator to 300, or "Form too large" may result on submit -->
|
||||
<c:forEach items="${book.entries}" var="addr" begin="0" end="299">
|
||||
<tr class="list${book.trClass}">
|
||||
<c:if test="${book.master || book.router}">
|
||||
<c:if test="${book.master || book.router || book.published}">
|
||||
<td class="checkbox"><input type="checkbox" name="checked" value="${addr.name}" alt="Mark for deletion"></td>
|
||||
</c:if>
|
||||
<td class="names"><a href="http://${addr.name}/">${addr.name}</a> -
|
||||
@@ -136,7 +139,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<c:if test="${book.master || book.router}">
|
||||
<c:if test="${book.master || book.router || book.published}">
|
||||
<div id="buttons">
|
||||
<p class="buttons"><input type="image" name="action" value="delete" src="images/delete.png" alt="Delete checked" />
|
||||
</p>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.8 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
package i2p.susi.webmail.pop3;
|
||||
|
||||
@@ -373,8 +373,7 @@ public class POP3MailBox {
|
||||
}
|
||||
if (socket != null) {
|
||||
try {
|
||||
if (sendCmd1a("")
|
||||
&& sendCmd1a("USER " + user)
|
||||
if (sendCmd1a("USER " + user)
|
||||
&& sendCmd1a("PASS " + pass)
|
||||
&& sendCmd1a("STAT") ) {
|
||||
|
||||
|
||||
@@ -28,10 +28,13 @@ public class UpdaterServlet extends GenericServlet {
|
||||
super.init(config);
|
||||
} catch (ServletException exp) {
|
||||
}
|
||||
/*
|
||||
UpdaterThread thread = new UpdaterThread();
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
System.out.println("INFO: Starting Syndie Updater " + Updater.VERSION);
|
||||
*/
|
||||
System.out.println("INFO: Syndie Updater DISABLED. Use the new Syndie from http://syndie.i2p.net/");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,11 @@
|
||||
<a href="blogs.jsp">blogs</a></p>
|
||||
<p><a href="post.jsp">Create</a> a new post of your own</p>
|
||||
<p><a href="about.html">Learn more</a> about Syndie</p>
|
||||
<p><b>NOTE:</b> This version of Syndie is being replaced by
|
||||
<a href="http://syndie.i2p.net">the new Syndie</a>!
|
||||
The new Syndie is a standalone application under active development.
|
||||
Please give the new Syndie a try, as it has lots more traffic
|
||||
than this version. Don't expect anybody to see your posts here.</p>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
</body></html>
|
||||
|
||||
@@ -32,7 +32,7 @@ public class CPUID {
|
||||
* initialization? this would otherwise use the Log component, but this makes
|
||||
* it easier for other systems to reuse this class
|
||||
*/
|
||||
private static final boolean _doLog = true;
|
||||
private static final boolean _doLog = System.getProperty("jcpuid.dontLog") == null;
|
||||
|
||||
//.matches() is a java 1.4+ addition, using a simplified version for 1.3+
|
||||
//private static final boolean isX86 = System.getProperty("os.arch").toLowerCase().matches("i?[x0-9]86(_64)?");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gnu.crypto.hash;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: BaseHash.java,v 1.10 2005/10/06 04:24:14 rsdio Exp $
|
||||
// $Id: BaseHashStandalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
@@ -46,9 +46,9 @@ package gnu.crypto.hash;
|
||||
/**
|
||||
* <p>A base abstract class to facilitate hash implementations.</p>
|
||||
*
|
||||
* @version $Revision: 1.10 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public abstract class BaseHash implements IMessageDigest {
|
||||
public abstract class BaseHashStandalone implements IMessageDigestStandalone {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -78,7 +78,7 @@ public abstract class BaseHash implements IMessageDigest {
|
||||
* @param hashSize the block size of the output in bytes.
|
||||
* @param blockSize the block size of the internal transform.
|
||||
*/
|
||||
protected BaseHash(String name, int hashSize, int blockSize) {
|
||||
protected BaseHashStandalone(String name, int hashSize, int blockSize) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
@@ -95,7 +95,7 @@ public abstract class BaseHash implements IMessageDigest {
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// IMessageDigest interface implementation ---------------------------------
|
||||
// IMessageDigestStandalone interface implementation ---------------------------------
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
@@ -1,7 +1,7 @@
|
||||
package gnu.crypto.hash;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: IMessageDigest.java,v 1.11 2005/10/06 04:24:14 rsdio Exp $
|
||||
// $Id: IMessageDigestStandalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
@@ -49,9 +49,9 @@ package gnu.crypto.hash;
|
||||
* <p>A hash (or message digest) algorithm produces its output by iterating a
|
||||
* basic compression function on blocks of data.</p>
|
||||
*
|
||||
* @version $Revision: 1.11 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public interface IMessageDigest extends Cloneable {
|
||||
public interface IMessageDigestStandalone extends Cloneable {
|
||||
|
||||
// Constants
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -1,7 +1,7 @@
|
||||
package gnu.crypto.hash;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: Sha256Standalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
|
||||
// $Id: Sha256Standalone.java,v 1.2 2006/03/16 16:45:19 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2003 Free Software Foundation, Inc.
|
||||
//
|
||||
@@ -59,9 +59,9 @@ package gnu.crypto.hash;
|
||||
* renamed from Sha256 to avoid conflicts with JVMs using gnu-crypto as their JCE
|
||||
* provider.
|
||||
*
|
||||
* @version $Revision: 1.1 $
|
||||
* @version $Revision: 1.2 $
|
||||
*/
|
||||
public class Sha256Standalone extends BaseHash {
|
||||
public class Sha256Standalone extends BaseHashStandalone {
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
private static final int[] k = {
|
||||
@@ -143,7 +143,7 @@ public class Sha256Standalone extends BaseHash {
|
||||
return new Sha256Standalone(this);
|
||||
}
|
||||
|
||||
// Implementation of concrete methods in BaseHash --------------------------
|
||||
// Implementation of concrete methods in BaseHashStandalone --------------------------
|
||||
|
||||
private int transformResult[] = new int[8];
|
||||
protected void transform(byte[] in, int offset) {
|
||||
|
||||
175
core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
Normal file
175
core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
Normal file
@@ -0,0 +1,175 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* fortuna instance that tries to avoid blocking if at all possible by using separate
|
||||
* filled buffer segments rather than one buffer (and blocking when that buffer's data
|
||||
* has been eaten)
|
||||
*/
|
||||
public class AsyncFortunaStandalone extends FortunaStandalone implements Runnable {
|
||||
private static final int BUFFERS = 16;
|
||||
private static final int BUFSIZE = 256*1024;
|
||||
private final byte asyncBuffers[][] = new byte[BUFFERS][BUFSIZE];
|
||||
private final int status[] = new int[BUFFERS];
|
||||
private int nextBuf = 0;
|
||||
|
||||
private static final int STATUS_NEED_FILL = 0;
|
||||
private static final int STATUS_FILLING = 1;
|
||||
private static final int STATUS_FILLED = 2;
|
||||
private static final int STATUS_LIVE = 3;
|
||||
|
||||
public AsyncFortunaStandalone() {
|
||||
super();
|
||||
for (int i = 0; i < BUFFERS; i++)
|
||||
status[i] = STATUS_NEED_FILL;
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
Thread refillThread = new Thread(this, "PRNG");
|
||||
refillThread.setDaemon(true);
|
||||
refillThread.setPriority(Thread.MIN_PRIORITY+1);
|
||||
refillThread.start();
|
||||
}
|
||||
|
||||
/** the seed is only propogated once the prng is started with startup() */
|
||||
public void seed(byte val[]) {
|
||||
Map props = new HashMap(1);
|
||||
props.put(SEED, (Object)val);
|
||||
init(props);
|
||||
//fillBlock();
|
||||
}
|
||||
|
||||
protected void allocBuffer() {}
|
||||
|
||||
/**
|
||||
* make the next available filled buffer current, scheduling any unfilled
|
||||
* buffers for refill, and blocking until at least one buffer is ready
|
||||
*/
|
||||
protected void rotateBuffer() {
|
||||
synchronized (asyncBuffers) {
|
||||
// wait until we get some filled
|
||||
long before = System.currentTimeMillis();
|
||||
long waited = 0;
|
||||
while (status[nextBuf] != STATUS_FILLED) {
|
||||
//System.out.println(Thread.currentThread().getName() + ": Next PRNG buffer "
|
||||
// + nextBuf + " isn't ready (" + status[nextBuf] + ")");
|
||||
//new Exception("source").printStackTrace();
|
||||
asyncBuffers.notifyAll();
|
||||
try {
|
||||
asyncBuffers.wait();
|
||||
} catch (InterruptedException ie) {}
|
||||
waited = System.currentTimeMillis()-before;
|
||||
}
|
||||
if (waited > 10*1000)
|
||||
System.out.println(Thread.currentThread().getName() + ": Took " + waited
|
||||
+ "ms for a full PRNG buffer to be found");
|
||||
//System.out.println(Thread.currentThread().getName() + ": Switching to prng buffer " + nextBuf);
|
||||
buffer = asyncBuffers[nextBuf];
|
||||
status[nextBuf] = STATUS_LIVE;
|
||||
int prev=nextBuf-1;
|
||||
if (prev<0)
|
||||
prev = BUFFERS-1;
|
||||
if (status[prev] == STATUS_LIVE)
|
||||
status[prev] = STATUS_NEED_FILL;
|
||||
nextBuf++;
|
||||
if (nextBuf >= BUFFERS)
|
||||
nextBuf = 0;
|
||||
asyncBuffers.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
int toFill = -1;
|
||||
try {
|
||||
synchronized (asyncBuffers) {
|
||||
for (int i = 0; i < BUFFERS; i++) {
|
||||
if (status[i] == STATUS_NEED_FILL) {
|
||||
status[i] = STATUS_FILLING;
|
||||
toFill = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toFill == -1) {
|
||||
//System.out.println(Thread.currentThread().getName() + ": All pending buffers full");
|
||||
asyncBuffers.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
if (toFill != -1) {
|
||||
//System.out.println(Thread.currentThread().getName() + ": Filling prng buffer " + toFill);
|
||||
long before = System.currentTimeMillis();
|
||||
doFill(asyncBuffers[toFill]);
|
||||
long after = System.currentTimeMillis();
|
||||
synchronized (asyncBuffers) {
|
||||
status[toFill] = STATUS_FILLED;
|
||||
//System.out.println(Thread.currentThread().getName() + ": Prng buffer " + toFill + " filled after " + (after-before));
|
||||
asyncBuffers.notifyAll();
|
||||
}
|
||||
Thread.yield();
|
||||
long waitTime = (after-before)*5;
|
||||
if (waitTime <= 0) // somehow postman saw waitTime show up as negative
|
||||
waitTime = 50;
|
||||
try { Thread.sleep(waitTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void fillBlock()
|
||||
{
|
||||
rotateBuffer();
|
||||
}
|
||||
|
||||
private void doFill(byte buf[]) {
|
||||
long start = System.currentTimeMillis();
|
||||
if (pool0Count >= MIN_POOL_SIZE
|
||||
&& System.currentTimeMillis() - lastReseed > 100)
|
||||
{
|
||||
reseedCount++;
|
||||
//byte[] seed = new byte[0];
|
||||
for (int i = 0; i < NUM_POOLS; i++)
|
||||
{
|
||||
if (reseedCount % (1 << i) == 0) {
|
||||
generator.addRandomBytes(pools[i].digest());
|
||||
}
|
||||
}
|
||||
lastReseed = System.currentTimeMillis();
|
||||
}
|
||||
generator.nextBytes(buf);
|
||||
long now = System.currentTimeMillis();
|
||||
long diff = now-lastRefill;
|
||||
lastRefill = now;
|
||||
long refillTime = now-start;
|
||||
//System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
AsyncFortunaStandalone rand = new AsyncFortunaStandalone();
|
||||
|
||||
byte seed[] = new byte[1024];
|
||||
rand.seed(seed);
|
||||
System.out.println("Before starting prng");
|
||||
rand.startup();
|
||||
System.out.println("Starting prng, waiting 1 minute");
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
System.out.println("PRNG started, beginning test");
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
byte buf[] = new byte[1024];
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
rand.nextBytes(buf);
|
||||
gos.write(buf);
|
||||
}
|
||||
long after = System.currentTimeMillis();
|
||||
gos.finish();
|
||||
byte compressed[] = baos.toByteArray();
|
||||
System.out.println("Compressed size of 1MB: " + compressed.length + " took " + (after-before));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
try { Thread.sleep(5*60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ import java.util.Map;
|
||||
* Modified slightly by jrandom for I2P (removing unneeded exceptions)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public abstract class BasePRNGStandalone implements IRandom {
|
||||
public abstract class BasePRNGStandalone implements IRandomStandalone {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -88,7 +88,7 @@ public abstract class BasePRNGStandalone implements IRandom {
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// IRandom interface implementation ----------------------------------------
|
||||
// IRandomStandalone interface implementation ----------------------------------------
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
|
||||
@@ -97,20 +97,22 @@ import net.i2p.crypto.CryptixAESKeyCache;
|
||||
* gnu-crypto implementation, which has been imported into GNU/classpath
|
||||
*
|
||||
*/
|
||||
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListener
|
||||
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListenerStandalone
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 0xFACADE;
|
||||
|
||||
private static final int SEED_FILE_SIZE = 64;
|
||||
private static final int NUM_POOLS = 32;
|
||||
private static final int MIN_POOL_SIZE = 64;
|
||||
private final Generator generator;
|
||||
private final Sha256Standalone[] pools;
|
||||
private long lastReseed;
|
||||
private int pool;
|
||||
private int pool0Count;
|
||||
private int reseedCount;
|
||||
static final int NUM_POOLS = 32;
|
||||
static final int MIN_POOL_SIZE = 64;
|
||||
final Generator generator;
|
||||
final Sha256Standalone[] pools;
|
||||
long lastReseed;
|
||||
int pool;
|
||||
int pool0Count;
|
||||
int reseedCount;
|
||||
static long refillCount = 0;
|
||||
static long lastRefill = System.currentTimeMillis();
|
||||
|
||||
public static final String SEED = "gnu.crypto.prng.fortuna.seed";
|
||||
|
||||
@@ -124,6 +126,9 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
lastReseed = 0;
|
||||
pool = 0;
|
||||
pool0Count = 0;
|
||||
allocBuffer();
|
||||
}
|
||||
protected void allocBuffer() {
|
||||
buffer = new byte[4*1024*1024]; //256]; // larger buffer to reduce churn
|
||||
}
|
||||
|
||||
@@ -145,6 +150,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
|
||||
public void fillBlock()
|
||||
{
|
||||
long start = System.currentTimeMillis();
|
||||
if (pool0Count >= MIN_POOL_SIZE
|
||||
&& System.currentTimeMillis() - lastReseed > 100)
|
||||
{
|
||||
@@ -159,6 +165,11 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
lastReseed = System.currentTimeMillis();
|
||||
}
|
||||
generator.nextBytes(buffer);
|
||||
long now = System.currentTimeMillis();
|
||||
long diff = now-lastRefill;
|
||||
lastRefill = now;
|
||||
long refillTime = now-start;
|
||||
System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
|
||||
}
|
||||
|
||||
public void addRandomByte(byte b)
|
||||
@@ -177,7 +188,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
pool = (pool + 1) % NUM_POOLS;
|
||||
}
|
||||
|
||||
public void addRandomEvent(RandomEvent event)
|
||||
public void addRandomEvent(RandomEventStandalone event)
|
||||
{
|
||||
if (event.getPoolNumber() < 0 || event.getPoolNumber() >= pools.length)
|
||||
throw new IllegalArgumentException("pool number out of range: "
|
||||
@@ -338,6 +349,34 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
byte in[] = new byte[16];
|
||||
byte out[] = new byte[16];
|
||||
byte key[] = new byte[32];
|
||||
try {
|
||||
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
|
||||
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
|
||||
long beforeAll = System.currentTimeMillis();
|
||||
for (int i = 0; i < 256; i++) {
|
||||
//long before =System.currentTimeMillis();
|
||||
for (int j = 0; j < 1024; j++)
|
||||
CryptixRijndael_Algorithm.blockEncrypt(in, out, 0, 0, cryptixKey);
|
||||
//long after = System.currentTimeMillis();
|
||||
//System.out.println("encrypting 16KB took " + (after-before));
|
||||
}
|
||||
long after = System.currentTimeMillis();
|
||||
System.out.println("encrypting 4MB took " + (after-beforeAll));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
try {
|
||||
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
|
||||
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
|
||||
byte data[] = new byte[4*1024*1024];
|
||||
long beforeAll = System.currentTimeMillis();
|
||||
//CryptixRijndael_Algorithm.ecbBulkEncrypt(data, data, cryptixKey);
|
||||
long after = System.currentTimeMillis();
|
||||
System.out.println("encrypting 4MB took " + (after-beforeAll));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
/*
|
||||
FortunaStandalone f = new FortunaStandalone();
|
||||
java.util.HashMap props = new java.util.HashMap();
|
||||
byte initSeed[] = new byte[1234];
|
||||
@@ -351,5 +390,6 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
}
|
||||
long time = System.currentTimeMillis() - before;
|
||||
System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps");
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: IRandom.java,v 1.12 2005/10/06 04:24:17 rsdio Exp $
|
||||
// $Id: IRandomStandalone.java,v 1.1 2005/10/22 13:10:00 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
|
||||
//
|
||||
@@ -82,9 +82,9 @@ import java.util.Map;
|
||||
* Menezes, A., van Oorschot, P. and S. Vanstone.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @version $Revision: 1.12 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public interface IRandom extends Cloneable {
|
||||
public interface IRandomStandalone extends Cloneable {
|
||||
|
||||
// Constants
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -110,32 +110,32 @@ public interface IRandom extends Cloneable {
|
||||
void init(Map attributes);
|
||||
|
||||
/**
|
||||
* <p>Returns the next 8 bits of random data generated from this instance.</p>
|
||||
*
|
||||
* @return the next 8 bits of random data generated from this instance.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitReachedException if this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
byte nextByte() throws IllegalStateException, LimitReachedException;
|
||||
* <p>Returns the next 8 bits of random data generated from this instance.</p>
|
||||
*
|
||||
* @return the next 8 bits of random data generated from this instance.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimLimitReachedExceptionStandalone this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
byte nextByte() throws IllegalStateException, LimitReachedExceptionStandalone;
|
||||
|
||||
/**
|
||||
* <p>Fills the designated byte array, starting from byte at index
|
||||
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
|
||||
* output of this generator instance.
|
||||
*
|
||||
* @param out the placeholder to contain the generated random bytes.
|
||||
* @param offset the starting index in <i>out</i> to consider. This method
|
||||
* does nothing if this parameter is not within <code>0</code> and
|
||||
* <code>out.length</code>.
|
||||
* @param length the maximum number of required random bytes. This method
|
||||
* does nothing if this parameter is less than <code>1</code>.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitReachedException if this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
* <p>Fills the designated byte array, starting from byte at index
|
||||
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
|
||||
* output of this generator instance.
|
||||
*
|
||||
* @param out the placeholder to contain the generated random bytes.
|
||||
* @param offset the starting index in <i>out</i> to consider. This method
|
||||
* does nothing if this parameter is not within <code>0</code> and
|
||||
* <code>out.length</code>.
|
||||
* @param length the maximum number of required random bytes. This method
|
||||
* does nothing if this parameter is less than <code>1</code>.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitLimitReachedExceptionStandalonehis instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
void nextBytes(byte[] out, int offset, int length)
|
||||
throws IllegalStateException, LimitReachedException;
|
||||
throws IllegalStateException, LimitReachedExceptionStandalone;
|
||||
|
||||
/**
|
||||
* <p>Supplement, or possibly replace, the random state of this PRNG with
|
||||
@@ -1,7 +1,7 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: LimitReachedException.java,v 1.5 2005/10/06 04:24:17 rsdio Exp $
|
||||
// $Id: LimitReachedExceptionStandalone.java,v 1.1 2005/10/22 13:10:00 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
@@ -47,9 +47,9 @@ package gnu.crypto.prng;
|
||||
* A checked exception that indicates that a pseudo random number generated has
|
||||
* reached its theoretical limit in generating random bytes.
|
||||
*
|
||||
* @version $Revision: 1.5 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class LimitReachedException extends Exception {
|
||||
public class LimitReachedExceptionStandalone extends Exception {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -57,11 +57,11 @@ public class LimitReachedException extends Exception {
|
||||
// Constructor(s)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public LimitReachedException() {
|
||||
public LimitReachedExceptionStandalone() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LimitReachedException(String msg) {
|
||||
public LimitReachedExceptionStandalone(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* RandomEventListener.java -- event listener
|
||||
/* RandomEventListenerStandalone.java -- event listener
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
@@ -47,7 +47,7 @@ import java.util.EventListener;
|
||||
* An interface for entropy accumulators that will be notified of random
|
||||
* events.
|
||||
*/
|
||||
public interface RandomEventListener extends EventListener
|
||||
public interface RandomEventListenerStandalone extends EventListener
|
||||
{
|
||||
void addRandomEvent(RandomEvent event);
|
||||
void addRandomEvent(RandomEventStandalone event);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* RandomEvent.java -- a random event.
|
||||
/* RandomEventStandalone.java -- a random event.
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
@@ -47,14 +47,14 @@ import java.util.EventObject;
|
||||
* An interface for entropy accumulators that will be notified of random
|
||||
* events.
|
||||
*/
|
||||
public class RandomEvent extends EventObject
|
||||
public class RandomEventStandalone extends EventObject
|
||||
{
|
||||
|
||||
private final byte sourceNumber;
|
||||
private final byte poolNumber;
|
||||
private final byte[] data;
|
||||
|
||||
public RandomEvent(Object source, byte sourceNumber, byte poolNumber,
|
||||
public RandomEventStandalone(Object source, byte sourceNumber, byte poolNumber,
|
||||
byte[] data)
|
||||
{
|
||||
super(source);
|
||||
@@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.62 $ $Date: 2006-05-18 17:31:09 $";
|
||||
public final static String VERSION = "0.6.1.20";
|
||||
public final static String ID = "$Revision: 1.69 $ $Date: 2006-10-08 20:44:55 $";
|
||||
public final static String VERSION = "0.6.1.27";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
||||
@@ -14,6 +14,7 @@ import net.i2p.crypto.DummyElGamalEngine;
|
||||
import net.i2p.crypto.DummyPooledRandomSource;
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.crypto.ElGamalEngine;
|
||||
import net.i2p.crypto.HMAC256Generator;
|
||||
import net.i2p.crypto.HMACGenerator;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.PersistentSessionKeyManager;
|
||||
@@ -67,8 +68,9 @@ public class I2PAppContext {
|
||||
private AESEngine _AESEngine;
|
||||
private LogManager _logManager;
|
||||
private HMACGenerator _hmac;
|
||||
private HMAC256Generator _hmac256;
|
||||
private SHA256Generator _sha;
|
||||
private Clock _clock;
|
||||
protected Clock _clock; // overridden in RouterContext
|
||||
private DSAEngine _dsa;
|
||||
private RoutingKeyGenerator _routingKeyGenerator;
|
||||
private RandomSource _random;
|
||||
@@ -82,8 +84,9 @@ public class I2PAppContext {
|
||||
private volatile boolean _AESEngineInitialized;
|
||||
private volatile boolean _logManagerInitialized;
|
||||
private volatile boolean _hmacInitialized;
|
||||
private volatile boolean _hmac256Initialized;
|
||||
private volatile boolean _shaInitialized;
|
||||
private volatile boolean _clockInitialized;
|
||||
protected volatile boolean _clockInitialized; // used in RouterContext
|
||||
private volatile boolean _dsaInitialized;
|
||||
private volatile boolean _routingKeyGeneratorInitialized;
|
||||
private volatile boolean _randomInitialized;
|
||||
@@ -353,6 +356,19 @@ public class I2PAppContext {
|
||||
_hmacInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public HMAC256Generator hmac256() {
|
||||
if (!_hmac256Initialized) initializeHMAC256();
|
||||
return _hmac256;
|
||||
}
|
||||
private void initializeHMAC256() {
|
||||
synchronized (this) {
|
||||
if (_hmac256 == null) {
|
||||
_hmac256 = new HMAC256Generator(this);
|
||||
}
|
||||
_hmac256Initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our SHA256 instance (see the hmac discussion for why its context specific)
|
||||
@@ -411,11 +427,11 @@ public class I2PAppContext {
|
||||
* enable simulators to play with clock skew among different instances.
|
||||
*
|
||||
*/
|
||||
public Clock clock() {
|
||||
public Clock clock() { // overridden in RouterContext
|
||||
if (!_clockInitialized) initializeClock();
|
||||
return _clock;
|
||||
}
|
||||
private void initializeClock() {
|
||||
protected void initializeClock() { // overridden in RouterContext
|
||||
synchronized (this) {
|
||||
if (_clock == null)
|
||||
_clock = new Clock(this);
|
||||
|
||||
@@ -320,9 +320,9 @@ public class DHSessionKeyBuilder {
|
||||
if (_myPrivateValue == null) generateMyValue();
|
||||
_sessionKey = calculateSessionKey(_myPrivateValue, _peerValue);
|
||||
} else {
|
||||
System.err.println("Not ready yet.. privateValue and peerValue must be set ("
|
||||
+ (_myPrivateValue != null ? "set" : "null") + ","
|
||||
+ (_peerValue != null ? "set" : "null") + ")");
|
||||
//System.err.println("Not ready yet.. privateValue and peerValue must be set ("
|
||||
// + (_myPrivateValue != null ? "set" : "null") + ","
|
||||
// + (_peerValue != null ? "set" : "null") + ")");
|
||||
}
|
||||
return _sessionKey;
|
||||
}
|
||||
|
||||
51
core/java/src/net/i2p/crypto/HMAC256Generator.java
Normal file
51
core/java/src/net/i2p/crypto/HMAC256Generator.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
|
||||
/**
|
||||
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
|
||||
* in {@link org.bouncycastle.crypto.macs.HMac} and
|
||||
* {@link org.bouncycastle.crypto.digests.MD5Digest}.
|
||||
*
|
||||
*/
|
||||
public class HMAC256Generator extends HMACGenerator {
|
||||
public HMAC256Generator(I2PAppContext context) { super(context); }
|
||||
|
||||
protected HMac acquire() {
|
||||
synchronized (_available) {
|
||||
if (_available.size() > 0)
|
||||
return (HMac)_available.remove(0);
|
||||
}
|
||||
// the HMAC is hardcoded to use SHA256 digest size
|
||||
// for backwards compatability. next time we have a backwards
|
||||
// incompatible change, we should update this by removing ", 32"
|
||||
return new HMac(new Sha256ForMAC());
|
||||
}
|
||||
|
||||
private class Sha256ForMAC extends Sha256Standalone implements Digest {
|
||||
public String getAlgorithmName() { return "sha256 for hmac"; }
|
||||
public int getDigestSize() { return 32; }
|
||||
public int doFinal(byte[] out, int outOff) {
|
||||
byte rv[] = digest();
|
||||
System.arraycopy(rv, 0, out, outOff, rv.length);
|
||||
reset();
|
||||
return rv.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
byte data[] = new byte[64];
|
||||
ctx.random().nextBytes(data);
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
Hash mac = ctx.hmac256().calculate(key, data);
|
||||
System.out.println(Base64.encode(mac.getData()));
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import org.bouncycastle.crypto.macs.HMac;
|
||||
public class HMACGenerator {
|
||||
private I2PAppContext _context;
|
||||
/** set of available HMAC instances for calculate */
|
||||
private List _available;
|
||||
protected List _available;
|
||||
/** set of available byte[] buffers for verify */
|
||||
private List _availableTmp;
|
||||
|
||||
@@ -85,7 +85,7 @@ public class HMACGenerator {
|
||||
return eq;
|
||||
}
|
||||
|
||||
private HMac acquire() {
|
||||
protected HMac acquire() {
|
||||
synchronized (_available) {
|
||||
if (_available.size() > 0)
|
||||
return (HMac)_available.remove(0);
|
||||
|
||||
@@ -9,10 +9,13 @@ package net.i2p.crypto;
|
||||
*
|
||||
*/
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
@@ -53,6 +56,18 @@ public class KeyGenerator {
|
||||
return key;
|
||||
}
|
||||
|
||||
private static final int PBE_ROUNDS = 1000;
|
||||
/** PBE the passphrase with the salt */
|
||||
public SessionKey generateSessionKey(byte salt[], byte passphrase[]) {
|
||||
byte salted[] = new byte[16+passphrase.length];
|
||||
System.arraycopy(salt, 0, salted, 0, Math.min(salt.length, 16));
|
||||
System.arraycopy(passphrase, 0, salted, 16, passphrase.length);
|
||||
byte h[] = _context.sha().calculateHash(salted).getData();
|
||||
for (int i = 1; i < PBE_ROUNDS; i++)
|
||||
_context.sha().calculateHash(h, 0, Hash.HASH_LENGTH, h, 0);
|
||||
return new SessionKey(h);
|
||||
}
|
||||
|
||||
/** standard exponent size */
|
||||
private static final int PUBKEY_EXPONENT_SIZE_FULL = 2048;
|
||||
/**
|
||||
@@ -95,7 +110,7 @@ public class KeyGenerator {
|
||||
* @return the corresponding PublicKey object
|
||||
*/
|
||||
public static PublicKey getPublicKey(PrivateKey priv) {
|
||||
BigInteger a = new NativeBigInteger(priv.toByteArray());
|
||||
BigInteger a = new NativeBigInteger(1, priv.toByteArray());
|
||||
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
|
||||
PublicKey pub = new PublicKey();
|
||||
byte [] pubBytes = aalpha.toByteArray();
|
||||
@@ -132,7 +147,7 @@ public class KeyGenerator {
|
||||
* @return a SigningPublicKey object
|
||||
*/
|
||||
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
|
||||
BigInteger x = new NativeBigInteger(priv.toByteArray());
|
||||
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
|
||||
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
byte [] pubBytes = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES);
|
||||
|
||||
@@ -67,7 +67,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
_log = context.logManager().getLog(TransientSessionKeyManager.class);
|
||||
_context = context;
|
||||
_outboundSessions = new HashMap(1024);
|
||||
_inboundTagSets = new HashMap(64*1024);
|
||||
_inboundTagSets = new HashMap(1024);
|
||||
context.statManager().createRateStat("crypto.sessionTagsExpired", "How many tags/sessions are expired?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
|
||||
context.statManager().createRateStat("crypto.sessionTagsRemaining", "How many tags/sessions are remaining after a cleanup?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
|
||||
SimpleTimer.getInstance().addEvent(new CleanupEvent(), 60*1000);
|
||||
|
||||
@@ -9,6 +9,7 @@ package net.i2p.data;
|
||||
*
|
||||
*/
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -770,9 +771,11 @@ public class DataHelper {
|
||||
* Read a newline delimited line from the stream, returning the line (without
|
||||
* the newline), or null if EOF reached before the newline was found
|
||||
*/
|
||||
public static String readLine(InputStream in) throws IOException {
|
||||
public static String readLine(InputStream in) throws IOException { return readLine(in, (Sha256Standalone)null); }
|
||||
/** update the hash along the way */
|
||||
public static String readLine(InputStream in, Sha256Standalone hash) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
boolean ok = readLine(in, buf);
|
||||
boolean ok = readLine(in, buf, hash);
|
||||
if (ok)
|
||||
return buf.toString();
|
||||
else
|
||||
@@ -785,8 +788,13 @@ public class DataHelper {
|
||||
* newline was found
|
||||
*/
|
||||
public static boolean readLine(InputStream in, StringBuffer buf) throws IOException {
|
||||
return readLine(in, buf, null);
|
||||
}
|
||||
/** update the hash along the way */
|
||||
public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
|
||||
int c = -1;
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (hash != null) hash.update((byte)c);
|
||||
if (c == '\n')
|
||||
break;
|
||||
buf.append((char)c);
|
||||
@@ -797,6 +805,10 @@ public class DataHelper {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException {
|
||||
hash.update(data);
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
public static List sortStructures(Collection dataStructures) {
|
||||
if (dataStructures == null) return new ArrayList();
|
||||
@@ -822,6 +834,8 @@ public class DataHelper {
|
||||
return (ms / (60 * 1000)) + "m";
|
||||
} else if (ms < 3 * 24 * 60 * 60 * 1000) {
|
||||
return (ms / (60 * 60 * 1000)) + "h";
|
||||
} else if (ms > 365l * 24l * 60l * 60l * 1000l) {
|
||||
return "n/a";
|
||||
} else {
|
||||
return (ms / (24 * 60 * 60 * 1000)) + "d";
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public class PrivateKey extends DataStructureImpl {
|
||||
public PrivateKey() {
|
||||
setData(null);
|
||||
}
|
||||
public PrivateKey(byte data[]) { setData(data); }
|
||||
|
||||
/** constructs from base64
|
||||
* @param base64Data a string of base64 data (the output of .toBase64() called
|
||||
|
||||
@@ -31,6 +31,11 @@ public class PublicKey extends DataStructureImpl {
|
||||
public PublicKey() {
|
||||
setData(null);
|
||||
}
|
||||
public PublicKey(byte data[]) {
|
||||
if ( (data == null) || (data.length != KEYSIZE_BYTES) )
|
||||
throw new IllegalArgumentException("Data must be specified, and the correct size");
|
||||
setData(data);
|
||||
}
|
||||
|
||||
/** constructs from base64
|
||||
* @param base64Data a string of base64 data (the output of .toBase64() called
|
||||
|
||||
@@ -86,7 +86,7 @@ public class RouterIdentity extends DataStructureImpl {
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ((_certificate == null) || (_publicKey == null) || (_signingKey == null))
|
||||
throw new DataFormatException("Not enough data to format the destination");
|
||||
throw new DataFormatException("Not enough data to format the router identity");
|
||||
_publicKey.writeBytes(out);
|
||||
_signingKey.writeBytes(out);
|
||||
_certificate.writeBytes(out);
|
||||
|
||||
@@ -53,6 +53,9 @@ public class RouterInfo extends DataStructureImpl {
|
||||
public static final String PROP_CAPABILITIES = "caps";
|
||||
public static final char CAPABILITY_HIDDEN = 'H';
|
||||
|
||||
// Public string of chars which serve as bandwidth capacity markers
|
||||
// NOTE: individual chars defined in Router.java
|
||||
public static final String BW_CAPABILITY_CHARS = "KLMNO";
|
||||
|
||||
public RouterInfo() {
|
||||
setIdentity(null);
|
||||
@@ -179,6 +182,12 @@ public class RouterInfo extends DataStructureImpl {
|
||||
return (Properties) _options.clone();
|
||||
}
|
||||
}
|
||||
public String getOption(String opt) {
|
||||
if (_options == null) return null;
|
||||
synchronized (_options) {
|
||||
return _options.getProperty(opt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a set of options or statistics that the router can expose
|
||||
@@ -334,6 +343,24 @@ public class RouterInfo extends DataStructureImpl {
|
||||
return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of this node's bandwidth tier,
|
||||
* or "Unknown"
|
||||
*/
|
||||
public String getBandwidthTier() {
|
||||
String bwTiers = BW_CAPABILITY_CHARS;
|
||||
String bwTier = "Unknown";
|
||||
String capabilities = getCapabilities();
|
||||
// Iterate through capabilities, searching for known bandwidth tier
|
||||
for (int i = 0; i < capabilities.length(); i++) {
|
||||
if (bwTiers.indexOf(String.valueOf(capabilities.charAt(i))) != -1) {
|
||||
bwTier = String.valueOf(capabilities.charAt(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (bwTier);
|
||||
}
|
||||
|
||||
public void addCapability(char cap) {
|
||||
if (_options == null) _options = new OrderedProperties();
|
||||
synchronized (_options) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class StatManager {
|
||||
_log = context.logManager().getLog(StatManager.class);
|
||||
_context = context;
|
||||
_frequencyStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_rateStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_rateStats = new HashMap(128); // synchronized only on add //Collections.synchronizedMap(new HashMap(128));
|
||||
_statLog = new BufferedStatLog(context);
|
||||
}
|
||||
|
||||
@@ -80,10 +80,12 @@ public class StatManager {
|
||||
* @param periods array of period lengths (in milliseconds)
|
||||
*/
|
||||
public void createRateStat(String name, String description, String group, long periods[]) {
|
||||
if (_rateStats.containsKey(name)) return;
|
||||
RateStat rs = new RateStat(name, description, group, periods);
|
||||
if (_statLog != null) rs.setStatLog(_statLog);
|
||||
_rateStats.put(name, rs);
|
||||
synchronized (_rateStats) {
|
||||
if (_rateStats.containsKey(name)) return;
|
||||
RateStat rs = new RateStat(name, description, group, periods);
|
||||
if (_statLog != null) rs.setStatLog(_statLog);
|
||||
_rateStats.put(name, rs);
|
||||
}
|
||||
}
|
||||
|
||||
/** update the given frequency statistic, taking note that an event occurred (and recalculating all frequencies) */
|
||||
@@ -94,7 +96,7 @@ public class StatManager {
|
||||
|
||||
/** update the given rate statistic, taking note that the given data point was received (and recalculating all rates) */
|
||||
public void addRateData(String name, long data, long eventDuration) {
|
||||
RateStat stat = (RateStat) _rateStats.get(name);
|
||||
RateStat stat = (RateStat) _rateStats.get(name); // unsynchronized
|
||||
if (stat != null) stat.addData(data, eventDuration);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,11 +112,19 @@ public class NtpClient {
|
||||
|
||||
// Process response
|
||||
NtpMessage msg = new NtpMessage(packet.getData());
|
||||
|
||||
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
|
||||
(msg.receiveTimestamp-msg.transmitTimestamp);
|
||||
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
|
||||
(msg.transmitTimestamp - destinationTimestamp)) / 2;
|
||||
socket.close();
|
||||
|
||||
// Stratum must be between 1 (atomic) and 15 (maximum defined value)
|
||||
// Anything else is right out, treat such responses like errors
|
||||
if ((msg.stratum < 1) || (msg.stratum > 15)) {
|
||||
//System.out.println("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
long rv = (long)(System.currentTimeMillis() + localClockOffset*1000);
|
||||
//System.out.println("host: " + address.getHostAddress() + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
|
||||
|
||||
@@ -26,7 +26,7 @@ public class Timestamper implements Runnable {
|
||||
private boolean _initialized;
|
||||
|
||||
private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final String DEFAULT_SERVER_LIST = "pool.ntp.org, pool.ntp.org, pool.ntp.org";
|
||||
private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org, 1.pool.ntp.org, 2.pool.ntp.org";
|
||||
private static final boolean DEFAULT_DISABLED = true;
|
||||
/** how many times do we have to query if we are changing the clock? */
|
||||
private static final int DEFAULT_CONCURRING_SERVERS = 3;
|
||||
|
||||
@@ -13,12 +13,16 @@ import net.i2p.time.Timestamper;
|
||||
* between the local computer's current time and the time as known by some reference
|
||||
* (such as an NTP synchronized clock).
|
||||
*
|
||||
* Protected members are used in the subclass RouterClock,
|
||||
* which has access to a router's transports (particularly peer clock skews)
|
||||
* to second-guess the sanity of clock adjustments.
|
||||
*
|
||||
*/
|
||||
public class Clock implements Timestamper.UpdateListener {
|
||||
private I2PAppContext _context;
|
||||
protected I2PAppContext _context;
|
||||
private Timestamper _timestamper;
|
||||
private long _startedOn;
|
||||
private boolean _statCreated;
|
||||
protected long _startedOn;
|
||||
protected boolean _statCreated;
|
||||
|
||||
public Clock(I2PAppContext context) {
|
||||
_context = context;
|
||||
@@ -36,10 +40,10 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
public Timestamper getTimestamper() { return _timestamper; }
|
||||
|
||||
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
|
||||
private Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
protected Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
|
||||
private volatile long _offset;
|
||||
private boolean _alreadyChanged;
|
||||
protected volatile long _offset;
|
||||
protected boolean _alreadyChanged;
|
||||
private Set _listeners;
|
||||
|
||||
/** if the clock is skewed by 3+ days, fuck 'em */
|
||||
@@ -132,7 +136,7 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void fireOffsetChanged(long delta) {
|
||||
protected void fireOffsetChanged(long delta) {
|
||||
synchronized (_listeners) {
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext();) {
|
||||
ClockUpdateListener lsnr = (ClockUpdateListener) iter.next();
|
||||
|
||||
@@ -13,6 +13,7 @@ import net.i2p.util.Log;
|
||||
public class EepPost {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private static final String CRLF = "\r\n";
|
||||
|
||||
public EepPost() {
|
||||
this(I2PAppContext.getGlobalContext());
|
||||
@@ -65,6 +66,7 @@ public class EepPost {
|
||||
_onCompletion = onCompletion;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Running the post task");
|
||||
Socket s = null;
|
||||
try {
|
||||
URL u = new URL(_url);
|
||||
@@ -81,17 +83,20 @@ public class EepPost {
|
||||
_proxyPort = p;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Connecting to the server/proxy...");
|
||||
s = new Socket(_proxyHost, _proxyPort);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Connected");
|
||||
OutputStream out = s.getOutputStream();
|
||||
String sep = getSeparator();
|
||||
long length = calcContentLength(sep, _fields);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Separator: " + sep + " content length: " + length);
|
||||
String header = getHeader(isProxy, path, h, p, sep, length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Header: \n" + header);
|
||||
out.write(header.getBytes());
|
||||
out.flush();
|
||||
if (false) {
|
||||
out.write(("--" + sep + "\ncontent-disposition: form-data; name=\"field1\"\n\nStuff goes here\n--" + sep + "--\n").getBytes());
|
||||
out.write(("--" + sep + CRLF + "content-disposition: form-data; name=\"field1\"" + CRLF + CRLF + "Stuff goes here" + CRLF + "--" + sep + "--" + CRLF).getBytes());
|
||||
} else {
|
||||
sendFields(out, sep, _fields);
|
||||
}
|
||||
@@ -121,18 +126,18 @@ public class EepPost {
|
||||
Object val = fields.get(key);
|
||||
if (val instanceof File) {
|
||||
File f = (File)val;
|
||||
len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"\n").length();
|
||||
len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"" + CRLF).length();
|
||||
//len += ("Content-length: " + f.length() + "\n").length();
|
||||
len += ("Content-Type: application/octet-stream\n\n").length();
|
||||
len += ("Content-Type: application/octet-stream" + CRLF + CRLF).length();
|
||||
len += f.length();
|
||||
len += 1; // nl
|
||||
len += CRLF.length(); // nl
|
||||
} else {
|
||||
len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"\n\n").length();
|
||||
len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"" + CRLF + CRLF).length();
|
||||
len += val.toString().length();
|
||||
len += 1; // nl
|
||||
len += CRLF.length(); // nl
|
||||
}
|
||||
}
|
||||
len += 2 + sep.length() + 2;
|
||||
len += 2 + sep.length() + 2 + CRLF.length(); //2 + sep.length() + 2;
|
||||
//len += 2;
|
||||
return len;
|
||||
}
|
||||
@@ -145,29 +150,29 @@ public class EepPost {
|
||||
else
|
||||
sendField(out, separator, field, val.toString());
|
||||
}
|
||||
out.write(("--" + separator + "--\n").getBytes());
|
||||
out.write(("--" + separator + "--" + CRLF).getBytes());
|
||||
}
|
||||
|
||||
private void sendFile(OutputStream out, String separator, String field, File file) throws IOException {
|
||||
long len = file.length();
|
||||
out.write(("--" + separator + "\n").getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"\n").getBytes());
|
||||
out.write(("--" + separator + CRLF).getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"" + CRLF).getBytes());
|
||||
//out.write(("Content-length: " + len + "\n").getBytes());
|
||||
out.write(("Content-Type: application/octet-stream\n\n").getBytes());
|
||||
out.write(("Content-Type: application/octet-stream" + CRLF + CRLF).getBytes());
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
byte buf[] = new byte[1024];
|
||||
int read = -1;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
out.write("\n".getBytes());
|
||||
out.write(CRLF.getBytes());
|
||||
in.close();
|
||||
}
|
||||
|
||||
private void sendField(OutputStream out, String separator, String field, String val) throws IOException {
|
||||
out.write(("--" + separator + "\n").getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"\n\n").getBytes());
|
||||
out.write(("--" + separator + CRLF).getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"" + CRLF + CRLF).getBytes());
|
||||
out.write(val.getBytes());
|
||||
out.write("\n".getBytes());
|
||||
out.write(CRLF.getBytes());
|
||||
}
|
||||
|
||||
private String getHeader(boolean isProxy, String path, String host, int port, String separator, long length) {
|
||||
@@ -179,16 +184,16 @@ public class EepPost {
|
||||
buf.append(":").append(port);
|
||||
}
|
||||
buf.append(path);
|
||||
buf.append(" HTTP/1.1\n");
|
||||
buf.append(" HTTP/1.1" + CRLF);
|
||||
buf.append("Host: ").append(host);
|
||||
if (port != 80)
|
||||
buf.append(":").append(port);
|
||||
buf.append("\n");
|
||||
buf.append("Connection: close\n");
|
||||
buf.append("Content-length: ").append(length).append("\n");
|
||||
buf.append(CRLF);
|
||||
buf.append("Connection: close" + CRLF);
|
||||
buf.append("Content-length: ").append(length).append(CRLF);
|
||||
buf.append("Content-type: multipart/form-data, boundary=").append(separator);
|
||||
buf.append("\n");
|
||||
buf.append("\n");
|
||||
buf.append(CRLF);
|
||||
buf.append(CRLF);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
51
core/java/src/net/i2p/util/Executor.java
Normal file
51
core/java/src/net/i2p/util/Executor.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
class Executor implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private List _readyEvents;
|
||||
public Executor(I2PAppContext ctx, Log log, List events) {
|
||||
_context = ctx;
|
||||
_readyEvents = events;
|
||||
}
|
||||
public void run() {
|
||||
while (true) {
|
||||
SimpleTimer.TimedEvent evt = null;
|
||||
synchronized (_readyEvents) {
|
||||
if (_readyEvents.size() <= 0)
|
||||
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
|
||||
if (_readyEvents.size() > 0)
|
||||
evt = (SimpleTimer.TimedEvent)_readyEvents.remove(0);
|
||||
}
|
||||
|
||||
if (evt != null) {
|
||||
long before = _context.clock().now();
|
||||
try {
|
||||
evt.timeReached();
|
||||
} catch (Throwable t) {
|
||||
log("wtf, event borked: " + evt, t);
|
||||
}
|
||||
long time = _context.clock().now() - before;
|
||||
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, event execution took " + time + ": " + evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String msg, Throwable t) {
|
||||
synchronized (this) {
|
||||
if (_log == null)
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
|
||||
}
|
||||
_log.log(Log.CRIT, msg, t);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import java.security.SecureRandom;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EntropyHarvester;
|
||||
|
||||
import gnu.crypto.prng.FortunaStandalone;
|
||||
import gnu.crypto.prng.AsyncFortunaStandalone;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -26,13 +26,13 @@ import java.io.IOException;
|
||||
*
|
||||
*/
|
||||
public class FortunaRandomSource extends RandomSource implements EntropyHarvester {
|
||||
private FortunaStandalone _fortuna;
|
||||
private AsyncFortunaStandalone _fortuna;
|
||||
private double _nextGaussian;
|
||||
private boolean _haveNextGaussian;
|
||||
|
||||
public FortunaRandomSource(I2PAppContext context) {
|
||||
super(context);
|
||||
_fortuna = new FortunaStandalone();
|
||||
_fortuna = new AsyncFortunaStandalone();
|
||||
byte seed[] = new byte[1024];
|
||||
if (initSeed(seed)) {
|
||||
_fortuna.seed(seed);
|
||||
@@ -41,6 +41,7 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
|
||||
sr.nextBytes(seed);
|
||||
_fortuna.seed(seed);
|
||||
}
|
||||
_fortuna.startup();
|
||||
// kickstart it
|
||||
_fortuna.nextBytes(seed);
|
||||
_haveNextGaussian = false;
|
||||
@@ -202,6 +203,13 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
RandomSource rand = I2PAppContext.getGlobalContext().random();
|
||||
if (true) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
if (rand.nextFloat() < 0)
|
||||
throw new RuntimeException("negative!");
|
||||
System.out.println("All positive");
|
||||
return;
|
||||
}
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
|
||||
for (int i = 0; i < 1024*1024; i++) {
|
||||
|
||||
@@ -48,6 +48,12 @@ public class I2PThread extends Thread {
|
||||
if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
|
||||
_createdBy = new Exception("Created by");
|
||||
}
|
||||
public I2PThread(Runnable r, String name, boolean isDaemon) {
|
||||
super(r, name);
|
||||
setDaemon(isDaemon);
|
||||
if ( (_log == null) || (_log.shouldLog(Log.DEBUG)) )
|
||||
_createdBy = new Exception("Created by");
|
||||
}
|
||||
|
||||
private void log(int level, String msg) { log(level, msg, null); }
|
||||
private void log(int level, String msg, Throwable t) {
|
||||
@@ -113,4 +119,4 @@ public class I2PThread extends Thread {
|
||||
} catch (Throwable tt) { // nop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class NativeBigInteger extends BigInteger {
|
||||
* initialization? this would otherwise use the Log component, but this makes
|
||||
* it easier for other systems to reuse this class
|
||||
*/
|
||||
private static final boolean _doLog = true;
|
||||
private static final boolean _doLog = System.getProperty("jbigi.dontLog") == null;
|
||||
|
||||
private final static String JBIGI_OPTIMIZATION_K6 = "k6";
|
||||
private final static String JBIGI_OPTIMIZATION_K6_2 = "k62";
|
||||
|
||||
@@ -31,14 +31,14 @@ public class SimpleTimer {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SimpleTimer.class);
|
||||
_events = new TreeMap();
|
||||
_eventTimes = new HashMap(1024);
|
||||
_eventTimes = new HashMap(256);
|
||||
_readyEvents = new ArrayList(4);
|
||||
I2PThread runner = new I2PThread(new SimpleTimerRunner());
|
||||
runner.setName(name);
|
||||
runner.setDaemon(true);
|
||||
runner.start();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
I2PThread executor = new I2PThread(new Executor());
|
||||
I2PThread executor = new I2PThread(new Executor(_context, _log, _readyEvents));
|
||||
executor.setName(name + "Executor " + i);
|
||||
executor.setDaemon(true);
|
||||
executor.start();
|
||||
@@ -114,7 +114,7 @@ public class SimpleTimer {
|
||||
long timeToAdd = System.currentTimeMillis() - now;
|
||||
if (timeToAdd > 50) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("timer contention: took " + timeToAdd + "ms to add a job");
|
||||
_log.warn("timer contention: took " + timeToAdd + "ms to add a job with " + totalEvents + " queued");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,14 +141,6 @@ public class SimpleTimer {
|
||||
public void timeReached();
|
||||
}
|
||||
|
||||
private void log(String msg, Throwable t) {
|
||||
synchronized (this) {
|
||||
if (_log == null)
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
|
||||
}
|
||||
_log.log(Log.CRIT, msg, t);
|
||||
}
|
||||
|
||||
private long _occurredTime;
|
||||
private long _occurredEventCount;
|
||||
private TimedEvent _recentEvents[] = new TimedEvent[5];
|
||||
@@ -228,30 +220,5 @@ public class SimpleTimer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Executor implements Runnable {
|
||||
public void run() {
|
||||
while (true) {
|
||||
TimedEvent evt = null;
|
||||
synchronized (_readyEvents) {
|
||||
if (_readyEvents.size() <= 0)
|
||||
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
|
||||
if (_readyEvents.size() > 0)
|
||||
evt = (TimedEvent)_readyEvents.remove(0);
|
||||
}
|
||||
|
||||
if (evt != null) {
|
||||
long before = _context.clock().now();
|
||||
try {
|
||||
evt.timeReached();
|
||||
} catch (Throwable t) {
|
||||
log("wtf, event borked: " + evt, t);
|
||||
}
|
||||
long time = _context.clock().now() - before;
|
||||
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, event execution took " + time + ": " + evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
378
history.txt
378
history.txt
@@ -1,4 +1,380 @@
|
||||
$Id: history.txt,v 1.479 2006-05-18 17:31:08 jrandom Exp $
|
||||
$Id: history.txt,v 1.550 2007-02-14 16:35:43 jrandom Exp $
|
||||
|
||||
* 2007-02-15 0.6.1.27 released
|
||||
|
||||
2007-02-15 jrandom
|
||||
* Limit the whispering floodfill sends to at most 3 randomly
|
||||
chosen from the known floodfill peers
|
||||
|
||||
2007-02-14 jrandom
|
||||
* Don't filter out KICK and H(ide oper status) IRC messages
|
||||
(thanks Takk and postman!)
|
||||
|
||||
2007-02-13 jrandom
|
||||
* Tell our peers about who we know in the floodfill netDb every
|
||||
6 hours or so, mitigating the situation where peers lose track
|
||||
of floodfill routers.
|
||||
* Disable the Syndie updater (people should use the new Syndie,
|
||||
not this one)
|
||||
* Disable the eepsite tunnel by default
|
||||
|
||||
2007-01-30 zzz
|
||||
* i2psnark: Don't hold _snarks lock while checking a snark,
|
||||
so web page is responsive at startup
|
||||
|
||||
2007-01-29 zzz
|
||||
* i2psnark: Add NickyB tracker
|
||||
|
||||
2007-01-28 zzz
|
||||
* i2psnark: Don't hold sendQueue lock while flushing output,
|
||||
to make everything run smoother
|
||||
|
||||
2007-01-27 zzz
|
||||
* i2psnark: Fix orphaned Snark reader tasks leading to OOMs
|
||||
|
||||
2007-01-20 Complication
|
||||
* Drop overlooked comment
|
||||
|
||||
2007-01-20 Complication
|
||||
* Modify ReseedHandler to query the "i2p.reseedURL" property from I2PAppContext
|
||||
instead of System, so setting a reseed URL in advanced configuration has effect.
|
||||
* Clean out obsolete reseed code from ConfigNetHandler.
|
||||
|
||||
2007-01-20 zzz
|
||||
* i2psnark: More choking rotation tweaks
|
||||
* Improve performance by not reading in the whole
|
||||
piece from disk for each request. A huge memory savings
|
||||
on 1MB torrents with many peers.
|
||||
|
||||
2007-01-17 zzz
|
||||
* Add new HTTP Proxy error message for non-http protocols
|
||||
|
||||
2007-01-17 zzz
|
||||
* Add note on Syndie index.html steering people to new Syndie
|
||||
|
||||
2007-01-16 zzz
|
||||
* i2psnark: Fix crash when autostart off and
|
||||
tcrrent started manually
|
||||
|
||||
2007-01-16 zzz
|
||||
* i2psnark: Fix bug caused by last i2psnark checkin
|
||||
(ConnectionAcceptor not started)
|
||||
* Don't start PeerCoordinator, ConnectionAcceptor,
|
||||
and TrackerClient unless starting torrent
|
||||
|
||||
2007-01-15 jrandom
|
||||
* small guard against unnecessary streaming lib reset packets
|
||||
(thanks Complication!)
|
||||
|
||||
2007-01-15 zzz
|
||||
* i2psnark: Add 'Stop All' link on web page
|
||||
* Add some links to trackers and forum on web page
|
||||
* Don't start tunnel if 'Autostart' unchecked
|
||||
* Fix torrent restart bug by reopening file descriptors
|
||||
|
||||
2007-01-14 zzz
|
||||
* i2psnark: Improvements for torrents with > 4 leechers:
|
||||
choke based on upload rate when seeding, and
|
||||
be smarter and fairer about rotating choked peers.
|
||||
* Handle two common i2psnark OOM situations rather
|
||||
than shutting down the whole thing.
|
||||
* Fix reporting to tracker of remaining bytes for
|
||||
torrents > 4GB (but ByteMonsoon still has a bug)
|
||||
|
||||
2006-10-29 zzz
|
||||
* i2psnark: Fix and enable generation of multifile torrents,
|
||||
print error if no tracker selected at create-torrent,
|
||||
fix stopping a torrent that hasn't started successfully,
|
||||
add eBook and GayTorrents trackers to form,
|
||||
web page formatting tweaks
|
||||
|
||||
* 2006-10-10 0.6.1.26 released
|
||||
|
||||
2006-10-29 Complication
|
||||
* Ensure we get NTP samples from more diverse sources
|
||||
(0.pool.ntp.org, 1.pool.ntp.org, etc)
|
||||
* Discard median-based peer skew calculator as framed average works,
|
||||
and adjusting its percentage can make it behave median-like
|
||||
* Require more data points (from at least 20 peers)
|
||||
before considering a peer skew measurement reliable
|
||||
|
||||
2006-10-10 jrandom
|
||||
* Removed the status display from the console, as its more confusing
|
||||
than informative (though the content is still displayed in the HTML)
|
||||
|
||||
2006-10-08 Complication
|
||||
* Add a framed average peer clock skew calculator
|
||||
* Add config property "router.clockOffsetSanityCheck" to determine
|
||||
if NTP-suggested clock offsets get sanity checked (default "true")
|
||||
* Reject NTP-suggested clock offsets if they'd increase peer clock skew
|
||||
by more than 5 seconds, or make it more than 20 seconds total
|
||||
* Decrease log level in getMedianPeerClockSkew()
|
||||
|
||||
2006-09-29 zzz
|
||||
* i2psnark: Second try at synchronization fix - synch addRequest()
|
||||
completely rather than just portions of it and requestNextPiece()
|
||||
|
||||
2006-09-27 jrandom
|
||||
* added HMAC-SHA256
|
||||
* properly use CRLF with EepPost
|
||||
* suppress jbigi/jcpuid messages if jbigi.dontLog/jcpuid.dontLog is set
|
||||
* PBE session key generation (with 1000 rounds of SHA256)
|
||||
* misc SDK helper functions
|
||||
|
||||
2006-09-26 Complication
|
||||
* Take back another inadverent logging change in NTCPConnection
|
||||
|
||||
2006-09-26 Complication
|
||||
* Take back an accidental log level change
|
||||
|
||||
2006-09-26 Complication
|
||||
* Subclass from Clock a RouterClock which can access router transports,
|
||||
with the goal of developing it to second-guess NTP results
|
||||
* Make transports report clock skew in seconds
|
||||
* Adjust renderStatusHTML() methods accordingly
|
||||
* Show average for NTCP clock skews too
|
||||
* Give transports a getClockSkews() method to report clock skews
|
||||
* Give transport manager a getClockSkews() method to aggregate results
|
||||
* Give comm system facade a getMedianPeerClockSkew() method which RouterClock calls
|
||||
(to observe results, add "net.i2p.router.transport.CommSystemFacadeImpl=WARN" to logging)
|
||||
* Extra explicitness in NTCP classes to denote unit of time.
|
||||
* Fix some places in NTCPConnection where milliseconds and seconds were confused
|
||||
|
||||
2006-09-25 zzz
|
||||
* i2psnark: Paranoid copy before writing pieces,
|
||||
recheck files on completion, redownload bad pieces
|
||||
* i2psnark: Don't contact tracker as often when seeding
|
||||
|
||||
2006-09-24 zzz
|
||||
* i2psnark: Add some synchronization to prevent rare problem
|
||||
after restoring orphan piece
|
||||
|
||||
2006-09-20 zzz
|
||||
* i2psnark: Eliminate duplicate requests caused by i2p-bt's
|
||||
rapid choke/unchokes
|
||||
* i2psnark: Truncate long TrackerErr messages on web page
|
||||
|
||||
2006-09-16 zzz
|
||||
* i2psnark: Implement retransmission of requests. This
|
||||
eliminates one cause of complete stalls with a peer.
|
||||
This problem is common on torrents with a small number of
|
||||
active peers where there are no choke/unchokes to kickstart things.
|
||||
|
||||
2006-09-13 zzz
|
||||
* i2psnark: Fix restoral of partial pieces broken by last patch
|
||||
|
||||
2006-09-13 zzz
|
||||
* i2psnark: Mark a peer's requests as unrequested on disconnect,
|
||||
preventing premature end game
|
||||
* i2psnark: Randomize selection of next piece during end game
|
||||
* i2psnark: Don't restore a partial piece to a peer that is already working on it
|
||||
* i2psnark: strip ".torrent" on web page
|
||||
* i2psnark: Limit piece size in generated torrent to 1MB max
|
||||
|
||||
2006-09-09 zzz
|
||||
* i2psnark: Add "Stalled" indication and stat totals on web page
|
||||
|
||||
2006-09-09 zzz
|
||||
* i2psnark: Fix bug where new peers would always be sent an "interested"
|
||||
regardless of actual interest
|
||||
* i2psnark: Reduce max piece size from 10MB to 1MB; larger may have severe
|
||||
memory and efficiency problems
|
||||
|
||||
* 2006-09-09 0.6.1.25 released
|
||||
|
||||
2006-09-08 jrandom
|
||||
* Tweak the PRNG logging so it only displays error messages if there are
|
||||
problems
|
||||
* Disable dynamic router keys for the time being, as they don't offer
|
||||
meaningful security, may hurt the router, and makes it harder to
|
||||
determine the network health. The code to restart on SSU IP change is
|
||||
still enabled however.
|
||||
* Disable tunnel load testing, leaning back on the tiered selection for
|
||||
the time being.
|
||||
* Spattering of bugfixes
|
||||
|
||||
2006-09-07 zzz
|
||||
* i2psnark: Increase output timeout from 2 min to 4 min
|
||||
* i2psnark: Orphan debug msg cleanup
|
||||
* i2psnark: More web rate report cleanup
|
||||
|
||||
2006-09-05 zzz
|
||||
* i2psnark: Implement basic partial-piece saves across connections
|
||||
* i2psnark: Implement keep-alive sending. This will keep non-i2psnark clients
|
||||
from dropping us for inactivity but also renders the 2-minute transmit-inactivity
|
||||
code in i2psnark ineffective. Will have to research why there is transmit but
|
||||
not receive inactivity code. With the current connection limit of 24 peers
|
||||
we aren't in any danger of keeping out new peers by keeping inactive ones.
|
||||
* i2psnark: Increase CHECK_PERIOD from 20 to 40 since nothing happens in 20 seconds
|
||||
* i2psnark: Fix dropped chunk handling
|
||||
* i2psnark: Web rate report cleanup
|
||||
|
||||
2006-09-04 zzz
|
||||
* i2psnark: Report cleared trackerErr immediately
|
||||
* i2psnark: Add trackerErr reporting after previous success; retry more quickly
|
||||
* i2psnark: Set up new connections more quickly
|
||||
* i2psnark: Don't delay tracker fetch when setting up lots of connections
|
||||
* i2psnark: Reduce MAX_UPLOADERS from 12 to 4
|
||||
|
||||
2006-09-04 zzz
|
||||
* Enable pipelining in i2psnark
|
||||
* Make i2psnark tunnel default be 1 + 0-1
|
||||
|
||||
2006-09-03 zzz
|
||||
* Add rate reporting to i2psnark
|
||||
|
||||
2006-09-03 Complication
|
||||
* Limit form size in SusiDNS to avoid exceeding a POST size limit on postback
|
||||
* Print messages about addressbook size to give better overview
|
||||
* Enable delete function in published addressbook
|
||||
|
||||
2006-08-21 Complication
|
||||
* Fix error reporting discrepancy (thanks for helping notice, yojoe!)
|
||||
|
||||
2006-08-03 jrandom
|
||||
* Decrease the recently modified tunnel building timeout, though keep
|
||||
the scaling on their processing
|
||||
|
||||
2006-07-31 jrandom
|
||||
* Increase the tunnel building timeout
|
||||
* Avoid a rare race (thanks bar!)
|
||||
* Fix the bandwidth capacity publishing code to factor in share percentage
|
||||
and outbound throttling (oops)
|
||||
|
||||
2006-07-29 Complication
|
||||
* Treat NTP responses from unexpected stratums like failures
|
||||
|
||||
* 2006-07-28 0.6.1.24 released
|
||||
|
||||
2006-07-28 jrandom
|
||||
* Don't try to reverify too many netDb entries at once (thanks
|
||||
cervantes and Complication!)
|
||||
|
||||
2006-07-28 jrandom
|
||||
* Actually fix the threading deadlock issue in the netDb (removing
|
||||
the synchronized access to individual kbuckets while validating
|
||||
individual entries) (thanks cervantes, postman, frosk, et al!)
|
||||
|
||||
* 2006-07-27 0.6.1.23 released
|
||||
|
||||
2006-07-27 jrandom
|
||||
* Cut down NTCP connection establishments once we know the peer is skewed
|
||||
(rather than wait for full establishment before verifying)
|
||||
* Removed a lock on the stats framework when accessing rates, which
|
||||
shouldn't be a problem, assuming rates are created (pretty much) all at
|
||||
once and merely updated during the lifetime of the jvm.
|
||||
|
||||
2006-07-27 jrandom
|
||||
* Further NTCP write status cleanup
|
||||
* Handle more oddly-timed NTCP disconnections (thanks bar!)
|
||||
|
||||
2006-07-26 jrandom
|
||||
* When dropping a netDb router reference, only accept newer
|
||||
references as part of the update check
|
||||
* If we have been up for a while, don't accept really old
|
||||
router references (published 2 or more days ago)
|
||||
* Drop router references once they are no longer valid, even if
|
||||
they were allowed in due to the lax restrictions on startup
|
||||
|
||||
2006-07-26 jrandom
|
||||
* Every time we create a new router identity, add an entry to the
|
||||
new "identlog.txt" text file in the I2P install directory. For
|
||||
debugging purposes, publish the count of how many identities the
|
||||
router has cycled through, though not the identities itself.
|
||||
* Cleaned up the way the multitransport shitlisting worked, and
|
||||
added per-transport shitlists
|
||||
* When dropping a router reference locally, first fire a netDb
|
||||
lookup for the entry
|
||||
* Take the peer selection filters into account when organizing the
|
||||
profiles (thanks Complication!)
|
||||
* Avoid some obvious configuration errors for the NTCP transport
|
||||
(invalid ports, "null" ip, etc)
|
||||
* Deal with some small NTCP bugs found in the wild (unresolveable
|
||||
hosts, strange network discons, etc)
|
||||
* Send our netDb info to peers we have direct NTCP connections to
|
||||
after each 6-12 hours of connection uptime
|
||||
* Clean up the NTCP reading and writing queue logic to avoid some
|
||||
potential delays
|
||||
* Allow people to specify the IP that the SSU transport binds on
|
||||
locally, via the advanced config "i2np.udp.bindInterface=1.2.3.4"
|
||||
|
||||
* 2006-07-18 0.6.1.22 released
|
||||
|
||||
2006-07-18 jrandom
|
||||
* Add a failsafe to the NTCP transport to make sure we keep
|
||||
pumping writes when we should.
|
||||
* Properly reallow 16-32KBps routers in the default config
|
||||
(thanks Complication!)
|
||||
|
||||
2006-07-16 Complication
|
||||
* Collect tunnel build agree/reject/expire statistics
|
||||
for each bandwidth tier of peers (and peers of unknown tiers,
|
||||
even if those shouldn't exist)
|
||||
|
||||
2006-07-14 jrandom
|
||||
* Improve the multitransport shitlisting (thanks Complication!)
|
||||
* Allow routers with a capacity of 16-32KBps to be used in tunnels under
|
||||
the default configuration (thanks for the stats Complication!)
|
||||
* Properly allow older router references to load on startup
|
||||
(thanks bar, Complication, et al!)
|
||||
* Add a new "i2p.alwaysAllowReseed" advanced config property, though
|
||||
hopefully today's changes should make this unnecessary (thanks void!)
|
||||
* Improved NTCP buffering
|
||||
* Close NTCP connections if we are too backlogged when writing to them
|
||||
|
||||
2006-07-04 jrandom
|
||||
* New NIO-based tcp transport (NTCP), enabled by default for outbound
|
||||
connections only. Those who configure their NAT/firewall to allow
|
||||
inbound connections and specify the external host and port
|
||||
(dyndns/etc is ok) on /config.jsp can receive inbound connections.
|
||||
SSU is still enabled for use by default for all users as a fallback.
|
||||
* Substantial bugfix to the tunnel gateway processing to transfer
|
||||
messages sequentially instead of interleaved
|
||||
* Renamed GNU/crypto classes to avoid name clashes with kaffe and other
|
||||
GNU/Classpath based JVMs
|
||||
* Adjust the Fortuna PRNG's pooling system to reduce contention on
|
||||
refill with a background thread to refill the output buffer
|
||||
* Add per-transport support for the shitlist
|
||||
* Add a new async pumped tunnel gateway to reduce tunnel dispatcher
|
||||
contention
|
||||
|
||||
2006-07-01 Complication
|
||||
* Ensure that the I2PTunnel web interface won't update tunnel settings
|
||||
for shared clients when a non-shared client is modified
|
||||
(thanks for spotting, BarkerJr!)
|
||||
|
||||
2006-06-14 cervantes
|
||||
* Small tweak to I2PTunnel CSS, so it looks better with desktops
|
||||
that use Bitstream Vera fonts @ 96 dpi
|
||||
|
||||
* 2006-06-14 0.6.1.21 released
|
||||
|
||||
2006-06-13 jrandom
|
||||
* Use a minimum uptime of 2 hours, not 4 (oops)
|
||||
|
||||
2006-06-13 jrandom
|
||||
* Cut down the proactive rejections due to queue size - if we are
|
||||
at the point of having decrypted the request off the queue, might
|
||||
as well let it through, rather than waste that decryption
|
||||
|
||||
2006-06-11 Kloug
|
||||
* Bugfix to the I2PTunnel IRC filter to support multiple concurrent
|
||||
outstanding pings/pongs
|
||||
|
||||
2006-06-10 jrandom
|
||||
* Further reduction in proactive rejections
|
||||
|
||||
2006-06-09 jrandom
|
||||
* Don't let the pending tunnel request queue grow beyond reason
|
||||
(letting things sit for up to 30s when they fail after 10s
|
||||
seems a bit... off)
|
||||
|
||||
2006-06-08 jrandom
|
||||
* Be more conservative in the proactive rejections
|
||||
|
||||
2006-06-04 Complication
|
||||
* Trim out sending a blank line before USER in susimail.
|
||||
Seemed to break in rare cases, thanks for reporting, Brachtus!
|
||||
|
||||
* 2006-06-04 0.6.1.20 released
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
; TC's hosts.txt guaranteed freshness
|
||||
; $Id: hosts.txt,v 1.166 2006/01/23 10:29:36 cervantes Exp $
|
||||
; $Id: hosts.txt,v 1.169 2006-12-16 17:31:07 jrandom Exp $
|
||||
; changelog:
|
||||
; (1.191) added trac.i2p
|
||||
; (1.190) added archive.syndie.i2p
|
||||
; (1.189) added mtn.i2p
|
||||
; (1.188) added downloads.legion.i2p, politguy.i2p, ninja.i2p
|
||||
; (1.187) added hidden.i2p, bk1k.i2p, antipiracyagency.i2p
|
||||
; (1.186) added decadence.i2p, freedomarchives.i2p, closedshop.i2p
|
||||
@@ -498,4 +501,7 @@ antipiracyagency.i2p=lnOKgQBEcsbZHgsuN5rJQA5mXK59fWPoCtag-EGfgYkbO1YbbPAqUZHqF4a
|
||||
downloads.legion.i2p=p0eQqZscgqFvpoQtftNXFVRJpNzMkW1gx0cEmVz5xcpT195DxoVEwGlQ34mP4Da5nnUggcCaHzW9JBduqxiZU73quiO6VYeE65b70EhS0mRgsoVaU9-nsqo7ikYZ0-Rr6Qrhn32M6vktf4b2KljmpHgaBnJzMsFiMaEj3QuxGY8Q4tH6P34tgKiv2hYzZ8DGCj9bcmzzW0LX8XwA9tufi4XGM31qAZu~CiW14J5I8AwDUQRnyaWiZ8OzN~o3qTbkPyMAfXnAewcoA~GJPF9oAb-lHdESGeSEE38Krk2OYv3gQNUTiZcVaEck3VktqFmQiHCWDtAO0z8DIv9qmSjCI41w0MTCVlXNPRDO-YCE9JHlZv5zvS1~uCKltJwKGAHxHv~8N4oKMjJiB52XHXo3sWk503NTF-OK29ng~T4qfDQgEL6mm~jFhF3X-aruSkbn3OcRhWEgSJ2YdQYERnSwZn8gg4k78kvx02reisAHBN04UKa9YW95~Tz4S5Mqn8lrAAAA
|
||||
politguy.i2p=nyQ37u3uP7Zm5YoFXL4MFEjJH6iQhrMs0K~Bk57fEIYRGJIG31~tqScmDLOUPjxiwzHEsbbLJo-o9rll7744HMOHmlfNGegKMO-n7~pWCVRW2XtMQL17NnZl97bPiDdymTVJoAKrs2ZfEayDYiiBxKbUlbcnT51O0~aTymSmlyh1i0VQajbQOveEdN-QDiOWEdBLZCQx7lg4BXT78Sb76qQVj4c6jdYRxP~q1nL1XHyjp~qd0jjLFNeTqS8YPdEDJFWAKo7M1bIYV4SUcj40GzGIuWRnaSY-utizQFrRUC0NcdwZK7Mo1JivGC0TFtWFJsQ-xx5ix4vP4vYeQ8rGiyED32s1kwII9rCBu9DmLLTwGBjTX0yjXI4XEH2Q-C0fgIw6YniDYQj50a3q1KTkaqiXm~EtnWqkv9dGvSYY8-mfBlRKD8k1X83-MhycdZHNrsWhGxA~zpBmKcMVHD0CDbW2ZF6sHyS3wPZiQ2haeBJL6mm7ZWQT6J875w3SP-nnAAAA
|
||||
ninja.i2p=XhyN8MgL6DwWE98g2DJlDLHy~02g58IF14LTb472cmSLRj~cHv7wyi1mxy21vtf12oPWRquwMqpn4vJbKtnoQrLqRBgKAWtHgvUWOKPA8HqOmP4g9di3vGLefUNHUjkZWPXANBs-HwWH~~zPV0zwjN5keEZLVNQKfUkc0-qJ77zfxrDAPVjUBlyx1nlH7HcSxgzXS8qNlQuxWEqlqOiPushnalVFZSul-kVzG0Zo1PIQQHDKA1kpmwr2EX5xvCq0PgDZgj~xc3xkTzBMXC-m~NnmB1NRrH80lpY3nWMo-ySTVS7KcHH--zPUkvQhE2PCjLcSQsqpK2xsq0XEQOeAqoeDhosc3XxQDnDTmgBC0Bnus4N6RMBZgBRpniFT4ofwgGmFS1nDGkI-F4fLJSQ4f~P4sUR6HCZnwWIEvcr7lJgr26RP584nev9Dzhr4dq1hfTesShfluB19KJ17eBsimy1xIGDbiAeCSVtUjEviyb1lm7C~we3c0oCUUt2NvOGWAAAA
|
||||
mtn.i2p=RqXC4xbFK6t3g2wk9SO4RjY7mj1c0DmtMra5c1Md8t-DcNPSjQFmqT97pcZ5IR1JDKqyCO7RI~aATTTMPQexoEeqK9-6Poeh2RA1C81FzcA9sHvjLeg3eB1Cju-sE-IDeFntEvCC4w7wWnpmLzCfdXK7OjSK1wYc6OkqPOLVDEy-N-4UUPlZFWWUghpjBGXGayXz6JRKtoMIwhFQaiKdRvAs32ozM9RM0NWzrCaRLZBIQ6Gg1Ys1wF0-oBJgC4T4CN6SxJNaz9Dfw4GNtPyD6lq3S1osY1ccflm3itvUt3JC1J9ypoXzylBE5MuS-LTgbgbMdMFty07AoWB~EY8TwW8EQQO07GSzB7hm53u0iCEH44GexhKHtQP-hYbIr3mklo89BvfWIRGMTwUkAzYojzC-vOyIh16LWrQQhGbLvByKQSdWk9nInm3GEfqRVtSpuqd4m6iHzrBDZ3fvKjbuywot3hDNHitOHOedmBNA8neCzLkod8b0Z4xx~qRIEObxAAAA
|
||||
archive.syndie.i2p=iXX0DadZTJQpPr1to0OmQ4xokHgx1HYd5ec7~zIjQ80W~p4kRCYJmEzibH2Kn59Gi04SAXeA2O9i3bNqfGCQjsbz7UcjPGrW6-UrckXVXW67Moxp7QWY6i-aKuVYM3bqYxUL2mWvcDzJ8D0ecMpvasxhxwXpdFn2J6CGboMxeGV8R3hwwlNYbYoKgHr74qEJaIZpm1FrRWvNHV5cMv363iWnPy72XspQefk79-VOjPsxfummosU7gqlxl5teyiGKNzMs3G6iJyfVHO8IlKtdn~P~ET9p7zWlTPgV8NTyCVB-Wn5S3JMkMGOFZR7wSlxSwGFpTFQKc7mxVTtLZ5nWcV2OhvOIxRZ31RvGJZyVs562RC5aMfyqcM5IHQiZVlmkhzJKIy9VDw8tKayQtRM-xeN5k6Qr7iMmYIRORwuAODkYApoMD9a0eJ6ZYOSgBMOCSvYcwfT8axRY~GabiHm0QC82mo-nDgrUypGKtOPMI9MIqMTsb8Yl-UGWn6twBAIzAAAA
|
||||
trac.i2p=OBnF9NtkEsPij2F-lp3bWDVrJsPQQPdq6adlpq0N4BY1XRjtDBZl~EpDdk7roq49~ptKAQG2cNUeBEKIIrdlZhJio5pMwUl6YinizzkNTFfZipB5OKoB7PBulxkw-N9mKMhS1btd9ajcV8tiP3xiv7VSlgiDwbdKg1fmkvNrVrJnzkN3-ey2kebYnbh7jjU2gPFUl~CwSEkIi6AK9EfqmFR-DUVohyygqAY~fi4EMeTVXGUqftXSNFYUwpRJgFrWRPTurtZnJK5403q67oEk0eWrPIZ8ytJWSBfffAXL3ts~0O1FZeKXUccsAl33j70~lklSolNVLJ40y-6X5ZLWajmX0ONU3j0qI5A~7fgNgsg-vKypPDuzl8ug-D~BmhqdAf0sRYmziDVwTgU~WRB6IzhhXFR6CbwrGXdgOGg2qNT1eOnMwGo3SMMJ7kK88VC5LdYg2dyiyjZATuvT92QdZglrVQIeBqAehcFjOBuycC1ED3AOak8D9Xplj7V6hN-HAAAA
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<i2p.news date="$Date: 2006-05-18 17:31:08 $">
|
||||
<i2p.release version="0.6.1.20" date="2006/05/18" minVersion="0.6"
|
||||
<i2p.news date="$Date: 2006-10-09 00:10:22 $">
|
||||
<i2p.release version="0.6.1.27" date="2007/02/15" minVersion="0.6"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-September/000878.html"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<info>
|
||||
<appname>i2p</appname>
|
||||
<appversion>0.6.1.20</appversion>
|
||||
<appversion>0.6.1.27</appversion>
|
||||
<authors>
|
||||
<author name="I2P" email="support@i2p.net"/>
|
||||
</authors>
|
||||
|
||||
@@ -9,13 +9,6 @@
|
||||
and they'll be reachable. The 'key' to your eepsite that you need to
|
||||
give to other people is shown on the eepsite's I2PTunnel
|
||||
<a href="http://localhost:7657/i2ptunnel/edit.jsp?tunnel=3">configuration page</a>). </p>
|
||||
<p>
|
||||
If you have any standard java web applications (.war files) such as
|
||||
<a href="http://wiki.blojsom.com/wiki/display/blojsom/About%2Bblojsom">blojsom</a>
|
||||
or <a href="http://snipsnap.org/space/start">SnipSnap</a>, simply drop their .war
|
||||
file into ./eepsite/webapps/ and they'll be reachable at
|
||||
http://$yourEeepsite/warFileName/</p>
|
||||
|
||||
<p>You can also reach your eepsite locally through
|
||||
<a href="http://localhost:7658/">http://localhost:7658/</a>. If you
|
||||
want to change the port number, edit the file ./eepsite/jetty.xml and
|
||||
|
||||
@@ -55,7 +55,7 @@ tunnel.3.i2cpHost=127.0.0.1
|
||||
tunnel.3.i2cpPort=7654
|
||||
tunnel.3.option.inbound.nickname=eepsite
|
||||
tunnel.3.option.outbound.nickname=eepsite
|
||||
tunnel.3.startOnLoad=true
|
||||
tunnel.3.startOnLoad=false
|
||||
|
||||
# postman's SMTP server - see www.postman.i2p
|
||||
tunnel.4.description=smtp server
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
#tunnelListPage .footer label {
|
||||
text-align : right;
|
||||
height : 24px;
|
||||
width : 160px;
|
||||
width : 360px;
|
||||
float : left;
|
||||
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
#tunnelListPage .footer label {
|
||||
text-align : right;
|
||||
height : 24px;
|
||||
width : 160px;
|
||||
width : 360px;
|
||||
float : left;
|
||||
|
||||
}
|
||||
|
||||
14
news.xml
14
news.xml
@@ -1,5 +1,5 @@
|
||||
<i2p.news date="$Date: 2006-05-30 22:19:24 $">
|
||||
<i2p.release version="0.6.1.20" date="2006/05/18" minVersion="0.6"
|
||||
<i2p.news date="$Date: 2007-02-13 22:04:11 $">
|
||||
<i2p.release version="0.6.1.27" date="2007/02/15" minVersion="0.6"
|
||||
anonurl="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/i2pupdate.sud"
|
||||
publicurl="http://dev.i2p.net/i2p/i2pupdate.sud"
|
||||
anonannouncement="http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/pipermail/i2p/2005-September/000878.html"
|
||||
@@ -10,13 +10,13 @@
|
||||
anonlogs="http://i2p/Nf3ab-ZFkmI-LyMt7GjgT-jfvZ3zKDl0L96pmGQXF1B82W2Bfjf0n7~288vafocjFLnQnVcmZd~-p0-Oolfo9aW2Rm-AhyqxnxyLlPBqGxsJBXjPhm1JBT4Ia8FB-VXt0BuY0fMKdAfWwN61-tj4zIcQWRxv3DFquwEf035K~Ra4SWOqiuJgTRJu7~o~DzHVljVgWIzwf8Z84cz0X33pv-mdG~~y0Bsc2qJVnYwjjR178YMcRSmNE0FVMcs6f17c6zqhMw-11qjKpY~EJfHYCx4lBWF37CD0obbWqTNUIbL~78vxqZRT3dgAgnLixog9nqTO-0Rh~NpVUZnoUi7fNR~awW5U3Cf7rU7nNEKKobLue78hjvRcWn7upHUF45QqTDuaM3yZa7OsjbcH-I909DOub2Q0Dno6vIwuA7yrysccN1sbnkwZbKlf4T6~iDdhaSLJd97QCyPOlbyUfYy9QLNExlRqKgNVJcMJRrIual~Lb1CLbnzt0uvobM57UpqSAAAA/meeting141"
|
||||
publiclogs="http://www.i2p.net/meeting141" />
|
||||
•
|
||||
2006-05-18: 0.6.1.19 <a href="http://dev.i2p/pipermail/i2p/2006-May/001290.html">released</a>
|
||||
with PRNG bugfixes, congestion handling and SSU improvements.
|
||||
2006-10-09: 0.6.1.26 <a href="http://dev.i2p/pipermail/i2p/2006-October/001312.html">released</a>
|
||||
with i2psnark and NTP fixes.
|
||||
<br />
|
||||
•
|
||||
2006-05-30:
|
||||
<a href="http://dev.i2p/pipermail/i2p/2006-May/001291.html">status notes</a>
|
||||
2007-02-13:
|
||||
<a href="http://dev.i2p/pipermail/i2p/2007-February/001334.html">status notes</a>
|
||||
and
|
||||
<a href="http://www.i2p/meeting181">meeting log</a>
|
||||
<a href="http://www.i2p/meeting200">meeting log</a>
|
||||
<br />
|
||||
</i2p.news>
|
||||
|
||||
@@ -3,7 +3,7 @@ grow over the next few minutes and you'll see some local "destinations" listed
|
||||
on the left (if not, <a href="#trouble">see below</a>). Once those show up,
|
||||
you can:</p>
|
||||
<ul>
|
||||
<li><b>blog anonymously</b> - check out <a href="/syndie/">Syndie</a></li>
|
||||
<li><b>blog anonymously</b> - check out <a href="http://syndie.i2pt/">Syndie</a></li>
|
||||
<li><b>chat anonymously</b> - fire up your own IRC client and connect to the
|
||||
server at <b>localhost port 6668</b>. This points at one of two anonymously hosted
|
||||
IRC servers, but neither you nor they know where the other is.</li>
|
||||
@@ -44,9 +44,8 @@ you can:</p>
|
||||
<a href="http://localhost:7658/">http://localhost:7658/</a>. Simply place your files in
|
||||
the <code>eepsite/docroot/</code> directory (or place any standard JSP/Servlet <code>.war</code>
|
||||
files under <code>eepsite/webapps</code>, or standard CGI script under <code>eepsite/cgi-bin</code>)
|
||||
and they'll show up. Your eepsite's
|
||||
<i>destination</i> (which uniquely and securely identifies it) can be found via the I2PTunnel
|
||||
<a href="/i2ptunnel/">configuration page</a> - on the details page of the "eepsite" tunnel.
|
||||
and they'll show up. After starting up an <a href="/i2ptunnel/">eepsite tunnel</a> pointing at it, your eepsite's
|
||||
<i>destination</i> (which uniquely and securely identifies it) will be visible.
|
||||
If you want other people to see your eepsite,
|
||||
you need to give them that really huge string. Just paste it into the
|
||||
<a href="http://forum.i2p/viewforum.php?f=16">Eepsite announce</a> forum, add it to
|
||||
|
||||
@@ -95,8 +95,18 @@ public class I2NPMessageHandler {
|
||||
cur++;
|
||||
_lastReadBegin = System.currentTimeMillis();
|
||||
I2NPMessage msg = I2NPMessageImpl.createMessage(_context, type);
|
||||
if (msg == null)
|
||||
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message");
|
||||
if (msg == null) {
|
||||
int sz = data.length-offset;
|
||||
boolean allZero = false;
|
||||
for (int i = offset; i < data.length; i++) {
|
||||
if (data[i] != 0) {
|
||||
allZero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new I2NPMessageException("The type "+ type + " is an unknown I2NP message (remaining sz="
|
||||
+ sz + " all zeros? " + allZero + ")");
|
||||
}
|
||||
try {
|
||||
_lastSize = msg.readBytes(data, type, cur);
|
||||
cur += _lastSize;
|
||||
|
||||
@@ -33,6 +33,18 @@ public abstract class CommSystemFacade implements Service {
|
||||
public int countActiveSendPeers() { return 0; }
|
||||
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
|
||||
|
||||
/**
|
||||
* Median clock skew of connected peers in seconds, or null if we cannot answer.
|
||||
* CommSystemFacadeImpl overrides this.
|
||||
*/
|
||||
public Long getMedianPeerClockSkew() { return null; }
|
||||
|
||||
/**
|
||||
* Return framed average clock skew of connected peers in seconds, or null if we cannot answer.
|
||||
* CommSystemFacadeImpl overrides this.
|
||||
*/
|
||||
public Long getFramedAveragePeerClockSkew(int percentToInclude) { return null; }
|
||||
|
||||
/**
|
||||
* Determine under what conditions we are remotely reachable.
|
||||
*
|
||||
|
||||
@@ -78,7 +78,11 @@ public class LoadTestManager {
|
||||
|
||||
private static final boolean DEFAULT_ENABLE = false;
|
||||
|
||||
/** disable all load testing for the moment */
|
||||
private static final boolean FORCE_DISABLE = true;
|
||||
|
||||
public static boolean isEnabled(I2PAppContext ctx) {
|
||||
if (FORCE_DISABLE) return false;
|
||||
String enable = ctx.getProperty("router.enableLoadTesting");
|
||||
if ( (DEFAULT_ENABLE) && (enable != null) && (!Boolean.valueOf(enable).booleanValue()) )
|
||||
return false;
|
||||
@@ -130,6 +134,7 @@ public class LoadTestManager {
|
||||
* Actually send the messages through the given tunnel
|
||||
*/
|
||||
private void runTest(LoadTestTunnelConfig tunnel) {
|
||||
if (!isEnabled(_context)) return;
|
||||
log(tunnel, "start");
|
||||
int peerMessages = getPeerMessages();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -208,9 +213,17 @@ public class LoadTestManager {
|
||||
|
||||
// this should take into consideration both the inbound and outbound tunnels
|
||||
// ... but it doesn't, yet.
|
||||
_context.messageRegistry().registerPending(new Selector(tunnel, payloadMessage.getUniqueId()),
|
||||
new SendAgain(_context, tunnel, payloadMessage.getUniqueId(), true),
|
||||
new SendAgain(_context, tunnel, payloadMessage.getUniqueId(), false),
|
||||
long uniqueId = -1;
|
||||
if (payloadMessage != null) {
|
||||
uniqueId = payloadMessage.getUniqueId();
|
||||
} else {
|
||||
tunnel.logComplete();
|
||||
_active.remove(tunnel);
|
||||
return;
|
||||
}
|
||||
_context.messageRegistry().registerPending(new Selector(tunnel, uniqueId),
|
||||
new SendAgain(_context, tunnel, uniqueId, true),
|
||||
new SendAgain(_context, tunnel, uniqueId, false),
|
||||
10*1000);
|
||||
_context.tunnelDispatcher().dispatchOutbound(payloadMessage, outbound.getSendTunnelId(0),
|
||||
inbound.getReceiveTunnelId(0),
|
||||
|
||||
@@ -48,6 +48,7 @@ public class OutNetMessage {
|
||||
private MessageSelector _replySelector;
|
||||
private Set _failedTransports;
|
||||
private long _sendBegin;
|
||||
private long _transmitBegin;
|
||||
private Exception _createdBy;
|
||||
private long _created;
|
||||
/** for debugging, contains a mapping of even name to Long (e.g. "begin sending", "handleOutbound", etc) */
|
||||
@@ -57,6 +58,10 @@ public class OutNetMessage {
|
||||
* (some JVMs have less than 10ms resolution, so the Long above doesn't guarantee order)
|
||||
*/
|
||||
private List _timestampOrder;
|
||||
private int _queueSize;
|
||||
private long _prepareBegin;
|
||||
private long _prepareEnd;
|
||||
private Object _preparationBuf;
|
||||
|
||||
public OutNetMessage(RouterContext context) {
|
||||
_context = context;
|
||||
@@ -148,6 +153,7 @@ public class OutNetMessage {
|
||||
_messageType = msg.getClass().getName();
|
||||
_messageTypeId = msg.getType();
|
||||
_messageId = msg.getUniqueId();
|
||||
_messageSize = _message.getMessageSize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +241,31 @@ public class OutNetMessage {
|
||||
/** when did the sending process begin */
|
||||
public long getSendBegin() { return _sendBegin; }
|
||||
public void beginSend() { _sendBegin = _context.clock().now(); }
|
||||
public void beginTransmission() { _transmitBegin = _context.clock().now(); }
|
||||
public void beginPrepare() { _prepareBegin = _context.clock().now(); }
|
||||
public void prepared() { prepared(null); }
|
||||
public void prepared(Object buf) {
|
||||
_prepareEnd = _context.clock().now();
|
||||
_preparationBuf = buf;
|
||||
}
|
||||
public Object releasePreparationBuffer() {
|
||||
Object rv = _preparationBuf;
|
||||
_preparationBuf = null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public long getCreated() { return _created; }
|
||||
/** time since the message was created */
|
||||
public long getLifetime() { return _context.clock().now() - _created; }
|
||||
/** time the transport tries to send the message (including any queueing) */
|
||||
public long getSendTime() { return _context.clock().now() - _sendBegin; }
|
||||
/** time during which the i2np message is actually in flight */
|
||||
public long getTransmissionTime() { return _context.clock().now() - _transmitBegin; }
|
||||
/** how long it took to prepare the i2np message for transmission (including serialization and transport layer encryption) */
|
||||
public long getPreparationTime() { return _prepareEnd - _prepareBegin; }
|
||||
/** number of messages ahead of this one going to the targetted peer when it is first enqueued */
|
||||
public int getQueueSize() { return _queueSize; }
|
||||
public void setQueueSize(int size) { _queueSize = size; }
|
||||
|
||||
/**
|
||||
* We've done what we need to do with the data from this message, though
|
||||
@@ -245,6 +273,8 @@ public class OutNetMessage {
|
||||
*/
|
||||
public void discardData() {
|
||||
long timeToDiscard = _context.clock().now() - _created;
|
||||
if ( (_message != null) && (_messageSize <= 0) )
|
||||
_messageSize = _message.getMessageSize();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Discard " + _messageSize + "byte " + _messageType + " message after "
|
||||
+ timeToDiscard);
|
||||
|
||||
@@ -240,8 +240,10 @@ public class Router {
|
||||
readConfig();
|
||||
|
||||
setupHandlers();
|
||||
if ("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false")))
|
||||
killKeys();
|
||||
if (ALLOW_DYNAMIC_KEYS) {
|
||||
if ("true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false")))
|
||||
killKeys();
|
||||
}
|
||||
|
||||
_context.messageValidator().startup();
|
||||
_context.tunnelDispatcher().startup();
|
||||
@@ -323,6 +325,7 @@ public class Router {
|
||||
ri.setPublished(_context.clock().now());
|
||||
Properties stats = _context.statPublisher().publishStatistics();
|
||||
stats.setProperty(RouterInfo.PROP_NETWORK_ID, NETWORK_ID+"");
|
||||
|
||||
ri.setOptions(stats);
|
||||
ri.setAddresses(_context.commSystem().createAddresses());
|
||||
|
||||
@@ -371,7 +374,8 @@ public class Router {
|
||||
|
||||
public void addCapabilities(RouterInfo ri) {
|
||||
int bwLim = Math.min(_context.bandwidthLimiter().getInboundKBytesPerSecond(),
|
||||
_context.bandwidthLimiter().getInboundKBytesPerSecond());
|
||||
_context.bandwidthLimiter().getOutboundKBytesPerSecond());
|
||||
bwLim = (int)(((float)bwLim) * getSharePercentage());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Adding capabilities w/ bw limit @ " + bwLim, new Exception("caps"));
|
||||
|
||||
@@ -444,15 +448,32 @@ public class Router {
|
||||
"keyBackup/publicSigning.key",
|
||||
"sessionKeys.dat" };
|
||||
|
||||
static final String IDENTLOG = "identlog.txt";
|
||||
public static void killKeys() {
|
||||
new Exception("Clearing identity files").printStackTrace();
|
||||
int remCount = 0;
|
||||
for (int i = 0; i < _rebuildFiles.length; i++) {
|
||||
File f = new File(_rebuildFiles[i]);
|
||||
if (f.exists()) {
|
||||
boolean removed = f.delete();
|
||||
if (removed)
|
||||
if (removed) {
|
||||
System.out.println("INFO: Removing old identity file: " + _rebuildFiles[i]);
|
||||
else
|
||||
remCount++;
|
||||
} else {
|
||||
System.out.println("ERROR: Could not remove old identity file: " + _rebuildFiles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (remCount > 0) {
|
||||
FileOutputStream log = null;
|
||||
try {
|
||||
log = new FileOutputStream(IDENTLOG, true);
|
||||
log.write((new Date() + ": Old router identity keys cleared\n").getBytes());
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
if (log != null)
|
||||
try { log.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -796,11 +817,19 @@ public class Router {
|
||||
finalShutdown(exitCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* disable dynamic key functionality for the moment, as it may be harmful and doesn't
|
||||
* add meaningful anonymity
|
||||
*/
|
||||
private static final boolean ALLOW_DYNAMIC_KEYS = false;
|
||||
|
||||
public void finalShutdown(int exitCode) {
|
||||
_log.log(Log.CRIT, "Shutdown(" + exitCode + ") complete", new Exception("Shutdown"));
|
||||
try { _context.logManager().shutdown(); } catch (Throwable t) { }
|
||||
if ("true".equalsIgnoreCase(_context.getProperty(PROP_DYNAMIC_KEYS, "false")))
|
||||
killKeys();
|
||||
if (ALLOW_DYNAMIC_KEYS) {
|
||||
if ("true".equalsIgnoreCase(_context.getProperty(PROP_DYNAMIC_KEYS, "false")))
|
||||
killKeys();
|
||||
}
|
||||
|
||||
File f = new File(getPingFile());
|
||||
f.delete();
|
||||
@@ -1300,4 +1329,4 @@ class PersistRouterInfoJob extends JobImpl {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
router/java/src/net/i2p/router/RouterClock.java
Normal file
104
router/java/src/net/i2p/router/RouterClock.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Alternate location for determining the time which takes into account an offset.
|
||||
* This offset will ideally be periodically updated so as to serve as the difference
|
||||
* between the local computer's current time and the time as known by some reference
|
||||
* (such as an NTP synchronized clock).
|
||||
*
|
||||
* RouterClock is a subclass of Clock with access to router transports.
|
||||
* Configuration permitting, it will block clock offset changes
|
||||
* which would increase peer clock skew.
|
||||
*/
|
||||
public class RouterClock extends Clock {
|
||||
|
||||
RouterContext _context;
|
||||
|
||||
public RouterClock(RouterContext context) {
|
||||
super(context);
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how far away from the "correct" time the computer is - a positive
|
||||
* value means that we are slow, while a negative value means we are fast.
|
||||
*
|
||||
*/
|
||||
public void setOffset(long offsetMs, boolean force) {
|
||||
|
||||
if (false) return;
|
||||
long delta = offsetMs - _offset;
|
||||
if (!force) {
|
||||
if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) {
|
||||
getLog().error("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
|
||||
return;
|
||||
}
|
||||
|
||||
// only allow substantial modifications before the first 10 minutes
|
||||
if (_alreadyChanged && (System.currentTimeMillis() - _startedOn > 10 * 60 * 1000)) {
|
||||
if ( (delta > MAX_LIVE_OFFSET) || (delta < 0 - MAX_LIVE_OFFSET) ) {
|
||||
getLog().log(Log.CRIT, "The clock has already been updated, but you want to change it by "
|
||||
+ delta + " to " + offsetMs + "? Did something break?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((delta < MIN_OFFSET_CHANGE) && (delta > 0 - MIN_OFFSET_CHANGE)) {
|
||||
getLog().debug("Not changing offset since it is only " + delta + "ms");
|
||||
_alreadyChanged = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// If so configured, check sanity of proposed clock offset
|
||||
if (Boolean.valueOf(_context.getProperty("router.clockOffsetSanityCheck","true")).booleanValue() == true) {
|
||||
|
||||
// Try calculating peer clock skew
|
||||
Long peerClockSkew = _context.commSystem().getFramedAveragePeerClockSkew(50);
|
||||
|
||||
if (peerClockSkew != null) {
|
||||
|
||||
// Predict the effect of applying the proposed clock offset
|
||||
long currentPeerClockSkew = peerClockSkew.longValue();
|
||||
long predictedPeerClockSkew = currentPeerClockSkew + (delta / 1000l);
|
||||
|
||||
// Fail sanity check if applying the offset would increase peer clock skew
|
||||
if ((Math.abs(predictedPeerClockSkew) > (Math.abs(currentPeerClockSkew) + 5)) ||
|
||||
(Math.abs(predictedPeerClockSkew) > 20)) {
|
||||
|
||||
getLog().error("Ignoring clock offset " + offsetMs + "ms (current " + _offset +
|
||||
"ms) since it would increase peer clock skew from " + currentPeerClockSkew +
|
||||
"s to " + predictedPeerClockSkew + "s. Broken server in pool.ntp.org?");
|
||||
return;
|
||||
} else {
|
||||
getLog().debug("Approving clock offset " + offsetMs + "ms (current " + _offset +
|
||||
"ms) since it would decrease peer clock skew from " + currentPeerClockSkew +
|
||||
"s to " + predictedPeerClockSkew + "s.");
|
||||
}
|
||||
}
|
||||
} // check sanity
|
||||
}
|
||||
|
||||
if (_alreadyChanged) {
|
||||
if (delta > 15*1000)
|
||||
getLog().log(Log.CRIT, "Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
else if (getLog().shouldLog(Log.INFO))
|
||||
getLog().info("Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
|
||||
if (!_statCreated)
|
||||
_context.statManager().createRateStat("clock.skew", "How far is the already adjusted clock being skewed?", "Clock", new long[] { 10*60*1000, 3*60*60*1000, 24*60*60*60 });
|
||||
_statCreated = true;
|
||||
_context.statManager().addRateData("clock.skew", delta, 0);
|
||||
} else {
|
||||
getLog().log(Log.INFO, "Initializing clock offset to " + offsetMs + "ms from " + _offset + "ms");
|
||||
}
|
||||
_alreadyChanged = true;
|
||||
_offset = offsetMs;
|
||||
fireOffsetChanged(delta);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import net.i2p.router.transport.OutboundMessageRegistry;
|
||||
import net.i2p.router.transport.VMCommSystem;
|
||||
import net.i2p.router.tunnel.pool.TunnelPoolManager;
|
||||
import net.i2p.router.tunnel.TunnelDispatcher;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.router.RouterClock;
|
||||
|
||||
/**
|
||||
* Build off the core I2P context to provide a root for a router instance to
|
||||
@@ -59,13 +61,15 @@ public class RouterContext extends I2PAppContext {
|
||||
private MessageValidator _messageValidator;
|
||||
private MessageStateMonitor _messageStateMonitor;
|
||||
private RouterThrottle _throttle;
|
||||
private RouterClock _clock;
|
||||
private Calculator _isFailingCalc;
|
||||
private Calculator _integrationCalc;
|
||||
private Calculator _speedCalc;
|
||||
private Calculator _reliabilityCalc;
|
||||
private Calculator _capacityCalc;
|
||||
private Calculator _oldSpeedCalc;
|
||||
|
||||
|
||||
|
||||
private static List _contexts = new ArrayList(1);
|
||||
|
||||
public RouterContext(Router router) { this(router, null); }
|
||||
@@ -323,4 +327,25 @@ public class RouterContext extends I2PAppContext {
|
||||
}
|
||||
return super.getProperty(propName, defaultVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* The context's synchronized clock, which is kept context specific only to
|
||||
* enable simulators to play with clock skew among different instances.
|
||||
*
|
||||
* It wouldn't be necessary to override clock(), except for the reason
|
||||
* that it triggers initializeClock() of which we definitely
|
||||
* need the local version to run.
|
||||
*/
|
||||
public Clock clock() {
|
||||
if (!_clockInitialized) initializeClock();
|
||||
return _clock;
|
||||
}
|
||||
protected void initializeClock() {
|
||||
synchronized (this) {
|
||||
if (_clock == null)
|
||||
_clock = new RouterClock(this);
|
||||
_clockInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -151,8 +151,8 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
else
|
||||
avg10m = tunnelTestTime10m.getLifetimeAverageValue();
|
||||
|
||||
if (avg10m < 2000)
|
||||
avg10m = 2000; // minimum before complaining
|
||||
if (avg10m < 5000)
|
||||
avg10m = 5000; // minimum before complaining
|
||||
|
||||
if ( (avg10m > 0) && (avg1m > avg10m * tunnelTestTimeGrowthFactor) ) {
|
||||
double probAccept = (avg10m*tunnelTestTimeGrowthFactor)/avg1m;
|
||||
@@ -163,7 +163,7 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Probabalistically accept tunnel request (p=" + probAccept
|
||||
+ " v=" + v + " test time avg 1m=" + avg1m + " 10m=" + avg10m + ")");
|
||||
} else {
|
||||
} else if (false) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Probabalistically refusing tunnel request (test time avg 1m=" + avg1m
|
||||
+ " 10m=" + avg10m + ")");
|
||||
@@ -227,8 +227,12 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
else
|
||||
timePerRequest = (int)rs.getLifetimeAverageValue();
|
||||
}
|
||||
float pctFull = (queuedRequests * timePerRequest) / (10*1000f);
|
||||
float pReject = 1 - ((1-pctFull) * (1-pctFull));
|
||||
float pctFull = (queuedRequests * timePerRequest) / (4*1000f);
|
||||
double pReject = Math.pow(pctFull, 16); //1 - ((1-pctFull) * (1-pctFull));
|
||||
// let it in because we drop overload- rejecting may be overkill,
|
||||
// especially since we've done the cpu-heavy lifting to figure out
|
||||
// whats up
|
||||
/*
|
||||
if ( (pctFull >= 1) || (pReject >= _context.random().nextFloat()) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting a new tunnel request because we have too many pending requests (" + queuedRequests
|
||||
@@ -236,6 +240,7 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
_context.statManager().addRateData("router.throttleTunnelQueueOverload", queuedRequests, timePerRequest);
|
||||
return TunnelHistory.TUNNEL_REJECT_TRANSIENT_OVERLOAD;
|
||||
}
|
||||
*/
|
||||
|
||||
// ok, all is well, let 'er in
|
||||
_context.statManager().addRateData("tunnel.bytesAllocatedAtAccept", (long)bytesAllocated, 60*10*1000);
|
||||
@@ -269,7 +274,10 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
_context.statManager().addRateData("router.throttleTunnelBytesUsed", used, maxKBps);
|
||||
_context.statManager().addRateData("router.throttleTunnelBytesAllowed", availBps, (long)bytesAllocated);
|
||||
|
||||
if (used1m > (maxKBps*1024)) {
|
||||
long overage = used1m - (maxKBps*1024);
|
||||
if ( (overage > 0) &&
|
||||
((overage/(float)(maxKBps*1024f)) > _context.random().nextFloat()) ) {
|
||||
|
||||
if (_log.shouldLog(Log.WARN)) _log.warn("Reject tunnel, 1m rate (" + used1m + ") indicates overload.");
|
||||
return false;
|
||||
}
|
||||
@@ -342,9 +350,9 @@ class RouterThrottleImpl implements RouterThrottle {
|
||||
/** dont ever probabalistically throttle tunnels if we have less than this many */
|
||||
private int getMinThrottleTunnels() {
|
||||
try {
|
||||
return Integer.parseInt(_context.getProperty("router.minThrottleTunnels", "40"));
|
||||
return Integer.parseInt(_context.getProperty("router.minThrottleTunnels", "1000"));
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 40;
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import net.i2p.CoreVersion;
|
||||
*
|
||||
*/
|
||||
public class RouterVersion {
|
||||
public final static String ID = "$Revision: 1.419 $ $Date: 2006-05-18 17:31:10 $";
|
||||
public final static String VERSION = "0.6.1.20";
|
||||
public final static String ID = "$Revision: 1.486 $ $Date: 2007-02-14 16:35:44 $";
|
||||
public final static String VERSION = "0.6.1.27";
|
||||
public final static long BUILD = 0;
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
|
||||
|
||||
@@ -10,47 +10,85 @@ package net.i2p.router;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.router.peermanager.PeerProfile;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* Routers are shitlisted only if none of our transports can talk to them
|
||||
* or their signed router info is completely screwy. Individual transports
|
||||
* manage their own unreachable lists and do not generally add to the overall
|
||||
* shitlist.
|
||||
*/
|
||||
public class Shitlist {
|
||||
private Log _log;
|
||||
private RouterContext _context;
|
||||
private Map _shitlist; // H(routerIdent) --> Date
|
||||
private Map _shitlistCause; // H(routerIdent) --> String
|
||||
private Map _entries;
|
||||
|
||||
private class Entry {
|
||||
/** when it should expire, per the i2p clock */
|
||||
long expireOn;
|
||||
/** why they were shitlisted */
|
||||
String cause;
|
||||
/** what transports they were shitlisted for (String), or null for all transports */
|
||||
Set transports;
|
||||
}
|
||||
|
||||
public final static long SHITLIST_DURATION_MS = 4*60*1000; // 4 minute shitlist
|
||||
|
||||
public Shitlist(RouterContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(Shitlist.class);
|
||||
_shitlist = new HashMap(5);
|
||||
_shitlistCause = new HashMap(5);
|
||||
_entries = new HashMap(32);
|
||||
_context.jobQueue().addJob(new Cleanup(_context));
|
||||
}
|
||||
|
||||
private class Cleanup extends JobImpl {
|
||||
private List _toUnshitlist;
|
||||
public Cleanup(RouterContext ctx) {
|
||||
super(ctx);
|
||||
_toUnshitlist = new ArrayList(4);
|
||||
}
|
||||
public String getName() { return "Cleanup shitlist"; }
|
||||
public void runJob() {
|
||||
_toUnshitlist.clear();
|
||||
long now = getContext().clock().now();
|
||||
synchronized (_entries) {
|
||||
for (Iterator iter = _entries.keySet().iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
Entry entry = (Entry)_entries.get(peer);
|
||||
if (entry.expireOn <= now) {
|
||||
iter.remove();
|
||||
_toUnshitlist.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _toUnshitlist.size(); i++) {
|
||||
Hash peer = (Hash)_toUnshitlist.get(i);
|
||||
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
|
||||
if (prof != null)
|
||||
prof.unshitlist();
|
||||
_context.messageHistory().unshitlist(peer);
|
||||
}
|
||||
|
||||
requeue(30*1000);
|
||||
}
|
||||
}
|
||||
|
||||
public int getRouterCount() {
|
||||
purge();
|
||||
synchronized (_shitlist) {
|
||||
return _shitlist.size();
|
||||
synchronized (_entries) {
|
||||
return _entries.size();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shitlistRouter(Hash peer) {
|
||||
return shitlistRouter(peer, null);
|
||||
}
|
||||
public boolean shitlistRouter(Hash peer, String reason) {
|
||||
public boolean shitlistRouter(Hash peer, String reason) { return shitlistRouter(peer, reason, null); }
|
||||
public boolean shitlistRouter(Hash peer, String reason, String transport) {
|
||||
if (peer == null) {
|
||||
_log.error("wtf, why did we try to shitlist null?", new Exception("shitfaced"));
|
||||
return false;
|
||||
@@ -73,18 +111,33 @@ public class Shitlist {
|
||||
if (period > 60*60*1000)
|
||||
period = 60*60*1000;
|
||||
|
||||
synchronized (_shitlist) {
|
||||
Date oldDate = (Date)_shitlist.put(peer, new Date(_context.clock().now() + period));
|
||||
wasAlready = (null == oldDate);
|
||||
if (reason != null) {
|
||||
if (!wasAlready || (!_shitlistCause.containsKey(peer)) )
|
||||
_shitlistCause.put(peer, reason);
|
||||
} else {
|
||||
_shitlistCause.remove(peer);
|
||||
Entry e = new Entry();
|
||||
e.expireOn = _context.clock().now() + period;
|
||||
e.cause = reason;
|
||||
e.transports = null;
|
||||
if (transport != null) {
|
||||
e.transports = new HashSet(1);
|
||||
e.transports.add(transport);
|
||||
}
|
||||
|
||||
synchronized (_entries) {
|
||||
Entry old = (Entry)_entries.put(peer, e);
|
||||
if (old != null) {
|
||||
wasAlready = true;
|
||||
_entries.put(peer, old);
|
||||
if (e.transports == null) {
|
||||
old.transports = null;
|
||||
} else if (old.transports != null) {
|
||||
old.transports.addAll(e.transports);
|
||||
}
|
||||
e = old;
|
||||
}
|
||||
}
|
||||
|
||||
_context.netDb().fail(peer);
|
||||
if (transport == null) {
|
||||
// we hate the peer on *any* transport
|
||||
_context.netDb().fail(peer);
|
||||
}
|
||||
//_context.tunnelManager().peerFailed(peer);
|
||||
//_context.messageRegistry().peerFailed(peer);
|
||||
if (!wasAlready)
|
||||
@@ -95,91 +148,107 @@ public class Shitlist {
|
||||
public void unshitlistRouter(Hash peer) {
|
||||
unshitlistRouter(peer, true);
|
||||
}
|
||||
private void unshitlistRouter(Hash peer, boolean realUnshitlist) {
|
||||
private void unshitlistRouter(Hash peer, boolean realUnshitlist) { unshitlistRouter(peer, realUnshitlist, null); }
|
||||
public void unshitlistRouter(Hash peer, String transport) { unshitlistRouter(peer, true, transport); }
|
||||
private void unshitlistRouter(Hash peer, boolean realUnshitlist, String transport) {
|
||||
if (peer == null) return;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unshitlisting router " + peer.toBase64());
|
||||
synchronized (_shitlist) {
|
||||
_shitlist.remove(peer);
|
||||
_shitlistCause.remove(peer);
|
||||
_log.info("Unshitlisting router " + peer.toBase64()
|
||||
+ (transport != null ? "/" + transport : ""));
|
||||
boolean fully = false;
|
||||
synchronized (_entries) {
|
||||
Entry e = (Entry)_entries.remove(peer);
|
||||
if ( (e == null) || (e.transports == null) || (transport == null) || (e.transports.size() <= 1) ) {
|
||||
// fully unshitlisted
|
||||
fully = true;
|
||||
} else {
|
||||
e.transports.remove(transport);
|
||||
if (e.transports.size() <= 0)
|
||||
fully = true;
|
||||
else
|
||||
_entries.put(peer, e);
|
||||
}
|
||||
}
|
||||
if (realUnshitlist) {
|
||||
if (fully) {
|
||||
if (realUnshitlist) {
|
||||
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
|
||||
if (prof != null)
|
||||
prof.unshitlist();
|
||||
}
|
||||
_context.messageHistory().unshitlist(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShitlisted(Hash peer) { return isShitlisted(peer, null); }
|
||||
public boolean isShitlisted(Hash peer, String transport) {
|
||||
boolean rv = false;
|
||||
boolean unshitlist = false;
|
||||
synchronized (_entries) {
|
||||
Entry entry = (Entry)_entries.get(peer);
|
||||
if (entry == null) {
|
||||
rv = false;
|
||||
} else {
|
||||
if (entry.expireOn <= _context.clock().now()) {
|
||||
_entries.remove(peer);
|
||||
unshitlist = true;
|
||||
rv = false;
|
||||
} else {
|
||||
if (entry.transports == null) {
|
||||
rv = true;
|
||||
} else if (entry.transports.contains(transport)) {
|
||||
rv = true;
|
||||
} else {
|
||||
rv = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unshitlist) {
|
||||
PeerProfile prof = _context.profileOrganizer().getProfile(peer);
|
||||
if (prof != null)
|
||||
prof.unshitlist();
|
||||
}
|
||||
_context.messageHistory().unshitlist(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() > _context.clock().now()) {
|
||||
return true;
|
||||
} else {
|
||||
unshitlistRouter(peer, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We already unshitlist on isShitlisted, but this purge
|
||||
* lets us get the correct value when rendering the HTML or
|
||||
* getting the shitlist count. wheee
|
||||
*
|
||||
*/
|
||||
private void purge() {
|
||||
Map shitlist = null;
|
||||
synchronized (_shitlist) {
|
||||
shitlist = new HashMap(_shitlist);
|
||||
_context.messageHistory().unshitlist(peer);
|
||||
}
|
||||
|
||||
long limit = _context.clock().now();
|
||||
|
||||
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, false);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public void renderStatusHTML(Writer out) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("<h2>Shitlist</h2>");
|
||||
Map shitlist = null;
|
||||
Map causes = null;
|
||||
Map entries = null;
|
||||
|
||||
purge();
|
||||
|
||||
synchronized (_shitlist) {
|
||||
shitlist = new HashMap(_shitlist);
|
||||
causes = new HashMap(_shitlistCause);
|
||||
synchronized (_entries) {
|
||||
entries = new HashMap(_entries);
|
||||
}
|
||||
buf.append("<ul>");
|
||||
|
||||
for (Iterator iter = shitlist.keySet().iterator(); iter.hasNext(); ) {
|
||||
int partial = 0;
|
||||
for (Iterator iter = entries.keySet().iterator(); iter.hasNext(); ) {
|
||||
Hash key = (Hash)iter.next();
|
||||
Date shitDate = (Date)shitlist.get(key);
|
||||
Entry entry = (Entry)entries.get(key);
|
||||
if ( (entry.transports != null) && (entry.transports.size() > 0) ) {
|
||||
partial++;
|
||||
continue;
|
||||
}
|
||||
buf.append("<li><b>").append(key.toBase64()).append("</b>");
|
||||
buf.append(" <a href=\"netdb.jsp#").append(key.toBase64().substring(0, 6)).append("\">(?)</a>");
|
||||
buf.append(" expiring on ");
|
||||
buf.append(shitDate);
|
||||
String cause = (String)causes.get(key);
|
||||
if (cause != null) {
|
||||
buf.append(" expiring in ");
|
||||
buf.append(DataHelper.formatDuration(entry.expireOn-_context.clock().now()));
|
||||
Set transports = entry.transports;
|
||||
if ( (transports != null) && (transports.size() > 0) )
|
||||
buf.append(" on the following transports: ").append(transports);
|
||||
if (entry.cause != null) {
|
||||
buf.append("<br />\n");
|
||||
buf.append(cause);
|
||||
buf.append(entry.cause);
|
||||
}
|
||||
buf.append("</li>\n");
|
||||
}
|
||||
buf.append("</ul>\n");
|
||||
buf.append("<i>Partial shitlisted peers (only blocked on some transports): ");
|
||||
buf.append(partial);
|
||||
buf.append("</i>\n");
|
||||
out.write(buf.toString());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ package net.i2p.router;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
@@ -100,7 +102,28 @@ public class StatisticsManager implements Service {
|
||||
// No longer expose, to make build tracking more expensive
|
||||
// stats.setProperty("router.id", RouterVersion.ID);
|
||||
// stats.setProperty("core.id", CoreVersion.ID);
|
||||
|
||||
|
||||
int newlines = 0;
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(Router.IDENTLOG);
|
||||
int c = -1;
|
||||
// perhaps later filter this to only include ident changes this
|
||||
// day/week/month
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (c == '\n')
|
||||
newlines++;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
} finally {
|
||||
if (in != null)
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (newlines > 0)
|
||||
stats.setProperty("stat_identities", newlines+"");
|
||||
|
||||
|
||||
if (_includePeerRankings) {
|
||||
if (false)
|
||||
stats.putAll(_context.profileManager().summarizePeers(_publishedStats));
|
||||
|
||||
@@ -320,7 +320,7 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
}
|
||||
boolean wantACK = true;
|
||||
int existingTags = GarlicMessageBuilder.estimateAvailableTags(getContext(), _leaseSet.getEncryptionKey());
|
||||
if (existingTags > 30)
|
||||
if ( (existingTags > 30) && (getContext().random().nextInt(100) >= 5) )
|
||||
wantACK = false;
|
||||
|
||||
long token = (wantACK ? getContext().random().nextLong(I2NPMessage.MAX_ID_VALUE) : -1);
|
||||
|
||||
@@ -44,7 +44,7 @@ class FloodOnlySearchJob extends FloodSearchJob {
|
||||
_isLease = isLease;
|
||||
_lookupsRemaining = 0;
|
||||
_dead = false;
|
||||
_out = new ArrayList(2);
|
||||
_out = Collections.synchronizedList(new ArrayList(2));
|
||||
_replySelector = new FloodOnlyLookupSelector(getContext(), this);
|
||||
_onReply = new FloodOnlyLookupMatchJob(getContext(), this);
|
||||
_onTimeout = new FloodOnlyLookupTimeoutJob(getContext(), this);
|
||||
@@ -70,7 +70,7 @@ class FloodOnlySearchJob extends FloodSearchJob {
|
||||
return;
|
||||
}
|
||||
OutNetMessage out = getContext().messageRegistry().registerPending(_replySelector, _onReply, _onTimeout, _timeoutMs);
|
||||
_out.add(out);
|
||||
synchronized (_out) { _out.add(out); }
|
||||
|
||||
for (int i = 0; _lookupsRemaining < CONCURRENT_SEARCHES && i < floodfillPeers.size(); i++) {
|
||||
Hash peer = (Hash)floodfillPeers.get(i);
|
||||
@@ -113,8 +113,10 @@ class FloodOnlySearchJob extends FloodSearchJob {
|
||||
if (_dead) return;
|
||||
_dead = true;
|
||||
}
|
||||
for (int i = 0; i < _out.size(); i++) {
|
||||
OutNetMessage out = (OutNetMessage)_out.get(i);
|
||||
List outBuf = null;
|
||||
synchronized (_out) { outBuf = new ArrayList(_out); }
|
||||
for (int i = 0; i < outBuf.size(); i++) {
|
||||
OutNetMessage out = (OutNetMessage)outBuf.get(i);
|
||||
getContext().messageRegistry().unregisterPending(out);
|
||||
}
|
||||
int timeRemaining = (int)(_origExpiration - getContext().clock().now());
|
||||
|
||||
@@ -227,10 +227,59 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
}
|
||||
|
||||
/** list of the Hashes of currently known floodfill peers */
|
||||
List getFloodfillPeers() {
|
||||
public List getFloodfillPeers() {
|
||||
FloodfillPeerSelector sel = (FloodfillPeerSelector)getPeerSelector();
|
||||
return sel.selectFloodfillParticipants(getKBuckets());
|
||||
}
|
||||
|
||||
protected void lookupBeforeDropping(Hash peer, RouterInfo info) {
|
||||
if (_context.jobQueue().getMaxLag() > 500) {
|
||||
// don't try to overload ourselves (e.g. failing 3000 router refs at
|
||||
// once, and then firing off 3000 netDb lookup tasks)
|
||||
super.lookupBeforeDropping(peer, info);
|
||||
return;
|
||||
}
|
||||
// this sends out the search to the floodfill peers even if we already have the
|
||||
// entry locally, firing no job if it gets a reply with an updated value (meaning
|
||||
// we shouldn't drop them but instead use the new data), or if they all time out,
|
||||
// firing the dropLookupFailedJob, which actually removes out local reference
|
||||
search(peer, new DropLookupFoundJob(_context, peer, info), new DropLookupFailedJob(_context, peer, info), 10*1000, false);
|
||||
}
|
||||
|
||||
private class DropLookupFailedJob extends JobImpl {
|
||||
private Hash _peer;
|
||||
private RouterInfo _info;
|
||||
|
||||
public DropLookupFailedJob(RouterContext ctx, Hash peer, RouterInfo info) {
|
||||
super(ctx);
|
||||
_peer = peer;
|
||||
_info = info;
|
||||
}
|
||||
public String getName() { return "Lookup on failure of netDb peer timed out"; }
|
||||
public void runJob() {
|
||||
dropAfterLookupFailed(_peer, _info);
|
||||
}
|
||||
}
|
||||
private class DropLookupFoundJob extends JobImpl {
|
||||
private Hash _peer;
|
||||
private RouterInfo _info;
|
||||
|
||||
public DropLookupFoundJob(RouterContext ctx, Hash peer, RouterInfo info) {
|
||||
super(ctx);
|
||||
_peer = peer;
|
||||
_info = info;
|
||||
}
|
||||
public String getName() { return "Lookup on failure of netDb peer matched"; }
|
||||
public void runJob() {
|
||||
RouterInfo updated = lookupRouterInfoLocally(_peer);
|
||||
if ( (updated != null) && (updated.getPublished() > _info.getPublished()) ) {
|
||||
// great, a legitimate update
|
||||
} else {
|
||||
// they just sent us what we already had. kill 'em both
|
||||
dropAfterLookupFailed(_peer, _info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ class KBucketSet {
|
||||
private I2PAppContext _context;
|
||||
private Hash _us;
|
||||
private KBucket _buckets[];
|
||||
private volatile int _size;
|
||||
|
||||
public final static int BASE = 8; // must go into KEYSIZE_BITS evenly
|
||||
public final static int KEYSIZE_BITS = Hash.HASH_LENGTH * 8;
|
||||
@@ -51,6 +52,8 @@ class KBucketSet {
|
||||
if (bucket >= 0) {
|
||||
int oldSize = _buckets[bucket].getKeyCount();
|
||||
int numInBucket = _buckets[bucket].add(peer);
|
||||
if (numInBucket != oldSize)
|
||||
_size++;
|
||||
if (numInBucket > BUCKET_SIZE) {
|
||||
// perhaps queue up coalesce job? naaahh.. lets let 'er grow for now
|
||||
}
|
||||
@@ -62,17 +65,26 @@ class KBucketSet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not an exact count (due to concurrency issues) but generally correct
|
||||
*
|
||||
*/
|
||||
public int size() {
|
||||
return _size;
|
||||
/*
|
||||
int size = 0;
|
||||
for (int i = 0; i < _buckets.length; i++)
|
||||
size += _buckets[i].getKeyCount();
|
||||
return size;
|
||||
*/
|
||||
}
|
||||
|
||||
public boolean remove(Hash entry) {
|
||||
int bucket = pickBucket(entry);
|
||||
KBucket kbucket = getBucket(bucket);
|
||||
boolean removed = kbucket.remove(entry);
|
||||
if (removed)
|
||||
_size--;
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
* dont accept any dbDtore of a router over 24 hours old (unless we dont
|
||||
* know anyone or just started up)
|
||||
*/
|
||||
private final static long ROUTER_INFO_EXPIRATION = 24*60*60*1000l;
|
||||
private final static long ROUTER_INFO_EXPIRATION = 3*24*60*60*1000l;
|
||||
|
||||
public KademliaNetworkDatabaseFacade(RouterContext context) {
|
||||
_context = context;
|
||||
@@ -278,8 +278,14 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.jobQueue().addJob(new DataPublisherJob(_context, this));
|
||||
// expire old leases
|
||||
_context.jobQueue().addJob(new ExpireLeasesJob(_context, this));
|
||||
// expire some routers in overly full kbuckets
|
||||
_context.jobQueue().addJob(new ExpireRoutersJob(_context, this));
|
||||
|
||||
// the ExpireRoutersJob never fired since the tunnel pool manager lied
|
||||
// and said all peers are in use (for no good reason), but this expire
|
||||
// thing was a bit overzealous anyway, since the kbuckets are only
|
||||
// relevent when the network is huuuuuuuuge.
|
||||
//// expire some routers in overly full kbuckets
|
||||
////_context.jobQueue().addJob(new ExpireRoutersJob(_context, this));
|
||||
|
||||
if (!_quiet) {
|
||||
// fill the passive queue periodically
|
||||
_context.jobQueue().addJob(new DataRepublishingSelectorJob(_context, this));
|
||||
@@ -456,9 +462,22 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
if (!_initialized) return null;
|
||||
DataStructure ds = _ds.get(key);
|
||||
if (ds != null) {
|
||||
if (ds instanceof RouterInfo)
|
||||
if (ds instanceof RouterInfo) {
|
||||
// more aggressive than perhaps is necessary, but makes sure we
|
||||
// drop old references that we had accepted on startup (since
|
||||
// startup allows some lax rules).
|
||||
boolean valid = true;
|
||||
try {
|
||||
valid = (null == validate(key, (RouterInfo)ds));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
valid = false;
|
||||
}
|
||||
if (!valid) {
|
||||
fail(key);
|
||||
return null;
|
||||
}
|
||||
return (RouterInfo)ds;
|
||||
else {
|
||||
} else {
|
||||
//_log.debug("Looking for a router [" + key + "] but it ISN'T a RouterInfo! " + ds, new Exception("Who thought that lease was a router?"));
|
||||
return null;
|
||||
}
|
||||
@@ -643,7 +662,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
|
||||
return "Invalid routerInfo signature on " + key.toBase64();
|
||||
} else if (!routerInfo.isCurrent(ROUTER_INFO_EXPIRATION)) {
|
||||
} else if (!routerInfo.isCurrent(ROUTER_INFO_EXPIRATION) && (_context.router().getUptime() > 60*60*1000) ) {
|
||||
if (routerInfo.getNetworkId() != Router.NETWORK_ID) {
|
||||
_context.shitlist().shitlistRouter(key, "Peer is not in our network");
|
||||
return "Peer is not in our network (" + routerInfo.getNetworkId() + ", wants "
|
||||
@@ -661,7 +680,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
+ " peers left (curPeer: " + key.toBase64() + " published on "
|
||||
+ new Date(routerInfo.getPublished()));
|
||||
}
|
||||
} else if (routerInfo.getPublished() > now + Router.CLOCK_FUDGE_FACTOR) {
|
||||
} else if (routerInfo.getPublished() > now + 2*Router.CLOCK_FUDGE_FACTOR) {
|
||||
long age = routerInfo.getPublished() - _context.clock().now();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Peer " + key.toBase64() + " published their routerInfo in the future?! ["
|
||||
@@ -671,6 +690,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
String rv = "Peer " + key.toBase64() + " is from another network, not accepting it (id="
|
||||
+ routerInfo.getNetworkId() + ", want " + Router.NETWORK_ID + ")";
|
||||
return rv;
|
||||
} else if ( (_context.router().getUptime() > 60*60*1000) && (routerInfo.getPublished() < now - 2*24*60*60*1000l) ) {
|
||||
long age = _context.clock().now() - routerInfo.getPublished();
|
||||
return "Peer " + key.toBase64() + " published " + DataHelper.formatDuration(age) + " ago";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -736,12 +758,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
}
|
||||
|
||||
_context.peerManager().removeCapabilities(dbEntry);
|
||||
boolean removed = _kb.remove(dbEntry);
|
||||
if (removed) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Removed kbucket entry for " + dbEntry);
|
||||
}
|
||||
lookupBeforeDropping(dbEntry, (RouterInfo)o);
|
||||
return;
|
||||
} else {
|
||||
// we always drop leaseSets that are failed [timed out],
|
||||
// regardless of how many routers we have. this is called on a lease if
|
||||
@@ -769,6 +787,30 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
}
|
||||
|
||||
protected void lookupBeforeDropping(Hash peer, RouterInfo info) {
|
||||
//bah, humbug.
|
||||
dropAfterLookupFailed(peer, info);
|
||||
}
|
||||
protected void dropAfterLookupFailed(Hash peer, RouterInfo info) {
|
||||
_context.peerManager().removeCapabilities(peer);
|
||||
boolean removed = _kb.remove(peer);
|
||||
if (removed) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Removed kbucket entry for " + peer);
|
||||
}
|
||||
|
||||
_ds.remove(peer);
|
||||
synchronized (_lastSent) {
|
||||
_lastSent.remove(peer);
|
||||
}
|
||||
synchronized (_explicitSendKeys) {
|
||||
_explicitSendKeys.remove(peer);
|
||||
}
|
||||
synchronized (_passiveSendKeys) {
|
||||
_passiveSendKeys.remove(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public void unpublish(LeaseSet localLeaseSet) {
|
||||
if (!_initialized) return;
|
||||
Hash h = localLeaseSet.getDestination().calculateHash();
|
||||
@@ -929,8 +971,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
renderRouterInfo(buf, ri, false);
|
||||
out.write(buf.toString());
|
||||
buf.setLength(0);
|
||||
String coreVersion = ri.getOptions().getProperty("coreVersion");
|
||||
String routerVersion = ri.getOptions().getProperty("router.version");
|
||||
String coreVersion = ri.getOption("coreVersion");
|
||||
String routerVersion = ri.getOption("router.version");
|
||||
if ( (coreVersion != null) && (routerVersion != null) ) {
|
||||
Map routerVersions = (Map)versions.get(coreVersion);
|
||||
if (routerVersions == null) {
|
||||
@@ -995,7 +1037,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
buf.append("Stats: <br /><i><code>\n");
|
||||
for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = info.getOptions().getProperty(key);
|
||||
String val = info.getOption(key);
|
||||
buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br />\n");
|
||||
}
|
||||
buf.append("</code></i><hr />\n");
|
||||
|
||||
@@ -159,7 +159,8 @@ class PersistentDataStore extends TransientDataStore {
|
||||
}
|
||||
|
||||
private void write(Hash key, DataStructure data) {
|
||||
_log.info("Writing key " + key);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Writing key " + key);
|
||||
FileOutputStream fos = null;
|
||||
File dbFile = null;
|
||||
try {
|
||||
@@ -349,9 +350,10 @@ class PersistentDataStore extends TransientDataStore {
|
||||
ri.readBytes(fis);
|
||||
if (ri.getNetworkId() != Router.NETWORK_ID) {
|
||||
corrupt = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("The router is from a different network: "
|
||||
+ ri.getIdentity().calculateHash().toBase64());
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("The router "
|
||||
+ ri.getIdentity().calculateHash().toBase64()
|
||||
+ " is from a different network");
|
||||
} else {
|
||||
try {
|
||||
_facade.store(ri.getIdentity().getHash(), ri);
|
||||
@@ -361,14 +363,16 @@ class PersistentDataStore extends TransientDataStore {
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Error reading the routerInfo from " + _routerFile.getAbsolutePath(), dfe);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error reading the routerInfo from " + _routerFile.getName(), dfe);
|
||||
corrupt = true;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
if (corrupt) _routerFile.delete();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error reading the RouterInfo from " + _routerFile.getAbsolutePath(), ioe);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to read the router reference in " + _routerFile.getName(), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,15 +841,17 @@ class SearchReplyJob extends JobImpl {
|
||||
if (!sendsBadInfo) {
|
||||
// we don't need to search for everthing we're given here - only ones that
|
||||
// are next in our search path...
|
||||
if (getContext().shitlist().isShitlisted(peer)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Not looking for a shitlisted peer...");
|
||||
getContext().statManager().addRateData("netDb.searchReplyValidationSkipped", 1, 0);
|
||||
} else {
|
||||
// note: no need to think about shitlisted targets in the netdb search, given
|
||||
// the floodfill's behavior
|
||||
//if (getContext().shitlist().isShitlisted(peer)) {
|
||||
// if (_log.shouldLog(Log.INFO))
|
||||
// _log.info("Not looking for a shitlisted peer...");
|
||||
// getContext().statManager().addRateData("netDb.searchReplyValidationSkipped", 1, 0);
|
||||
//} else {
|
||||
//getContext().netDb().lookupRouterInfo(peer, new ReplyVerifiedJob(getContext(), peer), new ReplyNotVerifiedJob(getContext(), peer), _timeoutMs);
|
||||
//_repliesPendingVerification++;
|
||||
shouldAdd = true;
|
||||
}
|
||||
//}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Peer " + _peer.toBase64() + " sends us bad replies, so not verifying " + peer.toBase64());
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.tunnel.pool.TunnelPeerSelector;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.Log;
|
||||
@@ -361,6 +362,8 @@ public class ProfileOrganizer {
|
||||
Hash cur = (Hash)_notFailingPeersList.get(curIndex);
|
||||
if (matches.contains(cur) ||
|
||||
(exclude != null && exclude.contains(cur))) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("matched? " + matches.contains(cur) + " exclude: " + exclude + " cur=" + cur.toBase64());
|
||||
continue;
|
||||
} else if (onlyNotFailing && _highCapacityPeers.containsKey(cur)) {
|
||||
// we dont want the good peers, just random ones
|
||||
@@ -368,12 +371,14 @@ public class ProfileOrganizer {
|
||||
} else {
|
||||
if (isSelectable(cur))
|
||||
selected.add(cur);
|
||||
else if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not selectable: " + cur.toBase64());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Selecting all not failing (strict? " + onlyNotFailing + " start=" + start
|
||||
+ ") found " + selected.size() + " new peers: " + selected);
|
||||
+ ") found " + selected.size() + " new peers: " + selected + " all=" + _notFailingPeersList.size() + " strict=" + _strictCapacityOrder.size());
|
||||
matches.addAll(selected);
|
||||
}
|
||||
if (matches.size() < howMany) {
|
||||
@@ -809,11 +814,16 @@ public class ProfileOrganizer {
|
||||
_log.warn("Peer " + peer.toBase64() + " is marked as hidden, disallowing its use");
|
||||
return false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Peer " + peer.toBase64() + " is locally known, allowing its use");
|
||||
// perhaps check to see if they are active?
|
||||
|
||||
return true;
|
||||
boolean exclude = TunnelPeerSelector.shouldExclude(_context, info);
|
||||
if (exclude) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Peer " + peer.toBase64() + " has capabilities or other stats suggesting we avoid it");
|
||||
return false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Peer " + peer.toBase64() + " is locally known, allowing its use");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
||||
@@ -16,11 +16,15 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
import java.util.Collections;
|
||||
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.ntcp.NTCPAddress;
|
||||
import net.i2p.router.transport.ntcp.NTCPTransport;
|
||||
import net.i2p.router.transport.tcp.TCPTransport;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -56,6 +60,48 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
|
||||
public int countActivePeers() { return (_manager == null ? 0 : _manager.countActivePeers()); }
|
||||
public int countActiveSendPeers() { return (_manager == null ? 0 : _manager.countActiveSendPeers()); }
|
||||
|
||||
/**
|
||||
* Framed average clock skew of connected peers in seconds, or null if we cannot answer.
|
||||
* Average is calculated over the middle "percentToInclude" peers.
|
||||
*/
|
||||
public Long getFramedAveragePeerClockSkew(int percentToInclude) {
|
||||
if (_manager == null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Returning null for framed averege peer clock skew (no transport manager)!");
|
||||
return null;
|
||||
}
|
||||
Vector skews = _manager.getClockSkews();
|
||||
if (skews == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Returning null for framed average peer clock skew (no data)!");
|
||||
return null;
|
||||
}
|
||||
if (skews.size() < 20) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Returning null for framed average peer clock skew (only " + skews.size() + " peers)!");
|
||||
return null;
|
||||
}
|
||||
// Going to calculate, sort them
|
||||
Collections.sort(skews);
|
||||
// Calculate frame size
|
||||
int frameSize = (skews.size() * percentToInclude / 100);
|
||||
int first = (skews.size() / 2) - (frameSize / 2);
|
||||
int last = (skews.size() / 2) + (frameSize / 2);
|
||||
// Sum skew values
|
||||
long sum = 0;
|
||||
for (int i = first; i < last; i++) {
|
||||
long value = ((Long) (skews.get(i))).longValue();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Adding clock skew " + i + " valued " + value + " s.");
|
||||
sum = sum + value;
|
||||
}
|
||||
// Calculate average
|
||||
Long framedAverageClockSkew = new Long(sum / frameSize);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Our framed average peer clock skew is " + framedAverageClockSkew + " s.");
|
||||
return framedAverageClockSkew;
|
||||
}
|
||||
|
||||
public List getBids(OutNetMessage msg) {
|
||||
return _manager.getBids(msg);
|
||||
}
|
||||
@@ -65,6 +111,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
|
||||
public TransportBid getNextBid(OutNetMessage msg) {
|
||||
return _manager.getNextBid(msg);
|
||||
}
|
||||
int getTransportCount() { return _manager.getTransportCount(); }
|
||||
|
||||
public void processMessage(OutNetMessage msg) {
|
||||
//GetBidsJob j = new GetBidsJob(_context, this, msg);
|
||||
@@ -89,20 +136,30 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
|
||||
|
||||
public Set createAddresses() {
|
||||
Map addresses = null;
|
||||
boolean newCreated = false;
|
||||
|
||||
if (_manager != null)
|
||||
if (_manager != null) {
|
||||
addresses = _manager.getAddresses();
|
||||
else
|
||||
} else {
|
||||
addresses = new HashMap(1);
|
||||
newCreated = true;
|
||||
}
|
||||
|
||||
if (!addresses.containsKey(TCPTransport.STYLE)) {
|
||||
RouterAddress addr = createTCPAddress();
|
||||
if (addr != null)
|
||||
addresses.put(TCPTransport.STYLE, addr);
|
||||
}
|
||||
if (!addresses.containsKey(NTCPTransport.STYLE)) {
|
||||
RouterAddress addr = createNTCPAddress(_context);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("NTCP address: " + addr);
|
||||
if (addr != null)
|
||||
addresses.put(NTCPTransport.STYLE, addr);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Creating addresses: " + addresses);
|
||||
_log.info("Creating addresses: " + addresses + " isNew? " + newCreated, new Exception("creator"));
|
||||
return new HashSet(addresses.values());
|
||||
}
|
||||
|
||||
@@ -134,4 +191,48 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
|
||||
addr.setTransportStyle(TCPTransport.STYLE);
|
||||
return addr;
|
||||
}
|
||||
|
||||
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
|
||||
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
|
||||
|
||||
public static RouterAddress createNTCPAddress(RouterContext ctx) {
|
||||
if (!TransportManager.enableNTCP(ctx)) return null;
|
||||
RouterAddress addr = new RouterAddress();
|
||||
addr.setCost(10);
|
||||
addr.setExpiration(null);
|
||||
Properties props = new Properties();
|
||||
String name = ctx.router().getConfigSetting(PROP_I2NP_NTCP_HOSTNAME);
|
||||
String port = ctx.router().getConfigSetting(PROP_I2NP_NTCP_PORT);
|
||||
/*
|
||||
boolean isNew = false;
|
||||
if (name == null) {
|
||||
name = "localhost";
|
||||
isNew = true;
|
||||
}
|
||||
if (port == null) {
|
||||
port = String.valueOf(ctx.random().nextInt(10240)+1024);
|
||||
isNew = true;
|
||||
}
|
||||
*/
|
||||
if ( (name == null) || (port == null) || (name.trim().length() <= 0) || ("null".equals(name)) )
|
||||
return null;
|
||||
try {
|
||||
int p = Integer.parseInt(port);
|
||||
if ( (p <= 0) || (p > 64*1024) )
|
||||
return null;
|
||||
} catch (NumberFormatException nfe) {
|
||||
return null;
|
||||
}
|
||||
props.setProperty(NTCPAddress.PROP_HOST, name);
|
||||
props.setProperty(NTCPAddress.PROP_PORT, port);
|
||||
addr.setOptions(props);
|
||||
addr.setTransportStyle(NTCPTransport.STYLE);
|
||||
//if (isNew) {
|
||||
if (false) return null;
|
||||
ctx.router().setConfigSetting(PROP_I2NP_NTCP_HOSTNAME, name);
|
||||
ctx.router().setConfigSetting(PROP_I2NP_NTCP_PORT, port);
|
||||
ctx.router().saveConfig();
|
||||
//}
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,13 +129,14 @@ public class FIFOBandwidthLimiter {
|
||||
* Request some bytes, blocking until they become available
|
||||
*
|
||||
*/
|
||||
public Request requestInbound(int bytesIn, String purpose) {
|
||||
public Request requestInbound(int bytesIn, String purpose) { return requestInbound(bytesIn, purpose, null, null); }
|
||||
public Request requestInbound(int bytesIn, String purpose, CompleteListener lsnr, Object attachment) {
|
||||
if (_inboundUnlimited) {
|
||||
_totalAllocatedInboundBytes += bytesIn;
|
||||
return _noop;
|
||||
}
|
||||
|
||||
SimpleRequest req = new SimpleRequest(bytesIn, 0, purpose);
|
||||
SimpleRequest req = new SimpleRequest(bytesIn, 0, purpose, lsnr, attachment);
|
||||
requestInbound(req, bytesIn, purpose);
|
||||
return req;
|
||||
}
|
||||
@@ -156,13 +157,14 @@ public class FIFOBandwidthLimiter {
|
||||
* Request some bytes, blocking until they become available
|
||||
*
|
||||
*/
|
||||
public Request requestOutbound(int bytesOut, String purpose) {
|
||||
public Request requestOutbound(int bytesOut, String purpose) { return requestOutbound(bytesOut, purpose, null, null); }
|
||||
public Request requestOutbound(int bytesOut, String purpose, CompleteListener lsnr, Object attachment) {
|
||||
if (_outboundUnlimited) {
|
||||
_totalAllocatedOutboundBytes += bytesOut;
|
||||
return _noop;
|
||||
}
|
||||
|
||||
SimpleRequest req = new SimpleRequest(0, bytesOut, purpose);
|
||||
SimpleRequest req = new SimpleRequest(0, bytesOut, purpose, lsnr, attachment);
|
||||
requestOutbound(req, bytesOut, purpose);
|
||||
return req;
|
||||
}
|
||||
@@ -296,8 +298,8 @@ public class FIFOBandwidthLimiter {
|
||||
_recvBps = (0.9f)*_recvBps + (0.1f)*((float)recv*1000)/(float)time;
|
||||
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("BW: time = " + time + " sent: " + sent + " recv: " + recv);
|
||||
//if (_log.shouldLog(Log.INFO))
|
||||
// _log.info("BW: time = " + time + " sent: " + _sendBps + " recv: " + _recvBps);
|
||||
_context.statManager().getStatLog().addData("bw", "bw.sendBps1s", (long)_sendBps, sent);
|
||||
_context.statManager().getStatLog().addData("bw", "bw.recvBps1s", (long)_recvBps, recv);
|
||||
}
|
||||
@@ -305,19 +307,19 @@ public class FIFOBandwidthLimiter {
|
||||
// Maintain an approximate average with a 15-second halflife
|
||||
// Weights (0.955 and 0.045) are tuned so that transition between two values (e.g. 0..10)
|
||||
// would reach their midpoint (e.g. 5) in 15s
|
||||
if (_sendBps15s <= 0)
|
||||
_sendBps15s = ((float)sent*15*1000f)/(float)time;
|
||||
else
|
||||
//if (_sendBps15s <= 0)
|
||||
// _sendBps15s = (0.045f)*((float)sent*15*1000f)/(float)time;
|
||||
//else
|
||||
_sendBps15s = (0.955f)*_sendBps15s + (0.045f)*((float)sent*1000f)/(float)time;
|
||||
|
||||
if (_recvBps15s <= 0)
|
||||
_recvBps15s = ((float)recv*15*1000f)/(float)time;
|
||||
else
|
||||
//if (_recvBps15s <= 0)
|
||||
// _recvBps15s = (0.045f)*((float)recv*15*1000f)/(float)time;
|
||||
//else
|
||||
_recvBps15s = (0.955f)*_recvBps15s + (0.045f)*((float)recv*1000)/(float)time;
|
||||
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("BW15: time = " + time + " sent: " + sent + " recv: " + recv);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("BW15: time = " + time + " sent: " + _sendBps + " recv: " + _recvBps);
|
||||
_context.statManager().getStatLog().addData("bw", "bw.sendBps15s", (long)_sendBps15s, sent);
|
||||
_context.statManager().getStatLog().addData("bw", "bw.recvBps15s", (long)_recvBps15s, recv);
|
||||
}
|
||||
@@ -432,10 +434,12 @@ public class FIFOBandwidthLimiter {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
if (req.getAllocationsSinceWait() > 0) {
|
||||
if ( (req.getAllocationsSinceWait() > 0) && (req.getCompleteListener() == null) ) {
|
||||
// we have already allocated some values to this request, but
|
||||
// they haven't taken advantage of it yet (most likely they're
|
||||
// IO bound)
|
||||
// (also, the complete listener is only set for situations where
|
||||
// waitForNextAllocation() is never called)
|
||||
continue;
|
||||
}
|
||||
// ok, they are really waiting for us to give them stuff
|
||||
@@ -482,8 +486,8 @@ public class FIFOBandwidthLimiter {
|
||||
locked_satisfyOutboundAvailable(satisfied);
|
||||
} else {
|
||||
// no bandwidth available
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Still denying the " + _pendingOutboundRequests.size()
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Still denying the " + _pendingOutboundRequests.size()
|
||||
+ " pending outbound requests (status: " + getStatus().toString()
|
||||
+ ", longest waited " + locked_getLongestOutboundWait() + " out)");
|
||||
}
|
||||
@@ -549,6 +553,8 @@ public class FIFOBandwidthLimiter {
|
||||
// we have already allocated some values to this request, but
|
||||
// they haven't taken advantage of it yet (most likely they're
|
||||
// IO bound)
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("multiple allocations since wait... ntcp shouldn't do this: " + req.getRequestName());
|
||||
continue;
|
||||
}
|
||||
// ok, they are really waiting for us to give them stuff
|
||||
@@ -563,6 +569,15 @@ public class FIFOBandwidthLimiter {
|
||||
req.allocateBytes(0, allocated);
|
||||
satisfied.add(req);
|
||||
if (req.getPendingOutboundRequested() > 0) {
|
||||
if (req.attachment() != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Allocating " + allocated + " bytes outbound as a partial grant to "
|
||||
+ req.getRequestName() + " (wanted "
|
||||
+ req.getTotalOutboundRequested() + " bytes, waited "
|
||||
+ waited
|
||||
+ "ms) pending " + _pendingOutboundRequests.size()
|
||||
+ ", longest waited " + locked_getLongestOutboundWait() + " out");
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Allocating " + allocated + " bytes outbound as a partial grant to "
|
||||
+ req.getRequestName() + " (wanted "
|
||||
@@ -571,6 +586,15 @@ public class FIFOBandwidthLimiter {
|
||||
+ "ms) pending " + _pendingOutboundRequests.size()
|
||||
+ ", longest waited " + locked_getLongestOutboundWait() + " out");
|
||||
} else {
|
||||
if (req.attachment() != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Allocating " + allocated + " bytes outbound to finish the partial grant to "
|
||||
+ req.getRequestName() + " (total "
|
||||
+ req.getTotalOutboundRequested() + " bytes, waited "
|
||||
+ waited
|
||||
+ "ms) pending " + _pendingOutboundRequests.size()
|
||||
+ ", longest waited " + locked_getLongestOutboundWait() + " out)");
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Allocating " + allocated + " bytes outbound to finish the partial grant to "
|
||||
+ req.getRequestName() + " (total "
|
||||
@@ -629,7 +653,10 @@ public class FIFOBandwidthLimiter {
|
||||
private String _target;
|
||||
private int _allocationsSinceWait;
|
||||
private boolean _aborted;
|
||||
private boolean _waited;
|
||||
List satisfiedBuffer;
|
||||
private CompleteListener _lsnr;
|
||||
private Object _attachment;
|
||||
|
||||
public SimpleRequest() {
|
||||
satisfiedBuffer = new ArrayList(1);
|
||||
@@ -639,7 +666,14 @@ public class FIFOBandwidthLimiter {
|
||||
satisfiedBuffer = new ArrayList(1);
|
||||
init(in, out, target);
|
||||
}
|
||||
public SimpleRequest(int in, int out, String target, CompleteListener lsnr, Object attachment) {
|
||||
satisfiedBuffer = new ArrayList(1);
|
||||
_lsnr = lsnr;
|
||||
_attachment = attachment;
|
||||
init(in, out, target);
|
||||
}
|
||||
public void init(int in, int out, String target) {
|
||||
_waited = false;
|
||||
_inTotal = in;
|
||||
_outTotal = out;
|
||||
_inAllocated = 0;
|
||||
@@ -659,36 +693,77 @@ public class FIFOBandwidthLimiter {
|
||||
public int getPendingInboundRequested() { return _inTotal - _inAllocated; }
|
||||
public boolean getAborted() { return _aborted; }
|
||||
public void abort() { _aborted = true; }
|
||||
public CompleteListener getCompleteListener() { return _lsnr; }
|
||||
public void setCompleteListener(CompleteListener lsnr) {
|
||||
boolean complete = false;
|
||||
synchronized (SimpleRequest.this) {
|
||||
_lsnr = lsnr;
|
||||
if (isComplete()) {
|
||||
complete = true;
|
||||
}
|
||||
}
|
||||
if (complete && lsnr != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("complete listener set AND completed: " + lsnr);
|
||||
lsnr.complete(SimpleRequest.this);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isComplete() { return (_outAllocated >= _outTotal) && (_inAllocated >= _inTotal); }
|
||||
|
||||
public void waitForNextAllocation() {
|
||||
_waited = true;
|
||||
_allocationsSinceWait = 0;
|
||||
if ( (_outAllocated >= _outTotal) &&
|
||||
(_inAllocated >= _inTotal) )
|
||||
return;
|
||||
boolean complete = false;
|
||||
try {
|
||||
synchronized (SimpleRequest.this) {
|
||||
SimpleRequest.this.wait();
|
||||
if (isComplete())
|
||||
complete = true;
|
||||
else
|
||||
SimpleRequest.this.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
if (complete && _lsnr != null)
|
||||
_lsnr.complete(SimpleRequest.this);
|
||||
}
|
||||
int getAllocationsSinceWait() { return _allocationsSinceWait; }
|
||||
int getAllocationsSinceWait() { return _waited ? _allocationsSinceWait : 0; }
|
||||
void allocateAll() {
|
||||
_inAllocated = _inTotal;
|
||||
_outAllocated = _outTotal;
|
||||
_outAllocated = _outTotal;
|
||||
_allocationsSinceWait++;
|
||||
if (_lsnr == null)
|
||||
_allocationsSinceWait++;
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("allocate all");
|
||||
notifyAllocation();
|
||||
}
|
||||
void allocateBytes(int in, int out) {
|
||||
_inAllocated += in;
|
||||
_outAllocated += out;
|
||||
_allocationsSinceWait++;
|
||||
if (_lsnr == null)
|
||||
_allocationsSinceWait++;
|
||||
if (isComplete()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("allocate " + in +"/"+ out + " completed, listener=" + _lsnr);
|
||||
}
|
||||
//notifyAllocation(); // handled within the satisfy* methods
|
||||
}
|
||||
void notifyAllocation() {
|
||||
boolean complete = false;
|
||||
synchronized (SimpleRequest.this) {
|
||||
if (isComplete())
|
||||
complete = true;
|
||||
SimpleRequest.this.notifyAll();
|
||||
}
|
||||
if (complete && _lsnr != null) {
|
||||
_lsnr.complete(SimpleRequest.this);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("at completion for " + _inTotal + "/" + _outTotal
|
||||
+ ", recvBps=" + _recvBps + "/"+ _recvBps15s + " listener is " + _lsnr);
|
||||
}
|
||||
}
|
||||
public void attach(Object obj) { _attachment = obj; }
|
||||
public Object attachment() { return _attachment; }
|
||||
public String toString() { return getRequestName(); }
|
||||
}
|
||||
|
||||
public interface Request {
|
||||
@@ -712,10 +787,20 @@ public class FIFOBandwidthLimiter {
|
||||
public boolean getAborted();
|
||||
/** thar be dragons */
|
||||
public void init(int in, int out, String target);
|
||||
public void setCompleteListener(CompleteListener lsnr);
|
||||
public void attach(Object obj);
|
||||
public Object attachment();
|
||||
public CompleteListener getCompleteListener();
|
||||
}
|
||||
|
||||
public interface CompleteListener {
|
||||
public void complete(Request req);
|
||||
}
|
||||
|
||||
private static final NoopRequest _noop = new NoopRequest();
|
||||
private static class NoopRequest implements Request {
|
||||
private CompleteListener _lsnr;
|
||||
private Object _attachment;
|
||||
public void abort() {}
|
||||
public boolean getAborted() { return false; }
|
||||
public int getPendingInboundRequested() { return 0; }
|
||||
@@ -726,5 +811,12 @@ public class FIFOBandwidthLimiter {
|
||||
public int getTotalOutboundRequested() { return 0; }
|
||||
public void waitForNextAllocation() {}
|
||||
public void init(int in, int out, String target) {}
|
||||
public CompleteListener getCompleteListener() { return _lsnr; }
|
||||
public void setCompleteListener(CompleteListener lsnr) {
|
||||
_lsnr = lsnr;
|
||||
lsnr.complete(NoopRequest.this);
|
||||
}
|
||||
public void attach(Object obj) { _attachment = obj; }
|
||||
public Object attachment() { return _attachment; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public class GetBidsJob extends JobImpl {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Attempt to send a message to a shitlisted peer - " + to);
|
||||
//context.messageRegistry().peerFailed(to);
|
||||
context.statManager().addRateData("transport.bidFailShitlisted", msg.getLifetime(), 0);
|
||||
fail(context, msg);
|
||||
return;
|
||||
}
|
||||
@@ -56,14 +57,20 @@ public class GetBidsJob extends JobImpl {
|
||||
if (to.equals(us)) {
|
||||
if (log.shouldLog(Log.ERROR))
|
||||
log.error("wtf, send a message to ourselves? nuh uh. msg = " + msg);
|
||||
context.statManager().addRateData("transport.bidFailSelf", msg.getLifetime(), 0);
|
||||
fail(context, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
TransportBid bid = facade.getNextBid(msg);
|
||||
if (bid == null) {
|
||||
if (msg.getFailedTransports().size() == 0) {
|
||||
int failedCount = msg.getFailedTransports().size();
|
||||
if (failedCount == 0) {
|
||||
context.statManager().addRateData("transport.bidFailNoTransports", msg.getLifetime(), 0);
|
||||
context.shitlist().shitlistRouter(to, "We share no common transports with them");
|
||||
} else if (failedCount >= facade.getTransportCount()) {
|
||||
context.statManager().addRateData("transport.bidFailAllTransports", msg.getLifetime(), 0);
|
||||
// fail after all transports were unsuccessful
|
||||
context.netDb().fail(to);
|
||||
}
|
||||
fail(context, msg);
|
||||
|
||||
@@ -171,6 +171,7 @@ public class OutboundMessageRegistry {
|
||||
}
|
||||
|
||||
public void unregisterPending(OutNetMessage msg) {
|
||||
if (msg == null) return;
|
||||
MessageSelector sel = msg.getReplySelector();
|
||||
boolean stillActive = false;
|
||||
synchronized (_selectorToMessage) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
import net.i2p.data.Hash;
|
||||
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
@@ -39,9 +41,12 @@ public interface Transport {
|
||||
|
||||
public int countActivePeers();
|
||||
public int countActiveSendPeers();
|
||||
public Vector getClockSkews();
|
||||
public List getMostRecentErrorMessages();
|
||||
|
||||
public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException;
|
||||
public short getReachabilityStatus();
|
||||
public void recheckReachability();
|
||||
|
||||
public boolean isUnreachable(Hash peer);
|
||||
}
|
||||
|
||||
@@ -10,13 +10,7 @@ package net.i2p.router.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
@@ -24,6 +18,7 @@ import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.MessageSelector;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
import net.i2p.router.RouterContext;
|
||||
@@ -39,6 +34,8 @@ public abstract class TransportImpl implements Transport {
|
||||
private RouterAddress _currentAddress;
|
||||
private List _sendPool;
|
||||
protected RouterContext _context;
|
||||
/** map from routerIdentHash to timestamp (Long) that the peer was last unreachable */
|
||||
private Map _unreachableEntries;
|
||||
|
||||
/**
|
||||
* Initialize the new transport
|
||||
@@ -56,6 +53,7 @@ public abstract class TransportImpl implements Transport {
|
||||
_context.statManager().createRateStat("transport.sendProcessingTime", "How long does it take from noticing that we want to send the message to having it completely sent (successfully or failed)?", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("transport.expiredOnQueueLifetime", "How long a message that expires on our outbound queue is processed", "Transport", new long[] { 60*1000l, 10*60*1000l, 60*60*1000l, 24*60*60*1000l } );
|
||||
_sendPool = new ArrayList(16);
|
||||
_unreachableEntries = new HashMap(16);
|
||||
_currentAddress = null;
|
||||
}
|
||||
|
||||
@@ -69,6 +67,13 @@ public abstract class TransportImpl implements Transport {
|
||||
*/
|
||||
public int countActiveSendPeers() { return 0; }
|
||||
|
||||
/**
|
||||
* Return our peer clock skews on a transport.
|
||||
* Vector composed of Long, each element representing a peer skew in seconds.
|
||||
* Dummy version. Transports override it.
|
||||
*/
|
||||
public Vector getClockSkews() { return new Vector(); }
|
||||
|
||||
public List getMostRecentErrorMessages() { return Collections.EMPTY_LIST; }
|
||||
/**
|
||||
* Nonblocking call to pull the next outbound message
|
||||
@@ -138,8 +143,11 @@ public abstract class TransportImpl implements Transport {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("afterSend slow: [success=" + sendSuccessful + "] " + msg.getMessageSize() + "byte "
|
||||
+ msg.getMessageType() + " " + msg.getMessageId() + " from "
|
||||
+ _context.routerHash().toBase64().substring(0,6) + " took " + msToSend);
|
||||
+ _context.routerHash().toBase64().substring(0,6) + " took " + msToSend
|
||||
+ "/" + msg.getTransmissionTime());
|
||||
}
|
||||
//if (true)
|
||||
// _log.error("(not error) I2NP message sent? " + sendSuccessful + " " + msg.getMessageId() + " after " + msToSend + "/" + msg.getTransmissionTime());
|
||||
|
||||
long lifetime = msg.getLifetime();
|
||||
if (lifetime > 3000) {
|
||||
@@ -147,7 +155,7 @@ public abstract class TransportImpl implements Transport {
|
||||
if (!sendSuccessful)
|
||||
level = Log.INFO;
|
||||
if (_log.shouldLog(level))
|
||||
_log.log(level, "afterSend slow (" + lifetime + "): [success=" + sendSuccessful + "]" + msg.getMessageSize() + "byte "
|
||||
_log.log(level, "afterSend slow (" + lifetime + "/" + msToSend + "/" + msg.getTransmissionTime() + "): [success=" + sendSuccessful + "]" + msg.getMessageSize() + "byte "
|
||||
+ msg.getMessageType() + " " + msg.getMessageId() + " from " + _context.routerHash().toBase64().substring(0,6)
|
||||
+ " to " + msg.getTarget().getIdentity().calculateHash().toBase64().substring(0,6) + ": " + msg.toString());
|
||||
} else {
|
||||
@@ -229,7 +237,7 @@ public abstract class TransportImpl implements Transport {
|
||||
if (allTime > 5*1000) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Took too long from preperation to afterSend(ok? " + sendSuccessful
|
||||
+ "): " + allTime + "ms " + " after failing on: "
|
||||
+ "): " + allTime + "ms/" + sendTime + "ms after failing on: "
|
||||
+ msg.getFailedTransports() + " and succeeding on " + getStyle());
|
||||
if ( (allTime > 60*1000) && (sendSuccessful) ) {
|
||||
// WTF!!@#
|
||||
@@ -299,6 +307,9 @@ public abstract class TransportImpl implements Transport {
|
||||
*
|
||||
*/
|
||||
public void messageReceived(I2NPMessage inMsg, RouterIdentity remoteIdent, Hash remoteIdentHash, long msToReceive, int bytesReceived) {
|
||||
//if (true)
|
||||
// _log.error("(not error) I2NP message received: " + inMsg.getUniqueId() + " after " + msToReceive);
|
||||
|
||||
int level = Log.INFO;
|
||||
if (msToReceive > 5000)
|
||||
level = Log.WARN;
|
||||
@@ -368,13 +379,68 @@ public abstract class TransportImpl implements Transport {
|
||||
public RouterContext getContext() { return _context; }
|
||||
public short getReachabilityStatus() { return CommSystemFacade.STATUS_UNKNOWN; }
|
||||
public void recheckReachability() {}
|
||||
|
||||
private static final long UNREACHABLE_PERIOD = 5*60*1000;
|
||||
public boolean isUnreachable(Hash peer) {
|
||||
long now = _context.clock().now();
|
||||
synchronized (_unreachableEntries) {
|
||||
Long when = (Long)_unreachableEntries.get(peer);
|
||||
if (when == null) return false;
|
||||
if (when.longValue() + UNREACHABLE_PERIOD < now) {
|
||||
_unreachableEntries.remove(peer);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** called when we can't reach a peer */
|
||||
public void markUnreachable(Hash peer) {
|
||||
long now = _context.clock().now();
|
||||
synchronized (_unreachableEntries) {
|
||||
_unreachableEntries.put(peer, new Long(now));
|
||||
}
|
||||
}
|
||||
/** called when we establish a peer connection (outbound or inbound) */
|
||||
public void markReachable(Hash peer) {
|
||||
// if *some* transport can reach them, then we shouldn't shitlist 'em
|
||||
_context.shitlist().unshitlistRouter(peer);
|
||||
synchronized (_unreachableEntries) {
|
||||
_unreachableEntries.remove(peer);
|
||||
}
|
||||
}
|
||||
private class CleanupUnreachable extends JobImpl {
|
||||
public CleanupUnreachable(RouterContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
public String getName() { return "Cleanup " + getStyle() + " unreachable list"; }
|
||||
public void runJob() {
|
||||
long now = getContext().clock().now();
|
||||
synchronized (_unreachableEntries) {
|
||||
for (Iterator iter = _unreachableEntries.keySet().iterator(); iter.hasNext(); ) {
|
||||
Hash peer = (Hash)iter.next();
|
||||
Long when = (Long)_unreachableEntries.get(peer);
|
||||
if (when.longValue() + UNREACHABLE_PERIOD < now)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
requeue(60*1000);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPubliclyRoutable(byte addr[]) {
|
||||
if ((addr[0]&0xFF) == 127) return false;
|
||||
if ((addr[0]&0xFF) == 10) return false;
|
||||
if ( ((addr[0]&0xFF) == 172) && ((addr[1]&0xFF) >= 16) && ((addr[1]&0xFF) <= 31) ) return false;
|
||||
if ( ((addr[0]&0xFF) == 192) && ((addr[1]&0xFF) == 168) ) return false;
|
||||
if ((addr[0]&0xFF) >= 224) return false; // no multicast
|
||||
return true; // or at least possible to be true
|
||||
if (addr.length == 4) {
|
||||
if ((addr[0]&0xFF) == 127) return false;
|
||||
if ((addr[0]&0xFF) == 10) return false;
|
||||
if ( ((addr[0]&0xFF) == 172) && ((addr[1]&0xFF) >= 16) && ((addr[1]&0xFF) <= 31) ) return false;
|
||||
if ( ((addr[0]&0xFF) == 192) && ((addr[1]&0xFF) == 168) ) return false;
|
||||
if ((addr[0]&0xFF) >= 224) return false; // no multicast
|
||||
return true; // or at least possible to be true
|
||||
} else if (addr.length == 16) {
|
||||
return false;
|
||||
} else {
|
||||
// ipv?
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Vector;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
@@ -27,6 +28,7 @@ import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.transport.tcp.TCPTransport;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.router.transport.ntcp.NTCPTransport;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class TransportManager implements TransportEventListener {
|
||||
@@ -36,11 +38,19 @@ public class TransportManager implements TransportEventListener {
|
||||
|
||||
private final static String PROP_DISABLE_TCP = "i2np.tcp.disable";
|
||||
private final static String PROP_ENABLE_UDP = "i2np.udp.enable";
|
||||
private static final String DEFAULT_ENABLE_UDP = "true";
|
||||
private final static String PROP_ENABLE_NTCP = "i2np.ntcp.enable";
|
||||
private final static String DEFAULT_ENABLE_NTCP = "true";
|
||||
private final static String DEFAULT_ENABLE_UDP = "true";
|
||||
|
||||
public TransportManager(RouterContext context) {
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(TransportManager.class);
|
||||
_context.statManager().createRateStat("transport.shitlistOnUnreachable", "Add a peer to the shitlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.noBidsYetNotAllUnreachable", "Add a peer to the shitlist since none of the transports can reach them", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.bidFailShitlisted", "Could not attempt to bid on message, as they were shitlisted", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.bidFailSelf", "Could not attempt to bid on message, as it targeted ourselves", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.bidFailNoTransports", "Could not attempt to bid on message, as none of the transports could attempt it", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("transport.bidFailAllTransports", "Could not attempt to bid on message, as all of the transports had failed", "Transport", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_transports = new ArrayList();
|
||||
}
|
||||
|
||||
@@ -76,6 +86,17 @@ public class TransportManager implements TransportEventListener {
|
||||
udp.setListener(this);
|
||||
_transports.add(udp);
|
||||
}
|
||||
enableNTCP(_context);
|
||||
NTCPTransport ntcp = new NTCPTransport(_context);
|
||||
ntcp.setListener(this);
|
||||
_transports.add(ntcp);
|
||||
}
|
||||
|
||||
static boolean enableNTCP(RouterContext ctx) {
|
||||
String enableNTCP = ctx.router().getConfigSetting(PROP_ENABLE_NTCP);
|
||||
if (enableNTCP == null)
|
||||
enableNTCP = DEFAULT_ENABLE_NTCP;
|
||||
return "true".equalsIgnoreCase(enableNTCP);
|
||||
}
|
||||
|
||||
public void startListening() {
|
||||
@@ -92,7 +113,7 @@ public class TransportManager implements TransportEventListener {
|
||||
|
||||
public void restart() {
|
||||
stopListening();
|
||||
try { Thread.sleep(1*1000); } catch (InterruptedException ie) {}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
startListening();
|
||||
}
|
||||
|
||||
@@ -103,6 +124,8 @@ public class TransportManager implements TransportEventListener {
|
||||
_transports.clear();
|
||||
}
|
||||
|
||||
int getTransportCount() { return _transports.size(); }
|
||||
|
||||
private boolean isSupported(Set addresses, Transport t) {
|
||||
for (Iterator iter = addresses.iterator(); iter.hasNext(); ) {
|
||||
RouterAddress addr = (RouterAddress)iter.next();
|
||||
@@ -128,6 +151,23 @@ public class TransportManager implements TransportEventListener {
|
||||
return peers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return our peer clock skews on all transports.
|
||||
* Vector composed of Long, each element representing a peer skew in seconds.
|
||||
* Note: this method returns them in whimsical order.
|
||||
*/
|
||||
public Vector getClockSkews() {
|
||||
Vector skews = new Vector();
|
||||
for (int i = 0; i < _transports.size(); i++) {
|
||||
Vector tempSkews = ((Transport)_transports.get(i)).getClockSkews();
|
||||
if ((tempSkews == null) || (tempSkews.size() <= 0)) continue;
|
||||
skews.addAll(tempSkews);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Transport manager returning " + skews.size() + " peer clock skews.");
|
||||
return skews;
|
||||
}
|
||||
|
||||
public short getReachabilityStatus() {
|
||||
if (_transports.size() <= 0) return CommSystemFacade.STATUS_UNKNOWN;
|
||||
short status[] = new short[_transports.size()];
|
||||
@@ -195,10 +235,16 @@ public class TransportManager implements TransportEventListener {
|
||||
}
|
||||
|
||||
public TransportBid getNextBid(OutNetMessage msg) {
|
||||
int unreachableTransports = 0;
|
||||
Hash peer = msg.getTarget().getIdentity().calculateHash();
|
||||
Set failedTransports = msg.getFailedTransports();
|
||||
TransportBid rv = null;
|
||||
for (int i = 0; i < _transports.size(); i++) {
|
||||
Transport t = (Transport)_transports.get(i);
|
||||
if (t.isUnreachable(peer)) {
|
||||
unreachableTransports++;
|
||||
continue;
|
||||
}
|
||||
if (failedTransports.contains(t.getStyle())) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Skipping transport " + t.getStyle() + " as it already failed");
|
||||
@@ -217,8 +263,16 @@ public class TransportManager implements TransportEventListener {
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Transport " + t.getStyle() + " did not produce a bid");
|
||||
if (t.isUnreachable(peer))
|
||||
unreachableTransports++;
|
||||
}
|
||||
}
|
||||
if (unreachableTransports >= _transports.size()) {
|
||||
_context.statManager().addRateData("transport.shitlistOnUnreachable", msg.getLifetime(), msg.getLifetime());
|
||||
_context.shitlist().shitlistRouter(peer, "Unreachable on any transport");
|
||||
} else if (rv == null) {
|
||||
_context.statManager().addRateData("transport.noBidsYetNotAllUnreachable", unreachableTransports, msg.getLifetime());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -260,6 +314,8 @@ public class TransportManager implements TransportEventListener {
|
||||
Transport t = (Transport)_transports.get(i);
|
||||
if (t.getCurrentAddress() != null)
|
||||
buf.append(t.getCurrentAddress()).append("\n\n");
|
||||
else
|
||||
buf.append(t.getStyle()).append(" is used for outbound connections only");
|
||||
}
|
||||
buf.append("</pre>\n");
|
||||
out.write(buf.toString());
|
||||
|
||||
@@ -0,0 +1,888 @@
|
||||
package net.i2p.router.transport.ntcp;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.DHSessionKeyBuilder;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/*
|
||||
* Alice contacts Bob
|
||||
* =========================================================
|
||||
* X+(H(X) xor Bob.identHash)----------------------------->
|
||||
* <----------------------------------------Y+E(H(X+Y)+tsB, sk, Y[239:255])
|
||||
* E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB+padding), sk, hX_xor_Bob.identHash[16:31])--->
|
||||
* <----------------------E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
|
||||
*
|
||||
* Alternately, when Bob receives a connection, it could be a
|
||||
* check connection (perhaps prompted by Bob asking for someone
|
||||
* to verify his listener). check connections are formatted per
|
||||
* {@link #isCheckInfo()}
|
||||
*/
|
||||
public class EstablishState {
|
||||
private RouterContext _context;
|
||||
private Log _log;
|
||||
|
||||
// bob receives (and alice sends)
|
||||
private byte _X[];
|
||||
private byte _hX_xor_bobIdentHash[];
|
||||
private int _aliceIdentSize;
|
||||
/** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */
|
||||
private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig;
|
||||
/** how long we expect _sz_aliceIdent_tsA_padding_aliceSig to be when its full */
|
||||
private int _sz_aliceIdent_tsA_padding_aliceSigSize;
|
||||
// alice receives (and bob sends)
|
||||
private byte _Y[];
|
||||
private transient byte _e_hXY_tsB[];
|
||||
private transient long _tsB;
|
||||
private transient long _tsA;
|
||||
private transient byte _e_bobSig[];
|
||||
|
||||
/** previously received encrypted block (or the IV) */
|
||||
private byte _prevEncrypted[];
|
||||
/** current encrypted block we are reading */
|
||||
private byte _curEncrypted[];
|
||||
/**
|
||||
* next index in _curEncrypted to write to (equals _curEncrypted length if the block is
|
||||
* ready to decrypt)
|
||||
*/
|
||||
private int _curEncryptedOffset;
|
||||
/** decryption buffer */
|
||||
private byte _curDecrypted[];
|
||||
|
||||
/** bytes received so far */
|
||||
private int _received;
|
||||
/** bytes sent so far */
|
||||
private int _sent;
|
||||
|
||||
private byte _extra[];
|
||||
|
||||
private DHSessionKeyBuilder _dh;
|
||||
|
||||
private NTCPTransport _transport;
|
||||
private NTCPConnection _con;
|
||||
private boolean _corrupt;
|
||||
/** error causing the corruption */
|
||||
private String _err;
|
||||
/** exception causing the error */
|
||||
private Exception _e;
|
||||
private boolean _verified;
|
||||
private boolean _confirmWritten;
|
||||
private boolean _failedBySkew;
|
||||
|
||||
public EstablishState(RouterContext ctx, NTCPTransport transport, NTCPConnection con) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
_transport = transport;
|
||||
_con = con;
|
||||
_verified = false;
|
||||
_corrupt = false;
|
||||
_confirmWritten = false;
|
||||
_dh = new DHSessionKeyBuilder();
|
||||
if (_con.isInbound()) {
|
||||
_X = new byte[256];
|
||||
_hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH];
|
||||
_sz_aliceIdent_tsA_padding_aliceSig = new ByteArrayOutputStream(512);
|
||||
} else {
|
||||
_X = _dh.getMyPublicValueBytes();
|
||||
_Y = new byte[256];
|
||||
_hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH];
|
||||
byte hx[] = ctx.sha().calculateHash(_X).getData();
|
||||
DataHelper.xor(hx, 0, con.getRemotePeer().calculateHash().getData(), 0, _hX_xor_bobIdentHash, 0, hx.length);
|
||||
}
|
||||
|
||||
_prevEncrypted = new byte[16];
|
||||
_curEncrypted = new byte[16];
|
||||
_curEncryptedOffset = 0;
|
||||
_curDecrypted = new byte[16];
|
||||
|
||||
_received = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse the contents of the buffer as part of the handshake. if the
|
||||
* handshake is completed and there is more data remaining, the buffer is
|
||||
* updated so that the next read will be the (still encrypted) remaining
|
||||
* data (available from getExtraBytes)
|
||||
*/
|
||||
public void receive(ByteBuffer src) {
|
||||
if (_corrupt || _verified)
|
||||
throw new IllegalStateException(prefix() + "received after completion [corrupt?" + _corrupt + " verified? " + _verified + "] on " + _con);
|
||||
if (!src.hasRemaining())
|
||||
return; // nothing to receive
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"receive " + src);
|
||||
if (_con.isInbound())
|
||||
receiveInbound(src);
|
||||
else
|
||||
receiveOutbound(src);
|
||||
}
|
||||
|
||||
/**
|
||||
* we have written all of the data required to confirm the connection
|
||||
* establishment
|
||||
*/
|
||||
public boolean confirmWritten() { return _confirmWritten; }
|
||||
|
||||
public boolean getFailedBySkew() { return _failedBySkew; }
|
||||
|
||||
/** we are Bob, so receive these bytes as part of an inbound connection */
|
||||
private void receiveInbound(ByteBuffer src) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"Receiving inbound: prev received=" + _received + " src.remaining=" + src.remaining());
|
||||
while (_received < _X.length && src.hasRemaining()) {
|
||||
byte c = src.get();
|
||||
_X[_received++] = c;
|
||||
//if (_log.shouldLog(Log.DEBUG)) _log.debug("recv x" + (int)c + " received=" + _received);
|
||||
if (_received >= _X.length) {
|
||||
if (isCheckInfo(_context, _context.routerHash(), _X)) {
|
||||
_context.statManager().addRateData("ntcp.inboundCheckConnection", 1, 0);
|
||||
fail("Incoming connection was a check connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (_received < _X.length + _hX_xor_bobIdentHash.length && src.hasRemaining()) {
|
||||
int i = _received-_X.length;
|
||||
_received++;
|
||||
byte c = src.get();
|
||||
_hX_xor_bobIdentHash[i] = c;
|
||||
//if (_log.shouldLog(Log.DEBUG)) _log.debug("recv bih" + (int)c + " received=" + _received);
|
||||
}
|
||||
|
||||
if (_received >= _X.length + _hX_xor_bobIdentHash.length) {
|
||||
if (_dh.getSessionKey() == null) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"Enough data for a DH received");
|
||||
|
||||
// first verify that Alice knows who she is trying to talk with and that the X
|
||||
// isn't corrupt
|
||||
Hash hX = _context.sha().calculateHash(_X);
|
||||
byte realXor[] = DataHelper.xor(hX.getData(), _context.routerHash().getData());
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
//_log.debug(prefix()+"_X = " + Base64.encode(_X));
|
||||
_log.debug(prefix()+"hx = " + Base64.encode(hX.getData()));
|
||||
_log.debug(prefix()+"bih=" + Base64.encode(_context.routerHash().getData()));
|
||||
_log.debug(prefix()+"xor=" + Base64.encode(realXor));
|
||||
}
|
||||
if (!DataHelper.eq(realXor, _hX_xor_bobIdentHash)) {
|
||||
_context.statManager().addRateData("ntcp.invalidHXxorBIH", 1, 0);
|
||||
fail("Invalid hX_xor");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// ok, they're actually trying to talk to us, and we got their (unauthenticated) X
|
||||
_dh.setPeerPublicValue(_X);
|
||||
_dh.getSessionKey(); // force the calc
|
||||
System.arraycopy(realXor, 16, _prevEncrypted, 0, _prevEncrypted.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
|
||||
|
||||
// now prepare our response: Y+E(H(X+Y)+tsB+padding, sk, Y[239:255])
|
||||
_Y = _dh.getMyPublicValueBytes();
|
||||
byte xy[] = new byte[_X.length+_Y.length];
|
||||
System.arraycopy(_X, 0, xy, 0, _X.length);
|
||||
System.arraycopy(_Y, 0, xy, _X.length, _Y.length);
|
||||
Hash hxy = _context.sha().calculateHash(xy);
|
||||
_tsB = _context.clock().now()/1000l; // our (Bob's) timestamp in seconds
|
||||
byte padding[] = new byte[12]; // the encrypted data needs an extra 12 bytes
|
||||
_context.random().nextBytes(padding);
|
||||
byte toEncrypt[] = new byte[hxy.getData().length+4+padding.length];
|
||||
System.arraycopy(hxy.getData(), 0, toEncrypt, 0, hxy.getData().length);
|
||||
byte tsB[] = DataHelper.toLong(4, _tsB);
|
||||
System.arraycopy(tsB, 0, toEncrypt, hxy.getData().length, tsB.length);
|
||||
//DataHelper.toLong(toEncrypt, hxy.getData().length, 4, _tsB);
|
||||
System.arraycopy(padding, 0,toEncrypt, hxy.getData().length+4, padding.length);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
//_log.debug(prefix()+"Y="+Base64.encode(_Y));
|
||||
//_log.debug(prefix()+"x+y="+Base64.encode(xy));
|
||||
_log.debug(prefix()+"h(x+y)="+Base64.encode(hxy.getData()));
|
||||
_log.debug(prefix()+"tsb="+Base64.encode(tsB));
|
||||
_log.debug(prefix()+"unencrypted H(X+Y)+tsB+padding: " + Base64.encode(toEncrypt));
|
||||
_log.debug(prefix()+"encryption iv= " + Base64.encode(_Y, _Y.length-16, 16));
|
||||
_log.debug(prefix()+"encryption key= " + _dh.getSessionKey().toBase64());
|
||||
}
|
||||
_e_hXY_tsB = new byte[toEncrypt.length];
|
||||
_context.aes().encrypt(toEncrypt, 0, _e_hXY_tsB, 0, _dh.getSessionKey(), _Y, _Y.length-16, toEncrypt.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"encrypted H(X+Y)+tsB+padding: " + Base64.encode(_e_hXY_tsB));
|
||||
byte write[] = new byte[_Y.length + _e_hXY_tsB.length];
|
||||
System.arraycopy(_Y, 0, write, 0, _Y.length);
|
||||
System.arraycopy(_e_hXY_tsB, 0, write, _Y.length, _e_hXY_tsB.length);
|
||||
|
||||
// ok, now that is prepared, we want to actually send it, so make sure we are up for writing
|
||||
_transport.getPumper().wantsWrite(_con, write);
|
||||
if (!src.hasRemaining()) return;
|
||||
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
|
||||
_context.statManager().addRateData("ntcp.invalidDH", 1, 0);
|
||||
fail("Invalid X", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ok, we are onto the encrypted area
|
||||
while (src.hasRemaining() && !_corrupt) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"Encrypted bytes available (" + src.hasRemaining() + ")");
|
||||
while (_curEncryptedOffset < _curEncrypted.length && src.hasRemaining()) {
|
||||
_curEncrypted[_curEncryptedOffset++] = src.get();
|
||||
_received++;
|
||||
}
|
||||
if (_curEncryptedOffset >= _curEncrypted.length) {
|
||||
_context.aes().decrypt(_curEncrypted, 0, _curDecrypted, 0, _dh.getSessionKey(), _prevEncrypted, 0, _curEncrypted.length);
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"full block read and decrypted: " + Base64.encode(_curDecrypted));
|
||||
|
||||
byte swap[] = new byte[16];
|
||||
_prevEncrypted = _curEncrypted;
|
||||
_curEncrypted = swap;
|
||||
_curEncryptedOffset = 0;
|
||||
|
||||
if (_aliceIdentSize <= 0) { // we are on the first decrypted block
|
||||
_aliceIdentSize = (int)DataHelper.fromLong(_curDecrypted, 0, 2);
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + Signature.SIGNATURE_BYTES;
|
||||
int rem = (_sz_aliceIdent_tsA_padding_aliceSigSize % 16);
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = 16-rem;
|
||||
_sz_aliceIdent_tsA_padding_aliceSigSize += padding;
|
||||
try {
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"alice ident size decrypted as " + _aliceIdentSize + ", making the padding at " + padding + " and total size at " + _sz_aliceIdent_tsA_padding_aliceSigSize);
|
||||
} else {
|
||||
// subsequent block...
|
||||
try {
|
||||
_sz_aliceIdent_tsA_padding_aliceSig.write(_curDecrypted);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error(prefix()+"Error writing to the baos?", ioe);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"subsequent block decrypted (" + _sz_aliceIdent_tsA_padding_aliceSig.size() + ")");
|
||||
|
||||
if (_sz_aliceIdent_tsA_padding_aliceSig.size() >= _sz_aliceIdent_tsA_padding_aliceSigSize) {
|
||||
verifyInbound();
|
||||
if (!_corrupt && _verified && src.hasRemaining())
|
||||
prepareExtra(src);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"verifying size (sz=" + _sz_aliceIdent_tsA_padding_aliceSig.size()
|
||||
+ " expected=" + _sz_aliceIdent_tsA_padding_aliceSigSize
|
||||
+ " corrupt=" + _corrupt
|
||||
+ " verified=" + _verified + " extra=" + (_extra != null ? _extra.length : 0) + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no more bytes available in the buffer, and only a partial
|
||||
// block was read, so we can't decrypt it.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"end of available data with only a partial block read (" + _curEncryptedOffset + ", " + _received + ")");
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"done with the data, not yet complete or corrupt");
|
||||
}
|
||||
}
|
||||
|
||||
/** we are Alice, so receive these bytes as part of an outbound connection */
|
||||
private void receiveOutbound(ByteBuffer src) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"Receive outbound " + src + " received=" + _received);
|
||||
|
||||
// recv Y+E(H(X+Y)+tsB, sk, Y[239:255])
|
||||
while (_received < _Y.length && src.hasRemaining()) {
|
||||
byte c = src.get();
|
||||
_Y[_received++] = c;
|
||||
//if (_log.shouldLog(Log.DEBUG)) _log.debug("recv x" + (int)c + " received=" + _received);
|
||||
if (_received >= _Y.length) {
|
||||
try {
|
||||
_dh.setPeerPublicValue(_Y);
|
||||
_dh.getSessionKey(); // force the calc
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
|
||||
_e_hXY_tsB = new byte[Hash.HASH_LENGTH+4+12];
|
||||
} catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
|
||||
_context.statManager().addRateData("ntcp.invalidDH", 1, 0);
|
||||
fail("Invalid X", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_e_hXY_tsB == null) return; // !src.hasRemaining
|
||||
|
||||
while (_received < _Y.length + _e_hXY_tsB.length && src.hasRemaining()) {
|
||||
int i = _received-_Y.length;
|
||||
_received++;
|
||||
byte c = src.get();
|
||||
_e_hXY_tsB[i] = c;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "recv _e_hXY_tsB " + (int)c + " received=" + _received);
|
||||
if (i+1 >= _e_hXY_tsB.length) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "received _e_hXY_tsB fully");
|
||||
byte hXY_tsB[] = new byte[_e_hXY_tsB.length];
|
||||
_context.aes().decrypt(_e_hXY_tsB, 0, hXY_tsB, 0, _dh.getSessionKey(), _Y, _Y.length-16, _e_hXY_tsB.length);
|
||||
byte XY[] = new byte[_X.length + _Y.length];
|
||||
System.arraycopy(_X, 0, XY, 0, _X.length);
|
||||
System.arraycopy(_Y, 0, XY, _X.length, _Y.length);
|
||||
Hash h = _context.sha().calculateHash(XY);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix() + "h(XY)=" + h.toBase64());
|
||||
if (!DataHelper.eq(h.getData(), 0, hXY_tsB, 0, Hash.HASH_LENGTH)) {
|
||||
_context.statManager().addRateData("ntcp.invalidHXY", 1, 0);
|
||||
fail("Invalid H(X+Y) - mitm attack attempted?");
|
||||
return;
|
||||
}
|
||||
_tsB = DataHelper.fromLong(hXY_tsB, Hash.HASH_LENGTH, 4); // their (Bob's) timestamp in seconds
|
||||
_tsA = _context.clock().now()/1000; // our (Alice's) timestamp in seconds
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"h(X+Y) is correct, tsA-tsB=" + (_tsA-_tsB));
|
||||
|
||||
// the skew is not authenticated yet, but it is certainly fatal to
|
||||
// the establishment, so fail hard if appropriate
|
||||
long diff = 1000*Math.abs(_tsA-_tsB);
|
||||
if (diff >= Router.CLOCK_FUDGE_FACTOR) {
|
||||
_context.statManager().addRateData("ntcp.invalidOutboundSkew", diff, 0);
|
||||
_transport.markReachable(_con.getRemotePeer().calculateHash());
|
||||
_context.shitlist().shitlistRouter(_con.getRemotePeer().calculateHash(), "Outbound clock skew of " + diff + " ms");
|
||||
fail("Clocks too skewed (" + diff + " ms)", null, true);
|
||||
return;
|
||||
} else if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(prefix()+"Clock skew: " + diff + " ms");
|
||||
}
|
||||
|
||||
// now prepare and send our response
|
||||
// send E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB), sk, hX_xor_Bob.identHash[16:31])
|
||||
int sigSize = _X.length+_Y.length+Hash.HASH_LENGTH+4+4;//+12;
|
||||
byte preSign[] = new byte[sigSize];
|
||||
System.arraycopy(_X, 0, preSign, 0, _X.length);
|
||||
System.arraycopy(_Y, 0, preSign, _X.length, _Y.length);
|
||||
System.arraycopy(_con.getRemotePeer().calculateHash().getData(), 0, preSign, _X.length+_Y.length, Hash.HASH_LENGTH);
|
||||
DataHelper.toLong(preSign, _X.length+_Y.length+Hash.HASH_LENGTH, 4, _tsA);
|
||||
DataHelper.toLong(preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4, 4, _tsB);
|
||||
// hXY_tsB has 12 bytes of padding (size=48, tsB=4 + hXY=32)
|
||||
//System.arraycopy(hXY_tsB, hXY_tsB.length-12, preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4+4, 12);
|
||||
//byte sigPad[] = new byte[padSig];
|
||||
//_context.random().nextBytes(sigPad);
|
||||
//System.arraycopy(sigPad, 0, preSign, _X.length+_Y.length+Hash.HASH_LENGTH+4+4, padSig);
|
||||
Signature sig = _context.dsa().sign(preSign, _context.keyManager().getSigningPrivateKey());
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG)) {
|
||||
// _log.debug(prefix()+"signing " + Base64.encode(preSign));
|
||||
//}
|
||||
|
||||
byte ident[] = _context.router().getRouterInfo().getIdentity().toByteArray();
|
||||
int min = 2+ident.length+4+Signature.SIGNATURE_BYTES;
|
||||
int rem = min % 16;
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = 16 - rem;
|
||||
byte preEncrypt[] = new byte[min+padding];
|
||||
DataHelper.toLong(preEncrypt, 0, 2, ident.length);
|
||||
System.arraycopy(ident, 0, preEncrypt, 2, ident.length);
|
||||
DataHelper.toLong(preEncrypt, 2+ident.length, 4, _tsA);
|
||||
byte pad[] = new byte[padding];
|
||||
_context.random().nextBytes(pad);
|
||||
System.arraycopy(pad, 0, preEncrypt, 2+ident.length+4, padding);
|
||||
System.arraycopy(sig.getData(), 0, preEncrypt, 2+ident.length+4+padding, Signature.SIGNATURE_BYTES);
|
||||
|
||||
_prevEncrypted = new byte[preEncrypt.length];
|
||||
_context.aes().encrypt(preEncrypt, 0, _prevEncrypted, 0, _dh.getSessionKey(), _hX_xor_bobIdentHash, _hX_xor_bobIdentHash.length-16, preEncrypt.length);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
//_log.debug(prefix() + "unencrypted response to Bob: " + Base64.encode(preEncrypt));
|
||||
//_log.debug(prefix() + "encrypted response to Bob: " + Base64.encode(_prevEncrypted));
|
||||
}
|
||||
// send 'er off (when the bw limiter says, etc)
|
||||
_transport.getPumper().wantsWrite(_con, _prevEncrypted);
|
||||
}
|
||||
}
|
||||
if (_received >= _Y.length + _e_hXY_tsB.length && src.hasRemaining()) {
|
||||
// we are receiving their confirmation
|
||||
|
||||
// recv E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
|
||||
int off = 0;
|
||||
if (_e_bobSig == null) {
|
||||
_e_bobSig = new byte[48];
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "receiving E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " + src.hasRemaining() + ")");
|
||||
} else {
|
||||
off = _received - _Y.length - _e_hXY_tsB.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "continuing to receive E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev) (remaining? " + src.hasRemaining() + " off=" + off + " recv=" + _received + ")");
|
||||
}
|
||||
while (src.hasRemaining() && off < _e_bobSig.length) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(prefix()+"recv bobSig received=" + _received);
|
||||
_e_bobSig[off++] = src.get();
|
||||
_received++;
|
||||
|
||||
if (off >= _e_bobSig.length) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix() + "received E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev): " + Base64.encode(_e_bobSig));
|
||||
byte bobSig[] = new byte[_e_bobSig.length];
|
||||
_context.aes().decrypt(_e_bobSig, 0, bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
|
||||
// ignore the padding
|
||||
byte bobSigData[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(bobSig, 0, bobSigData, 0, Signature.SIGNATURE_BYTES);
|
||||
Signature sig = new Signature(bobSigData);
|
||||
|
||||
byte toVerify[] = new byte[_X.length+_Y.length+Hash.HASH_LENGTH+4+4];
|
||||
int voff = 0;
|
||||
System.arraycopy(_X, 0, toVerify, voff, _X.length); voff += _X.length;
|
||||
System.arraycopy(_Y, 0, toVerify, voff, _Y.length); voff += _Y.length;
|
||||
System.arraycopy(_context.routerHash().getData(), 0, toVerify, voff, Hash.HASH_LENGTH); voff += Hash.HASH_LENGTH;
|
||||
DataHelper.toLong(toVerify, voff, 4, _tsA); voff += 4;
|
||||
DataHelper.toLong(toVerify, voff, 4, _tsB); voff += 4;
|
||||
|
||||
_verified = _context.dsa().verifySignature(sig, toVerify, _con.getRemotePeer().getSigningPublicKey());
|
||||
if (!_verified) {
|
||||
_context.statManager().addRateData("ntcp.invalidSignature", 1, 0);
|
||||
fail("Signature was invalid - attempt to spoof " + _con.getRemotePeer().calculateHash().toBase64() + "?");
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "signature verified from Bob. done!");
|
||||
prepareExtra(src);
|
||||
byte nextWriteIV[] = new byte[16];
|
||||
System.arraycopy(_prevEncrypted, _prevEncrypted.length-16, nextWriteIV, 0, 16);
|
||||
byte nextReadIV[] = new byte[16];
|
||||
System.arraycopy(_e_bobSig, _e_bobSig.length-16, nextReadIV, 0, nextReadIV.length);
|
||||
_con.finishOutboundEstablishment(_dh.getSessionKey(), (_tsA-_tsB), nextWriteIV, nextReadIV); // skew in seconds
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** did the handshake fail for some reason? */
|
||||
public boolean isCorrupt() { return _err != null; }
|
||||
/** @return is the handshake complete and valid? */
|
||||
public boolean isComplete() { return _verified; }
|
||||
|
||||
/**
|
||||
* we are establishing an outbound connection, so prepare ourselves by
|
||||
* queueing up the write of the first part of the handshake
|
||||
*/
|
||||
public void prepareOutbound() {
|
||||
if (_received <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "write out the first part of our handshake");
|
||||
byte toWrite[] = new byte[_X.length + _hX_xor_bobIdentHash.length];
|
||||
System.arraycopy(_X, 0, toWrite, 0, _X.length);
|
||||
System.arraycopy(_hX_xor_bobIdentHash, 0, toWrite, _X.length, _hX_xor_bobIdentHash.length);
|
||||
_transport.getPumper().wantsWrite(_con, toWrite);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"prepare outbound with received=" + _received);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure the signatures are correct, and if they are, update the
|
||||
* NIOConnection with the session key / peer ident / clock skew / iv.
|
||||
* The NIOConnection itself is responsible for registering with the
|
||||
* transport
|
||||
*/
|
||||
private void verifyInbound() {
|
||||
if (_corrupt) return;
|
||||
byte b[] = _sz_aliceIdent_tsA_padding_aliceSig.toByteArray();
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug(prefix()+"decrypted sz(etc) data: " + Base64.encode(b));
|
||||
|
||||
try {
|
||||
RouterIdentity alice = new RouterIdentity();
|
||||
int sz = (int)DataHelper.fromLong(b, 0, 2);
|
||||
if ( (sz <= 0) || (sz > b.length-2-4-Signature.SIGNATURE_BYTES) ) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSize", sz, 0);
|
||||
fail("size is invalid", new Exception("size is " + sz));
|
||||
return;
|
||||
}
|
||||
byte aliceData[] = new byte[sz];
|
||||
System.arraycopy(b, 2, aliceData, 0, sz);
|
||||
alice.fromByteArray(aliceData);
|
||||
long tsA = DataHelper.fromLong(b, 2+sz, 4);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(768);
|
||||
baos.write(_X);
|
||||
baos.write(_Y);
|
||||
baos.write(_context.routerHash().getData());
|
||||
baos.write(DataHelper.toLong(4, tsA));
|
||||
baos.write(DataHelper.toLong(4, _tsB));
|
||||
//baos.write(b, 2+sz+4, b.length-2-sz-4-Signature.SIGNATURE_BYTES);
|
||||
|
||||
byte toVerify[] = baos.toByteArray();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(prefix()+"checking " + Base64.encode(toVerify, 0, 16));
|
||||
//_log.debug(prefix()+"check pad " + Base64.encode(b, 2+sz+4, 12));
|
||||
}
|
||||
|
||||
byte s[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(b, b.length-s.length, s, 0, s.length);
|
||||
Signature sig = new Signature(s);
|
||||
_verified = _context.dsa().verifySignature(sig, toVerify, alice.getSigningPublicKey());
|
||||
if (_verified) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "verification successful for " + _con);
|
||||
|
||||
long diff = 1000*Math.abs(tsA-_tsB);
|
||||
if (diff >= Router.CLOCK_FUDGE_FACTOR) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSkew", diff, 0);
|
||||
_transport.markReachable(alice.calculateHash());
|
||||
_context.shitlist().shitlistRouter(alice.calculateHash(), "Clock skew of " + diff + " ms");
|
||||
fail("Clocks too skewed (" + diff + " ms)", null, true);
|
||||
return;
|
||||
} else if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(prefix()+"Clock skew: " + diff + " ms");
|
||||
}
|
||||
|
||||
sendInboundConfirm(alice, tsA);
|
||||
_con.setRemotePeer(alice);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix()+"e_bobSig is " + _e_bobSig.length + " bytes long");
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(_e_bobSig, _e_bobSig.length-16, iv, 0, 16);
|
||||
_con.finishInboundEstablishment(_dh.getSessionKey(), (tsA-_tsB), iv, _prevEncrypted); // skew in seconds
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(prefix()+"Verified remote peer as " + alice.calculateHash().toBase64());
|
||||
} else {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundSignature", 1, 0);
|
||||
fail("Peer verification failed - spoof of " + alice.calculateHash().toBase64() + "?");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundIOE", 1, 0);
|
||||
fail("Error verifying peer", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_context.statManager().addRateData("ntcp.invalidInboundDFE", 1, 0);
|
||||
fail("Error verifying peer", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInboundConfirm(RouterIdentity alice, long tsA) {
|
||||
// send Alice E(S(X+Y+Alice.identHash+tsA+tsB), sk, prev)
|
||||
byte toSign[] = new byte[256+256+32+4+4];
|
||||
int off = 0;
|
||||
System.arraycopy(_X, 0, toSign, off, 256); off += 256;
|
||||
System.arraycopy(_Y, 0, toSign, off, 256); off += 256;
|
||||
Hash h = alice.calculateHash();
|
||||
System.arraycopy(h.getData(), 0, toSign, off, 32); off += 32;
|
||||
DataHelper.toLong(toSign, off, 4, tsA); off += 4;
|
||||
DataHelper.toLong(toSign, off, 4, _tsB); off += 4;
|
||||
|
||||
Signature sig = _context.dsa().sign(toSign, _context.keyManager().getSigningPrivateKey());
|
||||
byte preSig[] = new byte[Signature.SIGNATURE_BYTES+8];
|
||||
byte pad[] = new byte[8];
|
||||
_context.random().nextBytes(pad);
|
||||
System.arraycopy(sig.getData(), 0, preSig, 0, Signature.SIGNATURE_BYTES);
|
||||
System.arraycopy(pad, 0, preSig, Signature.SIGNATURE_BYTES, pad.length);
|
||||
_e_bobSig = new byte[preSig.length];
|
||||
_context.aes().encrypt(preSig, 0, _e_bobSig, 0, _dh.getSessionKey(), _e_hXY_tsB, _e_hXY_tsB.length-16, _e_bobSig.length);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "Sending encrypted inbound confirmation");
|
||||
_transport.getPumper().wantsWrite(_con, _e_bobSig);
|
||||
}
|
||||
|
||||
/** anything left over in the byte buffer after verification is extra */
|
||||
private void prepareExtra(ByteBuffer buf) {
|
||||
int remaining = buf.remaining();
|
||||
if (remaining > 0) {
|
||||
_extra = new byte[remaining];
|
||||
buf.get(_extra);
|
||||
_received += remaining;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(prefix() + "prepare extra " + remaining + " (total received: " + _received + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* if complete, this will contain any bytes received as part of the
|
||||
* handshake that were after the actual handshake. This may return null.
|
||||
*/
|
||||
public byte[] getExtraBytes() { return _extra; }
|
||||
|
||||
private void fail(String reason) { fail(reason, null); }
|
||||
private void fail(String reason, Exception e) { fail(reason, e, false); }
|
||||
private void fail(String reason, Exception e, boolean bySkew) {
|
||||
_corrupt = true;
|
||||
_failedBySkew = bySkew;
|
||||
_err = reason;
|
||||
_e = e;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(prefix()+"Failed to establish: " + _err, e);
|
||||
}
|
||||
|
||||
public String getError() { return _err; }
|
||||
public Exception getException() { return _e; }
|
||||
|
||||
private String prefix() { return toString(); }
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append("est").append(System.identityHashCode(this));
|
||||
if (_con.isInbound()) buf.append(" inbound");
|
||||
else buf.append(" outbound");
|
||||
if (_corrupt) buf.append(" corrupt");
|
||||
if (_verified) buf.append(" verified");
|
||||
if (_con.isEstablished()) buf.append(" established");
|
||||
buf.append(": ");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* a check info connection will receive 256 bytes containing:
|
||||
* - 32 bytes of uninterpreted, ignored data
|
||||
* - 1 byte size
|
||||
* - that many bytes making up the local router's IP address (as reached by the remote side)
|
||||
* - 2 byte port number that the local router was reached on
|
||||
* - 4 byte i2p network time as known by the remote side (seconds since the epoch)
|
||||
* - uninterpreted padding data, up to byte 223
|
||||
* - xor of the local router's identity hash and the SHA256 of bytes 32 through bytes 223
|
||||
*/
|
||||
private static boolean isCheckInfo(I2PAppContext ctx, Hash us, byte first256[]) {
|
||||
Log log = ctx.logManager().getLog(EstablishState.class);
|
||||
int off = 32; // ignore the first 32 bytes
|
||||
Hash h = ctx.sha().calculateHash(first256, off, first256.length-32-off);
|
||||
byte xor[] = DataHelper.xor(h.getData(), us.getData());
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("check hash: " + h.toBase64() + " xor: " + Base64.encode(xor));
|
||||
if (DataHelper.eq(xor, 0, first256, first256.length-32, 32)) {
|
||||
// ok, data is as expected
|
||||
// parse our IP/port/etc out of the first256
|
||||
int ipSize = (int)DataHelper.fromLong(first256, off, 1);
|
||||
off++;
|
||||
byte ip[] = new byte[ipSize];
|
||||
System.arraycopy(first256, off, ip, 0, ipSize);
|
||||
try {
|
||||
InetAddress ourIP = InetAddress.getByAddress(ip);
|
||||
off += ipSize;
|
||||
int port = (int)DataHelper.fromLong(first256, off, 2);
|
||||
off += 2;
|
||||
long now = DataHelper.fromLong(first256, off, 4);
|
||||
off += 4;
|
||||
long skewSeconds = (ctx.clock().now()/1000)-now;
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Check info received: our IP: " + ourIP + " our port: " + port
|
||||
+ " skew: " + skewSeconds + " s");
|
||||
} catch (UnknownHostException uhe) {
|
||||
// ipSize is invalid
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Invalid IP received on check connection (size: " + ipSize + ")");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Not a checkInfo connection");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkHost(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.err.println("Usage: EstablishState ipOrHostname portNum peerHashBase64");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
String host = args[0];
|
||||
int port = Integer.parseInt(args[1]);
|
||||
byte peer[] = Base64.decode(args[2]);
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
byte toSend[] = new byte[256];
|
||||
ctx.random().nextBytes(toSend);
|
||||
int off = 32;
|
||||
byte ip[] = s.getInetAddress().getAddress();
|
||||
DataHelper.toLong(toSend, off, 1, ip.length);
|
||||
off++;
|
||||
System.arraycopy(ip, 0, toSend, off, ip.length);
|
||||
off += ip.length;
|
||||
DataHelper.toLong(toSend, off, 2, port);
|
||||
off += 2;
|
||||
long now = ctx.clock().now()/1000;
|
||||
DataHelper.toLong(toSend, off, 4, now);
|
||||
off += 4;
|
||||
Hash h = ctx.sha().calculateHash(toSend, 32, toSend.length-32-32);
|
||||
DataHelper.xor(peer, 0, h.getData(), 0, toSend, toSend.length-32, peer.length);
|
||||
System.out.println("check hash: " + h.toBase64());
|
||||
|
||||
out.write(toSend);
|
||||
out.flush();
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if (args.length == 3) {
|
||||
checkHost(args);
|
||||
return;
|
||||
}
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
try {
|
||||
java.net.Socket s = new java.net.Socket("localhost", 9094);
|
||||
OutputStream out = s.getOutputStream();
|
||||
DHSessionKeyBuilder dh = new DHSessionKeyBuilder();
|
||||
byte X[] = dh.getMyPublicValueBytes();
|
||||
|
||||
// SEND X+(H(X) xor Bob.identHash)----------------------------->
|
||||
out.write(X);
|
||||
System.out.println("sent X =" + Base64.encode(X));
|
||||
byte bih[] = Base64.decode("HuRdDx9t-RaZfYkYvacRwP~6s9mvbdkYzIMrpUCsZIo=");
|
||||
System.out.println("bih = " + Base64.encode(bih));
|
||||
Hash hx = ctx.sha().calculateHash(X);
|
||||
System.out.println("hx = " + Base64.encode(hx.getData()));
|
||||
byte hx_xor_bih[] = DataHelper.xor(bih, hx.getData());
|
||||
System.out.println("xor = " + Base64.encode(hx_xor_bih));
|
||||
out.write(hx_xor_bih);
|
||||
out.flush();
|
||||
// DONE SENDING X+(H(X) xor Bob.identHash)----------------------------->
|
||||
|
||||
// NOW READ Y+E(H(X+Y)+tsB+padding, sk, Y[239:255])
|
||||
InputStream in = s.getInputStream();
|
||||
byte toRead[] = new byte[256+(32+4+12)];
|
||||
int read = 0;
|
||||
while (read < toRead.length) {
|
||||
int r = in.read(toRead, read, toRead.length-read);
|
||||
if (r == -1)
|
||||
throw new EOFException("eof at read=" + read);
|
||||
read += r;
|
||||
}
|
||||
byte Y[] = new byte[256];
|
||||
System.arraycopy(toRead, 0, Y, 0, Y.length);
|
||||
dh.setPeerPublicValue(Y);
|
||||
byte decrypted[] = new byte[(32+4+12)];
|
||||
ctx.aes().decrypt(toRead, Y.length, decrypted, 0, dh.getSessionKey(), Y, Y.length-16, decrypted.length);
|
||||
//display y, encrypted, decrypted, hx+y, tsb, padding
|
||||
//unencrypted H(X+Y)+tsB+padding: bSJIv1ynFw9MhIqbObOpCqeZxtFvKEx-ilcsZQ31zYNEnVXyHCZagLbdQYRmd1oq
|
||||
System.out.println("dh session key: " + dh.getSessionKey().toBase64());
|
||||
System.out.println("decryption iv: " + Base64.encode(Y, Y.length-16, 16));
|
||||
System.out.println("Y = " + Base64.encode(Y));
|
||||
byte xy[] = new byte[512];
|
||||
System.arraycopy(X, 0, xy, 0, X.length);
|
||||
System.arraycopy(Y, 0, xy, X.length, Y.length);
|
||||
System.out.println("h(x+y): " + ctx.sha().calculateHash(xy).toBase64());
|
||||
System.out.println("encrypted H(X+Y)+tsB+padding: " + Base64.encode(toRead, Y.length, toRead.length-Y.length));
|
||||
System.out.println("unencrypted H(X+Y)+tsB+padding: " + Base64.encode(decrypted));
|
||||
long tsB = DataHelper.fromLong(decrypted, 32, 4);
|
||||
|
||||
//try { Thread.sleep(40*1000); } catch (InterruptedException ie) {}
|
||||
|
||||
RouterIdentity alice = new RouterIdentity();
|
||||
Object k[] = ctx.keyGenerator().generatePKIKeypair();
|
||||
PublicKey pub = (PublicKey)k[0];
|
||||
PrivateKey priv = (PrivateKey)k[1];
|
||||
k = ctx.keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey spub = (SigningPublicKey)k[0];
|
||||
SigningPrivateKey spriv = (SigningPrivateKey)k[1];
|
||||
alice.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
|
||||
alice.setPublicKey(pub);
|
||||
alice.setSigningPublicKey(spub);
|
||||
|
||||
// SEND E(#+Alice.identity+tsA+padding+S(X+Y+Bob.identHash+tsA+tsB+padding), sk, hX_xor_Bob.identHash[16:31])--->
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
byte aliceb[] = alice.toByteArray();
|
||||
long tsA = ctx.clock().now()/1000l;
|
||||
baos.write(DataHelper.toLong(2, aliceb.length));
|
||||
baos.write(aliceb);
|
||||
baos.write(DataHelper.toLong(4, tsA));
|
||||
|
||||
int base = baos.size() + Signature.SIGNATURE_BYTES;
|
||||
int rem = base % 16;
|
||||
int padding = 0;
|
||||
if (rem > 0)
|
||||
padding = 16 - rem;
|
||||
byte pad[] = new byte[padding];
|
||||
ctx.random().nextBytes(pad);
|
||||
baos.write(pad);
|
||||
base += padding;
|
||||
|
||||
ByteArrayOutputStream sbaos = new ByteArrayOutputStream(512);
|
||||
sbaos.write(X);
|
||||
sbaos.write(Y);
|
||||
sbaos.write(bih);
|
||||
sbaos.write(DataHelper.toLong(4, tsA));
|
||||
sbaos.write(DataHelper.toLong(4, tsB));
|
||||
//sbaos.write(pad);
|
||||
Signature sig = ctx.dsa().sign(sbaos.toByteArray(), spriv);
|
||||
baos.write(sig.toByteArray());
|
||||
|
||||
byte unencrypted[] = baos.toByteArray();
|
||||
byte toWrite[] = new byte[unencrypted.length];
|
||||
System.out.println("unencrypted.length = " + unencrypted.length + " alice.size = " + aliceb.length + " padding = " + padding + " base = " + base);
|
||||
ctx.aes().encrypt(unencrypted, 0, toWrite, 0, dh.getSessionKey(), hx_xor_bih, 16, unencrypted.length);
|
||||
|
||||
out.write(toWrite);
|
||||
out.flush();
|
||||
|
||||
System.out.println("unencrypted: " + Base64.encode(unencrypted));
|
||||
System.out.println("encrypted: " + Base64.encode(toWrite));
|
||||
System.out.println("Local peer: " + alice.calculateHash().toBase64());
|
||||
|
||||
// now check bob's signature
|
||||
|
||||
SigningPublicKey bobPubKey = null;
|
||||
try {
|
||||
RouterInfo info = new RouterInfo();
|
||||
info.readBytes(new FileInputStream("/home/jrandom/routers/router1/netDb/routerInfo-HuRdDx9t-RaZfYkYvacRwP~6s9mvbdkYzIMrpUCsZIo=.dat"));
|
||||
bobPubKey = info.getIdentity().getSigningPublicKey();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Reading in bob's sig");
|
||||
|
||||
byte bobRead[] = new byte[48];
|
||||
read = 0;
|
||||
while (read < bobRead.length) {
|
||||
int r = in.read(bobRead, read, bobRead.length-read);
|
||||
if (r == -1)
|
||||
throw new EOFException("eof at read=" + read);
|
||||
read += r;
|
||||
}
|
||||
|
||||
// bob should have sent E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev)
|
||||
byte preSig[] = new byte[Signature.SIGNATURE_BYTES+8];
|
||||
ctx.aes().decrypt(bobRead, 0, preSig, 0, dh.getSessionKey(), toRead, toRead.length-16, preSig.length);
|
||||
byte bobSigData[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(preSig, 0, bobSigData, 0, Signature.SIGNATURE_BYTES); // ignore the padding
|
||||
System.out.println("Bob's sig: " + Base64.encode(bobSigData));
|
||||
|
||||
byte signed[] = new byte[256+256+32+4+4];
|
||||
int off = 0;
|
||||
System.arraycopy(X, 0, signed, off, 256); off += 256;
|
||||
System.arraycopy(Y, 0, signed, off, 256); off += 256;
|
||||
Hash h = alice.calculateHash();
|
||||
System.arraycopy(h.getData(), 0, signed, off, 32); off += 32;
|
||||
DataHelper.toLong(signed, off, 4, tsA); off += 4;
|
||||
DataHelper.toLong(signed, off, 4, tsB); off += 4;
|
||||
|
||||
Signature bobSig = new Signature(bobSigData);
|
||||
boolean ok = ctx.dsa().verifySignature(bobSig, signed, bobPubKey);
|
||||
|
||||
System.out.println("bob's sig matches? " + ok);
|
||||
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
byte fakeI2NPbuf[] = new byte[128];
|
||||
ctx.random().nextBytes(fakeI2NPbuf);
|
||||
out.write(fakeI2NPbuf);
|
||||
out.flush();
|
||||
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user