logConfigHandler=lc;
getPubKey = new NodeGetPubkey(this);
startupTime = System.currentTimeMillis();
SimpleFieldSet oldConfig = config.getSimpleFieldSet();
// Setup node-specific configuration
final SubConfig nodeConfig = new SubConfig("node", config);
final SubConfig installConfig = new SubConfig("node.install", config);
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 = new SubConfig("fproxy", config);
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);
DiffieHellman.init(random);
// 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.showFriendsVisibilityAlertLong", 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");
dbFile = userDir.file("node.db4o");
dbFileCrypt = userDir.file("node.db4o.crypt");
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;
}
}
try {
setupDatabase(databaseKey);
} catch (MasterKeysWrongPasswordException e2) {
System.out.println("Client database node.db4o is encrypted!");
databaseAwaitingPassword = true;
} catch (MasterKeysFileSizeException e2) {
System.err.println("Unable to decrypt database: master.keys file too " + e2.sizeToString() + "!");
} catch (IOException e2) {
System.err.println("Unable to access master.keys file to decrypt database: "+e2);
e2.printStackTrace();
}
// 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(maxHTL < 0) throw new InvalidConfigValueException("Impossible max HTL");
maxHTL = val;
}
}, false);
maxHTL = nodeConfig.getShort("maxHTL");
// 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.routeAccordingToOurPeersLocationLong", 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);
nodeDBHandle = darknetCrypto.getNodeHandle(db);
if(db != null) {
db.commit();
if(logMINOR) Logger.minor(this, "COMMITTED");
}
// 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);
nodeStats.setOutputLimit(obwLimit);
} catch (IllegalArgumentException e) {
throw new InvalidConfigValueException(e);
}
synchronized(Node.this) {
outputBandwidthLimit = obwLimit;
}
}
});
int obwLimit = nodeConfig.getInt("outputBandwidthLimit");
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;
}
try {
nodeStats.setInputLimit(ibwLimit);
} catch (IllegalArgumentException e) {
throw new InvalidConfigValueException(e);
}
inputBandwidthLimit = ibwLimit;
}
}
});
int ibwLimit = nodeConfig.getInt("inputBandwidthLimit");
if(ibwLimit == -1) {
inputLimitDefault = true;
ibwLimit = obwLimit * 4;
}
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, new SubConfig("node.load", config), 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, nodeDBHandle, databaseKey, db, 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 = new SubConfig("node.opennet", config);
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