/* 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. */ /* Freenet 0.7 node. */ package freenet.node; import static freenet.node.stats.DataStoreKeyType.CHK; import static freenet.node.stats.DataStoreKeyType.PUB_KEY; import static freenet.node.stats.DataStoreKeyType.SSK; import static freenet.node.stats.DataStoreType.CACHE; import static freenet.node.stats.DataStoreType.CLIENT; import static freenet.node.stats.DataStoreType.SLASHDOT; import static freenet.node.stats.DataStoreType.STORE; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Random; import java.util.Set; import org.tanukisoftware.wrapper.WrapperManager; import freenet.client.FetchContext; import freenet.clients.fcp.FCPMessage; import freenet.clients.fcp.FeedMessage; import freenet.clients.http.SecurityLevelsToadlet; import freenet.clients.http.SimpleToadletServer; import freenet.config.EnumerableOptionCallback; import freenet.config.FreenetFilePersistentConfig; import freenet.config.InvalidConfigValueException; import freenet.config.NodeNeedRestartException; import freenet.config.PersistentConfig; import freenet.config.SubConfig; import freenet.crypt.DSAPublicKey; import freenet.crypt.ECDH; import freenet.crypt.MasterSecret; import freenet.crypt.PersistentRandomSource; import freenet.crypt.RandomSource; import freenet.crypt.Yarrow; import freenet.io.comm.DMT; import freenet.io.comm.DisconnectedException; import freenet.io.comm.FreenetInetAddress; import freenet.io.comm.IOStatisticCollector; import freenet.io.comm.Message; import freenet.io.comm.MessageCore; import freenet.io.comm.MessageFilter; import freenet.io.comm.Peer; import freenet.io.comm.PeerParseException; import freenet.io.comm.ReferenceSignatureVerificationException; import freenet.io.comm.TrafficClass; import freenet.io.comm.UdpSocketHandler; import freenet.io.xfer.PartiallyReceivedBlock; import freenet.keys.CHKBlock; import freenet.keys.CHKVerifyException; import freenet.keys.ClientCHK; import freenet.keys.ClientCHKBlock; import freenet.keys.ClientKey; import freenet.keys.ClientKeyBlock; import freenet.keys.ClientSSK; import freenet.keys.ClientSSKBlock; import freenet.keys.Key; import freenet.keys.KeyBlock; import freenet.keys.KeyVerifyException; import freenet.keys.NodeCHK; import freenet.keys.NodeSSK; import freenet.keys.SSKBlock; import freenet.keys.SSKVerifyException; import freenet.l10n.BaseL10n; import freenet.l10n.NodeL10n; import freenet.node.DarknetPeerNode.FRIEND_TRUST; import freenet.node.DarknetPeerNode.FRIEND_VISIBILITY; import freenet.node.NodeDispatcher.NodeDispatcherCallback; import freenet.node.OpennetManager.ConnectionType; import freenet.node.SecurityLevels.NETWORK_THREAT_LEVEL; import freenet.node.SecurityLevels.PHYSICAL_THREAT_LEVEL; import freenet.node.probe.Listener; import freenet.node.probe.Type; import freenet.node.stats.DataStoreInstanceType; import freenet.node.stats.DataStoreStats; import freenet.node.stats.NotAvailNodeStoreStats; import freenet.node.stats.StoreCallbackStats; import freenet.node.updater.NodeUpdateManager; import freenet.node.useralerts.JVMVersionAlert; import freenet.node.useralerts.MeaningfulNodeNameUserAlert; import freenet.node.useralerts.NotEnoughNiceLevelsUserAlert; import freenet.node.useralerts.SimpleUserAlert; import freenet.node.useralerts.TimeSkewDetectedUserAlert; import freenet.node.useralerts.UserAlert; import freenet.pluginmanager.ForwardPort; import freenet.pluginmanager.PluginDownLoaderOfficialHTTPS; import freenet.pluginmanager.PluginManager; import freenet.store.BlockMetadata; import freenet.store.CHKStore; import freenet.store.FreenetStore; import freenet.store.KeyCollisionException; import freenet.store.NullFreenetStore; import freenet.store.PubkeyStore; import freenet.store.RAMFreenetStore; import freenet.store.SSKStore; import freenet.store.SlashdotStore; import freenet.store.StorableBlock; import freenet.store.StoreCallback; import freenet.store.caching.CachingFreenetStore; import freenet.store.caching.CachingFreenetStoreTracker; import freenet.store.saltedhash.ResizablePersistentIntBuffer; import freenet.store.saltedhash.SaltedHashFreenetStore; import freenet.support.Executor; import freenet.support.Fields; import freenet.support.HTMLNode; import freenet.support.HexUtil; import freenet.support.JVMVersion; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.PooledExecutor; import freenet.support.PrioritizedTicker; import freenet.support.ShortBuffer; import freenet.support.SimpleFieldSet; import freenet.support.Ticker; import freenet.support.TokenBucket; import freenet.support.api.BooleanCallback; import freenet.support.api.IntCallback; import freenet.support.api.LongCallback; import freenet.support.api.ShortCallback; import freenet.support.api.StringCallback; import freenet.support.io.ArrayBucketFactory; import freenet.support.io.Closer; import freenet.support.io.FileUtil; import freenet.support.io.NativeThread; import freenet.support.math.MersenneTwister; import freenet.support.transport.ip.HostnameSyntaxException; /** * @author amphibian */ public class Node implements TimeSkewDetectorCallback { public class MigrateOldStoreData implements Runnable { private final boolean clientCache; public MigrateOldStoreData(boolean clientCache) { this.clientCache = clientCache; if(clientCache) { oldCHKClientCache = chkClientcache; oldPKClientCache = pubKeyClientcache; oldSSKClientCache = sskClientcache; } else { oldCHK = chkDatastore; oldPK = pubKeyDatastore; oldSSK = sskDatastore; oldCHKCache = chkDatastore; oldPKCache = pubKeyDatastore; oldSSKCache = sskDatastore; } } @Override public void run() { System.err.println("Migrating old "+(clientCache ? "client cache" : "datastore")); if(clientCache) { migrateOldStore(oldCHKClientCache, chkClientcache, true); StoreCallback<? extends StorableBlock> old; synchronized(Node.this) { old = oldCHKClientCache; oldCHKClientCache = null; } closeOldStore(old); migrateOldStore(oldPKClientCache, pubKeyClientcache, true); synchronized(Node.this) { old = oldPKClientCache; oldPKClientCache = null; } closeOldStore(old); migrateOldStore(oldSSKClientCache, sskClientcache, true); synchronized(Node.this) { old = oldSSKClientCache; oldSSKClientCache = null; } closeOldStore(old); } else { migrateOldStore(oldCHK, chkDatastore, false); oldCHK = null; migrateOldStore(oldPK, pubKeyDatastore, false); oldPK = null; migrateOldStore(oldSSK, sskDatastore, false); oldSSK = null; migrateOldStore(oldCHKCache, chkDatacache, false); oldCHKCache = null; migrateOldStore(oldPKCache, pubKeyDatacache, false); oldPKCache = null; migrateOldStore(oldSSKCache, sskDatacache, false); oldSSKCache = null; } System.err.println("Finished migrating old "+(clientCache ? "client cache" : "datastore")); } } volatile CHKStore oldCHK; volatile PubkeyStore oldPK; volatile SSKStore oldSSK; volatile CHKStore oldCHKCache; volatile PubkeyStore oldPKCache; volatile SSKStore oldSSKCache; volatile CHKStore oldCHKClientCache; volatile PubkeyStore oldPKClientCache; volatile SSKStore oldSSKClientCache; private <T extends StorableBlock> void migrateOldStore(StoreCallback<T> old, StoreCallback<T> newStore, boolean canReadClientCache) { FreenetStore<T> store = old.getStore(); if(store instanceof RAMFreenetStore) { RAMFreenetStore<T> ramstore = (RAMFreenetStore<T>)store; try { ramstore.migrateTo(newStore, canReadClientCache); } catch (IOException e) { Logger.error(this, "Caught migrating old store: "+e, e); } ramstore.clear(); } else if(store instanceof SaltedHashFreenetStore) { Logger.error(this, "Migrating from from a saltedhashstore not fully supported yet: will not keep old keys"); } } public <T extends StorableBlock> void closeOldStore(StoreCallback<T> old) { FreenetStore<T> store = old.getStore(); if(store instanceof SaltedHashFreenetStore) { SaltedHashFreenetStore<T> saltstore = (SaltedHashFreenetStore<T>) store; saltstore.close(); saltstore.destruct(); } } private static volatile boolean logMINOR; private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } private static MeaningfulNodeNameUserAlert nodeNameUserAlert; private static TimeSkewDetectedUserAlert timeSkewDetectedUserAlert; public class NodeNameCallback extends StringCallback { NodeNameCallback() { } @Override public String get() { String name; synchronized(this) { name = myName; } if(name.startsWith("Node id|")|| name.equals("MyFirstFreenetNode") || name.startsWith("Freenet node with no name #")){ clientCore.alerts.register(nodeNameUserAlert); }else{ clientCore.alerts.unregister(nodeNameUserAlert); } return name; } @Override public void set(String val) throws InvalidConfigValueException { if(get().equals(val)) return; else if(val.length() > 128) throw new InvalidConfigValueException("The given node name is too long ("+val+')'); else if("".equals(val)) val = "~none~"; synchronized(this) { myName = val; } // We'll broadcast the new name to our connected darknet peers via a differential node reference SimpleFieldSet fs = new SimpleFieldSet(true); fs.putSingle("myName", myName); peers.locallyBroadcastDiffNodeRef(fs, true, false); // We call the callback once again to ensure MeaningfulNodeNameUserAlert // has been unregistered ... see #1595 get(); } } private class StoreTypeCallback extends StringCallback implements EnumerableOptionCallback { @Override public String get() { synchronized(Node.this) { return storeType; } } @Override public void set(String val) throws InvalidConfigValueException, NodeNeedRestartException { boolean found = false; for (String p : getPossibleValues()) { if (p.equals(val)) { found = true; break; } } if (!found) throw new InvalidConfigValueException("Invalid store type"); String type; synchronized(Node.this) { type = storeType; } if(type.equals("ram")) { synchronized(this) { // Serialise this part. makeStore(val); } } else { synchronized(Node.this) { storeType = val; } throw new NodeNeedRestartException("Store type cannot be changed on the fly"); } } @Override public String[] getPossibleValues() { return new String[] { "salt-hash", "ram" }; } } private class ClientCacheTypeCallback extends StringCallback implements EnumerableOptionCallback { @Override public String get() { synchronized(Node.this) { return clientCacheType; } } @Override public void set(String val) throws InvalidConfigValueException, NodeNeedRestartException { boolean found = false; for (String p : getPossibleValues()) { if (p.equals(val)) { found = true; break; } } if (!found) throw new InvalidConfigValueException("Invalid store type"); synchronized(this) { // Serialise this part. String suffix = getStoreSuffix(); if (val.equals("salt-hash")) { byte[] key; try { synchronized(Node.this) { if(keys == null) throw new MasterKeysWrongPasswordException(); key = keys.clientCacheMasterKey; clientCacheType = val; } } catch (MasterKeysWrongPasswordException e1) { setClientCacheAwaitingPassword(); throw new InvalidConfigValueException("You must enter the password"); } try { initSaltHashClientCacheFS(suffix, true, key); } catch (NodeInitException e) { Logger.error(this, "Unable to create new store", e); System.err.println("Unable to create new store: "+e); e.printStackTrace(); // FIXME l10n both on the NodeInitException and the wrapper message throw new InvalidConfigValueException("Unable to create new store: "+e); } } else if(val.equals("ram")) { initRAMClientCacheFS(); } else /*if(val.equals("none")) */{ initNoClientCacheFS(); } synchronized(Node.this) { clientCacheType = val; } } } @Override public String[] getPossibleValues() { return new String[] { "salt-hash", "ram", "none" }; } } private static class L10nCallback extends StringCallback implements EnumerableOptionCallback { @Override public String get() { return NodeL10n.getBase().getSelectedLanguage().fullName; } @Override public void set(String val) throws InvalidConfigValueException { if(val == null || get().equalsIgnoreCase(val)) return; try { NodeL10n.getBase().setLanguage(BaseL10n.LANGUAGE.mapToLanguage(val)); } catch (MissingResourceException e) { throw new InvalidConfigValueException(e.getLocalizedMessage()); } PluginManager.setLanguage(NodeL10n.getBase().getSelectedLanguage()); } @Override public String[] getPossibleValues() { return BaseL10n.LANGUAGE.valuesWithFullNames(); } } /** Encryption key for client.dat.crypt or client.dat.bak.crypt */ private DatabaseKey databaseKey; /** Encryption keys, if loaded, null if waiting for a password. We must be able to write them, * and they're all used elsewhere anyway, so there's no point trying not to keep them in memory. */ private MasterKeys keys; /** Stats */ public final NodeStats nodeStats; /** Config object for the whole node. */ public final PersistentConfig config; // Static stuff related to logger /** Directory to log to */ static File logDir; /** Maximum size of gzipped logfiles */ static long maxLogSize; /** Log config handler */ public static LoggingConfigHandler logConfigHandler; public static final int PACKETS_IN_BLOCK = 32; public static final int PACKET_SIZE = 1024; public static final double DECREMENT_AT_MIN_PROB = 0.25; public static final double DECREMENT_AT_MAX_PROB = 0.5; // Send keepalives every 7-14 seconds. Will be acked and if necessary resent. // Old behaviour was keepalives every 14-28. Even that was adequate for a 30 second // timeout. Most nodes don't need to send keepalives because they are constantly busy, // this is only an issue for disabled darknet connections, very quiet private networks // etc. public static final long KEEPALIVE_INTERVAL = SECONDS.toMillis(7); // If no activity for 30 seconds, node is dead // 35 seconds allows plenty of time for resends etc even if above is 14 sec as it is on older nodes. public static final long MAX_PEER_INACTIVITY = SECONDS.toMillis(35); /** Time after which a handshake is assumed to have failed. */ public static final int HANDSHAKE_TIMEOUT = (int) MILLISECONDS.toMillis(4800); // Keep the below within the 30 second assumed timeout. // Inter-handshake time must be at least 2x handshake timeout public static final int MIN_TIME_BETWEEN_HANDSHAKE_SENDS = HANDSHAKE_TIMEOUT*2; // 10-20 secs public static final int RANDOMIZED_TIME_BETWEEN_HANDSHAKE_SENDS = HANDSHAKE_TIMEOUT*2; // avoid overlap when the two handshakes are at the same time public static final int MIN_TIME_BETWEEN_VERSION_PROBES = HANDSHAKE_TIMEOUT*4; public static final int RANDOMIZED_TIME_BETWEEN_VERSION_PROBES = HANDSHAKE_TIMEOUT*2; // 20-30 secs public static final int MIN_TIME_BETWEEN_VERSION_SENDS = HANDSHAKE_TIMEOUT*4; public static final int RANDOMIZED_TIME_BETWEEN_VERSION_SENDS = HANDSHAKE_TIMEOUT*2; // 20-30 secs public static final int MIN_TIME_BETWEEN_BURSTING_HANDSHAKE_BURSTS = HANDSHAKE_TIMEOUT*24; // 2-5 minutes public static final int RANDOMIZED_TIME_BETWEEN_BURSTING_HANDSHAKE_BURSTS = HANDSHAKE_TIMEOUT*36; public static final int MIN_BURSTING_HANDSHAKE_BURST_SIZE = 1; // 1-4 handshake sends per burst public static final int RANDOMIZED_BURSTING_HANDSHAKE_BURST_SIZE = 3; // If we don't receive any packets at all in this period, from any node, tell the user public static final long ALARM_TIME = MINUTES.toMillis(1); static final long MIN_INTERVAL_BETWEEN_INCOMING_SWAP_REQUESTS = MILLISECONDS.toMillis(900); static final long MIN_INTERVAL_BETWEEN_INCOMING_PROBE_REQUESTS = MILLISECONDS.toMillis(1000); public static final int SYMMETRIC_KEY_LENGTH = 32; // 256 bits - note that this isn't used everywhere to determine it /** Datastore directory */ private final ProgramDirectory storeDir; /** Datastore properties */ private String storeType; private boolean storeUseSlotFilters; private boolean storeSaltHashResizeOnStart; /** Minimum total datastore size */ static final long MIN_STORE_SIZE = 32 * 1024 * 1024; /** Default datastore size (must be at least MIN_STORE_SIZE) */ static final long DEFAULT_STORE_SIZE = 32 * 1024 * 1024; /** Minimum client cache size */ static final long MIN_CLIENT_CACHE_SIZE = 0; /** Default client cache size (must be at least MIN_CLIENT_CACHE_SIZE) */ static final long DEFAULT_CLIENT_CACHE_SIZE = 10 * 1024 * 1024; /** Minimum slashdot cache size */ static final long MIN_SLASHDOT_CACHE_SIZE = 0; /** Default slashdot cache size (must be at least MIN_SLASHDOT_CACHE_SIZE) */ static final long DEFAULT_SLASHDOT_CACHE_SIZE = 10 * 1024 * 1024; /** The number of bytes per key total in all the different datastores. All the datastores * are always the same size in number of keys. */ public static final int sizePerKey = CHKBlock.DATA_LENGTH + CHKBlock.TOTAL_HEADERS_LENGTH + DSAPublicKey.PADDED_SIZE + SSKBlock.DATA_LENGTH + SSKBlock.TOTAL_HEADERS_LENGTH; /** The maximum number of keys stored in each of the datastores, cache and store combined. */ private long maxTotalKeys; long maxCacheKeys; long maxStoreKeys; /** The maximum size of the datastore. Kept to avoid rounding turning 5G into 5368698672 */ private long maxTotalDatastoreSize; /** If true, store shrinks occur immediately even if they are over 10% of the store size. If false, * we just set the storeSize and do an offline shrink on the next startup. Online shrinks do not * preserve the most recently used data so are not recommended. */ private boolean storeForceBigShrinks; private final SemiOrderedShutdownHook shutdownHook; /** The CHK datastore. Long term storage; data should only be inserted here if * this node is the closest location on the chain so far, and it is on an * insert (because inserts will always reach the most specialized node; if we * allow requests to store here, then we get pollution by inserts for keys not * close to our specialization). These conclusions derived from Oskar's simulations. */ private CHKStore chkDatastore; /** The SSK datastore. See description for chkDatastore. */ private SSKStore sskDatastore; /** The store of DSAPublicKeys (by hash). See description for chkDatastore. */ private PubkeyStore pubKeyDatastore; /** Client cache store type */ private String clientCacheType; /** Client cache could not be opened so is a RAMFS until the correct password is entered */ private boolean clientCacheAwaitingPassword; private boolean databaseAwaitingPassword; /** Client cache maximum cached keys for each type */ long maxClientCacheKeys; /** Maximum size of the client cache. Kept to avoid rounding problems. */ private long maxTotalClientCacheSize; /** The CHK datacache. Short term cache which stores everything that passes * through this node. */ private CHKStore chkDatacache; /** The SSK datacache. Short term cache which stores everything that passes * through this node. */ private SSKStore sskDatacache; /** The public key datacache (by hash). Short term cache which stores * everything that passes through this node. */ private PubkeyStore pubKeyDatacache; /** The CHK client cache. Caches local requests only. */ private CHKStore chkClientcache; /** The SSK client cache. Caches local requests only. */ private SSKStore sskClientcache; /** The pubkey client cache. Caches local requests only. */ private PubkeyStore pubKeyClientcache; // These only cache keys for 30 minutes. // FIXME make the first two configurable private long maxSlashdotCacheSize; private int maxSlashdotCacheKeys; static final long PURGE_INTERVAL = SECONDS.toMillis(60); private CHKStore chkSlashdotcache; private SlashdotStore<CHKBlock> chkSlashdotcacheStore; private SSKStore sskSlashdotcache; private SlashdotStore<SSKBlock> sskSlashdotcacheStore; private PubkeyStore pubKeySlashdotcache; private SlashdotStore<DSAPublicKey> pubKeySlashdotcacheStore; /** If false, only ULPRs will use the slashdot cache. If true, everything does. */ private boolean useSlashdotCache; /** If true, we write stuff to the datastore even though we shouldn't because the HTL is * too high. However it is flagged as old so it won't be included in the Bloom filter for * sharing purposes. */ private boolean writeLocalToDatastore; final NodeGetPubkey getPubKey; /** FetchContext for ARKs */ public final FetchContext arkFetcherContext; /** IP detector */ public final NodeIPDetector ipDetector; /** For debugging/testing, set this to true to stop the * probabilistic decrement at the edges of the HTLs. */ boolean disableProbabilisticHTLs; public final RequestTracker tracker; /** Semi-unique ID for swap requests. Used to identify us so that the * topology can be reconstructed. */ public long swapIdentifier; private String myName; public final LocationManager lm; /** My peers */ public final PeerManager peers; /** Node-reference directory (node identity, peers, etc) */ final ProgramDirectory nodeDir; /** Config directory (l10n overrides, etc) */ final ProgramDirectory cfgDir; /** User data directory (bookmarks, download lists, etc) */ final ProgramDirectory userDir; /** Run-time state directory (bootID, PRNG seed, etc) */ final ProgramDirectory runDir; /** Plugin directory */ final ProgramDirectory pluginDir; /** File to write crypto master keys into, possibly passworded */ final File masterKeysFile; /** Directory to put extra peer data into */ final File extraPeerDataDir; private volatile boolean hasPanicked; /** Strong RNG */ public final RandomSource random; /** JCA-compliant strong RNG. WARNING: DO NOT CALL THIS ON THE MAIN NETWORK * HANDLING THREADS! In some configurations it can block, potentially * forever, on nextBytes()! */ public final SecureRandom secureRandom; /** Weak but fast RNG */ public final Random fastWeakRandom; /** The object which handles incoming messages and allows us to wait for them */ final MessageCore usm; // Darknet stuff NodeCrypto darknetCrypto; // Back compat private boolean showFriendsVisibilityAlert; // Opennet stuff private final NodeCryptoConfig opennetCryptoConfig; OpennetManager opennet; private volatile boolean isAllowedToConnectToSeednodes; private int maxOpennetPeers; private boolean acceptSeedConnections; private boolean passOpennetRefsThroughDarknet; // General stuff public final Executor executor; public final PacketSender ps; public final PrioritizedTicker ticker; final DNSRequester dnsr; final NodeDispatcher dispatcher; public final UptimeEstimator uptime; public final TokenBucket outputThrottle; public boolean throttleLocalData; private int outputBandwidthLimit; private int inputBandwidthLimit; boolean inputLimitDefault; final boolean enableARKs; final boolean enablePerNodeFailureTables; final boolean enableULPRDataPropagation; final boolean enableSwapping; private volatile boolean publishOurPeersLocation; private volatile boolean routeAccordingToOurPeersLocation; boolean enableSwapQueueing; boolean enablePacketCoalescing; public static final short DEFAULT_MAX_HTL = (short)18; private short maxHTL; private boolean skipWrapperWarning; private int maxPacketSize; /** Should inserts ignore low backoff times by default? */ public static final boolean IGNORE_LOW_BACKOFF_DEFAULT = false; /** Definition of "low backoff times" for above. */ public static final long LOW_BACKOFF = SECONDS.toMillis(30); /** Should inserts be fairly blatently prioritised on accept by default? */ public static final boolean PREFER_INSERT_DEFAULT = false; /** Should inserts fork when the HTL reaches cacheability? */ public static final boolean FORK_ON_CACHEABLE_DEFAULT = true; public final IOStatisticCollector collector; /** Type identifier for fproxy node to node messages, as sent on DMT.nodeToNodeMessage's */ public static final int N2N_MESSAGE_TYPE_FPROXY = 1; /** Type identifier for differential node reference messages, as sent on DMT.nodeToNodeMessage's */ public static final int N2N_MESSAGE_TYPE_DIFFNODEREF = 2; /** Identifier within fproxy messages for simple, short text messages to be displayed on the homepage as useralerts */ public static final int N2N_TEXT_MESSAGE_TYPE_USERALERT = 1; /** Identifier within fproxy messages for an offer to transfer a file */ public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER = 2; /** Identifier within fproxy messages for accepting an offer to transfer a file */ public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED = 3; /** Identifier within fproxy messages for rejecting an offer to transfer a file */ public static final int N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_REJECTED = 4; /** Identified within friend feed for the recommendation of a bookmark */ public static final int N2N_TEXT_MESSAGE_TYPE_BOOKMARK = 5; /** Identified within friend feed for the recommendation of a file */ public static final int N2N_TEXT_MESSAGE_TYPE_DOWNLOAD = 6; public static final int EXTRA_PEER_DATA_TYPE_N2NTM = 1; public static final int EXTRA_PEER_DATA_TYPE_PEER_NOTE = 2; public static final int EXTRA_PEER_DATA_TYPE_QUEUED_TO_SEND_N2NM = 3; public static final int EXTRA_PEER_DATA_TYPE_BOOKMARK = 4; public static final int EXTRA_PEER_DATA_TYPE_DOWNLOAD = 5; public static final int PEER_NOTE_TYPE_PRIVATE_DARKNET_COMMENT = 1; /** The bootID of the last time the node booted up. Or -1 if we don't know due to * permissions problems, or we suspect that the node has been booted and not * written the file e.g. if we can't write it. So if we want to compare data * gathered in the last session and only recorded to disk on a clean shutdown * to data we have now, we just include the lastBootID. */ public final long lastBootID; public final long bootID; public final long startupTime; private SimpleToadletServer toadlets; public final NodeClientCore clientCore; // ULPRs, RecentlyFailed, per node failure tables, are all managed by FailureTable. final FailureTable failureTable; // The version we were before we restarted. public int lastVersion; /** NodeUpdater **/ public final NodeUpdateManager nodeUpdater; public final SecurityLevels securityLevels; // Things that's needed to keep track of public final PluginManager pluginManager; // Helpers public final InetAddress localhostAddress; public final FreenetInetAddress fLocalhostAddress; // The node starter private static NodeStarter nodeStarter; // The watchdog will be silenced until it's true private boolean hasStarted; private boolean isStopping = false; /** * Minimum uptime for us to consider a node an acceptable place to store a key. We store a key * to the datastore only if it's from an insert, and we are a sink, but when calculating whether * we are a sink we ignore nodes which have less uptime (percentage) than this parameter. */ static final int MIN_UPTIME_STORE_KEY = 40; private volatile boolean isPRNGReady = false; private boolean storePreallocate; private boolean enableRoutedPing; /** * Minimum bandwidth limit in bytes considered usable: 10 KiB. If there is an attempt to set a limit below this - * excluding the reserved -1 for input bandwidth - the callback will throw. See the callbacks for * outputBandwidthLimit and inputBandwidthLimit. 10 KiB are equivalent to 50 GiB traffic per month. */ private static final int minimumBandwidth = 10 * 1024; /** Quality of Service mark we will use for all outgoing packets (opennet/darknet) */ private TrafficClass trafficClass; public TrafficClass getTrafficClass() { return trafficClass; } /* * Gets minimum bandwidth in bytes considered usable. * * @see #minimumBandwidth */ public static int getMinimumBandwidth() { return minimumBandwidth; } /** * Returns an exception with an explanation that the given bandwidth limit is too low. * * See the Node.bandwidthMinimum localization string. * @param limit Bandwidth limit in bytes. */ private InvalidConfigValueException lowBandwidthLimit(int limit) { return new InvalidConfigValueException(l10n("bandwidthMinimum", new String[] { "limit", "minimum" }, new String[] { Integer.toString(limit), Integer.toString(minimumBandwidth) })); } /** * Dispatches a probe request with the specified settings * @see freenet.node.probe.Probe#start(byte, long, Type, Listener) */ public void startProbe(final byte htl, final long uid, final Type type, final Listener listener) { dispatcher.probe.start(htl, uid, type, listener); } /** * Read all storable settings (identity etc) from the node file. * @param filename The name of the file to read from. * @throws IOException throw when I/O error occur */ private void readNodeFile(String filename) throws IOException { // REDFLAG: Any way to share this code with NodePeer? FileInputStream fis = new FileInputStream(filename); InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); BufferedReader br = new BufferedReader(isr); SimpleFieldSet fs = new SimpleFieldSet(br, false, true); br.close(); // Read contents String[] udp = fs.getAll("physical.udp"); if((udp != null) && (udp.length > 0)) { for(String udpAddr : udp) { // Just keep the first one with the correct port number. Peer p; try { p = new Peer(udpAddr, false, true); } catch (HostnameSyntaxException e) { Logger.error(this, "Invalid hostname or IP Address syntax error while parsing our darknet node reference: "+udpAddr); System.err.println("Invalid hostname or IP Address syntax error while parsing our darknet node reference: "+udpAddr); continue; } catch (PeerParseException e) { throw (IOException)new IOException().initCause(e); } if(p.getPort() == getDarknetPortNumber()) { // DNSRequester doesn't deal with our own node ipDetector.setOldIPAddress(p.getFreenetAddress()); break; } } } darknetCrypto.readCrypto(fs); swapIdentifier = Fields.bytesToLong(darknetCrypto.identityHashHash); String loc = fs.get("location"); double locD = Location.getLocation(loc); if (locD == -1.0) throw new IOException("Invalid location: " + loc); lm.setLocation(locD); myName = fs.get("myName"); if(myName == null) { myName = newName(); } String verString = fs.get("version"); if(verString == null) { Logger.error(this, "No version!"); System.err.println("No version!"); } else { lastVersion = Version.getArbitraryBuildNumber(verString, -1); } } public void makeStore(String val) throws InvalidConfigValueException { String suffix = getStoreSuffix(); if (val.equals("salt-hash")) { try { initSaltHashFS(suffix, true, null); } catch (NodeInitException e) { Logger.error(this, "Unable to create new store", e); System.err.println("Unable to create new store: "+e); e.printStackTrace(); // FIXME l10n both on the NodeInitException and the wrapper message throw new InvalidConfigValueException("Unable to create new store: "+e); } } else { initRAMFS(); } synchronized(Node.this) { storeType = val; } } private String newName() { return "Freenet node with no name #"+random.nextLong(); } private final Object writeNodeFileSync = new Object(); public void writeNodeFile() { synchronized(writeNodeFileSync) { writeNodeFile(nodeDir.file("node-"+getDarknetPortNumber()), nodeDir.file("node-"+getDarknetPortNumber()+".bak")); } } public void writeOpennetFile() { OpennetManager om = opennet; if(om != null) om.writeFile(); } private void writeNodeFile(File orig, File backup) { SimpleFieldSet fs = darknetCrypto.exportPrivateFieldSet(); if(orig.exists()) backup.delete(); FileOutputStream fos = null; try { fos = new FileOutputStream(backup); fs.writeTo(fos); fos.close(); fos = null; FileUtil.renameTo(backup, orig); } catch (IOException ioe) { Logger.error(this, "IOE :"+ioe.getMessage(), ioe); return; } finally { Closer.close(fos); } } private void initNodeFileSettings() { Logger.normal(this, "Creating new node file from scratch"); // Don't need to set getDarknetPortNumber() // FIXME use a real IP! darknetCrypto.initCrypto(); swapIdentifier = Fields.bytesToLong(darknetCrypto.identityHashHash); myName = newName(); } /** * Read the config file from the arguments. * Then create a node. * Anything that needs static init should ideally be in here. * @param args */ public static void main(String[] args) throws IOException { NodeStarter.main(args); } public boolean isUsingWrapper(){ if(nodeStarter!=null && WrapperManager.isControlledByNativeWrapper()) return true; else return false; } public NodeStarter getNodeStarter(){ return nodeStarter; } /** * Create a Node from a Config object. * @param config The Config object for this node. * @param r The random number generator for this node. Passed in because we may want * to use a non-secure RNG for e.g. one-JVM live-code simulations. Should be a Yarrow in * a production node. Yarrow will be used if that parameter is null * @param weakRandom The fast random number generator the node will use. If null a MT * instance will be used, seeded from the secure PRNG. * @param lc logging config Handler * @param ns NodeStarter * @param executor Executor * @throws NodeInitException If the node initialization fails. */ Node(PersistentConfig config, RandomSource r, RandomSource weakRandom, LoggingConfigHandler lc, NodeStarter ns, Executor executor) throws NodeInitException { this.shutdownHook = SemiOrderedShutdownHook.get(); // Easy stuff String tmp = "Initializing Node using Freenet Build #"+Version.buildNumber()+" r"+Version.cvsRevision()+" and freenet-ext Build #"+NodeStarter.extBuildNumber+" r"+NodeStarter.extRevisionNumber+" with "+System.getProperty("java.vendor")+" JVM version "+System.getProperty("java.version")+" running on "+System.getProperty("os.arch")+' '+System.getProperty("os.name")+' '+System.getProperty("os.version"); fixCertsFiles(); Logger.normal(this, tmp); System.out.println(tmp); collector = new IOStatisticCollector(); this.executor = executor; nodeStarter=ns; if(logConfigHandler != lc) logConfigHandler=lc; getPubKey = new NodeGetPubkey(this); startupTime = System.currentTimeMillis(); SimpleFieldSet oldConfig = config.getSimpleFieldSet(); // Setup node-specific configuration final SubConfig nodeConfig = config.createSubConfig("node"); final SubConfig installConfig = config.createSubConfig("node.install"); int sortOrder = 0; // Directory for node-related files other than store this.userDir = setupProgramDir(installConfig, "userDir", ".", "Node.userDir", "Node.userDirLong", nodeConfig); this.cfgDir = setupProgramDir(installConfig, "cfgDir", getUserDir().toString(), "Node.cfgDir", "Node.cfgDirLong", nodeConfig); this.nodeDir = setupProgramDir(installConfig, "nodeDir", getUserDir().toString(), "Node.nodeDir", "Node.nodeDirLong", nodeConfig); this.runDir = setupProgramDir(installConfig, "runDir", getUserDir().toString(), "Node.runDir", "Node.runDirLong", nodeConfig); this.pluginDir = setupProgramDir(installConfig, "pluginDir", userDir().file("plugins").toString(), "Node.pluginDir", "Node.pluginDirLong", nodeConfig); // l10n stuffs nodeConfig.register("l10n", Locale.getDefault().getLanguage().toLowerCase(), sortOrder++, false, true, "Node.l10nLanguage", "Node.l10nLanguageLong", new L10nCallback()); try { new NodeL10n(BaseL10n.LANGUAGE.mapToLanguage(nodeConfig.getString("l10n")), getCfgDir()); } catch (MissingResourceException e) { try { new NodeL10n(BaseL10n.LANGUAGE.mapToLanguage(nodeConfig.getOption("l10n").getDefault()), getCfgDir()); } catch (MissingResourceException e1) { new NodeL10n(BaseL10n.LANGUAGE.mapToLanguage(BaseL10n.LANGUAGE.getDefault().shortCode), getCfgDir()); } } // FProxy config needs to be here too SubConfig fproxyConfig = config.createSubConfig("fproxy"); try { toadlets = new SimpleToadletServer(fproxyConfig, new ArrayBucketFactory(), executor, this); fproxyConfig.finishedInitialization(); toadlets.start(); } catch (IOException e4) { Logger.error(this, "Could not start web interface: "+e4, e4); System.err.println("Could not start web interface: "+e4); e4.printStackTrace(); throw new NodeInitException(NodeInitException.EXIT_COULD_NOT_START_FPROXY, "Could not start FProxy: "+e4); } catch (InvalidConfigValueException e4) { System.err.println("Invalid config value, cannot start web interface: "+e4); e4.printStackTrace(); throw new NodeInitException(NodeInitException.EXIT_COULD_NOT_START_FPROXY, "Could not start FProxy: "+e4); } final NativeThread entropyGatheringThread = new NativeThread(new Runnable() { long tLastAdded = -1; private void recurse(File f) { if(isPRNGReady) return; extendTimeouts(); File[] subDirs = f.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.exists() && pathname.canRead() && pathname.isDirectory(); } }); // @see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5086412 if(subDirs != null) for(File currentDir : subDirs) recurse(currentDir); } @Override public void run() { try { // Delay entropy generation helper hack if enough entropy available Thread.sleep(100); } catch (InterruptedException e) { } if(isPRNGReady) return; System.out.println("Not enough entropy available."); System.out.println("Trying to gather entropy (randomness) by reading the disk..."); if(File.separatorChar == '/') { if(new File("/dev/hwrng").exists()) System.out.println("/dev/hwrng exists - have you installed rng-tools?"); else System.out.println("You should consider installing a better random number generator e.g. haveged."); } extendTimeouts(); for(File root : File.listRoots()) { if(isPRNGReady) return; recurse(root); } } /** This is ridiculous, but for some users it can take more than an hour, and timing out sucks * a few bytes and then times out again. :( */ static final int EXTEND_BY = 60*60*1000; private void extendTimeouts() { long now = System.currentTimeMillis(); if(now - tLastAdded < EXTEND_BY/2) return; long target = tLastAdded + EXTEND_BY; while(target < now) target += EXTEND_BY; long extend = target - now; assert(extend < Integer.MAX_VALUE); assert(extend > 0); WrapperManager.signalStarting((int)extend); tLastAdded = now; } }, "Entropy Gathering Thread", NativeThread.MIN_PRIORITY, true); // Setup RNG if needed : DO NOT USE IT BEFORE THAT POINT! if (r == null) { // Preload required freenet.crypt.Util and freenet.crypt.Rijndael classes (selftest can delay Yarrow startup and trigger false lack-of-enthropy message) freenet.crypt.Util.mdProviders.size(); freenet.crypt.ciphers.Rijndael.getProviderName(); File seed = userDir.file("prng.seed"); FileUtil.setOwnerRW(seed); entropyGatheringThread.start(); // Can block. this.random = new Yarrow(seed); // http://bugs.sun.com/view_bug.do;jsessionid=ff625daf459fdffffffffcd54f1c775299e0?bug_id=4705093 // This might block on /dev/random while doing new SecureRandom(). Once it's created, it won't block. ECDH.blockingInit(); } else { this.random = r; // if it's not null it's because we are running in the simulator } // This can block too. this.secureRandom = NodeStarter.getGlobalSecureRandom(); isPRNGReady = true; toadlets.getStartupToadlet().setIsPRNGReady(); if(weakRandom == null) { byte buffer[] = new byte[16]; random.nextBytes(buffer); this.fastWeakRandom = new MersenneTwister(buffer); }else this.fastWeakRandom = weakRandom; nodeNameUserAlert = new MeaningfulNodeNameUserAlert(this); this.config = config; lm = new LocationManager(random, this); try { localhostAddress = InetAddress.getByName("127.0.0.1"); } catch (UnknownHostException e3) { // Does not do a reverse lookup, so this is impossible throw new Error(e3); } fLocalhostAddress = new FreenetInetAddress(localhostAddress); this.securityLevels = new SecurityLevels(this, config); // Location of master key nodeConfig.register("masterKeyFile", "master.keys", sortOrder++, true, true, "Node.masterKeyFile", "Node.masterKeyFileLong", new StringCallback() { @Override public String get() { if(masterKeysFile == null) return "none"; else return masterKeysFile.getPath(); } @Override public void set(String val) throws InvalidConfigValueException, NodeNeedRestartException { // FIXME l10n // FIXME wipe the old one and move throw new InvalidConfigValueException("Node.masterKeyFile cannot be changed on the fly, you must shutdown, wipe the old file and reconfigure"); } }); String value = nodeConfig.getString("masterKeyFile"); File f; if (value.equalsIgnoreCase("none")) { f = null; } else { f = new File(value); if(f.exists() && !(f.canWrite() && f.canRead())) throw new NodeInitException(NodeInitException.EXIT_CANT_WRITE_MASTER_KEYS, "Cannot read from and write to master keys file "+f); } masterKeysFile = f; FileUtil.setOwnerRW(masterKeysFile); nodeConfig.register("showFriendsVisibilityAlert", false, sortOrder++, true, false, "Node.showFriendsVisibilityAlert", "Node.showFriendsVisibilityAlert", new BooleanCallback() { @Override public Boolean get() { synchronized(Node.this) { return showFriendsVisibilityAlert; } } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { synchronized(this) { if(val == showFriendsVisibilityAlert) return; if(val) return; } unregisterFriendsVisibilityAlert(); } }); showFriendsVisibilityAlert = nodeConfig.getBoolean("showFriendsVisibilityAlert"); byte[] clientCacheKey = null; MasterSecret persistentSecret = null; for(int i=0;i<2; i++) { try { if(securityLevels.physicalThreatLevel == PHYSICAL_THREAT_LEVEL.MAXIMUM) { keys = MasterKeys.createRandom(secureRandom); } else { keys = MasterKeys.read(masterKeysFile, secureRandom, ""); } clientCacheKey = keys.clientCacheMasterKey; persistentSecret = keys.getPersistentMasterSecret(); databaseKey = keys.createDatabaseKey(secureRandom); if(securityLevels.getPhysicalThreatLevel() == PHYSICAL_THREAT_LEVEL.HIGH) { System.err.println("Physical threat level is set to HIGH but no password, resetting to NORMAL - probably timing glitch"); securityLevels.resetPhysicalThreatLevel(PHYSICAL_THREAT_LEVEL.NORMAL); } break; } catch (MasterKeysWrongPasswordException e) { break; } catch (MasterKeysFileSizeException e) { System.err.println("Impossible: master keys file "+masterKeysFile+" too " + e.sizeToString() + "! Deleting to enable startup, but you will lose your client cache."); masterKeysFile.delete(); } catch (IOException e) { break; } } // Boot ID bootID = random.nextLong(); // Fixed length file containing boot ID. Accessed with random access file. So hopefully it will always be // written. Note that we set lastBootID to -1 if we can't _write_ our ID as well as if we can't read it, // because if we can't write it then we probably couldn't write it on the last bootup either. File bootIDFile = runDir.file("bootID"); int BOOT_FILE_LENGTH = 64 / 4; // A long in padded hex bytes long oldBootID = -1; RandomAccessFile raf = null; try { raf = new RandomAccessFile(bootIDFile, "rw"); if(raf.length() < BOOT_FILE_LENGTH) { oldBootID = -1; } else { byte[] buf = new byte[BOOT_FILE_LENGTH]; raf.readFully(buf); String s = new String(buf, "ISO-8859-1"); try { oldBootID = Fields.bytesToLong(HexUtil.hexToBytes(s)); } catch (NumberFormatException e) { oldBootID = -1; } raf.seek(0); } String s = HexUtil.bytesToHex(Fields.longToBytes(bootID)); byte[] buf = s.getBytes("ISO-8859-1"); if(buf.length != BOOT_FILE_LENGTH) System.err.println("Not 16 bytes for boot ID "+bootID+" - WTF??"); raf.write(buf); } catch (IOException e) { oldBootID = -1; // If we have an error in reading, *or in writing*, we don't reliably know the last boot ID. } finally { Closer.close(raf); } lastBootID = oldBootID; nodeConfig.register("disableProbabilisticHTLs", false, sortOrder++, true, false, "Node.disablePHTLS", "Node.disablePHTLSLong", new BooleanCallback() { @Override public Boolean get() { return disableProbabilisticHTLs; } @Override public void set(Boolean val) throws InvalidConfigValueException { disableProbabilisticHTLs = val; } }); disableProbabilisticHTLs = nodeConfig.getBoolean("disableProbabilisticHTLs"); nodeConfig.register("maxHTL", DEFAULT_MAX_HTL, sortOrder++, true, false, "Node.maxHTL", "Node.maxHTLLong", new ShortCallback() { @Override public Short get() { return maxHTL; } @Override public void set(Short val) throws InvalidConfigValueException { if(val < 0) throw new InvalidConfigValueException("Impossible max HTL"); maxHTL = val; } }, false); maxHTL = nodeConfig.getShort("maxHTL"); class TrafficClassCallback extends StringCallback implements EnumerableOptionCallback { @Override public String get() { return trafficClass.name(); } @Override public void set(String tcName) throws InvalidConfigValueException, NodeNeedRestartException { try { trafficClass = TrafficClass.fromNameOrValue(tcName); } catch (IllegalArgumentException e) { throw new InvalidConfigValueException(e); } throw new NodeNeedRestartException("TrafficClass cannot change on the fly"); } @Override public String[] getPossibleValues() { ArrayList<String> array = new ArrayList<String>(); for (TrafficClass tc : TrafficClass.values()) array.add(tc.name()); return array.toArray(new String[0]); } } nodeConfig.register("trafficClass", TrafficClass.getDefault().name(), sortOrder++, true, false, "Node.trafficClass", "Node.trafficClassLong", new TrafficClassCallback()); String trafficClassValue = nodeConfig.getString("trafficClass"); try { trafficClass = TrafficClass.fromNameOrValue(trafficClassValue); } catch (IllegalArgumentException e) { Logger.error(this, "Invalid trafficClass:"+trafficClassValue+" resetting the value to default.", e); trafficClass = TrafficClass.getDefault(); } // FIXME maybe these should persist? They need to be private. decrementAtMax = random.nextDouble() <= DECREMENT_AT_MAX_PROB; decrementAtMin = random.nextDouble() <= DECREMENT_AT_MIN_PROB; // Determine where to bind to usm = new MessageCore(executor); // FIXME maybe these configs should actually be under a node.ip subconfig? ipDetector = new NodeIPDetector(this); sortOrder = ipDetector.registerConfigs(nodeConfig, sortOrder); // ARKs enabled? nodeConfig.register("enableARKs", true, sortOrder++, true, false, "Node.enableARKs", "Node.enableARKsLong", new BooleanCallback() { @Override public Boolean get() { return enableARKs; } @Override public void set(Boolean val) throws InvalidConfigValueException { throw new InvalidConfigValueException("Cannot change on the fly"); } @Override public boolean isReadOnly() { return true; } }); enableARKs = nodeConfig.getBoolean("enableARKs"); nodeConfig.register("enablePerNodeFailureTables", true, sortOrder++, true, false, "Node.enablePerNodeFailureTables", "Node.enablePerNodeFailureTablesLong", new BooleanCallback() { @Override public Boolean get() { return enablePerNodeFailureTables; } @Override public void set(Boolean val) throws InvalidConfigValueException { throw new InvalidConfigValueException("Cannot change on the fly"); } @Override public boolean isReadOnly() { return true; } }); enablePerNodeFailureTables = nodeConfig.getBoolean("enablePerNodeFailureTables"); nodeConfig.register("enableULPRDataPropagation", true, sortOrder++, true, false, "Node.enableULPRDataPropagation", "Node.enableULPRDataPropagationLong", new BooleanCallback() { @Override public Boolean get() { return enableULPRDataPropagation; } @Override public void set(Boolean val) throws InvalidConfigValueException { throw new InvalidConfigValueException("Cannot change on the fly"); } @Override public boolean isReadOnly() { return true; } }); enableULPRDataPropagation = nodeConfig.getBoolean("enableULPRDataPropagation"); nodeConfig.register("enableSwapping", true, sortOrder++, true, false, "Node.enableSwapping", "Node.enableSwappingLong", new BooleanCallback() { @Override public Boolean get() { return enableSwapping; } @Override public void set(Boolean val) throws InvalidConfigValueException { throw new InvalidConfigValueException("Cannot change on the fly"); } @Override public boolean isReadOnly() { return true; } }); enableSwapping = nodeConfig.getBoolean("enableSwapping"); /* * Publish our peers' locations is enabled, even in MAXIMUM network security and/or HIGH friends security, * because a node which doesn't publish its peers' locations will get dramatically less traffic. * * Publishing our peers' locations does make us slightly more vulnerable to some attacks, but I don't think * it's a big difference: swapping reveals the same information, it just doesn't update as quickly. This * may help slightly, but probably not dramatically against a clever attacker. * * FIXME review this decision. */ nodeConfig.register("publishOurPeersLocation", true, sortOrder++, true, false, "Node.publishOurPeersLocation", "Node.publishOurPeersLocationLong", new BooleanCallback() { @Override public Boolean get() { return publishOurPeersLocation; } @Override public void set(Boolean val) throws InvalidConfigValueException { publishOurPeersLocation = val; } }); publishOurPeersLocation = nodeConfig.getBoolean("publishOurPeersLocation"); nodeConfig.register("routeAccordingToOurPeersLocation", true, sortOrder++, true, false, "Node.routeAccordingToOurPeersLocation", "Node.routeAccordingToOurPeersLocation", new BooleanCallback() { @Override public Boolean get() { return routeAccordingToOurPeersLocation; } @Override public void set(Boolean val) throws InvalidConfigValueException { routeAccordingToOurPeersLocation = val; } }); routeAccordingToOurPeersLocation = nodeConfig.getBoolean("routeAccordingToOurPeersLocation"); nodeConfig.register("enableSwapQueueing", true, sortOrder++, true, false, "Node.enableSwapQueueing", "Node.enableSwapQueueingLong", new BooleanCallback() { @Override public Boolean get() { return enableSwapQueueing; } @Override public void set(Boolean val) throws InvalidConfigValueException { enableSwapQueueing = val; } }); enableSwapQueueing = nodeConfig.getBoolean("enableSwapQueueing"); nodeConfig.register("enablePacketCoalescing", true, sortOrder++, true, false, "Node.enablePacketCoalescing", "Node.enablePacketCoalescingLong", new BooleanCallback() { @Override public Boolean get() { return enablePacketCoalescing; } @Override public void set(Boolean val) throws InvalidConfigValueException { enablePacketCoalescing = val; } }); enablePacketCoalescing = nodeConfig.getBoolean("enablePacketCoalescing"); // Determine the port number // @see #191 if(oldConfig != null && "-1".equals(oldConfig.get("node.listenPort"))) throw new NodeInitException(NodeInitException.EXIT_COULD_NOT_BIND_USM, "Your freenet.ini file is corrupted! 'listenPort=-1'"); NodeCryptoConfig darknetConfig = new NodeCryptoConfig(nodeConfig, sortOrder++, false, securityLevels); sortOrder += NodeCryptoConfig.OPTION_COUNT; darknetCrypto = new NodeCrypto(this, false, darknetConfig, startupTime, enableARKs); // Must be created after darknetCrypto dnsr = new DNSRequester(this); ps = new PacketSender(this); ticker = new PrioritizedTicker(executor, getDarknetPortNumber()); if(executor instanceof PooledExecutor) ((PooledExecutor)executor).setTicker(ticker); Logger.normal(Node.class, "Creating node..."); shutdownHook.addEarlyJob(new Thread() { @Override public void run() { if (opennet != null) opennet.stop(false); } }); shutdownHook.addEarlyJob(new Thread() { @Override public void run() { darknetCrypto.stop(); } }); // Bandwidth limit nodeConfig.register("outputBandwidthLimit", "15K", sortOrder++, false, true, "Node.outBWLimit", "Node.outBWLimitLong", new IntCallback() { @Override public Integer get() { //return BlockTransmitter.getHardBandwidthLimit(); return outputBandwidthLimit; } @Override public void set(Integer obwLimit) throws InvalidConfigValueException { checkOutputBandwidthLimit(obwLimit); try { outputThrottle.changeNanosAndBucketSize(SECONDS.toNanos(1) / obwLimit, obwLimit/2); } catch (IllegalArgumentException e) { throw new InvalidConfigValueException(e); } synchronized(Node.this) { outputBandwidthLimit = obwLimit; } } }); int obwLimit = nodeConfig.getInt("outputBandwidthLimit"); if (obwLimit < minimumBandwidth) { obwLimit = minimumBandwidth; // upgrade slow nodes automatically Logger.normal(Node.class, "Output bandwidth was lower than minimum bandwidth. Increased to minimum bandwidth."); } outputBandwidthLimit = obwLimit; try { checkOutputBandwidthLimit(outputBandwidthLimit); } catch (InvalidConfigValueException e) { throw new NodeInitException(NodeInitException.EXIT_BAD_BWLIMIT, e.getMessage()); } // Bucket size of 0.5 seconds' worth of bytes. // Add them at a rate determined by the obwLimit. // Maximum forced bytes 80%, in other words, 20% of the bandwidth is reserved for // block transfers, so we will use that 20% for block transfers even if more than 80% of the limit is used for non-limited data (resends etc). int bucketSize = obwLimit/2; // Must have at least space for ONE PACKET. // FIXME: make compatible with alternate transports. bucketSize = Math.max(bucketSize, 2048); try { outputThrottle = new TokenBucket(bucketSize, SECONDS.toNanos(1) / obwLimit, obwLimit/2); } catch (IllegalArgumentException e) { throw new NodeInitException(NodeInitException.EXIT_BAD_BWLIMIT, e.getMessage()); } nodeConfig.register("inputBandwidthLimit", "-1", sortOrder++, false, true, "Node.inBWLimit", "Node.inBWLimitLong", new IntCallback() { @Override public Integer get() { if(inputLimitDefault) return -1; return inputBandwidthLimit; } @Override public void set(Integer ibwLimit) throws InvalidConfigValueException { synchronized(Node.this) { checkInputBandwidthLimit(ibwLimit); if(ibwLimit == -1) { inputLimitDefault = true; ibwLimit = outputBandwidthLimit * 4; } else { inputLimitDefault = false; } inputBandwidthLimit = ibwLimit; } } }); int ibwLimit = nodeConfig.getInt("inputBandwidthLimit"); if(ibwLimit == -1) { inputLimitDefault = true; ibwLimit = obwLimit * 4; } else if (ibwLimit < minimumBandwidth) { ibwLimit = minimumBandwidth; // upgrade slow nodes automatically Logger.normal(Node.class, "Input bandwidth was lower than minimum bandwidth. Increased to minimum bandwidth."); } inputBandwidthLimit = ibwLimit; try { checkInputBandwidthLimit(inputBandwidthLimit); } catch (InvalidConfigValueException e) { throw new NodeInitException(NodeInitException.EXIT_BAD_BWLIMIT, e.getMessage()); } nodeConfig.register("throttleLocalTraffic", false, sortOrder++, true, false, "Node.throttleLocalTraffic", "Node.throttleLocalTrafficLong", new BooleanCallback() { @Override public Boolean get() { return throttleLocalData; } @Override public void set(Boolean val) throws InvalidConfigValueException { throttleLocalData = val; } }); throttleLocalData = nodeConfig.getBoolean("throttleLocalTraffic"); String s = "Testnet mode DISABLED. You may have some level of anonymity. :)\n"+ "Note that this version of Freenet is still a very early alpha, and may well have numerous bugs and design flaws.\n"+ "In particular: YOU ARE WIDE OPEN TO YOUR IMMEDIATE PEERS! They can eavesdrop on your requests with relatively little difficulty at present (correlation attacks etc)."; Logger.normal(this, s); System.err.println(s); File nodeFile = nodeDir.file("node-"+getDarknetPortNumber()); File nodeFileBackup = nodeDir.file("node-"+getDarknetPortNumber()+".bak"); // After we have set up testnet and IP address, load the node file try { // FIXME should take file directly? readNodeFile(nodeFile.getPath()); } catch (IOException e) { try { System.err.println("Trying to read node file backup ..."); readNodeFile(nodeFileBackup.getPath()); } catch (IOException e1) { if(nodeFile.exists() || nodeFileBackup.exists()) { System.err.println("No node file or cannot read, (re)initialising crypto etc"); System.err.println(e1.toString()); e1.printStackTrace(); System.err.println("After:"); System.err.println(e.toString()); e.printStackTrace(); } else { System.err.println("Creating new cryptographic keys..."); } initNodeFileSettings(); } } // Then read the peers peers = new PeerManager(this, shutdownHook); tracker = new RequestTracker(peers, ticker); usm.setDispatcher(dispatcher=new NodeDispatcher(this)); uptime = new UptimeEstimator(runDir, ticker, darknetCrypto.identityHash); // ULPRs failureTable = new FailureTable(this); nodeStats = new NodeStats(this, sortOrder, config.createSubConfig("node.load"), obwLimit, ibwLimit, lastVersion); // clientCore needs new load management and other settings from stats. clientCore = new NodeClientCore(this, config, nodeConfig, installConfig, getDarknetPortNumber(), sortOrder, oldConfig, fproxyConfig, toadlets, databaseKey, persistentSecret); toadlets.setCore(clientCore); if (JVMVersion.isTooOld()) { clientCore.alerts.register(new JVMVersionAlert()); } if(showFriendsVisibilityAlert) registerFriendsVisibilityAlert(); // Node updater support System.out.println("Initializing Node Updater"); try { nodeUpdater = NodeUpdateManager.maybeCreate(this, config); } catch (InvalidConfigValueException e) { e.printStackTrace(); throw new NodeInitException(NodeInitException.EXIT_COULD_NOT_START_UPDATER, "Could not create Updater: "+e); } // Opennet final SubConfig opennetConfig = config.createSubConfig("node.opennet"); opennetConfig.register("connectToSeednodes", true, 0, true, false, "Node.withAnnouncement", "Node.withAnnouncementLong", new BooleanCallback() { @Override public Boolean get() { return isAllowedToConnectToSeednodes; } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { if (get().equals(val)) return; synchronized(Node.this) { isAllowedToConnectToSeednodes = val; if(opennet != null) throw new NodeNeedRestartException(l10n("connectToSeednodesCannotBeChangedMustDisableOpennetOrReboot")); } } }); isAllowedToConnectToSeednodes = opennetConfig.getBoolean("connectToSeednodes"); // Can be enabled on the fly opennetConfig.register("enabled", false, 0, true, true, "Node.opennetEnabled", "Node.opennetEnabledLong", new BooleanCallback() { @Override public Boolean get() { synchronized(Node.this) { return opennet != null; } } @Override public void set(Boolean val) throws InvalidConfigValueException { OpennetManager o; synchronized(Node.this) { if(val == (opennet != null)) return; if(val) { try { o = opennet = new OpennetManager(Node.this, opennetCryptoConfig, System.currentTimeMillis(), isAllowedToConnectToSeednodes); } catch (NodeInitException e) { opennet = null; throw new InvalidConfigValueException(e.getMessage()); } } else { o = opennet; opennet = null; } } if(val) o.start(); else o.stop(true); ipDetector.ipDetectorManager.notifyPortChange(getPublicInterfacePorts()); } }); boolean opennetEnabled = opennetConfig.getBoolean("enabled"); opennetConfig.register("maxOpennetPeers", OpennetManager.MAX_PEERS_FOR_SCALING, 1, true, false, "Node.maxOpennetPeers", "Node.maxOpennetPeersLong", new IntCallback() { @Override public Integer get() { return maxOpennetPeers; } @Override public void set(Integer inputMaxOpennetPeers) throws InvalidConfigValueException { if(inputMaxOpennetPeers < 0) throw new InvalidConfigValueException(l10n("mustBePositive")); if(inputMaxOpennetPeers > OpennetManager.MAX_PEERS_FOR_SCALING) throw new InvalidConfigValueException(l10n("maxOpennetPeersMustBeTwentyOrLess", "maxpeers", Integer.toString(OpennetManager.MAX_PEERS_FOR_SCALING))); maxOpennetPeers = inputMaxOpennetPeers; } } , false); maxOpennetPeers = opennetConfig.getInt("maxOpennetPeers"); if(maxOpennetPeers > OpennetManager.MAX_PEERS_FOR_SCALING) { Logger.error(this, "maxOpennetPeers may not be over "+OpennetManager.MAX_PEERS_FOR_SCALING); maxOpennetPeers = OpennetManager.MAX_PEERS_FOR_SCALING; } opennetCryptoConfig = new NodeCryptoConfig(opennetConfig, 2 /* 0 = enabled */, true, securityLevels); if(opennetEnabled) { opennet = new OpennetManager(this, opennetCryptoConfig, System.currentTimeMillis(), isAllowedToConnectToSeednodes); // Will be started later } else { opennet = null; } securityLevels.addNetworkThreatLevelListener(new SecurityLevelListener<NETWORK_THREAT_LEVEL>() { @Override public void onChange(NETWORK_THREAT_LEVEL oldLevel, NETWORK_THREAT_LEVEL newLevel) { if(newLevel == NETWORK_THREAT_LEVEL.HIGH || newLevel == NETWORK_THREAT_LEVEL.MAXIMUM) { OpennetManager om; synchronized(Node.this) { om = opennet; if(om != null) opennet = null; } if(om != null) { om.stop(true); ipDetector.ipDetectorManager.notifyPortChange(getPublicInterfacePorts()); } } else if(newLevel == NETWORK_THREAT_LEVEL.NORMAL || newLevel == NETWORK_THREAT_LEVEL.LOW) { OpennetManager o = null; synchronized(Node.this) { if(opennet == null) { try { o = opennet = new OpennetManager(Node.this, opennetCryptoConfig, System.currentTimeMillis(), isAllowedToConnectToSeednodes); } catch (NodeInitException e) { opennet = null; Logger.error(this, "UNABLE TO ENABLE OPENNET: "+e, e); clientCore.alerts.register(new SimpleUserAlert(false, l10n("enableOpennetFailedTitle"), l10n("enableOpennetFailed", "message", e.getLocalizedMessage()), l10n("enableOpennetFailed", "message", e.getLocalizedMessage()), UserAlert.ERROR)); } } } if(o != null) { o.start(); ipDetector.ipDetectorManager.notifyPortChange(getPublicInterfacePorts()); } } Node.this.config.store(); } }); opennetConfig.register("acceptSeedConnections", false, 2, true, true, "Node.acceptSeedConnectionsShort", "Node.acceptSeedConnections", new BooleanCallback() { @Override public Boolean get() { return acceptSeedConnections; } @Override public void set(Boolean val) throws InvalidConfigValueException { acceptSeedConnections = val; } }); acceptSeedConnections = opennetConfig.getBoolean("acceptSeedConnections"); if(acceptSeedConnections && opennet != null) opennet.crypto.socket.getAddressTracker().setHugeTracker(); opennetConfig.finishedInitialization(); nodeConfig.register("passOpennetPeersThroughDarknet", true, sortOrder++, true, false, "Node.passOpennetPeersThroughDarknet", "Node.passOpennetPeersThroughDarknetLong", new BooleanCallback() { @Override public Boolean get() { synchronized(Node.this) { return passOpennetRefsThroughDarknet; } } @Override public void set(Boolean val) throws InvalidConfigValueException { synchronized(Node.this) { passOpennetRefsThroughDarknet = val; } } }); passOpennetRefsThroughDarknet = nodeConfig.getBoolean("passOpennetPeersThroughDarknet"); this.extraPeerDataDir = userDir.file("extra-peer-data-"+getDarknetPortNumber()); if (!((extraPeerDataDir.exists() && extraPeerDataDir.isDirectory()) || (extraPeerDataDir.mkdir()))) { String msg = "Could not find or create extra peer data directory"; throw new NodeInitException(NodeInitException.EXIT_BAD_DIR, msg); } // Name nodeConfig.register("name", myName, sortOrder++, false, true, "Node.nodeName", "Node.nodeNameLong", new NodeNameCallback()); myName = nodeConfig.getString("name"); // Datastore nodeConfig.register("storeForceBigShrinks", false, sortOrder++, true, false, "Node.forceBigShrink", "Node.forceBigShrinkLong", new BooleanCallback() { @Override public Boolean get() { synchronized(Node.this) { return storeForceBigShrinks; } } @Override public void set(Boolean val) throws InvalidConfigValueException { synchronized(Node.this) { storeForceBigShrinks = val; } } }); // Datastore nodeConfig.register("storeType", "ram", sortOrder++, true, true, "Node.storeType", "Node.storeTypeLong", new StoreTypeCallback()); storeType = nodeConfig.getString("storeType"); /* * Very small initial store size, since the node will preallocate it when starting up for the first time, * BLOCKING STARTUP, and since everyone goes through the wizard anyway... */ nodeConfig.register("storeSize", DEFAULT_STORE_SIZE, sortOrder++, false, true, "Node.storeSize", "Node.storeSizeLong", new LongCallback() { @Override public Long get() { return maxTotalDatastoreSize; } @Override public void set(Long storeSize) throws InvalidConfigValueException { if(storeSize < MIN_STORE_SIZE) throw new InvalidConfigValueException(l10n("invalidStoreSize")); long newMaxStoreKeys = storeSize / sizePerKey; if(newMaxStoreKeys == maxTotalKeys) return; // Update each datastore synchronized(Node.this) { maxTotalDatastoreSize = storeSize; maxTotalKeys = newMaxStoreKeys; maxStoreKeys = maxTotalKeys / 2; maxCacheKeys = maxTotalKeys - maxStoreKeys; } try { chkDatastore.setMaxKeys(maxStoreKeys, storeForceBigShrinks); chkDatacache.setMaxKeys(maxCacheKeys, storeForceBigShrinks); pubKeyDatastore.setMaxKeys(maxStoreKeys, storeForceBigShrinks); pubKeyDatacache.setMaxKeys(maxCacheKeys, storeForceBigShrinks); sskDatastore.setMaxKeys(maxStoreKeys, storeForceBigShrinks); sskDatacache.setMaxKeys(maxCacheKeys, storeForceBigShrinks); } catch (IOException e) { // FIXME we need to be able to tell the user. Logger.error(this, "Caught "+e+" resizing the datastore", e); System.err.println("Caught "+e+" resizing the datastore"); e.printStackTrace(); } //Perhaps a bit hackish...? Seems like this should be near it's definition in NodeStats. nodeStats.avgStoreCHKLocation.changeMaxReports((int)maxStoreKeys); nodeStats.avgCacheCHKLocation.changeMaxReports((int)maxCacheKeys); nodeStats.avgSlashdotCacheCHKLocation.changeMaxReports((int)maxCacheKeys); nodeStats.avgClientCacheCHKLocation.changeMaxReports((int)maxCacheKeys); nodeStats.avgStoreSSKLocation.changeMaxReports((int)maxStoreKeys); nodeStats.avgCacheSSKLocation.changeMaxReports((int)maxCacheKeys); nodeStats.avgSlashdotCacheSSKLocation.changeMaxReports((int)maxCacheKeys); nodeStats.avgClientCacheSSKLocation.changeMaxReports((int)maxCacheKeys); } }, true); maxTotalDatastoreSize = nodeConfig.getLong("storeSize"); if(maxTotalDatastoreSize < MIN_STORE_SIZE && !storeType.equals("ram")) { // totally arbitrary minimum! throw new NodeInitException(NodeInitException.EXIT_INVALID_STORE_SIZE, "Store size too small"); } maxTotalKeys = maxTotalDatastoreSize / sizePerKey; nodeConfig.register("storeUseSlotFilters", true, sortOrder++, true, false, "Node.storeUseSlotFilters", "Node.storeUseSlotFiltersLong", new BooleanCallback() { public Boolean get() { synchronized(Node.this) { return storeUseSlotFilters; } } public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { synchronized(Node.this) { storeUseSlotFilters = val; } // FIXME l10n throw new NodeNeedRestartException("Need to restart to change storeUseSlotFilters"); } }); storeUseSlotFilters = nodeConfig.getBoolean("storeUseSlotFilters"); nodeConfig.register("storeSaltHashSlotFilterPersistenceTime", ResizablePersistentIntBuffer.DEFAULT_PERSISTENCE_TIME, sortOrder++, true, false, "Node.storeSaltHashSlotFilterPersistenceTime", "Node.storeSaltHashSlotFilterPersistenceTimeLong", new IntCallback() { @Override public Integer get() { return ResizablePersistentIntBuffer.getPersistenceTime(); } @Override public void set(Integer val) throws InvalidConfigValueException, NodeNeedRestartException { if(val >= -1) ResizablePersistentIntBuffer.setPersistenceTime(val); else throw new InvalidConfigValueException(l10n("slotFilterPersistenceTimeError")); } }, false); nodeConfig.register("storeSaltHashResizeOnStart", false, sortOrder++, true, false, "Node.storeSaltHashResizeOnStart", "Node.storeSaltHashResizeOnStartLong", new BooleanCallback() { @Override public Boolean get() { return storeSaltHashResizeOnStart; } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { storeSaltHashResizeOnStart = val; } }); storeSaltHashResizeOnStart = nodeConfig.getBoolean("storeSaltHashResizeOnStart"); this.storeDir = setupProgramDir(installConfig, "storeDir", userDir().file("datastore").getPath(), "Node.storeDirectory", "Node.storeDirectoryLong", nodeConfig); installConfig.finishedInitialization(); final String suffix = getStoreSuffix(); maxStoreKeys = maxTotalKeys / 2; maxCacheKeys = maxTotalKeys - maxStoreKeys; /* * On Windows, setting the file length normally involves writing lots of zeros. * So it's an uninterruptible system call that takes a loooong time. On OS/X, * presumably the same is true. If the RNG is fast enough, this means that * setting the length and writing random data take exactly the same amount * of time. On most versions of Unix, holes can be created. However on all * systems, predictable disk usage is a good thing. So lets turn it on by * default for now, on all systems. The datastore can be read but mostly not * written while the random data is being written. */ nodeConfig.register("storePreallocate", true, sortOrder++, true, true, "Node.storePreallocate", "Node.storePreallocateLong", new BooleanCallback() { @Override public Boolean get() { return storePreallocate; } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { storePreallocate = val; if (storeType.equals("salt-hash")) { setPreallocate(chkDatastore, val); setPreallocate(chkDatacache, val); setPreallocate(pubKeyDatastore, val); setPreallocate(pubKeyDatacache, val); setPreallocate(sskDatastore, val); setPreallocate(sskDatacache, val); } } private void setPreallocate(StoreCallback<?> datastore, boolean val) { // Avoid race conditions by checking first. FreenetStore<?> store = datastore.getStore(); if(store instanceof SaltedHashFreenetStore) ((SaltedHashFreenetStore<?>)store).setPreallocate(val); }} ); storePreallocate = nodeConfig.getBoolean("storePreallocate"); if(File.separatorChar == '/' && System.getProperty("os.name").toLowerCase().indexOf("mac os") < 0) { securityLevels.addPhysicalThreatLevelListener(new SecurityLevelListener<SecurityLevels.PHYSICAL_THREAT_LEVEL>() { @Override public void onChange(PHYSICAL_THREAT_LEVEL oldLevel, PHYSICAL_THREAT_LEVEL newLevel) { try { if(newLevel == PHYSICAL_THREAT_LEVEL.LOW) nodeConfig.set("storePreallocate", false); else nodeConfig.set("storePreallocate", true); } catch (NodeNeedRestartException e) { // Ignore } catch (InvalidConfigValueException e) { // Ignore } } }); } securityLevels.addPhysicalThreatLevelListener(new SecurityLevelListener<SecurityLevels.PHYSICAL_THREAT_LEVEL>() { @Override public void onChange(PHYSICAL_THREAT_LEVEL oldLevel, PHYSICAL_THREAT_LEVEL newLevel) { if(newLevel == PHYSICAL_THREAT_LEVEL.MAXIMUM) { synchronized(this) { clientCacheAwaitingPassword = false; databaseAwaitingPassword = false; } try { killMasterKeysFile(); clientCore.clientLayerPersister.disableWrite(); clientCore.clientLayerPersister.waitForNotWriting(); clientCore.clientLayerPersister.deleteAllFiles(); } catch (IOException e) { masterKeysFile.delete(); Logger.error(this, "Unable to securely delete "+masterKeysFile); System.err.println(NodeL10n.getBase().getString("SecurityLevels.cantDeletePasswordFile", "filename", masterKeysFile.getAbsolutePath())); clientCore.alerts.register(new SimpleUserAlert(true, NodeL10n.getBase().getString("SecurityLevels.cantDeletePasswordFileTitle"), NodeL10n.getBase().getString("SecurityLevels.cantDeletePasswordFile"), NodeL10n.getBase().getString("SecurityLevels.cantDeletePasswordFileTitle"), UserAlert.CRITICAL_ERROR)); } } if(oldLevel == PHYSICAL_THREAT_LEVEL.MAXIMUM && newLevel != PHYSICAL_THREAT_LEVEL.HIGH) { // Not passworded. // Create the master.keys. // Keys must exist. try { MasterKeys keys; synchronized(this) { keys = Node.this.keys; } keys.changePassword(masterKeysFile, "", secureRandom); } catch (IOException e) { Logger.error(this, "Unable to create encryption keys file: "+masterKeysFile+" : "+e, e); System.err.println("Unable to create encryption keys file: "+masterKeysFile+" : "+e); e.printStackTrace(); } } } }); if(securityLevels.physicalThreatLevel == PHYSICAL_THREAT_LEVEL.MAXIMUM) { try { killMasterKeysFile(); } catch (IOException e) { String msg = "Unable to securely delete old master.keys file when switching to MAXIMUM seclevel!!"; System.err.println(msg); throw new NodeInitException(NodeInitException.EXIT_CANT_WRITE_MASTER_KEYS, msg); } } long defaultCacheSize; long memoryLimit = NodeStarter.getMemoryLimitBytes(); // This is tricky because systems with low memory probably also have slow disks, but using // up too much memory can be catastrophic... // Total alchemy, FIXME! if(memoryLimit == Long.MAX_VALUE || memoryLimit < 0) defaultCacheSize = 1024*1024; else if(memoryLimit <= 128*1024*1024) defaultCacheSize = 0; // Turn off completely for very small memory. else { // 9 stores, total should be 5% of memory, up to maximum of 1MB per store at 308MB+ defaultCacheSize = Math.min(1024*1024, (memoryLimit - 128*1024*1024) / (20*9)); } nodeConfig.register("cachingFreenetStoreMaxSize", defaultCacheSize, sortOrder++, true, false, "Node.cachingFreenetStoreMaxSize", "Node.cachingFreenetStoreMaxSizeLong", new LongCallback() { @Override public Long get() { synchronized(Node.this) { return cachingFreenetStoreMaxSize; } } @Override public void set(Long val) throws InvalidConfigValueException, NodeNeedRestartException { if(val < 0) throw new InvalidConfigValueException(l10n("invalidMemoryCacheSize")); // Any positive value is legal. In particular, e.g. 1200 bytes would cause us to cache SSKs but not CHKs. synchronized(Node.this) { cachingFreenetStoreMaxSize = val; } throw new NodeNeedRestartException("Caching Maximum Size cannot be changed on the fly"); } }, true); cachingFreenetStoreMaxSize = nodeConfig.getLong("cachingFreenetStoreMaxSize"); if(cachingFreenetStoreMaxSize < 0) throw new NodeInitException(NodeInitException.EXIT_BAD_CONFIG, l10n("invalidMemoryCacheSize")); nodeConfig.register("cachingFreenetStorePeriod", "300k", sortOrder++, true, false, "Node.cachingFreenetStorePeriod", "Node.cachingFreenetStorePeriod", new LongCallback() { @Override public Long get() { synchronized(Node.this) { return cachingFreenetStorePeriod; } } @Override public void set(Long val) throws InvalidConfigValueException, NodeNeedRestartException { synchronized(Node.this) { cachingFreenetStorePeriod = val; } throw new NodeNeedRestartException("Caching Period cannot be changed on the fly"); } }, true); cachingFreenetStorePeriod = nodeConfig.getLong("cachingFreenetStorePeriod"); if(cachingFreenetStoreMaxSize > 0 && cachingFreenetStorePeriod > 0) { cachingFreenetStoreTracker = new CachingFreenetStoreTracker(cachingFreenetStoreMaxSize, cachingFreenetStorePeriod, ticker); } boolean shouldWriteConfig = false; if(storeType.equals("bdb-index")) { System.err.println("Old format Berkeley DB datastore detected."); System.err.println("This datastore format is no longer supported."); System.err.println("The old datastore will be securely deleted."); storeType = "salt-hash"; shouldWriteConfig = true; deleteOldBDBIndexStoreFiles(); } if (storeType.equals("salt-hash")) { initRAMFS(); initSaltHashFS(suffix, false, null); } else { initRAMFS(); } if(databaseAwaitingPassword) createPasswordUserAlert(); // Client cache // Default is 10MB, in memory only. The wizard will change this. nodeConfig.register("clientCacheType", "ram", sortOrder++, true, true, "Node.clientCacheType", "Node.clientCacheTypeLong", new ClientCacheTypeCallback()); clientCacheType = nodeConfig.getString("clientCacheType"); nodeConfig.register("clientCacheSize", DEFAULT_CLIENT_CACHE_SIZE, sortOrder++, false, true, "Node.clientCacheSize", "Node.clientCacheSizeLong", new LongCallback() { @Override public Long get() { return maxTotalClientCacheSize; } @Override public void set(Long storeSize) throws InvalidConfigValueException { if(storeSize < MIN_CLIENT_CACHE_SIZE) throw new InvalidConfigValueException(l10n("invalidStoreSize")); long newMaxStoreKeys = storeSize / sizePerKey; if(newMaxStoreKeys == maxClientCacheKeys) return; // Update each datastore synchronized(Node.this) { maxTotalClientCacheSize = storeSize; maxClientCacheKeys = newMaxStoreKeys; } try { chkClientcache.setMaxKeys(maxClientCacheKeys, storeForceBigShrinks); pubKeyClientcache.setMaxKeys(maxClientCacheKeys, storeForceBigShrinks); sskClientcache.setMaxKeys(maxClientCacheKeys, storeForceBigShrinks); } catch (IOException e) { // FIXME we need to be able to tell the user. Logger.error(this, "Caught "+e+" resizing the clientcache", e); System.err.println("Caught "+e+" resizing the clientcache"); e.printStackTrace(); } } }, true); maxTotalClientCacheSize = nodeConfig.getLong("clientCacheSize"); if(maxTotalClientCacheSize < MIN_CLIENT_CACHE_SIZE) { throw new NodeInitException(NodeInitException.EXIT_INVALID_STORE_SIZE, "Client cache size too small"); } maxClientCacheKeys = maxTotalClientCacheSize / sizePerKey; boolean startedClientCache = false; if (clientCacheType.equals("salt-hash")) { if(clientCacheKey == null) { System.err.println("Cannot open client-cache, it is passworded"); setClientCacheAwaitingPassword(); } else { initSaltHashClientCacheFS(suffix, false, clientCacheKey); startedClientCache = true; } } else if(clientCacheType.equals("none")) { initNoClientCacheFS(); startedClientCache = true; } else { // ram initRAMClientCacheFS(); startedClientCache = true; } if(!startedClientCache) initRAMClientCacheFS(); if(!clientCore.loadedDatabase() && databaseKey != null) { try { lateSetupDatabase(databaseKey); } catch (MasterKeysWrongPasswordException e2) { System.err.println("Impossible: "+e2); e2.printStackTrace(); } catch (MasterKeysFileSizeException e2) { System.err.println("Impossible: "+e2); e2.printStackTrace(); } catch (IOException e2) { System.err.println("Unable to load database: "+e2); e2.printStackTrace(); } } nodeConfig.register("useSlashdotCache", true, sortOrder++, true, false, "Node.useSlashdotCache", "Node.useSlashdotCacheLong", new BooleanCallback() { @Override public Boolean get() { return useSlashdotCache; } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { useSlashdotCache = val; } }); useSlashdotCache = nodeConfig.getBoolean("useSlashdotCache"); nodeConfig.register("writeLocalToDatastore", false, sortOrder++, true, false, "Node.writeLocalToDatastore", "Node.writeLocalToDatastoreLong", new BooleanCallback() { @Override public Boolean get() { return writeLocalToDatastore; } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { writeLocalToDatastore = val; } }); writeLocalToDatastore = nodeConfig.getBoolean("writeLocalToDatastore"); // LOW network *and* physical seclevel = writeLocalToDatastore securityLevels.addNetworkThreatLevelListener(new SecurityLevelListener<NETWORK_THREAT_LEVEL>() { @Override public void onChange(NETWORK_THREAT_LEVEL oldLevel, NETWORK_THREAT_LEVEL newLevel) { if(newLevel == NETWORK_THREAT_LEVEL.LOW && securityLevels.getPhysicalThreatLevel() == PHYSICAL_THREAT_LEVEL.LOW) writeLocalToDatastore = true; else writeLocalToDatastore = false; } }); securityLevels.addPhysicalThreatLevelListener(new SecurityLevelListener<PHYSICAL_THREAT_LEVEL>() { @Override public void onChange(PHYSICAL_THREAT_LEVEL oldLevel, PHYSICAL_THREAT_LEVEL newLevel) { if(newLevel == PHYSICAL_THREAT_LEVEL.LOW && securityLevels.getNetworkThreatLevel() == NETWORK_THREAT_LEVEL.LOW) writeLocalToDatastore = true; else writeLocalToDatastore = false; } }); nodeConfig.register("slashdotCacheLifetime", MINUTES.toMillis(30), sortOrder++, true, false, "Node.slashdotCacheLifetime", "Node.slashdotCacheLifetimeLong", new LongCallback() { @Override public Long get() { return chkSlashdotcacheStore.getLifetime(); } @Override public void set(Long val) throws InvalidConfigValueException, NodeNeedRestartException { if(val < 0) throw new InvalidConfigValueException("Must be positive!"); chkSlashdotcacheStore.setLifetime(val); pubKeySlashdotcacheStore.setLifetime(val); sskSlashdotcacheStore.setLifetime(val); } }, false); long slashdotCacheLifetime = nodeConfig.getLong("slashdotCacheLifetime"); nodeConfig.register("slashdotCacheSize", DEFAULT_SLASHDOT_CACHE_SIZE, sortOrder++, false, true, "Node.slashdotCacheSize", "Node.slashdotCacheSizeLong", new LongCallback() { @Override public Long get() { return maxSlashdotCacheSize; } @Override public void set(Long storeSize) throws InvalidConfigValueException { if(storeSize < MIN_SLASHDOT_CACHE_SIZE) throw new InvalidConfigValueException(l10n("invalidStoreSize")); int newMaxStoreKeys = (int) Math.min(storeSize / sizePerKey, Integer.MAX_VALUE); if(newMaxStoreKeys == maxSlashdotCacheKeys) return; // Update each datastore synchronized(Node.this) { maxSlashdotCacheSize = storeSize; maxSlashdotCacheKeys = newMaxStoreKeys; } try { chkSlashdotcache.setMaxKeys(maxSlashdotCacheKeys, storeForceBigShrinks); pubKeySlashdotcache.setMaxKeys(maxSlashdotCacheKeys, storeForceBigShrinks); sskSlashdotcache.setMaxKeys(maxSlashdotCacheKeys, storeForceBigShrinks); } catch (IOException e) { // FIXME we need to be able to tell the user. Logger.error(this, "Caught "+e+" resizing the slashdotcache", e); System.err.println("Caught "+e+" resizing the slashdotcache"); e.printStackTrace(); } } }, true); maxSlashdotCacheSize = nodeConfig.getLong("slashdotCacheSize"); if(maxSlashdotCacheSize < MIN_SLASHDOT_CACHE_SIZE) { throw new NodeInitException(NodeInitException.EXIT_INVALID_STORE_SIZE, "Slashdot cache size too small"); } maxSlashdotCacheKeys = (int) Math.min(maxSlashdotCacheSize / sizePerKey, Integer.MAX_VALUE); chkSlashdotcache = new CHKStore(); chkSlashdotcacheStore = new SlashdotStore<CHKBlock>(chkSlashdotcache, maxSlashdotCacheKeys, slashdotCacheLifetime, PURGE_INTERVAL, ticker, this.clientCore.tempBucketFactory); pubKeySlashdotcache = new PubkeyStore(); pubKeySlashdotcacheStore = new SlashdotStore<DSAPublicKey>(pubKeySlashdotcache, maxSlashdotCacheKeys, slashdotCacheLifetime, PURGE_INTERVAL, ticker, this.clientCore.tempBucketFactory); getPubKey.setLocalSlashdotcache(pubKeySlashdotcache); sskSlashdotcache = new SSKStore(getPubKey); sskSlashdotcacheStore = new SlashdotStore<SSKBlock>(sskSlashdotcache, maxSlashdotCacheKeys, slashdotCacheLifetime, PURGE_INTERVAL, ticker, this.clientCore.tempBucketFactory); // MAXIMUM seclevel = no slashdot cache. securityLevels.addNetworkThreatLevelListener(new SecurityLevelListener<NETWORK_THREAT_LEVEL>() { @Override public void onChange(NETWORK_THREAT_LEVEL oldLevel, NETWORK_THREAT_LEVEL newLevel) { if(newLevel == NETWORK_THREAT_LEVEL.MAXIMUM) useSlashdotCache = false; else if(oldLevel == NETWORK_THREAT_LEVEL.MAXIMUM) useSlashdotCache = true; } }); nodeConfig.register("skipWrapperWarning", false, sortOrder++, true, false, "Node.skipWrapperWarning", "Node.skipWrapperWarningLong", new BooleanCallback() { @Override public void set(Boolean value) throws InvalidConfigValueException, NodeNeedRestartException { skipWrapperWarning = value; } @Override public Boolean get() { return skipWrapperWarning; } }); skipWrapperWarning = nodeConfig.getBoolean("skipWrapperWarning"); nodeConfig.register("maxPacketSize", 1280, sortOrder++, true, true, "Node.maxPacketSize", "Node.maxPacketSizeLong", new IntCallback() { @Override public Integer get() { synchronized(Node.this) { return maxPacketSize; } } @Override public void set(Integer val) throws InvalidConfigValueException, NodeNeedRestartException { synchronized(Node.this) { if(val == maxPacketSize) return; if(val < UdpSocketHandler.MIN_MTU) throw new InvalidConfigValueException("Must be over 576"); if(val > 1492) throw new InvalidConfigValueException("Larger than ethernet frame size unlikely to work!"); maxPacketSize = val; } updateMTU(); } }, true); maxPacketSize = nodeConfig.getInt("maxPacketSize"); nodeConfig.register("enableRoutedPing", false, sortOrder++, true, false, "Node.enableRoutedPing", "Node.enableRoutedPingLong", new BooleanCallback() { @Override public Boolean get() { synchronized(Node.this) { return enableRoutedPing; } } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { synchronized(Node.this) { enableRoutedPing = val; } } }); enableRoutedPing = nodeConfig.getBoolean("enableRoutedPing"); updateMTU(); /* Take care that no configuration options are registered after this point; they will not persist * between restarts. */ nodeConfig.finishedInitialization(); if(shouldWriteConfig) config.store(); writeNodeFile(); // Initialize the plugin manager Logger.normal(this, "Initializing Plugin Manager"); System.out.println("Initializing Plugin Manager"); pluginManager = new PluginManager(this, lastVersion); shutdownHook.addEarlyJob(new NativeThread("Shutdown plugins", NativeThread.HIGH_PRIORITY, true) { @Override public void realRun() { pluginManager.stop(SECONDS.toMillis(30)); // FIXME make it configurable?? } }); // FIXME // Short timeouts and JVM timeouts with nothing more said than the above have been seen... // I don't know why... need a stack dump... // For now just give it an extra 2 minutes. If it doesn't start in that time, // it's likely (on reports so far) that a restart will fix it. // And we have to get a build out because ALL plugins are now failing to load, // including the absolutely essential (for most nodes) JSTUN and UPnP. WrapperManager.signalStarting((int) MINUTES.toMillis(2)); FetchContext ctx = clientCore.makeClient((short)0, true, false).getFetchContext(); ctx.allowSplitfiles = false; ctx.dontEnterImplicitArchives = true; ctx.maxArchiveRestarts = 0; ctx.maxMetadataSize = 256; ctx.maxNonSplitfileRetries = 10; ctx.maxOutputLength = 4096; ctx.maxRecursionLevel = 2; ctx.maxTempLength = 4096; this.arkFetcherContext = ctx; registerNodeToNodeMessageListener(N2N_MESSAGE_TYPE_FPROXY, fproxyN2NMListener); registerNodeToNodeMessageListener(Node.N2N_MESSAGE_TYPE_DIFFNODEREF, diffNoderefListener); // FIXME this is a hack // toadlet server should start after all initialized // see NodeClientCore line 437 if (toadlets.isEnabled()) { toadlets.finishStart(); toadlets.createFproxy(); toadlets.removeStartupToadlet(); } Logger.normal(this, "Node constructor completed"); System.out.println("Node constructor completed"); } /** Delete files from old BDB-index datastore. */ private void deleteOldBDBIndexStoreFiles() { File dbDir = storeDir.file("database-"+getDarknetPortNumber()); FileUtil.removeAll(dbDir); File dir = storeDir.dir(); File[] list = dir.listFiles(); for(File f : list) { String name = f.getName(); if(f.isFile() && name.toLowerCase().matches("((chk)|(ssk)|(pubkey))-[0-9]*\\.((store)|(cache))(\\.((keys)|(lru)))?")) { System.out.println("Deleting old datastore file \""+f+"\""); try { FileUtil.secureDelete(f); } catch (IOException e) { System.err.println("Failed to delete old datastore file \""+f+"\": "+e); e.printStackTrace(); } } } } private void fixCertsFiles() { // Hack to update certificates file to fix update.cmd // startssl.pem: Might be useful for old versions of update.sh too? File certs = new File(PluginDownLoaderOfficialHTTPS.certfileOld); fixCertsFile(certs); if(FileUtil.detectedOS.isWindows) { // updater\startssl.pem: Needed for Windows update.cmd. certs = new File("updater", PluginDownLoaderOfficialHTTPS.certfileOld); fixCertsFile(certs); } } private void fixCertsFile(File certs) { long oldLength = certs.exists() ? certs.length() : -1; try { File tmpFile = File.createTempFile(PluginDownLoaderOfficialHTTPS.certfileOld, ".tmp", new File(".")); PluginDownLoaderOfficialHTTPS.writeCertsTo(tmpFile); if(FileUtil.renameTo(tmpFile, certs)) { long newLength = certs.length(); if(newLength != oldLength) System.err.println("Updated "+certs+" so that update scripts will work"); } else { if(certs.length() != tmpFile.length()) { System.err.println("Cannot update "+certs+" : last-resort update scripts (in particular update.cmd on Windows) may not work"); File manual = new File(PluginDownLoaderOfficialHTTPS.certfileOld+".new"); manual.delete(); if(tmpFile.renameTo(manual)) System.err.println("Please delete "+certs+" and rename "+manual+" over it"); else tmpFile.delete(); } } } catch (IOException e) { } } /** ** Sets up a program directory using the config value defined by the given ** parameters. */ public ProgramDirectory setupProgramDir(SubConfig installConfig, String cfgKey, String defaultValue, String shortdesc, String longdesc, String moveErrMsg, SubConfig oldConfig) throws NodeInitException { ProgramDirectory dir = new ProgramDirectory(moveErrMsg); int sortOrder = ProgramDirectory.nextOrder(); // forceWrite=true because currently it can't be changed on the fly, also for packages installConfig.register(cfgKey, defaultValue, sortOrder, true, true, shortdesc, longdesc, dir.getStringCallback()); String dirName = installConfig.getString(cfgKey); try { dir.move(dirName); } catch (IOException e) { throw new NodeInitException(NodeInitException.EXIT_BAD_DIR, "could not set up directory: " + longdesc); } return dir; } protected ProgramDirectory setupProgramDir(SubConfig installConfig, String cfgKey, String defaultValue, String shortdesc, String longdesc, SubConfig oldConfig) throws NodeInitException { return setupProgramDir(installConfig, cfgKey, defaultValue, shortdesc, longdesc, null, oldConfig); } public void lateSetupDatabase(DatabaseKey databaseKey) throws MasterKeysWrongPasswordException, MasterKeysFileSizeException, IOException { if(clientCore.loadedDatabase()) return; System.out.println("Starting late database initialisation"); try { if(!clientCore.lateInitDatabase(databaseKey)) failLateInitDatabase(); } catch (NodeInitException e) { failLateInitDatabase(); } } private void failLateInitDatabase() { System.err.println("Failed late initialisation of database, closing..."); } public void killMasterKeysFile() throws IOException { MasterKeys.killMasterKeys(masterKeysFile); } private void setClientCacheAwaitingPassword() { createPasswordUserAlert(); synchronized(this) { clientCacheAwaitingPassword = true; } } /** Called when the client layer needs the decryption password. */ void setDatabaseAwaitingPassword() { synchronized(this) { databaseAwaitingPassword = true; } } private final UserAlert masterPasswordUserAlert = new UserAlert() { final long creationTime = System.currentTimeMillis(); @Override public String anchor() { return "password"; } @Override public String dismissButtonText() { return null; } @Override public long getUpdatedTime() { return creationTime; } @Override public FCPMessage getFCPMessage() { return new FeedMessage(getTitle(), getShortText(), getText(), getPriorityClass(), getUpdatedTime()); } @Override public HTMLNode getHTMLText() { HTMLNode content = new HTMLNode("div"); SecurityLevelsToadlet.generatePasswordFormPage(false, clientCore.getToadletContainer(), content, false, false, false, null, null); return content; } @Override public short getPriorityClass() { return UserAlert.ERROR; } @Override public String getShortText() { return NodeL10n.getBase().getString("SecurityLevels.enterPassword"); } @Override public String getText() { return NodeL10n.getBase().getString("SecurityLevels.enterPassword"); } @Override public String getTitle() { return NodeL10n.getBase().getString("SecurityLevels.enterPassword"); } @Override public boolean isEventNotification() { return false; } @Override public boolean isValid() { synchronized(Node.this) { return clientCacheAwaitingPassword || databaseAwaitingPassword; } } @Override public void isValid(boolean validity) { // Ignore } @Override public void onDismiss() { // Ignore } @Override public boolean shouldUnregisterOnDismiss() { return false; } @Override public boolean userCanDismiss() { return false; } }; private void createPasswordUserAlert() { this.clientCore.alerts.register(masterPasswordUserAlert); } private void initRAMClientCacheFS() { chkClientcache = new CHKStore(); new RAMFreenetStore<CHKBlock>(chkClientcache, (int) Math.min(Integer.MAX_VALUE, maxClientCacheKeys)); pubKeyClientcache = new PubkeyStore(); new RAMFreenetStore<DSAPublicKey>(pubKeyClientcache, (int) Math.min(Integer.MAX_VALUE, maxClientCacheKeys)); sskClientcache = new SSKStore(getPubKey); new RAMFreenetStore<SSKBlock>(sskClientcache, (int) Math.min(Integer.MAX_VALUE, maxClientCacheKeys)); } private void initNoClientCacheFS() { chkClientcache = new CHKStore(); new NullFreenetStore<CHKBlock>(chkClientcache); pubKeyClientcache = new PubkeyStore(); new NullFreenetStore<DSAPublicKey>(pubKeyClientcache); sskClientcache = new SSKStore(getPubKey); new NullFreenetStore<SSKBlock>(sskClientcache); } private String getStoreSuffix() { return "-" + getDarknetPortNumber(); } private void finishInitSaltHashFS(final String suffix, NodeClientCore clientCore) { if(clientCore.alerts == null) throw new NullPointerException(); chkDatastore.getStore().setUserAlertManager(clientCore.alerts); chkDatacache.getStore().setUserAlertManager(clientCore.alerts); pubKeyDatastore.getStore().setUserAlertManager(clientCore.alerts); pubKeyDatacache.getStore().setUserAlertManager(clientCore.alerts); sskDatastore.getStore().setUserAlertManager(clientCore.alerts); sskDatacache.getStore().setUserAlertManager(clientCore.alerts); } private void initRAMFS() { chkDatastore = new CHKStore(); new RAMFreenetStore<CHKBlock>(chkDatastore, (int) Math.min(Integer.MAX_VALUE, maxStoreKeys)); chkDatacache = new CHKStore(); new RAMFreenetStore<CHKBlock>(chkDatacache, (int) Math.min(Integer.MAX_VALUE, maxCacheKeys)); pubKeyDatastore = new PubkeyStore(); new RAMFreenetStore<DSAPublicKey>(pubKeyDatastore, (int) Math.min(Integer.MAX_VALUE, maxStoreKeys)); pubKeyDatacache = new PubkeyStore(); getPubKey.setDataStore(pubKeyDatastore, pubKeyDatacache); new RAMFreenetStore<DSAPublicKey>(pubKeyDatacache, (int) Math.min(Integer.MAX_VALUE, maxCacheKeys)); sskDatastore = new SSKStore(getPubKey); new RAMFreenetStore<SSKBlock>(sskDatastore, (int) Math.min(Integer.MAX_VALUE, maxStoreKeys)); sskDatacache = new SSKStore(getPubKey); new RAMFreenetStore<SSKBlock>(sskDatacache, (int) Math.min(Integer.MAX_VALUE, maxCacheKeys)); } private long cachingFreenetStoreMaxSize; private long cachingFreenetStorePeriod; private CachingFreenetStoreTracker cachingFreenetStoreTracker; private void initSaltHashFS(final String suffix, boolean dontResizeOnStart, byte[] masterKey) throws NodeInitException { try { final CHKStore chkDatastore = new CHKStore(); final FreenetStore<CHKBlock> chkDataFS = makeStore("CHK", true, chkDatastore, dontResizeOnStart, masterKey); final CHKStore chkDatacache = new CHKStore(); final FreenetStore<CHKBlock> chkCacheFS = makeStore("CHK", false, chkDatacache, dontResizeOnStart, masterKey); ((SaltedHashFreenetStore<CHKBlock>) chkCacheFS.getUnderlyingStore()).setAltStore(((SaltedHashFreenetStore<CHKBlock>) chkDataFS.getUnderlyingStore())); final PubkeyStore pubKeyDatastore = new PubkeyStore(); final FreenetStore<DSAPublicKey> pubkeyDataFS = makeStore("PUBKEY", true, pubKeyDatastore, dontResizeOnStart, masterKey); final PubkeyStore pubKeyDatacache = new PubkeyStore(); final FreenetStore<DSAPublicKey> pubkeyCacheFS = makeStore("PUBKEY", false, pubKeyDatacache, dontResizeOnStart, masterKey); ((SaltedHashFreenetStore<DSAPublicKey>) pubkeyCacheFS.getUnderlyingStore()).setAltStore(((SaltedHashFreenetStore<DSAPublicKey>) pubkeyDataFS.getUnderlyingStore())); final SSKStore sskDatastore = new SSKStore(getPubKey); final FreenetStore<SSKBlock> sskDataFS = makeStore("SSK", true, sskDatastore, dontResizeOnStart, masterKey); final SSKStore sskDatacache = new SSKStore(getPubKey); final FreenetStore<SSKBlock> sskCacheFS = makeStore("SSK", false, sskDatacache, dontResizeOnStart, masterKey); ((SaltedHashFreenetStore<SSKBlock>) sskCacheFS.getUnderlyingStore()).setAltStore(((SaltedHashFreenetStore<SSKBlock>) sskDataFS.getUnderlyingStore())); boolean delay = chkDataFS.start(ticker, false) | chkCacheFS.start(ticker, false) | pubkeyDataFS.start(ticker, false) | pubkeyCacheFS.start(ticker, false) | sskDataFS.start(ticker, false) | sskCacheFS.start(ticker, false); if(delay) { System.err.println("Delayed init of datastore"); initRAMFS(); final Runnable migrate = new MigrateOldStoreData(false); this.getTicker().queueTimedJob(new Runnable() { @Override public void run() { System.err.println("Starting delayed init of datastore"); try { chkDataFS.start(ticker, true); chkCacheFS.start(ticker, true); pubkeyDataFS.start(ticker, true); pubkeyCacheFS.start(ticker, true); sskDataFS.start(ticker, true); sskCacheFS.start(ticker, true); } catch (IOException e) { Logger.error(this, "Failed to start datastore: "+e, e); System.err.println("Failed to start datastore: "+e); e.printStackTrace(); return; } Node.this.chkDatastore = chkDatastore; Node.this.chkDatacache = chkDatacache; Node.this.pubKeyDatastore = pubKeyDatastore; Node.this.pubKeyDatacache = pubKeyDatacache; getPubKey.setDataStore(pubKeyDatastore, pubKeyDatacache); Node.this.sskDatastore = sskDatastore; Node.this.sskDatacache = sskDatacache; finishInitSaltHashFS(suffix, clientCore); System.err.println("Finishing delayed init of datastore"); migrate.run(); } }, "Start store", 0, true, false); // Use Ticker to guarantee that this runs *after* constructors have completed. } else { Node.this.chkDatastore = chkDatastore; Node.this.chkDatacache = chkDatacache; Node.this.pubKeyDatastore = pubKeyDatastore; Node.this.pubKeyDatacache = pubKeyDatacache; getPubKey.setDataStore(pubKeyDatastore, pubKeyDatacache); Node.this.sskDatastore = sskDatastore; Node.this.sskDatacache = sskDatacache; this.getTicker().queueTimedJob(new Runnable() { @Override public void run() { Node.this.chkDatastore = chkDatastore; Node.this.chkDatacache = chkDatacache; Node.this.pubKeyDatastore = pubKeyDatastore; Node.this.pubKeyDatacache = pubKeyDatacache; getPubKey.setDataStore(pubKeyDatastore, pubKeyDatacache); Node.this.sskDatastore = sskDatastore; Node.this.sskDatacache = sskDatacache; finishInitSaltHashFS(suffix, clientCore); } }, "Start store", 0, true, false); } } catch (IOException e) { System.err.println("Could not open store: " + e); e.printStackTrace(); throw new NodeInitException(NodeInitException.EXIT_STORE_OTHER, e.getMessage()); } } private void initSaltHashClientCacheFS(final String suffix, boolean dontResizeOnStart, byte[] clientCacheMasterKey) throws NodeInitException { try { final CHKStore chkClientcache = new CHKStore(); final FreenetStore<CHKBlock> chkDataFS = makeClientcache("CHK", true, chkClientcache, dontResizeOnStart, clientCacheMasterKey); final PubkeyStore pubKeyClientcache = new PubkeyStore(); final FreenetStore<DSAPublicKey> pubkeyDataFS = makeClientcache("PUBKEY", true, pubKeyClientcache, dontResizeOnStart, clientCacheMasterKey); final SSKStore sskClientcache = new SSKStore(getPubKey); final FreenetStore<SSKBlock> sskDataFS = makeClientcache("SSK", true, sskClientcache, dontResizeOnStart, clientCacheMasterKey); boolean delay = chkDataFS.start(ticker, false) | pubkeyDataFS.start(ticker, false) | sskDataFS.start(ticker, false); if(delay) { System.err.println("Delayed init of client-cache"); initRAMClientCacheFS(); final Runnable migrate = new MigrateOldStoreData(true); getTicker().queueTimedJob(new Runnable() { @Override public void run() { System.err.println("Starting delayed init of client-cache"); try { chkDataFS.start(ticker, true); pubkeyDataFS.start(ticker, true); sskDataFS.start(ticker, true); } catch (IOException e) { Logger.error(this, "Failed to start client-cache: "+e, e); System.err.println("Failed to start client-cache: "+e); e.printStackTrace(); return; } Node.this.chkClientcache = chkClientcache; Node.this.pubKeyClientcache = pubKeyClientcache; getPubKey.setLocalDataStore(pubKeyClientcache); Node.this.sskClientcache = sskClientcache; System.err.println("Finishing delayed init of client-cache"); migrate.run(); } }, "Migrate store", 0, true, false); } else { Node.this.chkClientcache = chkClientcache; Node.this.pubKeyClientcache = pubKeyClientcache; getPubKey.setLocalDataStore(pubKeyClientcache); Node.this.sskClientcache = sskClientcache; } } catch (IOException e) { System.err.println("Could not open store: " + e); e.printStackTrace(); throw new NodeInitException(NodeInitException.EXIT_STORE_OTHER, e.getMessage()); } } private <T extends StorableBlock> FreenetStore<T> makeClientcache(String type, boolean isStore, StoreCallback<T> cb, boolean dontResizeOnStart, byte[] clientCacheMasterKey) throws IOException { FreenetStore<T> store = makeStore(type, "clientcache", maxClientCacheKeys, cb, dontResizeOnStart, clientCacheMasterKey); return store; } private <T extends StorableBlock> FreenetStore<T> makeStore(String type, boolean isStore, StoreCallback<T> cb, boolean dontResizeOnStart, byte[] clientCacheMasterKey) throws IOException { String store = isStore ? "store" : "cache"; long maxKeys = isStore ? maxStoreKeys : maxCacheKeys; return makeStore(type, store, maxKeys, cb, dontResizeOnStart, clientCacheMasterKey); } private <T extends StorableBlock> FreenetStore<T> makeStore(String type, String store, long maxKeys, StoreCallback<T> cb, boolean lateStart, byte[] clientCacheMasterKey) throws IOException { Logger.normal(this, "Initializing "+type+" Data"+store); System.out.println("Initializing "+type+" Data"+store+" (" + maxStoreKeys + " keys)"); SaltedHashFreenetStore<T> fs = SaltedHashFreenetStore.<T>construct(getStoreDir(), type+"-"+store, cb, random, maxKeys, storeUseSlotFilters, shutdownHook, storePreallocate, storeSaltHashResizeOnStart && !lateStart, lateStart ? ticker : null, clientCacheMasterKey); cb.setStore(fs); if(cachingFreenetStoreMaxSize > 0) return new CachingFreenetStore<T>(cb, fs, cachingFreenetStoreTracker); else return fs; } public void start(boolean noSwaps) throws NodeInitException { // IMPORTANT: Read the peers only after we have finished initializing Node. // Peer constructors are complex and can call methods on Node. peers.tryReadPeers(nodeDir.file("peers-"+getDarknetPortNumber()).getPath(), darknetCrypto, null, false, false); peers.updatePMUserAlert(); dispatcher.start(nodeStats); // must be before usm dnsr.start(); peers.start(); // must be before usm nodeStats.start(); uptime.start(); failureTable.start(); darknetCrypto.start(); if(opennet != null) opennet.start(); ps.start(nodeStats); ticker.start(); scheduleVersionTransition(); usm.start(ticker); if(isUsingWrapper()) { Logger.normal(this, "Using wrapper correctly: "+nodeStarter); System.out.println("Using wrapper correctly: "+nodeStarter); } else { Logger.error(this, "NOT using wrapper (at least not correctly). Your freenet-ext.jar <http://downloads.freenetproject.org/alpha/freenet-ext.jar> and/or wrapper.conf <https://emu.freenetproject.org/svn/trunk/apps/installer/installclasspath/config/wrapper.conf> need to be updated."); System.out.println("NOT using wrapper (at least not correctly). Your freenet-ext.jar <http://downloads.freenetproject.org/alpha/freenet-ext.jar> and/or wrapper.conf <https://emu.freenetproject.org/svn/trunk/apps/installer/installclasspath/config/wrapper.conf> need to be updated."); } Logger.normal(this, "Freenet 0.7.5 Build #"+Version.buildNumber()+" r"+Version.cvsRevision()); System.out.println("Freenet 0.7.5 Build #"+Version.buildNumber()+" r"+Version.cvsRevision()); Logger.normal(this, "FNP port is on "+darknetCrypto.getBindTo()+ ':' +getDarknetPortNumber()); System.out.println("FNP port is on "+darknetCrypto.getBindTo()+ ':' +getDarknetPortNumber()); // Start services // SubConfig pluginManagerConfig = new SubConfig("pluginmanager3", config); // pluginManager3 = new freenet.plugin_new.PluginManager(pluginManagerConfig); ipDetector.start(); // Start sending swaps lm.start(); // Node Updater try{ Logger.normal(this, "Starting the node updater"); nodeUpdater.start(); }catch (Exception e) { e.printStackTrace(); throw new NodeInitException(NodeInitException.EXIT_COULD_NOT_START_UPDATER, "Could not start Updater: "+e); } /* TODO: Make sure that this is called BEFORE any instances of HTTPFilter are created. * HTTPFilter uses checkForGCJCharConversionBug() which returns the value of the static * variable jvmHasGCJCharConversionBug - and this is initialized in the following function. * If this is not possible then create a separate function to check for the GCJ bug and * call this function earlier. */ checkForEvilJVMBugs(); if(!NativeThread.HAS_ENOUGH_NICE_LEVELS) clientCore.alerts.register(new NotEnoughNiceLevelsUserAlert()); this.clientCore.start(config); tracker.startDeadUIDChecker(); // After everything has been created, write the config file back to disk. if(config instanceof FreenetFilePersistentConfig) { FreenetFilePersistentConfig cfg = (FreenetFilePersistentConfig) config; cfg.finishedInit(this.ticker); cfg.setHasNodeStarted(); } config.store(); // Process any data in the extra peer data directory peers.readExtraPeerData(); Logger.normal(this, "Started node"); hasStarted = true; } private void scheduleVersionTransition() { long now = System.currentTimeMillis(); long transition = Version.transitionTime(); if(now < transition) ticker.queueTimedJob(new Runnable() { @Override public void run() { freenet.support.Logger.OSThread.logPID(this); for(PeerNode pn: peers.myPeers()) { pn.updateVersionRoutablity(); } } }, transition - now); } private static boolean jvmHasGCJCharConversionBug=false; private void checkForEvilJVMBugs() { // Now check whether we are likely to get the EvilJVMBug. // If we are running a Sun/Oracle or Blackdown JVM, on Linux, and LD_ASSUME_KERNEL is not set, then we are. String jvmVendor = System.getProperty("java.vm.vendor"); String jvmSpecVendor = System.getProperty("java.specification.vendor",""); String javaVersion = System.getProperty("java.version"); String jvmName = System.getProperty("java.vm.name"); String osName = System.getProperty("os.name"); String osVersion = System.getProperty("os.version"); boolean isOpenJDK = false; //boolean isOracle = false; if(logMINOR) Logger.minor(this, "JVM vendor: "+jvmVendor+", JVM name: "+jvmName+", JVM version: "+javaVersion+", OS name: "+osName+", OS version: "+osVersion); if(jvmName.startsWith("OpenJDK ")) { isOpenJDK = true; } //Add some checks for "Oracle" to futureproof against them renaming from "Sun". //Should have no effect because if a user has downloaded a new enough file for Oracle to have changed the name these bugs shouldn't apply. //Still, one never knows and this code might be extended to cover future bugs. if((!isOpenJDK) && (jvmVendor.startsWith("Sun ") || jvmVendor.startsWith("Oracle ")) || (jvmVendor.startsWith("The FreeBSD Foundation") && (jvmSpecVendor.startsWith("Sun ") || jvmSpecVendor.startsWith("Oracle "))) || (jvmVendor.startsWith("Apple "))) { //isOracle = true; // Sun/Oracle bugs // Spurious OOMs // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4855795 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=2138757 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=2138759 // Fixed in 1.5.0_10 and 1.4.2_13 boolean is150 = javaVersion.startsWith("1.5.0_"); boolean is160 = javaVersion.startsWith("1.6.0_"); if(is150 || is160) { String[] split = javaVersion.split("_"); String secondPart = split[1]; if(secondPart.indexOf("-") != -1) { split = secondPart.split("-"); secondPart = split[0]; } int subver = Integer.parseInt(secondPart); Logger.minor(this, "JVM version: "+javaVersion+" subver: "+subver+" from "+secondPart); } } else if (jvmVendor.startsWith("Apple ") || jvmVendor.startsWith("\"Apple ")) { //Note that Sun/Oracle does not produce VMs for the Macintosh operating system, dont ask the user to find one... } else if(!isOpenJDK) { if(jvmVendor.startsWith("Free Software Foundation")) { // GCJ/GIJ. try { javaVersion = System.getProperty("java.version").split(" ")[0].replaceAll("[.]",""); int jvmVersionInt = Integer.parseInt(javaVersion); if(jvmVersionInt <= 422 && jvmVersionInt >= 100) // make sure that no bogus values cause true jvmHasGCJCharConversionBug=true; } catch(Throwable t) { Logger.error(this, "GCJ version check is broken!", t); } clientCore.alerts.register(new SimpleUserAlert(true, l10n("usingGCJTitle"), l10n("usingGCJ"), l10n("usingGCJTitle"), UserAlert.WARNING)); } } if(!isUsingWrapper() && !skipWrapperWarning) { clientCore.alerts.register(new SimpleUserAlert(true, l10n("notUsingWrapperTitle"), l10n("notUsingWrapper"), l10n("notUsingWrapperShort"), UserAlert.WARNING)); } // Unfortunately debian's version of OpenJDK appears to have segfaulting issues. // Which presumably are exploitable. // So we can't recommend people switch just yet. :( // if(isOracle && Rijndael.AesCtrProvider == null) { // if(!(FileUtil.detectedOS == FileUtil.OperatingSystem.Windows || FileUtil.detectedOS == FileUtil.OperatingSystem.MacOS)) // clientCore.alerts.register(new SimpleUserAlert(true, l10n("usingOracleTitle"), l10n("usingOracle"), l10n("usingOracleTitle"), UserAlert.WARNING)); // } } public static boolean checkForGCJCharConversionBug() { return jvmHasGCJCharConversionBug; // should be initialized on early startup } private String l10n(String key) { return NodeL10n.getBase().getString("Node."+key); } private String l10n(String key, String pattern, String value) { return NodeL10n.getBase().getString("Node."+key, pattern, value); } private String l10n(String key, String[] pattern, String[] value) { return NodeL10n.getBase().getString("Node."+key, pattern, value); } /** * Export volatile data about the node as a SimpleFieldSet */ public SimpleFieldSet exportVolatileFieldSet() { return nodeStats.exportVolatileFieldSet(); } /** * Do a routed ping of another node on the network by its location. * @param loc2 The location of the other node to ping. It must match * exactly. * @param pubKeyHash The hash of the pubkey of the target node. We match * by location; this is just a shortcut if we get close. * @return The number of hops it took to find the node, if it was found. * Otherwise -1. */ public int routedPing(double loc2, byte[] pubKeyHash) { long uid = random.nextLong(); int initialX = random.nextInt(); Message m = DMT.createFNPRoutedPing(uid, loc2, maxHTL, initialX, pubKeyHash); Logger.normal(this, "Message: "+m); dispatcher.handleRouted(m, null); // FIXME: might be rejected MessageFilter mf1 = MessageFilter.create().setField(DMT.UID, uid).setType(DMT.FNPRoutedPong).setTimeout(5000); try { //MessageFilter mf2 = MessageFilter.create().setField(DMT.UID, uid).setType(DMT.FNPRoutedRejected).setTimeout(5000); // Ignore Rejected - let it be retried on other peers m = usm.waitFor(mf1/*.or(mf2)*/, null); } catch (DisconnectedException e) { Logger.normal(this, "Disconnected in waiting for pong"); return -1; } if(m == null) return -1; if(m.getSpec() == DMT.FNPRoutedRejected) return -1; return m.getInt(DMT.COUNTER) - initialX; } /** * Look for a block in the datastore, as part of a request. * @param key The key to fetch. * @param uid The UID of the request (for logging only). * @param promoteCache Whether to promote the key if found. * @param canReadClientCache If the request is local, we can read the client cache. * @param canWriteClientCache If the request is local, and the client hasn't turned off * writing to the client cache, we can write to the client cache. * @param canWriteDatastore If the request HTL is too high, including if it is local, we * cannot write to the datastore. * @return A KeyBlock for the key requested or null. */ private KeyBlock makeRequestLocal(Key key, long uid, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore, boolean offersOnly) { KeyBlock kb = null; if (key instanceof NodeCHK) { kb = fetch(key, false, canReadClientCache, canWriteClientCache, canWriteDatastore, null); } else if (key instanceof NodeSSK) { NodeSSK sskKey = (NodeSSK) key; DSAPublicKey pubKey = sskKey.getPubKey(); if (pubKey == null) { pubKey = getPubKey.getKey(sskKey.getPubKeyHash(), canReadClientCache, offersOnly, null); if (logMINOR) Logger.minor(this, "Fetched pubkey: " + pubKey); try { sskKey.setPubKey(pubKey); } catch (SSKVerifyException e) { Logger.error(this, "Error setting pubkey: " + e, e); } } if (pubKey != null) { if (logMINOR) Logger.minor(this, "Got pubkey: " + pubKey); kb = fetch(sskKey, canReadClientCache, canWriteClientCache, canWriteDatastore, false, null); } else { if (logMINOR) Logger.minor(this, "Not found because no pubkey: " + uid); } } else throw new IllegalStateException("Unknown key type: " + key.getClass()); if (kb != null) { // Probably somebody waiting for it. Trip it. if (clientCore != null && clientCore.requestStarters != null) { if (kb instanceof CHKBlock) { clientCore.requestStarters.chkFetchSchedulerBulk.tripPendingKey(kb); clientCore.requestStarters.chkFetchSchedulerRT.tripPendingKey(kb); } else { clientCore.requestStarters.sskFetchSchedulerBulk.tripPendingKey(kb); clientCore.requestStarters.sskFetchSchedulerRT.tripPendingKey(kb); } } failureTable.onFound(kb); return kb; } return null; } /** * Check the datastore, then if the key is not in the store, * check whether another node is requesting the same key at * the same HTL, and if all else fails, create a new * RequestSender for the key/htl. * @param closestLocation The closest location to the key so far. * @param localOnly If true, only check the datastore. * @return A KeyBlock if the data is in the store, otherwise * a RequestSender, unless the HTL is 0, in which case NULL. * RequestSender. */ public Object makeRequestSender(Key key, short htl, long uid, RequestTag tag, PeerNode source, boolean localOnly, boolean ignoreStore, boolean offersOnly, boolean canReadClientCache, boolean canWriteClientCache, boolean realTimeFlag) { boolean canWriteDatastore = canWriteDatastoreRequest(htl); if(logMINOR) Logger.minor(this, "makeRequestSender("+key+ ',' +htl+ ',' +uid+ ',' +source+") on "+getDarknetPortNumber()); // In store? if(!ignoreStore) { KeyBlock kb = makeRequestLocal(key, uid, canReadClientCache, canWriteClientCache, canWriteDatastore, offersOnly); if (kb != null) return kb; } if(localOnly) return null; if(logMINOR) Logger.minor(this, "Not in store locally"); // Transfer coalescing - match key only as HTL irrelevant RequestSender sender = key instanceof NodeCHK ? tracker.getTransferringRequestSenderByKey((NodeCHK)key, realTimeFlag) : null; if(sender != null) { if(logMINOR) Logger.minor(this, "Data already being transferred: "+sender); sender.setTransferCoalesced(); tag.setSender(sender, true); return sender; } // HTL == 0 => Don't search further if(htl == 0) { if(logMINOR) Logger.minor(this, "No HTL"); return null; } sender = new RequestSender(key, null, htl, uid, tag, this, source, offersOnly, canWriteClientCache, canWriteDatastore, realTimeFlag); tag.setSender(sender, false); sender.start(); if(logMINOR) Logger.minor(this, "Created new sender: "+sender); return sender; } /** Can we write to the datastore for a given request? * We do not write to the datastore until 2 hops below maximum. This is an average of 4 * hops from the originator. Thus, data returned from local requests is never cached, * finally solving The Register's attack, Bloom filter sharing doesn't give away your local * requests and inserts, and *anything starting at high HTL* is not cached, including stuff * from other nodes which hasn't been decremented far enough yet, so it's not ONLY local * requests that don't get cached. */ boolean canWriteDatastoreRequest(short htl) { return htl <= (maxHTL - 2); } /** Can we write to the datastore for a given insert? * We do not write to the datastore until 3 hops below maximum. This is an average of 5 * hops from the originator. Thus, data sent by local inserts is never cached, * finally solving The Register's attack, Bloom filter sharing doesn't give away your local * requests and inserts, and *anything starting at high HTL* is not cached, including stuff * from other nodes which hasn't been decremented far enough yet, so it's not ONLY local * inserts that don't get cached. */ boolean canWriteDatastoreInsert(short htl) { return htl <= (maxHTL - 3); } /** * Fetch a block from the datastore. * @param key * @param canReadClientCache * @param canWriteClientCache * @param canWriteDatastore * @param forULPR * @param mustBeMarkedAsPostCachingChanges If true, the key must have the * ENTRY_NEW_BLOCK flag (if saltedhash), indicating that it a) has been added * since the caching changes in 1224 (since we didn't delete the stores), and b) * that it wasn't added due to low network security caching everything, unless we * are currently in low network security mode. Only applies to main store. */ public KeyBlock fetch(Key key, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR, BlockMetadata meta) { if(key instanceof NodeSSK) return fetch((NodeSSK)key, false, canReadClientCache, canWriteClientCache, canWriteDatastore, forULPR, meta); else if(key instanceof NodeCHK) return fetch((NodeCHK)key, false, canReadClientCache, canWriteClientCache, canWriteDatastore, forULPR, meta); else throw new IllegalArgumentException(); } public SSKBlock fetch(NodeSSK key, boolean dontPromote, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR, BlockMetadata meta) { double loc=key.toNormalizedDouble(); double dist=Location.distance(lm.getLocation(), loc); if(canReadClientCache) { try { SSKBlock block = sskClientcache.fetch(key, dontPromote || !canWriteClientCache, canReadClientCache, forULPR, false, meta); if(block != null) { nodeStats.avgClientCacheSSKSuccess.report(loc); if (dist > nodeStats.furthestClientCacheSSKSuccess) nodeStats.furthestClientCacheSSKSuccess=dist; if(logDEBUG) Logger.debug(this, "Found key "+key+" in client-cache"); return block; } } catch (IOException e) { Logger.error(this, "Could not read from client cache: "+e, e); } } if(forULPR || useSlashdotCache || canReadClientCache) { try { SSKBlock block = sskSlashdotcache.fetch(key, dontPromote, canReadClientCache, forULPR, false, meta); if(block != null) { nodeStats.avgSlashdotCacheSSKSuccess.report(loc); if (dist > nodeStats.furthestSlashdotCacheSSKSuccess) nodeStats.furthestSlashdotCacheSSKSuccess=dist; if(logDEBUG) Logger.debug(this, "Found key "+key+" in slashdot-cache"); return block; } } catch (IOException e) { Logger.error(this, "Could not read from slashdot/ULPR cache: "+e, e); } } boolean ignoreOldBlocks = !writeLocalToDatastore; if(canReadClientCache) ignoreOldBlocks = false; if(logMINOR) dumpStoreHits(); try { nodeStats.avgRequestLocation.report(loc); SSKBlock block = sskDatastore.fetch(key, dontPromote || !canWriteDatastore, canReadClientCache, forULPR, ignoreOldBlocks, meta); if(block == null) { SSKStore store = oldSSK; if(store != null) block = store.fetch(key, dontPromote || !canWriteDatastore, canReadClientCache, forULPR, ignoreOldBlocks, meta); } if(block != null) { nodeStats.avgStoreSSKSuccess.report(loc); if (dist > nodeStats.furthestStoreSSKSuccess) nodeStats.furthestStoreSSKSuccess=dist; if(logDEBUG) Logger.debug(this, "Found key "+key+" in store"); return block; } block=sskDatacache.fetch(key, dontPromote || !canWriteDatastore, canReadClientCache, forULPR, ignoreOldBlocks, meta); if(block == null) { SSKStore store = oldSSKCache; if(store != null) block = store.fetch(key, dontPromote || !canWriteDatastore, canReadClientCache, forULPR, ignoreOldBlocks, meta); } if (block != null) { nodeStats.avgCacheSSKSuccess.report(loc); if (dist > nodeStats.furthestCacheSSKSuccess) nodeStats.furthestCacheSSKSuccess=dist; if(logDEBUG) Logger.debug(this, "Found key "+key+" in cache"); } return block; } catch (IOException e) { Logger.error(this, "Cannot fetch data: "+e, e); return null; } } public CHKBlock fetch(NodeCHK key, boolean dontPromote, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR, BlockMetadata meta) { double loc=key.toNormalizedDouble(); double dist=Location.distance(lm.getLocation(), loc); if(canReadClientCache) { try { CHKBlock block = chkClientcache.fetch(key, dontPromote || !canWriteClientCache, false, meta); if(block != null) { nodeStats.avgClientCacheCHKSuccess.report(loc); if (dist > nodeStats.furthestClientCacheCHKSuccess) nodeStats.furthestClientCacheCHKSuccess=dist; return block; } } catch (IOException e) { Logger.error(this, "Could not read from client cache: "+e, e); } } if(forULPR || useSlashdotCache || canReadClientCache) { try { CHKBlock block = chkSlashdotcache.fetch(key, dontPromote, false, meta); if(block != null) { nodeStats.avgSlashdotCacheCHKSucess.report(loc); if (dist > nodeStats.furthestSlashdotCacheCHKSuccess) nodeStats.furthestSlashdotCacheCHKSuccess=dist; return block; } } catch (IOException e) { Logger.error(this, "Could not read from slashdot/ULPR cache: "+e, e); } } boolean ignoreOldBlocks = !writeLocalToDatastore; if(canReadClientCache) ignoreOldBlocks = false; if(logMINOR) dumpStoreHits(); try { nodeStats.avgRequestLocation.report(loc); CHKBlock block = chkDatastore.fetch(key, dontPromote || !canWriteDatastore, ignoreOldBlocks, meta); if(block == null) { CHKStore store = oldCHK; if(store != null) block = store.fetch(key, dontPromote || !canWriteDatastore, ignoreOldBlocks, meta); } if (block != null) { nodeStats.avgStoreCHKSuccess.report(loc); if (dist > nodeStats.furthestStoreCHKSuccess) nodeStats.furthestStoreCHKSuccess=dist; return block; } block=chkDatacache.fetch(key, dontPromote || !canWriteDatastore, ignoreOldBlocks, meta); if(block == null) { CHKStore store = oldCHKCache; if(store != null) block = store.fetch(key, dontPromote || !canWriteDatastore, ignoreOldBlocks, meta); } if (block != null) { nodeStats.avgCacheCHKSuccess.report(loc); if (dist > nodeStats.furthestCacheCHKSuccess) nodeStats.furthestCacheCHKSuccess=dist; } return block; } catch (IOException e) { Logger.error(this, "Cannot fetch data: "+e, e); return null; } } CHKStore getChkDatacache() { return chkDatacache; } CHKStore getChkDatastore() { return chkDatastore; } SSKStore getSskDatacache() { return sskDatacache; } SSKStore getSskDatastore() { return sskDatastore; } CHKStore getChkSlashdotCache() { return chkSlashdotcache; } CHKStore getChkClientCache() { return chkClientcache; } SSKStore getSskSlashdotCache() { return sskSlashdotcache; } SSKStore getSskClientCache() { return sskClientcache; } /** * This method returns all statistics info for our data store stats table * * @return map that has an entry for each data store instance type and corresponding stats */ public Map<DataStoreInstanceType, DataStoreStats> getDataStoreStats() { Map<DataStoreInstanceType, DataStoreStats> map = new LinkedHashMap<DataStoreInstanceType, DataStoreStats>(); map.put(new DataStoreInstanceType(CHK, STORE), new StoreCallbackStats(chkDatastore, nodeStats.chkStoreStats())); map.put(new DataStoreInstanceType(CHK, CACHE), new StoreCallbackStats(chkDatacache, nodeStats.chkCacheStats())); map.put(new DataStoreInstanceType(CHK, SLASHDOT), new StoreCallbackStats(chkSlashdotcache,nodeStats.chkSlashDotCacheStats())); map.put(new DataStoreInstanceType(CHK, CLIENT), new StoreCallbackStats(chkClientcache, nodeStats.chkClientCacheStats())); map.put(new DataStoreInstanceType(SSK, STORE), new StoreCallbackStats(sskDatastore, nodeStats.sskStoreStats())); map.put(new DataStoreInstanceType(SSK, CACHE), new StoreCallbackStats(sskDatacache, nodeStats.sskCacheStats())); map.put(new DataStoreInstanceType(SSK, SLASHDOT), new StoreCallbackStats(sskSlashdotcache, nodeStats.sskSlashDotCacheStats())); map.put(new DataStoreInstanceType(SSK, CLIENT), new StoreCallbackStats(sskClientcache, nodeStats.sskClientCacheStats())); map.put(new DataStoreInstanceType(PUB_KEY, STORE), new StoreCallbackStats(pubKeyDatastore, new NotAvailNodeStoreStats())); map.put(new DataStoreInstanceType(PUB_KEY, CACHE), new StoreCallbackStats(pubKeyDatacache, new NotAvailNodeStoreStats())); map.put(new DataStoreInstanceType(PUB_KEY, SLASHDOT), new StoreCallbackStats(pubKeySlashdotcache, new NotAvailNodeStoreStats())); map.put(new DataStoreInstanceType(PUB_KEY, CLIENT), new StoreCallbackStats(pubKeyClientcache, new NotAvailNodeStoreStats())); return map; } public long getMaxTotalKeys() { return maxTotalKeys; } long timeLastDumpedHits; public void dumpStoreHits() { long now = System.currentTimeMillis(); if(now - timeLastDumpedHits > 5000) { timeLastDumpedHits = now; } else return; Logger.minor(this, "Distribution of hits and misses over stores:\n"+ "CHK Datastore: "+chkDatastore.hits()+ '/' +(chkDatastore.hits()+chkDatastore.misses())+ '/' +chkDatastore.keyCount()+ "\nCHK Datacache: "+chkDatacache.hits()+ '/' +(chkDatacache.hits()+chkDatacache.misses())+ '/' +chkDatacache.keyCount()+ "\nSSK Datastore: "+sskDatastore.hits()+ '/' +(sskDatastore.hits()+sskDatastore.misses())+ '/' +sskDatastore.keyCount()+ "\nSSK Datacache: "+sskDatacache.hits()+ '/' +(sskDatacache.hits()+sskDatacache.misses())+ '/' +sskDatacache.keyCount()); } public void storeShallow(CHKBlock block, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR) { store(block, false, canWriteClientCache, canWriteDatastore, forULPR); } /** * Store a datum. * @param block * a KeyBlock * @param deep If true, insert to the store as well as the cache. Do not set * this to true unless the store results from an insert, and this node is the * closest node to the target; see the description of chkDatastore. */ public void store(KeyBlock block, boolean deep, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR) throws KeyCollisionException { if(block instanceof CHKBlock) store((CHKBlock)block, deep, canWriteClientCache, canWriteDatastore, forULPR); else if(block instanceof SSKBlock) store((SSKBlock)block, deep, false, canWriteClientCache, canWriteDatastore, forULPR); else throw new IllegalArgumentException("Unknown keytype "); } private void store(CHKBlock block, boolean deep, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR) { try { double loc = block.getKey().toNormalizedDouble(); if (canWriteClientCache) { chkClientcache.put(block, false); nodeStats.avgClientCacheCHKLocation.report(loc); } if ((forULPR || useSlashdotCache) && !(canWriteDatastore || writeLocalToDatastore)) { chkSlashdotcache.put(block, false); nodeStats.avgSlashdotCacheCHKLocation.report(loc); } if (canWriteDatastore || writeLocalToDatastore) { if (deep) { chkDatastore.put(block, !canWriteDatastore); nodeStats.avgStoreCHKLocation.report(loc); } chkDatacache.put(block, !canWriteDatastore); nodeStats.avgCacheCHKLocation.report(loc); } if (canWriteDatastore || forULPR || useSlashdotCache) failureTable.onFound(block); } catch (IOException e) { Logger.error(this, "Cannot store data: "+e, e); } catch (Throwable t) { System.err.println(t); t.printStackTrace(); Logger.error(this, "Caught "+t+" storing data", t); } if(clientCore != null && clientCore.requestStarters != null) { clientCore.requestStarters.chkFetchSchedulerBulk.tripPendingKey(block); clientCore.requestStarters.chkFetchSchedulerRT.tripPendingKey(block); } } /** Store the block if this is a sink. Call for inserts. */ public void storeInsert(SSKBlock block, boolean deep, boolean overwrite, boolean canWriteClientCache, boolean canWriteDatastore) throws KeyCollisionException { store(block, deep, overwrite, canWriteClientCache, canWriteDatastore, false); } /** Store only to the cache, and not the store. Called by requests, * as only inserts cause data to be added to the store. */ public void storeShallow(SSKBlock block, boolean canWriteClientCache, boolean canWriteDatastore, boolean fromULPR) throws KeyCollisionException { store(block, false, canWriteClientCache, canWriteDatastore, fromULPR); } public void store(SSKBlock block, boolean deep, boolean overwrite, boolean canWriteClientCache, boolean canWriteDatastore, boolean forULPR) throws KeyCollisionException { try { // Store the pubkey before storing the data, otherwise we can get a race condition and // end up deleting the SSK data. double loc = block.getKey().toNormalizedDouble(); getPubKey.cacheKey((block.getKey()).getPubKeyHash(), (block.getKey()).getPubKey(), deep, canWriteClientCache, canWriteDatastore, forULPR || useSlashdotCache, writeLocalToDatastore); if(canWriteClientCache) { sskClientcache.put(block, overwrite, false); nodeStats.avgClientCacheSSKLocation.report(loc); } if((forULPR || useSlashdotCache) && !(canWriteDatastore || writeLocalToDatastore)) { sskSlashdotcache.put(block, overwrite, false); nodeStats.avgSlashdotCacheSSKLocation.report(loc); } if(canWriteDatastore || writeLocalToDatastore) { if(deep) { sskDatastore.put(block, overwrite, !canWriteDatastore); nodeStats.avgStoreSSKLocation.report(loc); } sskDatacache.put(block, overwrite, !canWriteDatastore); nodeStats.avgCacheSSKLocation.report(loc); } if(canWriteDatastore || forULPR || useSlashdotCache) failureTable.onFound(block); } catch (IOException e) { Logger.error(this, "Cannot store data: "+e, e); } catch (KeyCollisionException e) { throw e; } catch (Throwable t) { System.err.println(t); t.printStackTrace(); Logger.error(this, "Caught "+t+" storing data", t); } if(clientCore != null && clientCore.requestStarters != null) { clientCore.requestStarters.sskFetchSchedulerBulk.tripPendingKey(block); clientCore.requestStarters.sskFetchSchedulerRT.tripPendingKey(block); } } final boolean decrementAtMax; final boolean decrementAtMin; /** * Decrement the HTL according to the policy of the given * NodePeer if it is non-null, or do something else if it is * null. */ public short decrementHTL(PeerNode source, short htl) { if(source != null) return source.decrementHTL(htl); // Otherwise... if(htl >= maxHTL) htl = maxHTL; if(htl <= 0) { return 0; } if(htl == maxHTL) { if(decrementAtMax || disableProbabilisticHTLs) htl--; return htl; } if(htl == 1) { if(decrementAtMin || disableProbabilisticHTLs) htl--; return htl; } return --htl; } /** * Fetch or create an CHKInsertSender for a given key/htl. * @param key The key to be inserted. * @param htl The current HTL. We can't coalesce inserts across * HTL's. * @param uid The UID of the caller's request chain, or a new * one. This is obviously not used if there is already an * CHKInsertSender running. * @param source The node that sent the InsertRequest, or null * if it originated locally. * @param ignoreLowBackoff * @param preferInsert */ public CHKInsertSender makeInsertSender(NodeCHK key, short htl, long uid, InsertTag tag, PeerNode source, byte[] headers, PartiallyReceivedBlock prb, boolean fromStore, boolean canWriteClientCache, boolean forkOnCacheable, boolean preferInsert, boolean ignoreLowBackoff, boolean realTimeFlag) { if(logMINOR) Logger.minor(this, "makeInsertSender("+key+ ',' +htl+ ',' +uid+ ',' +source+",...,"+fromStore); CHKInsertSender is = null; is = new CHKInsertSender(key, uid, tag, headers, htl, source, this, prb, fromStore, canWriteClientCache, forkOnCacheable, preferInsert, ignoreLowBackoff,realTimeFlag); is.start(); // CHKInsertSender adds itself to insertSenders return is; } /** * Fetch or create an SSKInsertSender for a given key/htl. * @param key The key to be inserted. * @param htl The current HTL. We can't coalesce inserts across * HTL's. * @param uid The UID of the caller's request chain, or a new * one. This is obviously not used if there is already an * SSKInsertSender running. * @param source The node that sent the InsertRequest, or null * if it originated locally. * @param ignoreLowBackoff * @param preferInsert */ public SSKInsertSender makeInsertSender(SSKBlock block, short htl, long uid, InsertTag tag, PeerNode source, boolean fromStore, boolean canWriteClientCache, boolean canWriteDatastore, boolean forkOnCacheable, boolean preferInsert, boolean ignoreLowBackoff, boolean realTimeFlag) { NodeSSK key = block.getKey(); if(key.getPubKey() == null) { throw new IllegalArgumentException("No pub key when inserting"); } getPubKey.cacheKey(key.getPubKeyHash(), key.getPubKey(), false, canWriteClientCache, canWriteDatastore, false, writeLocalToDatastore); Logger.minor(this, "makeInsertSender("+key+ ',' +htl+ ',' +uid+ ',' +source+",...,"+fromStore); SSKInsertSender is = null; is = new SSKInsertSender(block, uid, tag, htl, source, this, fromStore, canWriteClientCache, forkOnCacheable, preferInsert, ignoreLowBackoff, realTimeFlag); is.start(); return is; } /** * @return Some status information. */ public String getStatus() { StringBuilder sb = new StringBuilder(); if (peers != null) sb.append(peers.getStatus()); else sb.append("No peers yet"); sb.append(tracker.getNumTransferringRequestSenders()); sb.append('\n'); return sb.toString(); } /** * @return TMCI peer list */ public String getTMCIPeerList() { StringBuilder sb = new StringBuilder(); if (peers != null) sb.append(peers.getTMCIPeerList()); else sb.append("No peers yet"); return sb.toString(); } /** Length of signature parameters R and S */ static final int SIGNATURE_PARAMETER_LENGTH = 32; public ClientKeyBlock fetchKey(ClientKey key, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore) throws KeyVerifyException { if(key instanceof ClientCHK) return fetch((ClientCHK)key, canReadClientCache, canWriteClientCache, canWriteDatastore); else if(key instanceof ClientSSK) return fetch((ClientSSK)key, canReadClientCache, canWriteClientCache, canWriteDatastore); else throw new IllegalStateException("Don't know what to do with "+key); } public ClientKeyBlock fetch(ClientSSK clientSSK, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore) throws SSKVerifyException { DSAPublicKey key = clientSSK.getPubKey(); if(key == null) { key = getPubKey.getKey(clientSSK.pubKeyHash, canReadClientCache, false, null); } if(key == null) return null; clientSSK.setPublicKey(key); SSKBlock block = fetch((NodeSSK)clientSSK.getNodeKey(true), false, canReadClientCache, canWriteClientCache, canWriteDatastore, false, null); if(block == null) { if(logMINOR) Logger.minor(this, "Could not find key for "+clientSSK); return null; } // Move the pubkey to the top of the LRU, and fix it if it // was corrupt. getPubKey.cacheKey(clientSSK.pubKeyHash, key, false, canWriteClientCache, canWriteDatastore, false, writeLocalToDatastore); return ClientSSKBlock.construct(block, clientSSK); } private ClientKeyBlock fetch(ClientCHK clientCHK, boolean canReadClientCache, boolean canWriteClientCache, boolean canWriteDatastore) throws CHKVerifyException { CHKBlock block = fetch(clientCHK.getNodeCHK(), false, canReadClientCache, canWriteClientCache, canWriteDatastore, false, null); if(block == null) return null; return new ClientCHKBlock(block, clientCHK); } public void exit(int reason) { try { this.park(); System.out.println("Goodbye."); System.out.println(reason); } finally { System.exit(reason); } } public void exit(String reason){ try { this.park(); System.out.println("Goodbye. from "+this+" ("+reason+ ')'); } finally { System.exit(0); } } /** * Returns true if the node is shutting down. * The packet receiver calls this for every packet, and boolean is atomic, so this method is not synchronized. */ public boolean isStopping() { return isStopping; } /** * Get the node into a state where it can be stopped safely * May be called twice - once in exit (above) and then again * from the wrapper triggered by calling System.exit(). Beware! */ public void park() { synchronized(this) { if(isStopping) return; isStopping = true; } try { Message msg = DMT.createFNPDisconnect(false, false, -1, new ShortBuffer(new byte[0])); peers.localBroadcast(msg, true, false, peers.ctrDisconn); } catch (Throwable t) { try { // E.g. if we haven't finished startup Logger.error(this, "Failed to tell peers we are going down: "+t, t); } catch (Throwable t1) { // Ignore. We don't want to mess up the exit process! } } config.store(); if(random instanceof PersistentRandomSource) { ((PersistentRandomSource) random).write_seed(true); } } public NodeUpdateManager getNodeUpdater(){ return nodeUpdater; } public DarknetPeerNode[] getDarknetConnections() { return peers.getDarknetPeers(); } public boolean addPeerConnection(PeerNode pn) { boolean retval = peers.addPeer(pn); peers.writePeersUrgent(pn.isOpennet()); return retval; } public void removePeerConnection(PeerNode pn) { peers.disconnectAndRemove(pn, true, false, false); } public void onConnectedPeer() { if(logMINOR) Logger.minor(this, "onConnectedPeer()"); ipDetector.onConnectedPeer(); } public int getFNPPort(){ return this.getDarknetPortNumber(); } public boolean isOudated() { return peers.isOutdated(); } private Map<Integer, NodeToNodeMessageListener> n2nmListeners = new HashMap<Integer, NodeToNodeMessageListener>(); public synchronized void registerNodeToNodeMessageListener(int type, NodeToNodeMessageListener listener) { n2nmListeners.put(type, listener); } /** * Handle a received node to node message */ public void receivedNodeToNodeMessage(Message m, PeerNode src) { int type = ((Integer) m.getObject(DMT.NODE_TO_NODE_MESSAGE_TYPE)).intValue(); ShortBuffer messageData = (ShortBuffer) m.getObject(DMT.NODE_TO_NODE_MESSAGE_DATA); receivedNodeToNodeMessage(src, type, messageData, false); } public void receivedNodeToNodeMessage(PeerNode src, int type, ShortBuffer messageData, boolean partingMessage) { boolean fromDarknet = src instanceof DarknetPeerNode; NodeToNodeMessageListener listener = null; synchronized(this) { listener = n2nmListeners.get(type); } if(listener == null) { Logger.error(this, "Unknown n2nm ID: "+type+" - discarding packet length "+messageData.getLength()); return; } listener.handleMessage(messageData.getData(), fromDarknet, src, type); } private NodeToNodeMessageListener diffNoderefListener = new NodeToNodeMessageListener() { @Override public void handleMessage(byte[] data, boolean fromDarknet, PeerNode src, int type) { Logger.normal(this, "Received differential node reference node to node message from "+src.getPeer()); SimpleFieldSet fs = null; try { fs = new SimpleFieldSet(new String(data, "UTF-8"), false, true, false); } catch (IOException e) { Logger.error(this, "IOException while parsing node to node message data", e); return; } if(fs.get("n2nType") != null) { fs.removeValue("n2nType"); } try { src.processDiffNoderef(fs); } catch (FSParseException e) { Logger.error(this, "FSParseException while parsing node to node message data", e); return; } } }; private NodeToNodeMessageListener fproxyN2NMListener = new NodeToNodeMessageListener() { @Override public void handleMessage(byte[] data, boolean fromDarknet, PeerNode src, int type) { if(!fromDarknet) { Logger.error(this, "Got N2NTM from non-darknet node ?!?!?!: from "+src); return; } DarknetPeerNode darkSource = (DarknetPeerNode) src; Logger.normal(this, "Received N2NTM from '"+darkSource.getPeer()+"'"); SimpleFieldSet fs = null; try { fs = new SimpleFieldSet(new String(data, "UTF-8"), false, true, false); } catch (UnsupportedEncodingException e) { throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e); } catch (IOException e) { Logger.error(this, "IOException while parsing node to node message data", e); return; } fs.putOverwrite("n2nType", Integer.toString(type)); fs.putOverwrite("receivedTime", Long.toString(System.currentTimeMillis())); fs.putOverwrite("receivedAs", "nodeToNodeMessage"); int fileNumber = darkSource.writeNewExtraPeerDataFile( fs, EXTRA_PEER_DATA_TYPE_N2NTM); if( fileNumber == -1 ) { Logger.error( this, "Failed to write N2NTM to extra peer data file for peer "+darkSource.getPeer()); } // Keep track of the fileNumber so we can potentially delete the extra peer data file later, the file is authoritative try { handleNodeToNodeTextMessageSimpleFieldSet(fs, darkSource, fileNumber); } catch (FSParseException e) { // Shouldn't happen throw new Error(e); } } }; /** * Handle a node to node text message SimpleFieldSet * @throws FSParseException */ public void handleNodeToNodeTextMessageSimpleFieldSet(SimpleFieldSet fs, DarknetPeerNode source, int fileNumber) throws FSParseException { if(logMINOR) Logger.minor(this, "Got node to node message: \n"+fs); int overallType = fs.getInt("n2nType"); fs.removeValue("n2nType"); if(overallType == Node.N2N_MESSAGE_TYPE_FPROXY) { handleFproxyNodeToNodeTextMessageSimpleFieldSet(fs, source, fileNumber); } else { Logger.error(this, "Received unknown node to node message type '"+overallType+"' from "+source.getPeer()); } } private void handleFproxyNodeToNodeTextMessageSimpleFieldSet(SimpleFieldSet fs, DarknetPeerNode source, int fileNumber) throws FSParseException { int type = fs.getInt("type"); if(type == Node.N2N_TEXT_MESSAGE_TYPE_USERALERT) { source.handleFproxyN2NTM(fs, fileNumber); } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER) { source.handleFproxyFileOffer(fs, fileNumber); } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_ACCEPTED) { source.handleFproxyFileOfferAccepted(fs, fileNumber); } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_FILE_OFFER_REJECTED) { source.handleFproxyFileOfferRejected(fs, fileNumber); } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_BOOKMARK) { source.handleFproxyBookmarkFeed(fs, fileNumber); } else if(type == Node.N2N_TEXT_MESSAGE_TYPE_DOWNLOAD) { source.handleFproxyDownloadFeed(fs, fileNumber); } else { Logger.error(this, "Received unknown fproxy node to node message sub-type '"+type+"' from "+source.getPeer()); } } public String getMyName() { return myName; } public MessageCore getUSM() { return usm; } public LocationManager getLocationManager() { return lm; } public int getSwaps() { return LocationManager.swaps; } public int getNoSwaps() { return LocationManager.noSwaps; } public int getStartedSwaps() { return LocationManager.startedSwaps; } public int getSwapsRejectedAlreadyLocked() { return LocationManager.swapsRejectedAlreadyLocked; } public int getSwapsRejectedNowhereToGo() { return LocationManager.swapsRejectedNowhereToGo; } public int getSwapsRejectedRateLimit() { return LocationManager.swapsRejectedRateLimit; } public int getSwapsRejectedRecognizedID() { return LocationManager.swapsRejectedRecognizedID; } public PeerNode[] getPeerNodes() { return peers.myPeers(); } public PeerNode[] getConnectedPeers() { return peers.connectedPeers(); } /** * Return a peer of the node given its ip and port, name or identity, as a String */ public PeerNode getPeerNode(String nodeIdentifier) { for(PeerNode pn: peers.myPeers()) { Peer peer = pn.getPeer(); String nodeIpAndPort = ""; if(peer != null) { nodeIpAndPort = peer.toString(); } String identity = pn.getIdentityString(); if(pn instanceof DarknetPeerNode) { DarknetPeerNode dpn = (DarknetPeerNode) pn; String name = dpn.myName; if(identity.equals(nodeIdentifier) || nodeIpAndPort.equals(nodeIdentifier) || name.equals(nodeIdentifier)) { return pn; } } else { if(identity.equals(nodeIdentifier) || nodeIpAndPort.equals(nodeIdentifier)) { return pn; } } } return null; } public boolean isHasStarted() { return hasStarted; } public void queueRandomReinsert(KeyBlock block) { clientCore.queueRandomReinsert(block); } public String getExtraPeerDataDir() { return extraPeerDataDir.getPath(); } public boolean noConnectedPeers() { return !peers.anyConnectedPeers(); } public double getLocation() { return lm.getLocation(); } public double getLocationChangeSession() { return lm.getLocChangeSession(); } public int getAverageOutgoingSwapTime() { return lm.getAverageSwapTime(); } public long getSendSwapInterval() { return lm.getSendSwapInterval(); } public int getNumberOfRemotePeerLocationsSeenInSwaps() { return lm.numberOfRemotePeerLocationsSeenInSwaps; } public boolean isAdvancedModeEnabled() { if(clientCore == null) return false; return clientCore.isAdvancedModeEnabled(); } public boolean isFProxyJavascriptEnabled() { return clientCore.isFProxyJavascriptEnabled(); } // FIXME convert these kind of threads to Checkpointed's and implement a handler // using the PacketSender/Ticker. Would save a few threads. public int getNumARKFetchers() { int x = 0; for(PeerNode p: peers.myPeers()) { if(p.isFetchingARK()) x++; } return x; } // FIXME put this somewhere else private volatile Object statsSync = new Object(); /** The total number of bytes of real data i.e. payload sent by the node */ private long totalPayloadSent; public void sentPayload(int len) { synchronized(statsSync) { totalPayloadSent += len; } } /** * Get the total number of bytes of payload (real data) sent by the node * * @return Total payload sent in bytes */ public long getTotalPayloadSent() { synchronized(statsSync) { return totalPayloadSent; } } public void setName(String key) throws InvalidConfigValueException, NodeNeedRestartException { config.get("node").getOption("name").setValue(key); } public Ticker getTicker() { return ticker; } public int getUnclaimedFIFOSize() { return usm.getUnclaimedFIFOSize(); } /** * Connect this node to another node (for purposes of testing) */ public void connectToSeednode(SeedServerTestPeerNode node) throws OpennetDisabledException, FSParseException, PeerParseException, ReferenceSignatureVerificationException { peers.addPeer(node,false,false); } public void connect(Node node, FRIEND_TRUST trust, FRIEND_VISIBILITY visibility) throws FSParseException, PeerParseException, ReferenceSignatureVerificationException, PeerTooOldException { peers.connect(node.darknetCrypto.exportPublicFieldSet(), darknetCrypto.packetMangler, trust, visibility); } public short maxHTL() { return maxHTL; } public int getDarknetPortNumber() { return darknetCrypto.portNumber; } public synchronized int getOutputBandwidthLimit() { return outputBandwidthLimit; } public synchronized int getInputBandwidthLimit() { if(inputLimitDefault) return outputBandwidthLimit * 4; return inputBandwidthLimit; } /** * @return total datastore size in bytes. */ public synchronized long getStoreSize() { return maxTotalDatastoreSize; } @Override public synchronized void setTimeSkewDetectedUserAlert() { if(timeSkewDetectedUserAlert == null) { timeSkewDetectedUserAlert = new TimeSkewDetectedUserAlert(); clientCore.alerts.register(timeSkewDetectedUserAlert); } } public File getNodeDir() { return nodeDir.dir(); } public File getCfgDir() { return cfgDir.dir(); } public File getUserDir() { return userDir.dir(); } public File getRunDir() { return runDir.dir(); } public File getStoreDir() { return storeDir.dir(); } public File getPluginDir() { return pluginDir.dir(); } public ProgramDirectory nodeDir() { return nodeDir; } public ProgramDirectory cfgDir() { return cfgDir; } public ProgramDirectory userDir() { return userDir; } public ProgramDirectory runDir() { return runDir; } public ProgramDirectory storeDir() { return storeDir; } public ProgramDirectory pluginDir() { return pluginDir; } public DarknetPeerNode createNewDarknetNode(SimpleFieldSet fs, FRIEND_TRUST trust, FRIEND_VISIBILITY visibility) throws FSParseException, PeerParseException, ReferenceSignatureVerificationException, PeerTooOldException { return new DarknetPeerNode(fs, this, darknetCrypto, false, trust, visibility); } public OpennetPeerNode createNewOpennetNode(SimpleFieldSet fs) throws FSParseException, OpennetDisabledException, PeerParseException, ReferenceSignatureVerificationException, PeerTooOldException { if(opennet == null) throw new OpennetDisabledException("Opennet is not currently enabled"); return new OpennetPeerNode(fs, this, opennet.crypto, opennet, false); } public SeedServerTestPeerNode createNewSeedServerTestPeerNode(SimpleFieldSet fs) throws FSParseException, OpennetDisabledException, PeerParseException, ReferenceSignatureVerificationException, PeerTooOldException { if(opennet == null) throw new OpennetDisabledException("Opennet is not currently enabled"); return new SeedServerTestPeerNode(fs, this, opennet.crypto, true); } public OpennetPeerNode addNewOpennetNode(SimpleFieldSet fs, ConnectionType connectionType) throws FSParseException, PeerParseException, ReferenceSignatureVerificationException { // FIXME: perhaps this should throw OpennetDisabledExcemption rather than returing false? if(opennet == null) return null; return opennet.addNewOpennetNode(fs, connectionType, false); } public byte[] getOpennetPubKeyHash() { return opennet.crypto.ecdsaPubKeyHash; } public byte[] getDarknetPubKeyHash() { return darknetCrypto.ecdsaPubKeyHash; } public synchronized boolean isOpennetEnabled() { return opennet != null; } public SimpleFieldSet exportDarknetPublicFieldSet() { return darknetCrypto.exportPublicFieldSet(); } public SimpleFieldSet exportOpennetPublicFieldSet() { return opennet.crypto.exportPublicFieldSet(); } public SimpleFieldSet exportDarknetPrivateFieldSet() { return darknetCrypto.exportPrivateFieldSet(); } public SimpleFieldSet exportOpennetPrivateFieldSet() { return opennet.crypto.exportPrivateFieldSet(); } /** * Should the IP detection code only use the IP address override and the bindTo information, * rather than doing a full detection? */ public synchronized boolean dontDetect() { // Only return true if bindTo is set on all ports which are in use if(!darknetCrypto.getBindTo().isRealInternetAddress(false, true, false)) return false; if(opennet != null) { if(opennet.crypto.getBindTo().isRealInternetAddress(false, true, false)) return false; } return true; } public int getOpennetFNPPort() { if(opennet == null) return -1; return opennet.crypto.portNumber; } public OpennetManager getOpennet() { return opennet; } public synchronized boolean passOpennetRefsThroughDarknet() { return passOpennetRefsThroughDarknet; } /** * Get the set of public ports that need to be forwarded. These are internal * ports, not necessarily external - they may be rewritten by the NAT. * @return A Set of ForwardPort's to be fed to port forward plugins. */ public Set<ForwardPort> getPublicInterfacePorts() { HashSet<ForwardPort> set = new HashSet<ForwardPort>(); // FIXME IPv6 support set.add(new ForwardPort("darknet", false, ForwardPort.PROTOCOL_UDP_IPV4, darknetCrypto.portNumber)); if(opennet != null) { NodeCrypto crypto = opennet.crypto; if(crypto != null) { set.add(new ForwardPort("opennet", false, ForwardPort.PROTOCOL_UDP_IPV4, crypto.portNumber)); } } return set; } /** * Get the time since the node was started in milliseconds. * * @return Uptime in milliseconds */ public long getUptime() { return System.currentTimeMillis() - usm.getStartedTime(); } public synchronized UdpSocketHandler[] getPacketSocketHandlers() { // FIXME better way to get these! if(opennet != null) { return new UdpSocketHandler[] { darknetCrypto.socket, opennet.crypto.socket }; // TODO Auto-generated method stub } else { return new UdpSocketHandler[] { darknetCrypto.socket }; } } public int getMaxOpennetPeers() { return maxOpennetPeers; } public void onAddedValidIP() { OpennetManager om; synchronized(this) { om = opennet; } if(om != null) { Announcer announcer = om.announcer; if(announcer != null) { announcer.maybeSendAnnouncement(); } } } public boolean isSeednode() { return acceptSeedConnections; } /** * Returns true if the packet receiver should try to decode/process packets that are not from a peer (i.e. from a seed connection) * The packet receiver calls this upon receiving an unrecognized packet. */ public boolean wantAnonAuth(boolean isOpennet) { if(isOpennet) return opennet != null && acceptSeedConnections; else return false; } // FIXME make this configurable // Probably should wait until we have non-opennet anon auth so we can add it to NodeCrypto. public boolean wantAnonAuthChangeIP(boolean isOpennet) { return !isOpennet; } public boolean opennetDefinitelyPortForwarded() { OpennetManager om; synchronized(this) { om = this.opennet; } if(om == null) return false; NodeCrypto crypto = om.crypto; if(crypto == null) return false; return crypto.definitelyPortForwarded(); } public boolean darknetDefinitelyPortForwarded() { if(darknetCrypto == null) return false; return darknetCrypto.definitelyPortForwarded(); } public boolean hasKey(Key key, boolean canReadClientCache, boolean forULPR) { // FIXME optimise! if(key instanceof NodeCHK) return fetch((NodeCHK)key, true, canReadClientCache, false, false, forULPR, null) != null; else return fetch((NodeSSK)key, true, canReadClientCache, false, false, forULPR, null) != null; } /** * Warning: does not announce change in location! */ public void setLocation(double loc) { lm.setLocation(loc); } public boolean peersWantKey(Key key) { return failureTable.peersWantKey(key, null); } private SimpleUserAlert alertMTUTooSmall; public final RequestClient nonPersistentClientBulk = new RequestClientBuilder().build(); public final RequestClient nonPersistentClientRT = new RequestClientBuilder().realTime().build(); public void setDispatcherHook(NodeDispatcherCallback cb) { this.dispatcher.setHook(cb); } public boolean shallWePublishOurPeersLocation() { return publishOurPeersLocation; } public boolean shallWeRouteAccordingToOurPeersLocation(int htl) { return routeAccordingToOurPeersLocation && htl > 1; } /** Can be called to decrypt client.dat* etc, or can be called when switching from another * security level to HIGH. */ public void setMasterPassword(String password, boolean inFirstTimeWizard) throws AlreadySetPasswordException, MasterKeysWrongPasswordException, MasterKeysFileSizeException, IOException { MasterKeys k; synchronized(this) { if(keys == null) { // Decrypting. keys = MasterKeys.read(masterKeysFile, secureRandom, password); databaseKey = keys.createDatabaseKey(secureRandom); } else { // Setting password when changing to HIGH from another mode. keys.changePassword(masterKeysFile, password, secureRandom); return; } k = keys; } setPasswordInner(k, inFirstTimeWizard); } private void setPasswordInner(MasterKeys keys, boolean inFirstTimeWizard) throws MasterKeysWrongPasswordException, MasterKeysFileSizeException, IOException { MasterSecret secret = keys.getPersistentMasterSecret(); clientCore.setupMasterSecret(secret); boolean wantClientCache = false; boolean wantDatabase = false; synchronized(this) { wantClientCache = clientCacheAwaitingPassword; wantDatabase = databaseAwaitingPassword; databaseAwaitingPassword = false; } if(wantClientCache) activatePasswordedClientCache(keys); if(wantDatabase) lateSetupDatabase(keys.createDatabaseKey(secureRandom)); } private void activatePasswordedClientCache(MasterKeys keys) { synchronized(this) { if(clientCacheType.equals("ram")) { System.err.println("RAM client cache cannot be passworded!"); return; } if(!clientCacheType.equals("salt-hash")) { System.err.println("Unknown client cache type, cannot activate passworded store: "+clientCacheType); return; } } Runnable migrate = new MigrateOldStoreData(true); String suffix = getStoreSuffix(); try { initSaltHashClientCacheFS(suffix, true, keys.clientCacheMasterKey); } catch (NodeInitException e) { Logger.error(this, "Unable to activate passworded client cache", e); System.err.println("Unable to activate passworded client cache: "+e); e.printStackTrace(); return; } synchronized(this) { clientCacheAwaitingPassword = false; } executor.execute(migrate, "Migrate data from previous store"); } public void changeMasterPassword(String oldPassword, String newPassword, boolean inFirstTimeWizard) throws MasterKeysWrongPasswordException, MasterKeysFileSizeException, IOException, AlreadySetPasswordException { if(securityLevels.getPhysicalThreatLevel() == PHYSICAL_THREAT_LEVEL.MAXIMUM) Logger.error(this, "Changing password while physical threat level is at MAXIMUM???"); if(masterKeysFile.exists()) { keys.changePassword(masterKeysFile, newPassword, secureRandom); setPasswordInner(keys, inFirstTimeWizard); } else { setMasterPassword(newPassword, inFirstTimeWizard); } } public static class AlreadySetPasswordException extends Exception { final private static long serialVersionUID = -7328456475029374032L; } public synchronized File getMasterPasswordFile() { return masterKeysFile; } boolean hasPanicked() { return hasPanicked; } public void panic() { hasPanicked = true; clientCore.clientLayerPersister.panic(); clientCore.clientLayerPersister.killAndWaitForNotRunning(); try { MasterKeys.killMasterKeys(getMasterPasswordFile()); } catch (IOException e) { System.err.println("Unable to wipe master passwords key file!"); System.err.println("Please delete " + getMasterPasswordFile() + " to ensure that nobody can recover your old downloads."); } // persistent-temp will be cleaned on restart. } public void finishPanic() { WrapperManager.restart(); System.exit(0); } public boolean awaitingPassword() { if(clientCacheAwaitingPassword) return true; if(databaseAwaitingPassword) return true; return false; } public boolean wantEncryptedDatabase() { return this.securityLevels.getPhysicalThreatLevel() != PHYSICAL_THREAT_LEVEL.LOW; } public boolean wantNoPersistentDatabase() { return this.securityLevels.getPhysicalThreatLevel() == PHYSICAL_THREAT_LEVEL.MAXIMUM; } public boolean hasDatabase() { return !clientCore.clientLayerPersister.isKilledOrNotLoaded(); } /** * @return canonical path of the database file in use. */ public String getDatabasePath() throws IOException { return clientCore.clientLayerPersister.getWriteFilename().toString(); } /** Should we commit the block to the store rather than the cache? * * <p>We used to check whether we are a sink by checking whether any peer has * a closer location than we do. Then we made low-uptime nodes exempt from * this calculation: if we route to a low uptime node with a closer location, * we want to store it anyway since he may go offline. The problem was that * if we routed to a low-uptime node, and there was another option that wasn't * low-uptime but was closer to the target than we were, then we would not * store in the store. Also, routing isn't always by the closest peer location: * FOAF and per-node failure tables change it. So now, we consider the nodes * we have actually routed to:</p> * * <p>Store in datastore if our location is closer to the target than:</p><ol> * <li>the source location (if any, and ignoring if low-uptime)</li> * <li>the locations of the nodes we just routed to (ditto)</li> * </ol> * * @param key * @param source * @param routedTo * @return */ public boolean shouldStoreDeep(Key key, PeerNode source, PeerNode[] routedTo) { double myLoc = getLocation(); double target = key.toNormalizedDouble(); double myDist = Location.distance(myLoc, target); // First, calculate whether we would have stored it using the old formula. if(logMINOR) Logger.minor(this, "Should store for "+key+" ?"); // Don't sink store if any of the nodes we routed to, or our predecessor, is both high-uptime and closer to the target than we are. if(source != null && !source.isLowUptime()) { if(Location.distance(source, target) < myDist) { if(logMINOR) Logger.minor(this, "Not storing because source is closer to target for "+key+" : "+source); return false; } } for(PeerNode pn : routedTo) { if(Location.distance(pn, target) < myDist && !pn.isLowUptime()) { if(logMINOR) Logger.minor(this, "Not storing because peer "+pn+" is closer to target for "+key+" his loc "+pn.getLocation()+" my loc "+myLoc+" target is "+target); return false; } else { if(logMINOR) Logger.minor(this, "Should store maybe, peer "+pn+" loc = "+pn.getLocation()+" my loc is "+myLoc+" target is "+target+" low uptime is "+pn.isLowUptime()); } } if(logMINOR) Logger.minor(this, "Should store returning true for "+key+" target="+target+" myLoc="+myLoc+" peers: "+routedTo.length); return true; } public boolean getWriteLocalToDatastore() { return writeLocalToDatastore; } public boolean getUseSlashdotCache() { return useSlashdotCache; } // FIXME remove the visibility alert after a few builds. public void createVisibilityAlert() { synchronized(this) { if(showFriendsVisibilityAlert) return; showFriendsVisibilityAlert = true; } // Wait until startup completed. this.getTicker().queueTimedJob(new Runnable() { @Override public void run() { config.store(); } }, 0); registerFriendsVisibilityAlert(); } private UserAlert visibilityAlert = new SimpleUserAlert(true, l10n("pleaseSetPeersVisibilityAlertTitle"), l10n("pleaseSetPeersVisibilityAlert"), l10n("pleaseSetPeersVisibilityAlert"), UserAlert.ERROR) { @Override public void onDismiss() { synchronized(Node.this) { showFriendsVisibilityAlert = false; } config.store(); unregisterFriendsVisibilityAlert(); } }; private void registerFriendsVisibilityAlert() { if(clientCore == null || clientCore.alerts == null) { // Wait until startup completed. this.getTicker().queueTimedJob(new Runnable() { @Override public void run() { registerFriendsVisibilityAlert(); } }, 0); return; } clientCore.alerts.register(visibilityAlert); } private void unregisterFriendsVisibilityAlert() { clientCore.alerts.unregister(visibilityAlert); } public int getMinimumMTU() { int mtu; synchronized(this) { mtu = maxPacketSize; } if(ipDetector != null) { int detected = ipDetector.getMinimumDetectedMTU(); if(detected < mtu) return detected; } return mtu; } public void updateMTU() { this.darknetCrypto.socket.calculateMaxPacketSize(); OpennetManager om = opennet; if(om != null) { om.crypto.socket.calculateMaxPacketSize(); } } public static boolean isTestnetEnabled() { return false; } public MersenneTwister createRandom() { byte[] buf = new byte[16]; random.nextBytes(buf); return new MersenneTwister(buf); } public boolean enableNewLoadManagement(boolean realTimeFlag) { NodeStats stats = this.nodeStats; if(stats == null) { Logger.error(this, "Calling enableNewLoadManagement before Node constructor completes! FIX THIS!", new Exception("error")); return false; } return stats.enableNewLoadManagement(realTimeFlag); } /** FIXME move to Probe.java? */ public boolean enableRoutedPing() { return enableRoutedPing; } public boolean updateIsUrgent() { OpennetManager om = getOpennet(); if(om != null) { if(om.announcer != null && om.announcer.isWaitingForUpdater()) return true; } if(peers.getPeerNodeStatusSize(PeerManager.PEER_NODE_STATUS_TOO_NEW, true) > PeerManager.OUTDATED_MIN_TOO_NEW_DARKNET) return true; return false; } public byte[] getPluginStoreKey(String storeIdentifier) { DatabaseKey key; synchronized(this) { key = databaseKey; } if(key != null) return key.getPluginStoreKey(storeIdentifier); else return null; } private void checkOutputBandwidthLimit(int obwLimit) throws InvalidConfigValueException { if(obwLimit <= 0) throw new InvalidConfigValueException(l10n("bwlimitMustBePositive")); if (obwLimit < minimumBandwidth) throw lowBandwidthLimit(obwLimit); } private void checkInputBandwidthLimit(int ibwLimit) throws InvalidConfigValueException { // Reserved value for limit based on output limit. if (ibwLimit == -1) { return; } if(ibwLimit <= 1) throw new InvalidConfigValueException(l10n("bandwidthLimitMustBePositiveOrMinusOne")); if (ibwLimit < minimumBandwidth) throw lowBandwidthLimit(ibwLimit); } public PluginManager getPluginManager() { return pluginManager; } DatabaseKey getDatabaseKey() { return databaseKey; } }