/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.node; import java.net.UnknownHostException; import freenet.config.InvalidConfigValueException; import freenet.config.NodeNeedRestartException; import freenet.config.SubConfig; import freenet.io.comm.FreenetInetAddress; import freenet.node.SecurityLevels.NETWORK_THREAT_LEVEL; import freenet.support.Logger; import freenet.support.api.BooleanCallback; import freenet.support.api.IntCallback; import freenet.support.api.StringCallback; /** * Tracks config parameters related to a NodeCrypto. The NodeCrypto may or may not exist. If it exists, * parameter changes are passed on to it, if it doesn't, they can be changed trivially. * * Allows users to set the opennet port number while opennet is disabled, enable opennet on the fly etc. * @author toad */ public class NodeCryptoConfig { /** Port number. -1 = choose a random available port number at activation time. */ private int portNumber; /** Bind address. 0.0.0.0 = all addresses. */ private FreenetInetAddress bindTo; /** If nonzero, 1/dropProbability = probability of UdpSocketHandler dropping a packet (for debugging * purposes; not static as we may need to simulate some nodes with more loss than others). */ private int dropProbability; /** The NodeCrypto, if there is one */ private NodeCrypto crypto; /** Whether we should prevent multiple connections to the same IP (taking into account other * NodeCrypto's - this will usually be set for opennet but not for darknet). */ private boolean oneConnectionPerAddress; /** If true, we will allow to connect to nodes via local (LAN etc) IP addresses, * regardless of any per-peer setting. */ private boolean alwaysAllowLocalAddresses; /** If true, assume we are NATed regardless of the evidence, and therefore always send * aggressive handshakes (every 10-30 seconds). */ private boolean assumeNATed; /** If true, include local addresses on noderefs */ public boolean includeLocalAddressesInNoderefs; /** If false we won't make any effort do disguise the length of packets */ private boolean paddDataPackets; NodeCryptoConfig(SubConfig config, int sortOrder, boolean isOpennet, SecurityLevels securityLevels) throws NodeInitException { config.register("listenPort", -1 /* means random */, sortOrder++, true, true, isOpennet ? "Node.opennetPort" : "Node.port", isOpennet ? "Node.opennetPortLong" : "Node.portLong", new IntCallback() { @Override public Integer get() { synchronized(NodeCryptoConfig.class) { if(crypto != null) portNumber = crypto.portNumber; return portNumber; } } @Override public void set(Integer val) throws InvalidConfigValueException { if(portNumber < -1 || portNumber == 0 || portNumber > 65535) { throw new InvalidConfigValueException("Invalid port number"); } synchronized(NodeCryptoConfig.class) { if(portNumber == val) return; // FIXME implement on the fly listenPort changing // Note that this sort of thing should be the exception rather than the rule!!!! if(crypto != null) throw new InvalidConfigValueException("Switching listenPort on the fly not yet supported"); portNumber = val; } } @Override public boolean isReadOnly() { return true; } }, false); try{ portNumber = config.getInt("listenPort"); }catch (Exception e){ // FIXME is this really necessary? Logger.error(this, "Caught "+e, e); System.err.println(e); e.printStackTrace(); portNumber = -1; } config.register("bindTo", "0.0.0.0", sortOrder++, true, true, "Node.bindTo", "Node.bindToLong", new NodeBindtoCallback()); try { bindTo = new FreenetInetAddress(config.getString("bindTo"), false); } catch (UnknownHostException e) { throw new NodeInitException(NodeInitException.EXIT_COULD_NOT_BIND_USM, "Invalid bindTo: "+config.getString("bindTo")); } config.register("testingDropPacketsEvery", 0, sortOrder++, true, false, "Node.dropPacketEvery", "Node.dropPacketEveryLong", new IntCallback() { @Override public Integer get() { synchronized(NodeCryptoConfig.this) { return dropProbability; } } @Override public void set(Integer val) throws InvalidConfigValueException { if(val < 0) throw new InvalidConfigValueException("testingDropPacketsEvery must not be negative"); synchronized(NodeCryptoConfig.this) { if(val == dropProbability) return; dropProbability = val; if(crypto == null) return; } crypto.onSetDropProbability(val); } }, false); dropProbability = config.getInt("testingDropPacketsEvery"); config.register("oneConnectionPerIP", isOpennet, sortOrder++, true, false, (isOpennet ? "OpennetManager" : "Node") + ".oneConnectionPerIP", (isOpennet ? "OpennetManager" : "Node") + ".oneConnectionPerIPLong", new BooleanCallback() { @Override public Boolean get() { synchronized(NodeCryptoConfig.this) { return oneConnectionPerAddress; } } @Override public void set(Boolean val) throws InvalidConfigValueException { synchronized(NodeCryptoConfig.this) { oneConnectionPerAddress = val; } } }); oneConnectionPerAddress = config.getBoolean("oneConnectionPerIP"); if(isOpennet) { securityLevels.addNetworkThreatLevelListener(new SecurityLevelListener<NETWORK_THREAT_LEVEL>() { @Override public void onChange(NETWORK_THREAT_LEVEL oldLevel, NETWORK_THREAT_LEVEL newLevel) { // Might be useful for nodes on the same NAT etc, so turn it off for LOW. Otherwise is sensible. // It's always off on darknet, since we can reasonably expect to know our peers, even if we are paranoid // about them! if(newLevel == NETWORK_THREAT_LEVEL.LOW) oneConnectionPerAddress = false; if(oldLevel == NETWORK_THREAT_LEVEL.LOW) oneConnectionPerAddress = true; } }); } config.register("alwaysAllowLocalAddresses", !isOpennet, sortOrder++, true, false, "Node.alwaysAllowLocalAddresses", "Node.alwaysAllowLocalAddressesLong", new BooleanCallback() { @Override public Boolean get() { synchronized(NodeCryptoConfig.this) { return alwaysAllowLocalAddresses; } } @Override public void set(Boolean val) throws InvalidConfigValueException { synchronized(NodeCryptoConfig.this) { alwaysAllowLocalAddresses = val; } } }); alwaysAllowLocalAddresses = config.getBoolean("alwaysAllowLocalAddresses"); config.register("assumeNATed", true, sortOrder++, true, true, "Node.assumeNATed", "Node.assumeNATedLong", new BooleanCallback() { @Override public Boolean get() { return assumeNATed; } @Override public void set(Boolean val) throws InvalidConfigValueException { assumeNATed = val; } }); assumeNATed = config.getBoolean("assumeNATed"); // Include local IPs in noderef file config.register("includeLocalAddressesInNoderefs", !isOpennet, sortOrder++, true, false, "NodeIPDectector.inclLocalAddress", "NodeIPDectector.inclLocalAddressLong", new BooleanCallback() { @Override public Boolean get() { return includeLocalAddressesInNoderefs; } @Override public void set(Boolean val) throws InvalidConfigValueException { includeLocalAddressesInNoderefs = val; } }); includeLocalAddressesInNoderefs = config.getBoolean("includeLocalAddressesInNoderefs"); // enable/disable Padding of outgoing packets (won't affect auth-packets) config.register("paddDataPackets", true, sortOrder++, true, false, "Node.paddDataPackets", "Node.paddDataPacketsLong", new BooleanCallback() { @Override public Boolean get() { return paddDataPackets; } @Override public void set(Boolean val) throws InvalidConfigValueException, NodeNeedRestartException { if (val.equals(get())) return; paddDataPackets = val; } }); paddDataPackets = config.getBoolean("paddDataPackets"); securityLevels.addNetworkThreatLevelListener(new SecurityLevelListener<NETWORK_THREAT_LEVEL>() { @Override public void onChange(NETWORK_THREAT_LEVEL oldLevel, NETWORK_THREAT_LEVEL newLevel) { // Might be useful for nodes which are running with a tight bandwidth quota to minimize the overhead, // so turn it off for LOW. Otherwise is sensible. if(newLevel == NETWORK_THREAT_LEVEL.LOW) paddDataPackets = false; if(oldLevel == NETWORK_THREAT_LEVEL.LOW) paddDataPackets = true; } }); } /** The number of config options i.e. the amount to increment sortOrder by */ public static final int OPTION_COUNT = 3; synchronized void starting(NodeCrypto crypto2) { if(crypto != null) throw new IllegalStateException("Replacing existing NodeCrypto "+crypto+" with "+crypto2); crypto = crypto2; } synchronized void started(NodeCrypto crypto2) { if(crypto != null) throw new IllegalStateException("Replacing existing NodeCrypto "+crypto+" with "+crypto2); } synchronized void maybeStarted(NodeCrypto crypto2) { } synchronized void stopping(NodeCrypto crypto2) { crypto = null; } public synchronized int getPort() { return portNumber; } class NodeBindtoCallback extends StringCallback { @Override public String get() { return bindTo.toString(); } @Override public void set(String val) throws InvalidConfigValueException { if(val.equals(get())) return; // FIXME why not? Can't we use freenet.io.NetworkInterface like everywhere else, just adapt it for UDP? throw new InvalidConfigValueException("Cannot be updated on the fly"); } @Override public boolean isReadOnly() { return true; } } public synchronized FreenetInetAddress getBindTo() { return bindTo; } public synchronized void setPort(int port) { portNumber = port; } public synchronized int getDropProbability() { return dropProbability; } public synchronized boolean oneConnectionPerAddress() { return oneConnectionPerAddress; } public synchronized boolean alwaysAllowLocalAddresses() { return alwaysAllowLocalAddresses; } public boolean alwaysHandshakeAggressively() { return assumeNATed; } public boolean includeLocalAddressesInNoderefs() { return includeLocalAddressesInNoderefs; } public boolean paddDataPackets() { return paddDataPackets; } }