/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node;
import java.util.HashSet;
import freenet.io.comm.ByteCounter;
import freenet.io.comm.DMT;
import freenet.io.comm.DisconnectedException;
import freenet.io.comm.Message;
import freenet.io.comm.MessageFilter;
import freenet.io.comm.NotConnectedException;
import freenet.io.comm.PeerParseException;
import freenet.io.comm.ReferenceSignatureVerificationException;
import freenet.node.OpennetManager.ConnectionType;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.Logger.LogLevel;
import freenet.support.io.NativeThread;
public class AnnounceSender implements PrioRunnable, ByteCounter {
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
// Constants
static final int ACCEPTED_TIMEOUT = 10000;
static final int ANNOUNCE_TIMEOUT = 120000; // longer than a regular request as have to transfer noderefs hop by hop etc
static final int END_TIMEOUT = 30000; // After received the completion message, wait 30 seconds for any late reordered replies
private final PeerNode source;
private final long uid;
private final OpennetManager om;
private final Node node;
private final long xferUID;
private final int noderefLength;
private final int paddedLength;
private byte[] noderefBuf;
private short htl;
private double target;
private final AnnouncementCallback cb;
private final PeerNode onlyNode;
private int forwardedRefs;
public AnnounceSender(double target, short htl, long uid, PeerNode source, OpennetManager om, Node node, long xferUID, int noderefLength, int paddedLength, AnnouncementCallback cb) {
this.source = source;
this.uid = uid;
this.om = om;
this.node = node;
this.onlyNode = null;
this.htl = htl;
this.xferUID = xferUID;
this.paddedLength = paddedLength;
this.noderefLength = noderefLength;
this.cb = cb;
}
public AnnounceSender(double target, OpennetManager om, Node node, AnnouncementCallback cb, PeerNode onlyNode) {
source = null;
this.uid = node.random.nextLong();
// Prevent it being routed back to us.
node.tracker.completed(uid);
this.om = om;
this.node = node;
this.htl = node.maxHTL();
this.target = target;
this.cb = cb;
this.onlyNode = onlyNode;
noderefBuf = om.crypto.myCompressedFullRef();
this.xferUID = 0;
this.paddedLength = 0;
this.noderefLength = 0;
}
@Override
public void run() {
try {
realRun();
node.nodeStats.reportAnnounceForwarded(forwardedRefs, source);
} catch (Throwable t) {
Logger.error(this, "Caught "+t+" announcing "+uid+" from "+source, t);
} finally {
if(source != null) {
source.completedAnnounce(uid);
}
node.tracker.completed(uid);
if(cb != null)
cb.completed();
node.nodeStats.endAnnouncement(uid);
}
}
private void realRun() {
boolean hasForwarded = false;
if(source != null) {
try {
source.sendAsync(DMT.createFNPAccepted(uid), null, this);
} catch (NotConnectedException e) {
return;
}
if(!transferNoderef()) return;
}
// Now route it.
HashSet<PeerNode> nodesRoutedTo = new HashSet<PeerNode>();
PeerNode next = null;
while(true) {
if(logMINOR) Logger.minor(this, "htl="+htl);
/*
* If we haven't routed to any node yet, decrement according to the source.
* If we have, decrement according to the node which just failed.
* Because:
* 1) If we always decrement according to source then we can be at max or min HTL
* for a long time while we visit *every* peer node. This is BAD!
* 2) The node which just failed can be seen as the requestor for our purposes.
*/
// Decrement at this point so we can DNF immediately on reaching HTL 0.
if(onlyNode == null)
htl = node.decrementHTL(hasForwarded ? next : source, htl);
if(htl == 0) {
// No more nodes.
complete();
return;
}
if(!node.isOpennetEnabled()) {
complete();
return;
}
if(onlyNode == null) {
// Route it
next = node.peers.closerPeer(source, nodesRoutedTo, target, true, node.isAdvancedModeEnabled(), -1,
null, null, htl, 0, source == null, false, false);
} else {
next = onlyNode;
if(nodesRoutedTo.contains(onlyNode)) {
rnf(onlyNode);
return;
}
}
if(next == null) {
// Backtrack
rnf(next);
return;
}
if(logMINOR) Logger.minor(this, "Routing request to "+next);
if(onlyNode == null)
next.reportRoutedTo(target, source == null, false, source, nodesRoutedTo);
nodesRoutedTo.add(next);
long xferUID = sendTo(next);
if(xferUID == -1) continue;
hasForwarded = true;
Message msg = null;
while(true) {
/**
* What are we waiting for?
* FNPAccepted - continue
* FNPRejectedLoop - go to another node
* FNPRejectedOverload - go to another node
*/
MessageFilter mfAccepted = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ACCEPTED_TIMEOUT).setType(DMT.FNPAccepted);
MessageFilter mfRejectedLoop = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ACCEPTED_TIMEOUT).setType(DMT.FNPRejectedLoop);
MessageFilter mfRejectedOverload = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ACCEPTED_TIMEOUT).setType(DMT.FNPRejectedOverload);
MessageFilter mfOpennetDisabled = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ACCEPTED_TIMEOUT).setType(DMT.FNPOpennetDisabled);
// mfRejectedOverload must be the last thing in the or
// So its or pointer remains null
// Otherwise we need to recreate it below
MessageFilter mf = mfAccepted.or(mfRejectedLoop.or(mfRejectedOverload.or(mfOpennetDisabled)));
try {
msg = node.usm.waitFor(mf, this);
if(logMINOR) Logger.minor(this, "first part got "+msg);
} catch (DisconnectedException e) {
Logger.normal(this, "Disconnected from "+next+" while waiting for Accepted on "+uid);
break;
}
if(msg == null) {
if(logMINOR) Logger.minor(this, "Timeout waiting for Accepted");
// Try next node
msg = null;
break;
}
if(msg.getSpec() == DMT.FNPRejectedLoop) {
if(logMINOR) Logger.minor(this, "Rejected loop");
// Find another node to route to
msg = null;
break;
}
if(msg.getSpec() == DMT.FNPRejectedOverload) {
if(logMINOR) Logger.minor(this, "Rejected: overload");
// Give up on this one, try another
msg = null;
break;
}
if(msg.getSpec() == DMT.FNPOpennetDisabled) {
if(logMINOR) Logger.minor(this, "Opennet disabled");
msg = null;
break;
}
if(msg.getSpec() != DMT.FNPAccepted) {
Logger.error(this, "Unrecognized message: "+msg);
continue;
}
break;
}
if((msg == null) || (msg.getSpec() != DMT.FNPAccepted)) {
// Try another node
continue;
}
if(logMINOR) Logger.minor(this, "Got Accepted");
if(cb != null)
cb.acceptedSomewhere();
// Send the rest
try {
sendRest(next, xferUID);
} catch (NotConnectedException e1) {
if(logMINOR)
Logger.minor(this, "Not connected while sending noderef on "+next);
continue;
}
// Otherwise, must be Accepted
// So wait...
while(true) {
MessageFilter mfAnnounceCompleted = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPOpennetAnnounceCompleted);
MessageFilter mfRouteNotFound = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPRouteNotFound);
MessageFilter mfRejectedOverload = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPRejectedOverload);
MessageFilter mfAnnounceReply = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPOpennetAnnounceReply);
MessageFilter mfOpennetDisabled = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPOpennetDisabled);
MessageFilter mfNotWanted = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPOpennetAnnounceNodeNotWanted);
MessageFilter mfOpennetNoderefRejected = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(ANNOUNCE_TIMEOUT).setType(DMT.FNPOpennetNoderefRejected);
MessageFilter mf = mfAnnounceCompleted.or(mfRouteNotFound.or(mfRejectedOverload.or(mfAnnounceReply.or(mfOpennetDisabled.or(mfNotWanted.or(mfOpennetNoderefRejected))))));
try {
msg = node.usm.waitFor(mf, this);
} catch (DisconnectedException e) {
Logger.normal(this, "Disconnected from "+next+" while waiting for announcement");
break;
}
if(logMINOR) Logger.minor(this, "second part got "+msg);
if(msg == null) {
// Fatal timeout, must be terminal (IS_LOCAL==true)
timedOut(next);
return;
}
if(msg.getSpec() == DMT.FNPOpennetNoderefRejected) {
int reason = msg.getInt(DMT.REJECT_CODE);
Logger.normal(this, "Announce rejected by "+next+" : "+DMT.getOpennetRejectedCode(reason));
msg = null;
break;
}
if(msg.getSpec() == DMT.FNPOpennetAnnounceCompleted) {
// Send the completion on immediately. We don't want to accumulate 30 seconds per hop!
complete();
mfAnnounceReply.setTimeout(END_TIMEOUT).setTimeoutRelativeToCreation(true);
mfNotWanted.setTimeout(END_TIMEOUT).setTimeoutRelativeToCreation(true);
mfAnnounceReply.clearOr();
mfNotWanted.clearOr();
mf = mfAnnounceReply.or(mfNotWanted);
while(true) {
try {
msg = node.usm.waitFor(mf, this);
} catch (DisconnectedException e) {
return;
}
if(msg == null) return;
if(msg.getSpec() == DMT.FNPOpennetAnnounceReply) {
validateForwardReply(msg, next);
continue;
}
if(msg.getSpec() == DMT.FNPOpennetAnnounceNodeNotWanted) {
if(cb != null)
cb.nodeNotWanted();
if(source != null) {
try {
sendNotWanted();
} catch (NotConnectedException e) {
Logger.warning(this, "Lost connection to source (announce completed)");
return;
}
}
continue;
}
}
}
if(msg.getSpec() == DMT.FNPRouteNotFound) {
// Backtrack within available hops
short newHtl = msg.getShort(DMT.HTL);
if(newHtl < 0) newHtl = 0;
if(newHtl < htl) htl = newHtl;
break;
}
if(msg.getSpec() == DMT.FNPRejectedOverload) {
// Give up on this one, try another
break;
}
if(msg.getSpec() == DMT.FNPOpennetDisabled) {
Logger.minor(this, "Opennet disabled");
msg = null;
break;
}
if(msg.getSpec() == DMT.FNPOpennetAnnounceReply) {
validateForwardReply(msg, next);
continue; // There may be more
}
if(msg.getSpec() == DMT.FNPOpennetAnnounceNodeNotWanted) {
if(cb != null)
cb.nodeNotWanted();
if(source != null) {
try {
sendNotWanted();
} catch (NotConnectedException e) {
Logger.warning(this, "Lost connection to source (announce not wanted)");
return;
}
}
continue; // This message is propagated, they will send a Completed or RNF
}
Logger.error(this, "Unexpected message: "+msg);
}
}
}
private int waitingForTransfers = 0;
/**
* Validate a reply, and relay it back to the source.
* @param msg2 The AnnouncementReply message.
* @return True unless we lost the connection to our request source.
*/
private void validateForwardReply(Message msg, final PeerNode next) {
final long xferUID = msg.getLong(DMT.TRANSFER_UID);
final int noderefLength = msg.getInt(DMT.NODEREF_LENGTH);
final int paddedLength = msg.getInt(DMT.PADDED_LENGTH);
synchronized(this) {
waitingForTransfers++;
}
Runnable r = new Runnable() {
@Override
public void run() {
try {
byte[] noderefBuf = OpennetManager.innerWaitForOpennetNoderef(xferUID, paddedLength, noderefLength, next, false, uid, true, AnnounceSender.this, node);
if(noderefBuf == null) {
return; // Don't relay
}
SimpleFieldSet fs = OpennetManager.validateNoderef(noderefBuf, 0, noderefLength, next, false);
if(fs == null) {
if(cb != null) cb.bogusNoderef("invalid noderef");
return; // Don't relay
}
if(source != null) {
// Now relay it
try {
forwardedRefs++;
om.sendAnnouncementReply(uid, source, noderefBuf, AnnounceSender.this);
if(cb != null) {
cb.relayedNoderef();
}
} catch (NotConnectedException e) {
// Hmmm...!
return;
}
} else {
// Add it
try {
OpennetPeerNode pn = node.addNewOpennetNode(fs, ConnectionType.ANNOUNCE);
if(cb != null) {
if(pn != null)
cb.addedNode(pn);
else
cb.nodeNotAdded();
}
} catch (FSParseException e) {
Logger.normal(this, "Failed to parse reply: "+e, e);
if(cb != null) cb.bogusNoderef("parse failed: "+e);
} catch (PeerParseException e) {
Logger.normal(this, "Failed to parse reply: "+e, e);
if(cb != null) cb.bogusNoderef("parse failed: "+e);
} catch (ReferenceSignatureVerificationException e) {
Logger.normal(this, "Failed to parse reply: "+e, e);
if(cb != null) cb.bogusNoderef("parse failed: "+e);
}
}
return;
} finally {
synchronized(AnnounceSender.this) {
waitingForTransfers--;
AnnounceSender.this.notifyAll();
}
}
}
};
try {
node.executor.execute(r);
} catch (Throwable t) {
synchronized(this) {
waitingForTransfers--;
}
}
}
/**
* Send an AnnouncementRequest.
* @param next The node to send the announcement to.
* @return True if the announcement was successfully sent.
*/
private long sendTo(PeerNode next) {
try {
return om.startSendAnnouncementRequest(uid, next, noderefBuf, this, target, htl);
} catch (NotConnectedException e) {
if(logMINOR) Logger.minor(this, "Disconnected");
return -1;
}
}
/**
* Send an AnnouncementRequest.
* @param next The node to send the announcement to.
* @return True if the announcement was successfully sent.
* @throws NotConnectedException
*/
private void sendRest(PeerNode next, long xferUID) throws NotConnectedException {
om.finishSentAnnouncementRequest(next, noderefBuf, this, xferUID);
}
private void timedOut(PeerNode next) {
Message msg = DMT.createFNPRejectedOverload(uid, true, false, false);
if(source != null) {
try {
source.sendAsync(msg, null, this);
} catch (NotConnectedException e) {
// Ok
}
}
if(cb != null) cb.nodeFailed(next, "timed out");
}
private void rnf(PeerNode next) {
Message msg = DMT.createFNPRouteNotFound(uid, htl);
if(source != null) {
try {
source.sendAsync(msg, null, this);
} catch (NotConnectedException e) {
// Ok
}
}
if(cb != null) {
if(next != null) cb.nodeFailed(next, "route not found");
else cb.noMoreNodes();
}
}
private void complete() {
synchronized(this) {
while(waitingForTransfers > 0) {
try {
wait();
} catch (InterruptedException e) {
// Ignore.
}
}
}
Message msg = DMT.createFNPOpennetAnnounceCompleted(uid);
if(source != null) {
try {
source.sendAsync(msg, null, this);
} catch (NotConnectedException e) {
// Oh well.
}
}
}
/**
* @return True unless the noderef is bogus.
*/
private boolean transferNoderef() {
noderefBuf = OpennetManager.innerWaitForOpennetNoderef(xferUID, paddedLength, noderefLength, source, false, uid, true, this, node);
if(noderefBuf == null) {
return false;
}
SimpleFieldSet fs = OpennetManager.validateNoderef(noderefBuf, 0, noderefLength, source, false);
if(fs == null) {
OpennetManager.rejectRef(uid, source, DMT.NODEREF_REJECTED_INVALID, this);
return false;
}
// If we want it, add it and send it.
try {
// Allow reconnection - sometimes one side has the ref and the other side doesn't.
if(om.addNewOpennetNode(fs, ConnectionType.ANNOUNCE, true) != null) {
sendOurRef(source, om.crypto.myCompressedFullRef());
} else {
if(logMINOR)
Logger.minor(this, "Don't need the node");
sendNotWanted();
// Okay, just route it.
}
} catch (FSParseException e) {
Logger.warning(this, "Rejecting noderef: "+e, e);
OpennetManager.rejectRef(uid, source, DMT.NODEREF_REJECTED_INVALID, this);
return false;
} catch (PeerParseException e) {
Logger.warning(this, "Rejecting noderef: "+e, e);
OpennetManager.rejectRef(uid, source, DMT.NODEREF_REJECTED_INVALID, this);
return false;
} catch (ReferenceSignatureVerificationException e) {
Logger.warning(this, "Rejecting noderef: "+e, e);
OpennetManager.rejectRef(uid, source, DMT.NODEREF_REJECTED_INVALID, this);
return false;
} catch (NotConnectedException e) {
Logger.normal(this, "Could not receive noderef, disconnected");
return false;
}
return true;
}
private void sendNotWanted() throws NotConnectedException {
Message msg = DMT.createFNPOpennetAnnounceNodeNotWanted(uid);
source.sendAsync(msg, null, this);
}
private void sendOurRef(PeerNode next, byte[] ref) throws NotConnectedException {
om.sendAnnouncementReply(uid, next, ref, this);
}
@Override
public void sentBytes(int x) {
node.nodeStats.announceByteCounter.sentBytes(x);
}
@Override
public void receivedBytes(int x) {
node.nodeStats.announceByteCounter.receivedBytes(x);
}
@Override
public void sentPayload(int x) {
node.nodeStats.announceByteCounter.sentPayload(x);
// Doesn't count.
}
@Override
public int getPriority() {
return NativeThread.HIGH_PRIORITY;
}
}