package freenet.node; import freenet.io.comm.PeerParseException; import freenet.io.comm.ReferenceSignatureVerificationException; import freenet.node.OpennetManager.ConnectionType; import freenet.node.OpennetManager.LinkLengthClass; import freenet.node.updater.NodeUpdateManager; import freenet.node.updater.UpdateOverMandatoryManager; import freenet.support.Logger; import freenet.support.SimpleFieldSet; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; public class OpennetPeerNode extends PeerNode { final OpennetManager opennet; private long timeLastSuccess; // Not persisted across restart, since after restart grace periods don't apply anyway (except disconnection, which is really separate anyway). private ConnectionType opennetNodeAddedReason; public OpennetPeerNode(SimpleFieldSet fs, Node node2, NodeCrypto crypto, OpennetManager opennet, boolean fromLocal) throws FSParseException, PeerParseException, ReferenceSignatureVerificationException, PeerTooOldException { super(fs, node2, crypto, fromLocal); if (fromLocal) { SimpleFieldSet metadata = fs.subset("metadata"); timeLastSuccess = metadata.getLong("timeLastSuccess", 0); } this.opennet = opennet; } @Override public PeerNodeStatus getStatus(boolean noHeavy) { return new OpennetPeerNodeStatus(this, noHeavy); } @Override public boolean isRoutingCompatible() { if(!node.isOpennetEnabled()) return false; return super.isRoutingCompatible(); } @Override public boolean isDarknet() { return false; } @Override public boolean isOpennet() { return true; } @Override public boolean isSeed() { return false; } enum NOT_DROP_REASON { DROPPABLE, TOO_NEW_PEER, TOO_LOW_UPTIME, RECONNECT_GRACE_PERIOD } public boolean isDroppable(boolean ignoreDisconnect) { return isDroppableWithReason(ignoreDisconnect) == NOT_DROP_REASON.DROPPABLE; } /** Is the peer droppable? * SIDE EFFECT: If we are now outside the grace period, we reset peerAddedTime and opennetPeerAddedReason. * Note that the caller must check separately whether the node is TOO OLD and connected. */ public NOT_DROP_REASON isDroppableWithReason(boolean ignoreDisconnect) { long now = System.currentTimeMillis(); int status = getPeerNodeStatus(); long age = now - getPeerAddedTime(); if(age < OpennetManager.DROP_MIN_AGE) { if(status == PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) { // New peer, never connected. // Allow it 1 minute to connect. if(age < OpennetManager.DROP_MIN_AGE_DISCONNECTED) return NOT_DROP_REASON.TOO_NEW_PEER; } else if(status != PeerManager.PEER_NODE_STATUS_DISCONNECTED) { // Based on the time added, *not* the last connected time. // This prevents various dubious ways of staying connected while not delivering anything useful. return NOT_DROP_REASON.TOO_NEW_PEER; // New node } } else { synchronized(this) { peerAddedTime = 0; opennetNodeAddedReason = null; } } if(now - node.usm.getStartedTime() < OpennetManager.DROP_STARTUP_DELAY) return NOT_DROP_REASON.TOO_LOW_UPTIME; // Give them time to connect after we startup if(!ignoreDisconnect) { synchronized(this) { // This only applies after it has connected, and only if !ignoreDisconnect. // Hence only DISCONNECTED and not NEVER CONNECTED. if((status == PeerManager.PEER_NODE_STATUS_DISCONNECTED) && (!super.neverConnected()) && now - timeLastDisconnect < OpennetManager.DROP_DISCONNECT_DELAY && now - timePrevDisconnect > OpennetManager.DROP_DISCONNECT_DELAY_COOLDOWN) { // Grace period for node restarting return NOT_DROP_REASON.RECONNECT_GRACE_PERIOD; } } } return NOT_DROP_REASON.DROPPABLE; } @Override public void onSuccess(boolean insert, boolean ssk) { if(insert || ssk) return; timeLastSuccess = System.currentTimeMillis(); opennet.onSuccess(this); } @Override public void onRemove() { opennet.onRemove(this); super.onRemove(); } @Override public synchronized SimpleFieldSet exportMetadataFieldSet(long now) { SimpleFieldSet fs = super.exportMetadataFieldSet(now); fs.put("timeLastSuccess", timeLastSuccess); return fs; } public final long timeLastSuccess() { return timeLastSuccess; } /** * Is the SimpleFieldSet a valid noderef? */ public static boolean validateRef(SimpleFieldSet ref) { if(!ref.getBoolean("opennet", false)) return false; return true; } @Override public boolean isRealConnection() { return true; } @Override public boolean recordStatus() { return true; } @Override public boolean equals(Object o) { if(o == this) return true; // Only equal to seednode of its own type. if(o instanceof OpennetPeerNode) { return super.equals(o); } else return false; } @Override public final boolean shouldDisconnectAndRemoveNow() { // Allow announced peers 15 minutes to download the auto-update. if(isConnected() && isUnroutableOlderVersion()) { return shouldDisconnectTooOld(); } return false; } /** If a node is TOO OLD, we should keep it connected for a brief period for it to * allow it to issue a UOM request, we should keep it connected while the UOM transfer * is in progress, but otherwise we should disconnect. */ private boolean shouldDisconnectTooOld() { long uptime = System.currentTimeMillis() - timeLastConnectionCompleted(); if(uptime < SECONDS.toMillis(30)) // Allow 30 seconds to send the UOM request. return false; // FIXME remove, paranoia if(uptime < HOURS.toMillis(1)) return false; NodeUpdateManager updater = node.nodeUpdater; if(updater == null) return true; // Not going to UOM. UpdateOverMandatoryManager uom = updater.uom; if(uom == null) return true; // Not going to UOM if(uptime > HOURS.toMillis(2)) { // UOM transfers can take ages, but there has to be some limit... return true; } if(timeSinceSentUOM() < SECONDS.toMillis(60)) { // Let it finish. // 60 seconds extra to ensure it has time to parse the jar and start fetching dependencies. return false; } return true; } @Override protected void onConnect() { super.onConnect(); opennet.crypto.socket.getAddressTracker().setPresumedGuiltyAt(System.currentTimeMillis() + HOURS.toMillis(1)); } private boolean wasDropped; synchronized void setWasDropped() { wasDropped = true; } synchronized boolean wasDropped() { return wasDropped; } synchronized boolean grabWasDropped() { boolean ret = wasDropped; wasDropped = false; return ret; } @Override public synchronized void setAddedReason(ConnectionType connectionType) { opennetNodeAddedReason = connectionType; } @Override public synchronized ConnectionType getAddedReason() { return opennetNodeAddedReason; } @Override /** Opennet nodes need to know when a peer node was added. * We do NOT clear it on connect, because we use it for determining whether we are in the initial grace period. * However we will reset it after the grace period expires, in isDroppableWithReason(). */ protected void maybeClearPeerAddedTimeOnConnect() { // Guarantee that it gets cleared. node.getTicker().queueTimedJob(new FastRunnable() { @Override public void run() { isDroppableWithReason(false); } }, OpennetManager.DROP_MIN_AGE+1); } @Override /* Opennet peers do not export the peer added time. It is only relevant for the grace period anyway. */ protected boolean shouldExportPeerAddedTime() { return false; } @Override protected void maybeClearPeerAddedTimeOnRestart(long now) { // Do nothing. } @Override public void fatalTimeout() { if(node.isStopping()) return; Logger.error(this, "Disconnecting "+this+" because of fatal timeout"); // Disconnect. forceDisconnect(); } @Override public boolean shallWeRouteAccordingToOurPeersLocation(int htl) { return node.shallWeRouteAccordingToOurPeersLocation(htl); } @Override boolean dontKeepFullFieldSet() { return true; } public LinkLengthClass linkLengthClass() { if(!Location.isValid(getLocation())) { Logger.error(this, "No location on "+this, new Exception("debug")); return LinkLengthClass.SHORT; // FIXME add unknown to enum? Would need more complex error handling... } // FIXME OPTIMISE This should not change since we don't swap on opennet. if(Location.distance(this, opennet.node.getLocation()) > OpennetManager.LONG_DISTANCE) return LinkLengthClass.LONG; else return LinkLengthClass.SHORT; } @Override public boolean isOpennetForNoderef() { return true; } @Override public boolean canAcceptAnnouncements() { return true; } @Override protected void writePeers() { node.peers.writePeers(true); } }